一、runtime
runtime 是由C、C++、汇编实现的一套API,为OC语言加入了面向对象,运行时的功能。
运行时(runtime)是指将数据类型的确定由编译时推迟到了运行时。如,extension-category 的区别
平时编写的OC代码,在程序运行过程中,其实最终会转换成Runtime的C语言代码,‘Runtime’是‘Objcect-c’的幕后工作者。
二、方法的本质,sel是什么?IMP是什么?两者之间的关系是什么?
方法的本质:发送消息,消息会有以下几个流程
1、快速查找(objc_msgSend)~cache_t 缓存消息
2、慢速查找~递归自己->父类 ~lookUpImpOrForward
3、查找不到消息:动态方法解析~resolveInstanceMethod
4、消息快速转发~forwardingTargetForSelector
5、消息慢速转发~methodSignatureForSelector & forwardInvocation
sel是方法编号~在read_images期间就编译进入了内存
imp就是我们函数实现指针,找imp就是找函数的过程
sel就相当于书本的目录 title
imp 就是书本的页码
查找具体的函数就是想看这本书里面具体篇章的内容
三、能否想编译后的得到的类中增加实例变量?能否向运行时创建的类中添加实例变量?
1:不能向编译后的得到的类中增加实例变量 2:只要类没有注册到内存还是可以添加
原因:我们编译好的实例变量存储的位置在ro,一旦编译完成,内存结构就完全确定了就无法修改
四、isKindOfClass 和 isMemberOfClass
五、[self class] 与 [super class]
有图可知[self class] 与 [super class] 这两个打印出来的结构都是一样,为什么没有将父类打印出来呢?依次分析
[[self class]]:对于这个的打印结果估计都不会意外,还是将寻找流程贴出来
如果obj存在就直接返回了isa,isa就是关联类。[super class]:这时就可以通过汇编的方式Debug->Debug Workflow -> Always Show Disassemble ,来查看调用流程
上面的@“class”我们知道,可以猜到是[self class],下面接着又有个 objc_msgSendSuper,这个估计就是[super class]时,调用的接口了然后去找一下 objc_msgSendSuper 的源码
这里传递了两个参数,SEL,这里知道,这时去看一下 objc_super仔细看一下注释就会发现 objc_super 主要有两个变量,然后就可以模拟一下这个环境
打印结果一模一样[[self class]] 与 [super class] 有什么区别呢?
[[self class]]走的是objc_msgsend,从自己去递归,消息接受者是 self,
[super class] 走的是objc_msgSendSuper,就会跳过自己的查找流程,直接去父类查找,消息接受者还是self。
在 LGSutdent 与父类中并没有去实现 class 方法,所以调用的 class方法其实调用的是 NSObject 里面class 方法
只是objc_msgSendSuper会更快直接跳过self的查找 在这个过程中的研究对象是 LGSutdent ,所以打印出来就是LGSutdent。六、weak原理
weak 修饰弱引用,在释放时能够自动置空,用来打破循环引用。
接下来通过汇编找到weak源码分析weak的实现流程 Debug->Debug Workflow -> Always Show Disassemble
然后点进去 添加一个段点,就会直接到 objc_initWeak 源码 接着往下看,看这个api storeWeak 点进去看源码 找一段关键的代码,点进去看 weak_register_no_lock ,传了一个弱引用表 这里有个通过weak_entry_for_referent拿到一个entry,拿到之后进行拼接,看下这个接口append_referrer 进入之后就会进行内部持有,如果超容就进行扩容,然后在进行其他处理 画个图 如果没有这个表,就进行创建数组 然后进行扩容并插入到weak_table里总结:Runtime 是如何实现weak的,为什么可以自动置 nil
1:通过Side Table 找到我们的 weak_table
2:weak_talbe 根据referent 找到或者创建 weak_entry_t
3:然后 append_referrer(entry,referrer)将我的新弱引用的对象加进去entry
4:最后weak_entry_insert 把entry加入到我们的weak_table
说明,weak并没有对内存进行管理,只是记录保存,weak进行释放的时候,拿到散列表weak_table,通过objc找到相应局里面entry数组,把它从整个数组中移调,什么时候释放呢,就是进行析构的时候。跟一下这个过程
释放当前对象 这里开始清除,传入的是一个objc_object *,然后就在weak_table去查找到它,找到这个实体就置空以达到释放的效果七、Method_Swizzling 坑点
runtime运用--避免数组越界时崩溃
首先我们先创建一个数组,4个元素,取一个会越界的下标
这时直接运行是肯定会崩溃的,要是想它不崩溃要怎么做呢,是不是需要拦截获取数组的方法呢,当获取越界是就给一个提示。接下来就操作一下,进行方法交换Swizzling,图形说明 代码实现NSArray+LG.h
+ (void)load;
NSArray+LG.m
#import "NSArray+LG.h"
#import "LGRuntimeTool.h"
#import <objc/runtime.h>
@implementation NSArray (LG)
+ (void)load{
//防止交换一次
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
[LGRuntimeTool lg_methodSwizzlingWithClass:objc_getClass("__NSArrayI") oriSEL:@selector(objectAtIndex:) swizzledSEL:@selector(lg_objectAtIndex:)];
[LGRuntimeTool lg_methodSwizzlingWithClass:objc_getClass("__NSArrayI") oriSEL:@selector(objectAtIndexedSubscript:) swizzledSEL:@selector(lg_objectAtIndexedSubscript:)];
});
}
// 交换的方法
- (id)lg_objectAtIndex:(NSUInteger)index{
if (index > self.count-1) {
NSLog(@"取值越界了:%lu > %lu",(unsigned long)index, self.count-1);
return nil;
}
return [self lg_objectAtIndex:index];
}
- (id)lg_objectAtIndexedSubscript:(NSUInteger)index {
if (index > self.count-1) {
NSLog(@"取值越界了:%lu > %lu",(unsigned long)index, self.count-1);
return nil;
}
return [self lg_objectAtIndexedSubscript:index];
}
LGRuntimeTool.h
交换方法
@param cls 交换对象
@param oriSEL 原始方法编号
@param swizzledSEL 交换的方法编号
*/
+ (void)lg_methodSwizzlingWithClass:(Class)cls oriSEL:(SEL)oriSEL swizzledSEL:(SEL)swizzledSEL;
LGRuntimeTool.m
#import "LGRuntimeTool.h"
#import <objc/runtime.h>
@implementation LGRuntimeTool
+ (void)lg_methodSwizzlingWithClass:(Class)cls oriSEL:(SEL)oriSEL swizzledSEL:(SEL)swizzledSEL{
if (!cls) NSLog(@"传入的交换类不能为空");
Method oriMethod = class_getInstanceMethod(cls, oriSEL);
Method swiMethod = class_getInstanceMethod(cls, swizzledSEL);
method_exchangeImplementations(oriMethod, swiMethod);
}
ViewController.m
#import "ViewController.h"
@interface ViewController ()
@property (nonatomic, strong) NSArray *dataArray;
@end
@implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
self.dataArray = @[@"AA",@"BB",@"CC",@"DD"];
[NSArray load];
NSLog(@"%@",self.dataArray[4]);
NSLog(@"%@",[self.dataArray objectAtIndex:4]);
}
打印
2020-04-11 13:25:13.124037+0800 005---Runtime应用[64014:5227823] 取值越界了:4 > 3
2020-04-11 13:25:13.124195+0800 005---Runtime应用[64014:5227823] (null)
2020-04-11 13:25:13.124037+0800 005---Runtime应用[64014:5227823] 取值越界了:4 > 3
2020-04-11 13:25:13.124195+0800 005---Runtime应用[64014:5227823] (null)
但是要是交换的方法可以多次执行会发生什么,验证一下就修改一下 NSArray+LG.m 里的 load 方法
然后在执行 崩溃了,所以要注意要这里有个坑点,执行方法交换的方法只能执行一次然后还有一个坑点,通过子类调用一个子类没有父类有的方法,进行方法交换
ViewController.m
#import "ViewController.h"
#import "LGPerson.h"
#import "LGStudent.h"
#import "LGStudent+LG.h"
@interface ViewController ()
@end
@implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
// 黑魔法坑点二: 子类没有实现 - 父类实现
LGStudent *s = [[LGStudent alloc] init];
//此时调用的是 lg_studentInstanceMethod
[s personInstanceMethod];
}
@end
LGStudent.m/.h 里什么也没有
#import "LGStudent.h"
@implementation LGStudent
@end
LGStudent+LG.h
@interface LGStudent (LG)
@end
LGStudent+LG.m
#import "LGStudent+LG.h"
#import "LGRuntimeTool.h"
#import <objc/runtime.h>
@implementation LGStudent (LG)
+ (void)load{
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
[LGRuntimeTool lg_methodSwizzlingWithClass:self oriSEL:@selector(personInstanceMethod) swizzledSEL:@selector(lg_studentInstanceMethod)];
});
}
// personInstanceMethod 我需要父类的这个方法的一些东西
// 给你加一个personInstanceMethod 方法
// imp
- (void)lg_studentInstanceMethod{
//此时调用的是父类中的personInstanceMethod,因此做了方法交换
[self lg_studentInstanceMethod];
NSLog(@"LGStudent分类添加的lg对象方法:%s",__func__);
}
@end
LGPerson.h
#import <Foundation/Foundation.h>
NS_ASSUME_NONNULL_BEGIN
@interface LGPerson : NSObject
- (void)personInstanceMethod;
+ (void)personClassMethod;
@end
LGPerson.m
#import "LGPerson.h"
@implementation LGPerson
- (void)personInstanceMethod{
NSLog(@"person对象方法:%s",__func__);
}
+ (void)personClassMethod{
NSLog(@"person类方法:%s",__func__);
}
@end
LGRuntimeTool.h
@interface LGRuntimeTool : NSObject
/**
交换方法
@param cls 交换对象
@param oriSEL 原始方法编号
@param swizzledSEL 交换的方法编号
*/
+ (void)lg_methodSwizzlingWithClass:(Class)cls oriSEL:(SEL)oriSEL swizzledSEL:(SEL)swizzledSEL;
LGRuntimeTool.m
#import "LGRuntimeTool.h"
#import <objc/runtime.h>
@implementation LGRuntimeTool
+ (void)lg_methodSwizzlingWithClass:(Class)cls oriSEL:(SEL)oriSEL swizzledSEL:(SEL)swizzledSEL{
if (!cls) NSLog(@"传入的交换类不能为空");
Method oriMethod = class_getInstanceMethod(cls, oriSEL);
Method swiMethod = class_getInstanceMethod(cls, swizzledSEL);
method_exchangeImplementations(oriMethod, swiMethod);
}
}
@end
打印结果:
如果此时,我在用父类调用自己的方法
运行结果:
就会闪退,因为子类LGStudent 调用父类方法的时候,用了一个方法交换,将父类的方法换成了 LGStudent 在自己这个类添加的一个方法。于是在父类调用自己的方法是,由于子类将方法换了,所以就会找到交换之后方法,但是那个方法又是在子类的,LGPerson 自己又没实现,所以运行时就会找不到这个方法,而崩溃。为了避免这个问题,此时我们就要在 LGRuntimeTool.m 类里做一下改动
解决办法:
1.会先尝试给自己添加要交换的方法 2.然后再将父类的IMP给Swizzle
LGRuntimeTool.m
#import "LGRuntimeTool.h"
#import <objc/runtime.h>
@implementation LGRuntimeTool
+ (void)lg_methodSwizzlingWithClass:(Class)cls oriSEL:(SEL)oriSEL swizzledSEL:(SEL)swizzledSEL{
if (!cls) NSLog(@"传入的交换类不能为空");
Method oriMethod = class_getInstanceMethod(cls, oriSEL);
Method swiMethod = class_getInstanceMethod(cls, swizzledSEL);
method_exchangeImplementations(oriMethod, swiMethod);
}
+ (void)lg_betterMethodSwizzlingWithClass:(Class)cls oriSEL:(SEL)oriSEL swizzledSEL:(SEL)swizzledSEL{
if (!cls) NSLog(@"传入的交换类不能为空");
// 一般交换方法: 交换自己有的方法 -- 走下面 因为自己有意味添加方法失败
// 交换自己没有实现的方法:
// 首先第一步:会先尝试给自己添加要交换的方法 :personInstanceMethod (SEL) -> swiMethod(IMP)
// 然后再将父类的IMP给swizzle personInstanceMethod(imp) -> swizzledSEL
//oriSEL:personInstanceMethod
Method oriMethod = class_getInstanceMethod(cls, oriSEL);
Method swiMethod = class_getInstanceMethod(cls, swizzledSEL);
// 尝试添加 向LGStudent 的 oriSEL(personInstanceMethod)添加 swiMethod(lg_studentInstanceMethod)方法
BOOL success = class_addMethod(cls, oriSEL, method_getImplementation(swiMethod), method_getTypeEncoding(oriMethod));
if (success) {// 自己没有 - 交换 - 没有父类进行处理 (重写一个)
//如果自己没有 personInstanceMethod ,并且添加 lg_studentInstanceMethod 实现成功时,
//就对 swizzledSEL(lg_studentInstanceMethod)替换为 oriMethod(personInstanceMethod)
class_replaceMethod(cls, swizzledSEL, method_getImplementation(oriMethod), method_getTypeEncoding(oriMethod));
}else{ // 自己有
method_exchangeImplementations(oriMethod, swiMethod);
}
}
@end
LGStudent+LG.m
+ (void)load{
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
[LGRuntimeTool lg_betterMethodSwizzlingWithClass:self oriSEL:@selector(personInstanceMethod) swizzledSEL:@selector(lg_studentInstanceMethod)];
});
}
运行结果:
这里能不崩溃的原因就是,只对LGStudent 调用的方法做了方法的添加和覆盖,没有对LGPerson 的方法做改动要是子类LGStudent 调用了一个子类和父类都没有实现的方法呢要怎么避免崩溃呢
就相当于只在子类有个申明
代码: LGStudent.h
@interface LGStudent : LGPerson
- (void)helloword;
@end
LGStudent+LG.m
#import "LGStudent+LG.h"
#import "LGRuntimeTool.h"
#import <objc/runtime.h>
@implementation LGStudent (LG)
+ (void)load{
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
[LGRuntimeTool lg_betterMethodSwizzlingWithClass:self oriSEL:@selector(helloword) swizzledSEL:@selector(lg_studentInstanceMethod)];
});
}
- (void)lg_studentInstanceMethod{
NSLog(@"LGStudent分类添加的lg对象方法:%s",__func__);
}
ViewController.m
#import "ViewController.h"
#import "LGPerson.h"
#import "LGStudent.h"
#import "LGStudent+LG.h"
@interface ViewController ()
@end
@implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
LGStudent *s = [[LGStudent alloc] init];
[s helloword];
}
打印结果:出现了死循环
是因为,在对LGStudent 添加完方法后,然后要进行替换的方法没有做实现,所以是会出问题的就造成了递归问题。为了避免这个问题,这是我们在做替换的时候要做一个判断,判断要替换的方法是否有实现IMP,判断实现不存在时,就对没有实现的方法(SEL),添加一个实现(IMP)
实现:
在 LGRuntimeTool.m 中重新写一个方法
+ (void)lg_bestMethodSwizzlingWithClass:(Class)cls oriSEL:(SEL)oriSEL swizzledSEL:(SEL)swizzledSEL{
if (!cls) NSLog(@"传入的交换类不能为空");
Method oriMethod = class_getInstanceMethod(cls, oriSEL);
Method swiMethod = class_getInstanceMethod(cls, swizzledSEL);
if (!oriMethod) {
// 在oriMethod为nil时,替换后将swizzledSEL复制一个不做任何事的空实现,代码如下:
class_addMethod(cls, oriSEL, method_getImplementation(swiMethod), method_getTypeEncoding(swiMethod));
method_setImplementation(swiMethod, imp_implementationWithBlock(^(id self, SEL _cmd){ }));
}
// 一般交换方法: 交换自己有的方法 -- 走下面 因为自己有意味添加方法失败
// 交换自己没有实现的方法:
// 首先第一步:会先尝试给自己添加要交换的方法 :personInstanceMethod (SEL) -> swiMethod(IMP)
// 然后再将父类的IMP给swizzle personInstanceMethod(imp) -> swizzledSEL
//oriSEL:personInstanceMethod
BOOL didAddMethod = class_addMethod(cls, oriSEL, method_getImplementation(swiMethod), method_getTypeEncoding(swiMethod));
if (didAddMethod) {
class_replaceMethod(cls, swizzledSEL, method_getImplementation(oriMethod), method_getTypeEncoding(oriMethod));
}else{
method_exchangeImplementations(oriMethod, swiMethod);
}
}
运行结果:
八、内存偏移
先给出一段代码 LGPerson.h
@interface LGPerson : NSObject
- (void)saySomething;
@end
LGPerson.m
#import "LGPerson.h"
@implementation LGPerson
- (void)saySomething{
NSLog(@"NB %s - %@",__func__,self.age);
}
@end
ViewController.m
- (void)viewDidLoad {
[super viewDidLoad];
id pcls = [LGPerson class];
void *pp= &pcls;
[(__bridge id)pp saySomething];
// p -> LGPerson 实例对象
LGPerson *p = [LGPerson alloc];
[p saySomething];
NSLog(@"面试题");
}
这时会打印什么呢?
两个结果一模一样,这个要怎么解释呢,画一幅图
因为isa偏移一下就是指向 LGPerson 类,与pCls指向 是一样的,然后pp 与 p 都是指向 LGPerson 类 的指针,所以说都能掉 saySomething。接下来再多打印一点其他,self.name
LGPerson.m
#import "LGPerson.h"
@implementation LGPerson
- (void)saySomething{
NSLog(@"NB %s - %@",__func__, self.name);
}
@end
打印结果:
很神奇为什么打印的结果是这样的呢?
栈的当前结构如图,当前pp指向ISA,当访问name时,类型为NSString,8字节,所以就会向下偏移,8个字节就到了VC。这就是传说中的野指针,才会打印出这个结果。