相关文章很多、以下只为亲自记录下,方便日后翻阅
因为oc是对c语言的扩展,并加入了面向对象和Samlltalk式的消息传递机制。这个扩展的实现就是runtime机制,其核心思想就是消息传递。
runtime进阶图:


消息传递:
实例方法的消息传递: 当一个方法被调用时,编译器会转成消息发送objc_msgSend(obj,foo),参数会包括对象本身及方法。而在runtime时执行的流程是,首先,通过obj的isa指针指向它的class;现在class的缓存中查找是否有这个方法,然后去方法列表中找这个方法,如果找不到就一直往superclass中去找这个方法。找到之后,就去执行它的实现IMP,并把这个方法存到缓存中,下次直接去缓存中拿。(方法名作为key,imp作为value存储)。
类方法的消息传递: 在元类中一层层的区寻找;
消息转发:
+ (BOOL)resolveClassMethod:(SEL)sel;
+ (BOOL)resolveInstanceMethod:(SEL)sel;
//后两个方法需要转发到其他的类处理
- (id)forwardingTargetForSelector:(SEL)aSelector;
- (void)forwardInvocation:(NSInvocation *)anInvocation;复制代码
第一个方法是当你调用一个不存在的类方法的时候,会调用这个方法,默认返回NO,你可以加上自己的处理然后返回YES。
第二个方法和第一个方法相似,只不过处理的是实例方法。
第三个方法是将你调用的不存在的方法重定向到一个其他声明了这个方法的类,只需要你返回一个有这个方法的target。
第四个方法是将你调用的不存在的方法打包成NSInvocation传给你。做完你自己的处理后,调用invokeWithTarget:方法让某个target触发这个方法。
- 动态方法解析:
首先,Objective-C运行时会调用 +resolveInstanceMethod:或者 +resolveClassMethod:,让你有机会提供一个函数实现。如果你添加了函数并返回YES, 那运行时系统就会重新启动一次消息发送的过程。
- (void)viewDidLoad {
[super viewDidLoad];
// Do any additional setup after loading the view, typically from a nib.
//执行foo函数
[self performSelector:@selector(foo:)];
}
+ (BOOL)resolveInstanceMethod:(SEL)sel {
if (sel == @selector(foo:)) {//如果是执行foo函数,就动态解析,指定新的IMP
class_addMethod([self class], sel, (IMP)fooMethod, "v@:");
return YES;
}
return [super resolveInstanceMethod:sel];
}
void fooMethod(id obj, SEL _cmd) {
NSLog(@"Doing foo");//新的foo函数
}
- 备用接受者:
如果目标对象实现了-forwardingTargetForSelector:,Runtime 这时就会调用这个方法,给你把这个消息转发给其他对象的机会。
#import "ViewController.h"
#import "objc/runtime.h"
@interface Person: NSObject
@end
@implementation Person
- (void)foo {
NSLog(@"Doing foo");//Person的foo函数
}
@end
@interface ViewController ()
@end
@implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
// Do any additional setup after loading the view, typically from a nib.
//执行foo函数
[self performSelector:@selector(foo)];
}
+ (BOOL)resolveInstanceMethod:(SEL)sel {
return YES;//返回YES,进入下一步转发
}
- (id)forwardingTargetForSelector:(SEL)aSelector {
if (aSelector == @selector(foo)) {
return [Person new];//返回Person对象,让Person对象接收这个消息
}
return [super forwardingTargetForSelector:aSelector];
}
@end
- 完整消息转发:
如果在上一步还不能处理未知消息,则唯一能做的就是启用完整的消息转发机制了。 首先它会发送-methodSignatureForSelector:消息获得函数的参数和返回值类型。如果-methodSignatureForSelector:返回nil,Runtime则会发出 -doesNotRecognizeSelector:消息,程序这时也就挂掉了。如果返回了一个函数签名,Runtime就会创建一个NSInvocation对象并发送 -forwardInvocation:消息给目标对象。
#import "ViewController.h"
#import "objc/runtime.h"
@interface Person: NSObject
@end
@implementation Person
- (void)foo {
NSLog(@"Doing foo");//Person的foo函数
}
@end
@interface ViewController ()
@end
@implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
// Do any additional setup after loading the view, typically from a nib.
//执行foo函数
[self performSelector:@selector(foo)];
}
+ (BOOL)resolveInstanceMethod:(SEL)sel {
return YES;//返回YES,进入下一步转发
}
- (id)forwardingTargetForSelector:(SEL)aSelector {
return nil;//返回nil,进入下一步转发
}
- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector {
if ([NSStringFromSelector(aSelector) isEqualToString:@"foo"]) {
return [NSMethodSignature signatureWithObjCTypes:"v@:"];//签名,进入forwardInvocation
}
return [super methodSignatureForSelector:aSelector];
}
- (void)forwardInvocation:(NSInvocation *)anInvocation {
SEL sel = anInvocation.selector;
Person *p = [Person new];
if([p respondsToSelector:sel]) {
[anInvocation invokeWithTarget:p];
}
else {
[self doesNotRecognizeSelector:sel];
}
}
@end
源码
oc中的class实际上是一个指向objc_class结构体的指针。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;
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;
struct objc_method {
SEL method_name OBJC2_UNAVAILABLE;
char *method_types OBJC2_UNAVAILABLE;
IMP method_imp OBJC2_UNAVAILABLE;
}
* SEL method_name 方法名
* char *method_types 方法类型
* IMP method_imp 方法实现 //指向方法具体实现
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]);
}
//获取方法列表
Method *methodList = class_copyMethodList([self class], &count);
for (unsigned int i; i<count; i++) {
Method method = methodList[i];
NSLog(@"method---->%@", NSStringFromSelector(method_getName(method)));
}
//获取成员变量列表
Ivar *ivarList = class_copyIvarList([self class], &count);
for (unsigned int i; i<count; i++) {
Ivar myIvar = ivarList[i];
const char *ivarName = ivar_getName(myIvar);
NSLog(@"Ivar---->%@", [NSString stringWithUTF8String:ivarName]);
}
//获取协议列表
__unsafe_unretained Protocol **protocolList = class_copyProtocolList([self class], &count);
for (unsigned int i; i<count; i++) {
Protocol *myProtocal = protocolList[i];
const char *protocolName = protocol_getName(myProtocal);
NSLog(@"protocol---->%@", [NSString stringWithUTF8String:protocolName]);
}
Objc_class结构体里保存了isa指针、父类、类名、版本、实力大小、实例变量列表、方法列表、缓存、协议列表。这个结构体重的isa指针指向元类,元类也是一个objc_class结构体,保存了创建类对象以及类方法所需的所有信息。

- 实例对象的isa指针指向相应的类对象,类对象的isa指针指向元类,super_class指针指向了父类的类对象
- 元类中的super_class指针指向父类的元类,元类的isa指针指向根元类;(比方说nsobject提下,根元类nsobjec的元类)
- NSObject的super_class为nil,isa指针指向NSObject的元类
- NSObject的元类的isa指向自己,super_class指向相应的类对象;
应用场景:
- 关联对象,给分类增加属性(分类是不能自定义属性和变量的。通过关联对象实现给分类增加属性)
//关联对象
void objc_setAssociatedObject(id object, const void *key, id value, objc_AssociationPolicy policy)
//获取关联的对象
id objc_getAssociatedObject(id object, const void *key)
//移除关联的对象
void objc_removeAssociatedObjects(id object)
#import "ViewController.h"
#import "objc/runtime.h"
@interface UIView (DefaultColor)
@property (nonatomic, strong) UIColor *defaultColor;
@end
@implementation UIView (DefaultColor)
@dynamic defaultColor;
static char kDefaultColorKey;
- (void)setDefaultColor:(UIColor *)defaultColor {
objc_setAssociatedObject(self, &kDefaultColorKey, defaultColor, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}
- (id)defaultColor {
return objc_getAssociatedObject(self, &kDefaultColorKey);
}
@end
@interface ViewController ()
@end
@implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
// Do any additional setup after loading the view, typically from a nib.
UIView *test = [UIView new];
test.defaultColor = [UIColor blackColor];
NSLog(@"%@", test.defaultColor);
}
@end
2、方法添加
class_addMethod([self class], sel, (IMP)fooMethod, "v@:");
事例:消息转发时的方法解析
3、方法替换
@implementation ViewController
+ (void)load {
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
Class class = [self class];
SEL originalSelector = @selector(viewDidLoad);
SEL swizzledSelector = @selector(jkviewDidLoad);
Method originalMethod = class_getInstanceMethod(class,originalSelector);
Method swizzledMethod = class_getInstanceMethod(class,swizzledSelector);
//judge the method named swizzledMethod is already existed.
BOOL didAddMethod = class_addMethod(class, originalSelector, method_getImplementation(swizzledMethod), method_getTypeEncoding(swizzledMethod));
// if swizzledMethod is already existed.
if (didAddMethod) {
class_replaceMethod(class, swizzledSelector, method_getImplementation(originalMethod), method_getTypeEncoding(originalMethod));
}
else {
method_exchangeImplementations(originalMethod, swizzledMethod);
}
});
}
- (void)jkviewDidLoad {
NSLog(@"替换的方法");
[self jkviewDidLoad];
}
- (void)viewDidLoad {
NSLog(@"自带的方法");
[super viewDidLoad];
}
@end
swizzling应该只在+load中完成。 在 Objective-C 的运行时中,每个类有两个方法都会自动调用。+load 是在一个类被初始装载时调用,+initialize 是在应用第一次调用该类的类方法或实例方法前调用的。两个方法都是可选的,并且只有在方法被实现的情况下才会被调用。
swizzling应该只在dispatch_once 中完成,由于swizzling 改变了全局的状态,所以我们需要确保每个预防措施在运行时都是可用的。原子操作就是这样一个用于确保代码只会被执行一次的预防措施,就算是在不同的线程中也能确保代码只执行一次。Grand Central Dispatch 的 dispatch_once满足了所需要的需求,并且应该被当做使用swizzling 的初始化单例方法的标准。
4、KVO的实现原理
KVO的实现依赖runtime
5、热更新(JSPatch)使用了rumtime是的消息转发;
JSPatch 是一个 iOS 动态更新框架,只需在项目中引入极小的引擎,就可以使用 JavaScript 调用任何 Objective-C 原生接口,获得脚本语言的优势:为项目动态添加模块,或替换项目原生代码动态修复 bug。
6、实现NSCoding的自动归档和自动解档
原理描述:用runtime提供的函数遍历Model自身所有属性,并对属性进行encode和decode操作。 核心方法:在Model的基类中重写方法:
- (id)initWithCoder:(NSCoder *)aDecoder {
if (self = [super init]) {
unsigned int outCount;
Ivar * ivars = class_copyIvarList([self class], &outCount);
for (int i = 0; i < outCount; i ++) {
Ivar ivar = ivars[i];
NSString * key = [NSString stringWithUTF8String:ivar_getName(ivar)];
[self setValue:[aDecoder decodeObjectForKey:key] forKey:key];
}
}
return self;
}
- (void)encodeWithCoder:(NSCoder *)aCoder {
unsigned int outCount;
Ivar * ivars = class_copyIvarList([self class], &outCount);
for (int i = 0; i < outCount; i ++) {
Ivar ivar = ivars[i];
NSString * key = [NSString stringWithUTF8String:ivar_getName(ivar)];
[aCoder encodeObject:[self valueForKey:key] forKey:key];
}
}
7、实现字典和模型的自动转换(MJExtension)
原理描述:用runtime提供的函数遍历Model自身所有属性,如果属性在json中有对应的值,则将其赋值。 核心方法:在NSObject的分类中添加方法:
- (instancetype)initWithDict:(NSDictionary *)dict {
if (self = [self init]) {
//(1)获取类的属性及属性对应的类型
NSMutableArray * keys = [NSMutableArray array];
NSMutableArray * attributes = [NSMutableArray array];
/*
* 例子
* name = value3 attribute = T@"NSString",C,N,V_value3
* name = value4 attribute = T^i,N,V_value4
*/
unsigned int outCount;
objc_property_t * properties = class_copyPropertyList([self class], &outCount);
for (int i = 0; i < outCount; i ++) {
objc_property_t property = properties[i];
//通过property_getName函数获得属性的名字
NSString * propertyName = [NSString stringWithCString:property_getName(property) encoding:NSUTF8StringEncoding];
[keys addObject:propertyName];
//通过property_getAttributes函数可以获得属性的名字和@encode编码
NSString * propertyAttribute = [NSString stringWithCString:property_getAttributes(property) encoding:NSUTF8StringEncoding];
[attributes addObject:propertyAttribute];
}
//立即释放properties指向的内存
free(properties);
//(2)根据类型给属性赋值
for (NSString * key in keys) {
if ([dict valueForKey:key] == nil) continue;
[self setValue:[dict valueForKey:key] forKey:key];
}
}
return self;
}
Q1 为什么要有元类:
类和实例对象本身都是对象 实例对象的isa指针指向描述这个对象的类,而类对象的isa指针指向描述这个类本身的类,就是元类;
Q2、为什么不支持同名不同参数函数?
因为method在内部存储的时候只存储了方法名,没有参数。所以不能区分
Q3、@property @synthesize @dynamic关系
@property的话,编译器会生成相应的实例变量和getter\setter方法 @synthesize表示如果没有手动实现setter和getter,编译器会自动加上这两个方法。
Q4、 objc在向一个对象发送消息时, 发生了什么? 1.根据对象的isa指针找到类对象id, 在查询类对象里面的methodLists方法函数列表 2.如果没有在好到, 在沿着superClass, 寻找父类,再在父类methodLists方法列表里面查询 3.最终找到SEL, 根据id和SEL确认IMP(指针函数), 在发送消息.
Q5、问题: 什么时候会报unrecognized selector错误? iOS有哪些机制来避免走到这一步?
1.当发送消息的时候, 我们会根据类里面的methodLists列表去查询我们要动用的SEL, 当查询不到的时候, 我们会一直沿着父类查询
2.当最终查询不到的时候我们会报unrecognized selector错误, 当系统查询不到方法的时候, 会调用+(BOOL)resolveInstanceMethod:(SEL)sel动态解释的方法来给我一次机会来添加, 调用不到的方法.
3.或者我们可以再次使用-(id)forwardingTargetForSelector:(SEL)aSelector重定向的方法来告诉系统,该调用什么方法,一来保证不会崩溃.
Q6、能否向编译后得到的类中增加实例变量?能否向运行时创建的类中添加实例变量? 为什么?
1.不能向编译后得到的类增加实例变量.
2.能向运行时创建的类中添加实例变量.
解释:
- 编译后的类已经注册在runtime中,类结构体中的objc_ivar_list实例变量的链表和instance_size实例变量的内存大小已经确定,runtime会调用class_setvarlayout或class_setWeaklvarLayout来处理strong``weak引用.所以不能向存在的类中添加实例变量.\
- 运行时创建的类是可以添加实例变量,调用class_addIvar函数. 但是的在调用objc_allocateClassPair之后,objc_registerClassPair之前,原因同上.
Q7 runtime如何实现weak变量的自动置nil?
1.runtime对注册的类, 会进行布局,对于weak对象会放入一个hash表中。 用weak指向的对象内存地址作为key,当此对象的引用计数为0的时候会dealloc.
2.假如weak指向的对象内存地址是A,那么就会以A为键, 在这个weak表中搜索,找到所有以A为键的weak对象,从而设置为nil.
Q8、给类添加一个属性后,在类结构体里哪些元素会发生变化
1.instance_size :实例的内存大小.
2.objc_ivar_list *ivars : 属性列表.