ObjectiveC Runtime 笔记

1
#import "objc/runtime.h"

Runtime术语

objc_msgSend的方法定义:

1
id objc_msgSend(id self, SEL op, ...);

SEL

1
typedef struct objc_selector *SEL;

SEL即selector,本质是方法名的常量字符串,所以同名方法就是同一个selector

id

1
typedef struct objc_object *id;

而objc_object为:

1
struct objc_object { Class isa; };

Class

1
typedef struct objc_class *Class;
1
2
3
4
5
6
7
8
9
10
11
12
13
14
struct objc_class {
Class isa OBJC_ISA_AVAILABILITY;
#if !__OBJC2__
Class super_class OBJC2_UNAVAILABLE;
const char *name OBJC2_UNAVAILABLE;
long version OBJC2_UNAVAILABLE;
long info OBJC2_UNAVAILABLE;
long instance_size OBJC2_UNAVAILABLE;
struct objc_ivar_list *ivars OBJC2_UNAVAILABLE;
struct objc_method_list **methodLists OBJC2_UNAVAILABLE;
struct objc_cache *cache OBJC2_UNAVAILABLE;
struct objc_protocol_list *protocols OBJC2_UNAVAILABLE;
#endif
} OBJC2_UNAVAILABLE;
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
struct objc_ivar_list {
int ivar_count OBJC2_UNAVAILABLE;
#ifdef __LP64__
int space OBJC2_UNAVAILABLE;
#endif
/* variable length structure */
struct objc_ivar ivar_list[1] OBJC2_UNAVAILABLE;
}
struct objc_method_list {
struct objc_method_list *obsolete OBJC2_UNAVAILABLE;

int method_count OBJC2_UNAVAILABLE;
#ifdef __LP64__
int space OBJC2_UNAVAILABLE;
#endif
/* variable length structure */
struct objc_method method_list[1] OBJC2_UNAVAILABLE;
}

Ivar

1
typedef struct objc_ivar *Ivar;
1
2
3
4
5
6
7
8
struct objc_ivar {
char *ivar_name OBJC2_UNAVAILABLE;
char *ivar_type OBJC2_UNAVAILABLE;
int ivar_offset OBJC2_UNAVAILABLE;
#ifdef __LP64__
int space OBJC2_UNAVAILABLE;
#endif
}

Method

Method代表类中某个方法:

1
typedef struct objc_method *Method;
1
2
3
4
5
struct objc_method {
SEL method_name OBJC2_UNAVAILABLE;
char *method_types OBJC2_UNAVAILABLE;
IMP method_imp OBJC2_UNAVAILABLE;
}
* 方法名SEL本质是个常量字符串 * 方法类型是个char指针,存储着方法的参数和返回值类型编码 * 方法实现IMP是个函数指针

IMP

1
typedef id (*IMP)(id, SEL, ...);

该函数指针指向的方法与objc_msgSend函数类型相同

Cache

1
typedef struct objc_cache *Cache
1
2
3
4
5
struct objc_cache {
unsigned int mask /* total = mask + 1 */ OBJC2_UNAVAILABLE;
unsigned int occupied OBJC2_UNAVAILABLE;
Method buckets[1] OBJC2_UNAVAILABLE;
};

Runtime会把被调用的方法存到Cache中,为方法调用进行性能优化。当实例对象接收到一个消息时先在Cache中查找,若未命中再去isa指向的类的方法列表中遍历查找。

Property

1
2
typedef struct objc_property *Property;
typedef struct objc_property *objc_property_t; //这个更常用

可以通过class_copyPropertyListprotocol_copyPropertyList方法来获取类和协议中的属性,返回类型是指向objc_property_t数组的指针:

1
2
objc_property_t *class_copyPropertyList(Class cls, unsigned int *outCount)
objc_property_t *protocol_copyPropertyList(Protocol *proto, unsigned int *outCount)
可以用property_getName函数来查找属性名称:
1
const char *property_getName(objc_property_t property)
可以用class_getPropertyprotocol_getProperty通过给出的名称来在类和协议中获取属性的引用:
1
2
objc_property_t class_getProperty(Class cls, const char *name)
objc_property_t protocol_getProperty(Protocol *proto, const char *name, BOOL isRequiredProperty, BOOL isInstanceProperty)
可以用property_getAttributes函数来获得属性的名称和@encode类型字符串:
1
const char *property_getAttributes(objc_property_t property)
把上面的代码放一起,就能从一个类中获取它的属性:
1
2
3
4
5
6
7
id LenderClass = objc_getClass("Lender");
unsigned int outCount, i;
objc_property_t *properties = class_copyPropertyList(LenderClass, &outCount);
for (i = 0; i < outCount; i++) {
objc_property_t property = properties[i];
fprintf(stdout, "%s %s\n", property_getName(property), property_getAttributes(property));
}


消息传递

objc_msgSend的流程

  1. 检测selector可不可以忽略,比如Mac开发中开启GC就会忽略retain/release调用
  2. 检测target是不是nil,ObjC的会忽略向nil发的消息
  3. 在target的Class中根据selector查找IMP
    1. 先从cache里面找,找得到就跳到对应的函数去执行
    2. cache里找不到就去找Class的方法列表
    3. 还找不到,就沿着继承链找超类的方法列表,直到NSObject类为止
    4. 实在找不到,进入动态方法解析和消息转发机制

方法的隐藏参数

每个方法都有两个隐藏参数:self为接收者对象,_cmd为方法本身的selector

获取方法地址

NSObject中有个methodForSelector:实例方法,可以用来获取selector对应的IMP:

1
2
3
void (*setter)(id, SEL, BOOL);
setter = (void (*)(id, SEL, BOOL))[target methodForSelector:@selector(setFilled:)];
for (int i = 0; i < 1000; i++) setter(targetList[i], @selector(setFilled:), YES);
这种做法很少用,除非需要持续大量重复调用某方法,才节省些消息传递开销

动态方法解析

@dynamic修饰属性:

1
@dynamic propertyName;
编译器不会为我们生成setPropertyName:和propertyName方法。当Runtime在Cache和继承链的所有方法分发列表中都找不到方法时,会调用resolveInstanceMethod:resolveClassMethod:来给程序员一次动态添加方法实现的机会,需要用class_addMethod函数向某类添加方法实现:
1
2
3
4
5
6
7
8
9
10
11
12
void dynamicMethodIMP(id self, SEL _cmd) {
// implementation ....
}
@implementation MyClass
+ (BOOL)resolveInstanceMethod:(SEL)aSEL {
if (aSEL == @selector(resolveThisMethodDynamically)) {
class_addMethod([self class], aSEL, (IMP) dynamicMethodIMP, "v@:");
return YES;
}
return [super resolveInstanceMethod:aSEL];
}
@end
上述例子为MyClass添加实例方法resolveThisMethodDynamically,其中 “v@:” 表示返回值和参数,参见Type Encoding

动态方法解析在消息转发机制之前执行,要让消息进入转发机制,就让resolveInstanceMethod:返回NO

消息转发

重定向

在消息转发之前,还有一次偷梁换柱的机会,通过重载forwardingTargetForSelector:可以替换消息的接收者:

1
2
3
4
5
6
- (id)forwardingTargetForSelector:(SEL)aSelector {
if(aSelector == @selector(mysteriousMethod:)){
return alternateObject;
}
return [super forwardingTargetForSelector:aSelector];
}

转发

在对象无法正常响应消息时才会调用forwardInvocation:,重载这个方法来定义任何你想要的转发逻辑:

1
2
3
4
5
6
- (void)forwardInvocation:(NSInvocation *)anInvocation {
if ([someOtherObject respondsToSelector:[anInvocation selector]])
[anInvocation invokeWithTarget:someOtherObject];
else
[super forwardInvocation:anInvocation];
}
forwardInvocation:之前,会先调用methodSignatureForSelector:来生成这里的anInvocation参数,所以在重写forwardInvocation:之前还要重写methodSignatureForSelector:,否则会因找不到signature而抛异常

转发可以实现类似多继承的效果,把消息转发给另一个对象,就好像借用或“继承”了另一个对象的方法

替代者对象

尽管转发能模拟多继承,但respondsToSelector:isKindOfClass:等方法只会考虑继承链,不会考虑转发链。

如果处于某种意图需要模拟继承,得重载respondsToSelector:isKindOfClass:instancesRespondToSelector:来加入转发逻辑:

1
2
3
4
5
6
7
8
9
10
- (BOOL)respondsToSelector:(SEL)aSelector {
if ([super respondsToSelector:aSelector])
return YES;
else {
/* Here, test whether the aSelector message can *
* be forwarded to another object and whether that *
* object can respond to it. Return YES if it can. */
}
return NO;
}
如果使用了协议,也得重载conformsToProtocol:

同样地,转发还得重载methodSignatureForSelector:,比如一个对象为给它的替代者对象转发消息,需要实现:

1
2
3
4
5
6
7
- (NSMethodSignature*)methodSignatureForSelector:(SEL)selector {
NSMethodSignature* signature = [super methodSignatureForSelector:selector];
if (!signature) {
signature = [surrogate methodSignatureForSelector:selector];
}
return signature;
}


Assiciate Objects

给category添加变量涉及下面三个函数:

1
2
3
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);
其中用来作key的是个内存地址,可直接用某个selector:
1
2
3
4
// NSObject+AssociatedObject.h
@interface NSObject (AssociatedObject)
@property (nonatomic, strong) id associatedObject;
@end
1
2
3
4
5
6
7
8
9
10
// NSObject+AssociatedObject.m
@implementation NSObject (AssociatedObject)
@dynamic associatedObject;

- (void)setAssociatedObject:(id)object {
objc_setAssociatedObject(self, @selector(associatedObject), object, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}
- (id)associatedObject {
return objc_getAssociatedObject(self, @selector(associatedObject));
}


几个细节

[super class] == [self class]

[self class]会转成

1
id objc_msgSend(id self, SEL op, ...)
向self发消息,从self的方法列表开始,沿继承链向上找@selector(class)的实现。由于没重载过这个方法,最终调用NSObject中的实现。

[super class]会转成

1
id objc_msgSendSuper(struct objc_super *super, SEL op, ...)

1
2
3
4
struct objc_super {
__unsafe_unretained id receiver;
__unsafe_unretained Class super_class;
};

其中receiver就是self,super_class就是实际超类。同样向self发消息,但从超类的方法列表开始,沿继承链向上找@selector(class)的实现。由于没重载过这个方法,最终调用NSObject中的实现。

Object & Class & MetaClass

Class也是个对象,Class的类叫MetaClass。有几个特点:

  • MetaClass的superclass继承链,和对应的原本Class的superclass继承链类似;只有根类特殊:NSObject的superclass为nil,MetaClass-of-NSObject的superclass为NSObject
  • Class的isa指向自己的MetaClass,所有MetaClass的isa指向MetaClass-of-NSObject

Category的加载

Category中的方法在加载时被加入对应的方法列表中:实例方法加入Class的方法列表, 类方法加入MetaClass的方法列表

如果有多个Category,则按加载的逆序把方法加入最终方法列表。比如某类O,按序加载了Category A和B,则最终方法列表是:[B中的方法, A中的方法, O中的方法]


参考