基本概念
Runtime 是一个底层的C语言的API,称为“运行时”。而OC在编译的时候并不知道要调用哪个方法函数,只有在运行的时候才知道调用的方法函数名称,来找到对应的方法函数进行调用。
类与对象的实质
对象的实质
1、对象是表示一个类的实例的结构体,是指向objc_object的结构体的指针。
2、objc_object这个结构体只有一个成员变量,即指向其类的isa指针。这样,当我们向一个Objective-C对象发送消息时,runtime会根据实例对象的isa指针找到这个实例对象所属的类。Runtime库会在类的方法列表及父类的方法列表中去寻找与消息对应的selector指向的方法,找到后即运行这个方法。
这是objc_object结构体源码
struct objc_object {
Class _Nonnull isa OBJC_ISA_AVAILABILITY;
};
typedef struct objc_object *id;
类的实质
1、类实际上是一个指向objc_class的结构体指针
struct objc_class {
Class _Nonnull isa OBJC_ISA_AVAILABILITY;
#if !__OBJC2__
Class _Nullable super_class OBJC2_UNAVAILABLE; 父类
const char * _Nonnull name OBJC2_UNAVAILABLE; 类名
long version OBJC2_UNAVAILABLE; 类的版本信息,默认为0
long info OBJC2_UNAVAILABLE; 类信息,供运行期使用的一些位标识
long instance_size OBJC2_UNAVAILABLE; 该类的实例变量大小
struct objc_ivar_list * _Nullable ivars OBJC2_UNAVAILABLE; 该类的成员变量链表
struct objc_method_list * _Nullable * _Nullable methodLists OBJC2_UNAVAILABLE; 方法定义的链表
struct objc_cache * _Nonnull cache OBJC2_UNAVAILABLE; 方法缓存
struct objc_protocol_list * _Nullable protocols OBJC2_UNAVAILABLE; 协议链表
#endif
} OBJC2_UNAVAILABLE;
元类
为了调用类方法,这个类的isa指针必须指向一个包含这些类方法的一个objc_class结构体。这就引出了meta-class的概念,meta-class中存储着一个类的所有类方法.所以,调用类方法的这个类对象的isa指针指向的就是meta-class,当我们向一个对象发送消息时,runtime会在这个对象所属的这个类的方法列表中查找方法;而向一个类发送消息时,会在这个类的meta-class的方法列表中查找。为了不让这种结构无限延伸下去,Objective-C的设计者让所有的meta-class的isa指向基类的meta-class,以此作为它们的所属类
用途
1、获取类的成员变量列表、属性列列表、方法列表、协议列表
//1、获取成员变量
//class_copyIvarList
//ivar_getName
Ivar *ivarList = class_copyIvarList([self class], &count);
for (unsigned int i=0; i<count; i++) {
Ivar myIvar = ivarList[i];
const char *ivarName = ivar_getName(myIvar);
NSLog(@"Ivar---->%@", [NSString stringWithUTF8String:ivarName]);
}
//2、获取属性
//class_copyPropertyList
//property_getName
unsigned int count;
//获取类的属性列表
objc_property_t *propertyList = class_copyPropertyList([self class], &count);
for (unsigned int i=0; i<count; i++) {
const char *propertyName = property_getName(propertyList[i]);
NSLog(@"property---->%@", [NSString stringWithUTF8String:propertyName]);
}
//3、方法
//class_copyMethodList
//method_getName
Method *methodList = class_copyMethodList([self class], &count);
for (unsigned int i=0; i<count; i++) {
Method method = methodList[i];
NSLog(@"method---->%@", NSStringFromSelector(method_getName(method)));
}
//4、协议
//class_copyProtocolList
//protocol_getName
__unsafe_unretained Protocol **protocolList = class_copyProtocolList([self class], &count);
for (unsigned int i=0; i<count; i++) {
Protocol *myProtocal = protocolList[i];
const char *protocolName = protocol_getName(myProtocal);
NSLog(@"protocol---->%@", [NSString stringWithUTF8String:protocolName]);
}
2、快速归档解档
1、原理就是通过runtime获取该类的所有成员变量或者属性,再进行一次性的归档或者解档。
2、该类需遵守NSCoding协议
1、归档
unsigned int count = 0;
//获得该类所有属性
objc_property_t *properties = class_copyPropertyList([self class], &count);
for (int i =0; i < count; i ++) {
objc_property_t property = properties[i];
const char *name = property_getName(property);//获得其属性的名称--->C语言的字符串
NSString *key = [NSString stringWithUTF8String:name];
// 编码每个属性,利用kVC取出每个属性对应的数值
[aCoder encodeObject:[self valueForKeyPath:key] forKey:key];
}
获得该类所有属性,利用kVC取出每个属性对应的值,最后归档每隔属性。
2、解档
unsigned int count = 0;
//获得指向该类所有属性的指针
objc_property_t *properties = class_copyPropertyList([self class], &count);//获取类属性列表
for (int i =0; i < count; i ++) {
objc_property_t property = properties[i];
const char *name = property_getName(property);//获取类属性的名称--->C语言的字符串
NSString *key = [NSString stringWithUTF8String:name];
[self setValue:[aDecoder decodeObjectForKey:key] forKeyPath:key];//解码每个属性,利用kVC取出每个属性对应的数值
}
获得该类所有属性,先解档每个属性,最后利用KVC取出每个属性的值。
3、关联对象
分类原则上不允许添加属性,几遍添加了属性,编译器也不会自动生成getter和setter方法。这时候可利用runtime的关联作用手动实现属性的getter和setter方法。
绑定
//第一个参数:被关联对象
//第二个参数:一个静态常亮,这个key与第三个参数(关联对象)一一对应。这是只想关联对象的一个指针。
//第三个参数:关联对象
//第四个参数:关联策略
//这个动态绑定有点像可变字典
objc_setAssociatedObject(self, &kName, age, OBJC_ASSOCIATION_COPY_NONATOMIC);
取值
objc_getAssociatedObject(self, &kName);
4、访问私有变量
Ivar ivar = class_getInstanceVariable([Model class], "_str1");
NSString * str1 = object_getIvar(model, ivar);
OC中没有真正意义上的私有变量和方法,要让成员变量私有,要放在.m文件中声明,不对外暴露。如果我们知道这个成员变量的名称,可以通过runtime获取成员变量,再通过getIvar来获取它的值
5、交换方法method Swizzling
1、每个类都维护一个方法(Method)列表,Method则包含SEL和其对应IMP的信息,方法交换做的事情就是把SEL和IMP的对应关系断开,并和新的IMP生成对应关系
使用场景1、字典和数组在保存的对象为nil时会产生崩溃。可以写个分类对addObject:这个方法进行交换。交换成自定义的方法。
使用场景2、判断每次调用imageWithNamed:图片是否加载成功。
使用场景不限这两个,啥都行,根据业务来定。
2、代码写在类或者分类的+(void)load方法里。这个方法只是在类加载到内存的时候调用,只调用一次。
1、获取新旧两个方法
class_getInstanceMethod
2、交换连个方法的实现
method_exchangeImplementations
6、消息的分发(函数调用的实质)
1、objc_msgSend 向一个类的实例发送消息,返回id类型数据。(这也是最常用的一个发送消息的方法)
2、objc_msgSend_stret 向一个类的实例发送消息,返回结构体类型数据。
3、objc_msgSendSuper 向一个类的实例的父类发送消息,返回id类型数据
4、objc_msgSendSuper_stret 向一个类的实例的父类发送消息,返回结构体类型的数据。
5、方法的调用过程:如果用实例对象调用实例方法,会到实例的isa指针指向的对象(也就是类对象)操作。如果调用的是类方法,就会到类对象的isa指针指向的对象(也就是元类对象)中操作。
1、首先,在相应操作的对象中的缓存方法列表中找调用的方法,如果找到,转向相应实现并执行
2、如果没找到,在相应操作的对象中的方法列表中找调用的方法,如果找到,转向相应实现执行
3、如果没找到,去父类指针所指向的对象中执行1,2.
4、以此类推,如果一直到根类还没找到,到崩溃之前还有三次拯救的机会,首先会调用动态分析方法resolveClassMethod或者resolveInstanceMethod
5、如果没有重写动态分析方法,会触发备用消息接收者方法,若没有处理就会调用消息转发的方法。要是都没有重写则崩溃
1、动态分析方法
1、调用未知类方法会触发+(BOOL)resolveClassMethod:(SEL)sel
2、调用未知实例方法会触发+(BOOL)resolveInstanceMethod:(SEL)sel
2、没有重写动态分析方法,会触发备用消息接收者方法,返回的对象不能是nil也不能self,如果返回的对象实现了(SEL)aSelector这个方法,那么返回的这个对象就成了消息的接收者,代替原来的对象执行那个方法。
触发-(id)forwardingTargetForSelector:(SEL)aSelector
3、触发消息转发方法-(void)forwardInvocation:(NSInvocation *)anInvocation 。forwardInvocation: 方法来对不能处理的消息做一些处理,也可以将消息转发给其他对象处理,而不抛出错误
6、动态添加方法
可在重写resolveClassMethod:和resolveInstanceMethod:的方法里使用。为找不到方法做防崩溃处理
class_addMethod:由四个参数
1、第一个是要添加方法的类
2、第二个是要添加的方法名
3、第三个是这个方法的实现函数的指针(值的注意的是,这个函数必须显式地把self和_cmd这两个参数写出来)
class_getMethodImplementation 可获取OC方法的指针。
4、第四个是方法的参数数组,在这里它是用的类型编码的方式进行表示的,因为方法一定含有self和_cmd这两个参数,所以字符数组的第二个和第三个字符一定是"@:",第一个字符代表返回值,这里为空用“v”来表示。