Objective-C Runtime

RunTime Header
Objective-C 是一门基于运行时的编程语言,这意味着所有方法、变量、类之间的链接,都会推迟到应用实际运行的最后一刻才会建立。这将给开发人员极高的灵活性,因为我们可以运行时修改这些链接。

isa指针

Objective-C 是一门面向对象的编程语言,每一个对象都是一个类的实例。在 Objective-C 中 NSObject 是所有类的root class。

在 Xcode 中按Shift + Command + O可以快速打开文件,输入 objc.h、 NSObject.h、 runtime.h查看头文件中定义

NSObject 中包含Class isa:

1
2
3
4
5
6
7
//NSObject.h
@interface NSObject <NSObject> {
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wobjc-interface-ivars"
Class isa OBJC_ISA_AVAILABILITY;
#pragma clang diagnostic pop
}

Class 定义如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
//objc.h
/// An opaque type that represents an Objective-C class.
typedef struct objc_class *Class;

/// Represents an instance of a class.
struct objc_object {
Class _Nonnull isa OBJC_ISA_AVAILABILITY;
};
/// A pointer to an instance of a class.
typedef struct objc_object *id;
#endif

/// An opaque type that represents a method selector.
typedef struct objc_selector *SEL;

/// A pointer to the function of a method implementation.
#if !OBJC_OLD_DISPATCH_PROTOTYPES
typedef void (*IMP)(void /* id, SEL, ... */ );
#else
typedef id _Nullable (*IMP)(id _Nonnull, SEL _Nonnull, ...);
#endif
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
//runtime.h
/// An opaque type that represents a method in a class definition.
typedef struct objc_method *Method;

/// An opaque type that represents an instance variable.
typedef struct objc_ivar *Ivar;

/// An opaque type that represents a category.
typedef struct objc_category *Category;

/// An opaque type that represents an Objective-C declared property.
typedef struct objc_property *objc_property_t;

struct objc_class {
Class _Nonnull isa OBJC_ISA_AVAILABILITY;

#if !__OBJC2__
Class _Nullable super_class;
const char * _Nonnull name;
long version;
long info;
long instance_size;
struct objc_ivar_list * _Nullable ivars; //成员列表指针
struct objc_method_list * _Nullable * _Nullable methodLists; //方法指针
struct objc_cache * _Nonnull cache; //方法搜索缓存指针
struct objc_protocol_list * _Nullable protocols; //协议列表
#endif

} OBJC2_UNAVAILABLE;

可以看到 methodLists 是一个二维指针,通过修改该指针指向的指针的值,就可以实现动态地为某一个类增加成员方法。这也是 Category 实现的原理。
cache 用来优化方法调用查找的性能。每当实例对象接收到一个消息时,优先在 cache 中查找。Runtime 系统会把被调用的方法存到 cache 中,避免每次都要遍历(子类->父类->….->NSObject)所有methodLists。

类本身也是一个对象,他是元类(metaclass)的实例。元类保存了类方法的列表。当一个类方法被调用时,元类会首先查找它本身是否有该类方法的实现,如果没有,则该元类会向它的父类查找该方法,直到一直找到继承链的头。

元类 (metaclass) 也是一个对象,那么元类的 isa 指针又指向哪里呢?为了设计上的完整,所有的元类的 isa 指针都会指向一个根元类 (root metaclass)。根元类 (root metaclass) 本身的 isa 指针指向自己,这样就行成了一个闭环。

下面这个图很好的说明这层关系:
Runtime isa

Messaging

objc_msgSend

编译后 Objective-C 函数调用的语法都会被翻译成一个 C 的函数调用 - objc_msgSend()

1
[receiver message]

转换后:

1
objc_msgSend(receiver, selector)

带参数情况:

1
objc_msgSend(receiver, selector, arg1, arg2, ...)

Runtime Messaging

消息传递主要是依赖 isa 指针,通过 isa 指针,objc_msgSend 从子类到根类一直遍历直到找到对应的 selectore。所以说方法是动态绑定到消息的,只有在运行时才确定是哪个方法。

方法中的隐藏参数

当 objc_msgSend 找到方法对应的实现时,它将直接调用该方法实现,并将消息中所有的参数都传递给方法实现,同时它还将传递两个隐藏的参数:

  • 接收消息的对象(也就是 self 指向的内容)
  • 方法选择器(_cmd指向的内容)

这两个参数在编译过程中自动插入,所以我们在方法中可以使用 self 关键字来引用实力本身。

super

为什么[self class][super class]返回值相同???

而当方法中的 super 关键字接收到消息时,编译器会创建一个 objc_super 结构体:

1
struct objc_super { id receiver; Class class; };

这个结构体指明了消息应该被传递给特定超类的定义。但 receiver 仍然是 self 本身。
当我们通过[super class]获取超类时,编译器将调用obj_msgSendSuper(struct objc_super *super, SEL op, ...),因为只有在 NSObject 类才能找到 class 方法,然后 class 方法调用 object_getClass(),接着调用objc_msgSend(objc_super->receiver, @selector(class)),传入的第一个参数是 self,与调用[self class]相同,所以我们得到的永远都是 self 的类型。

获取方法地址

我们可以避开消息绑定直接获取方法的地址并调用。如果需要大量重复调用某个方法,避免消息发送会更高效:

1
2
3
4
5
6
7
void (*setter)(id, SEL, BOOL);
int i;

setter = (void (*)(id, SEL, BOOL))[target
methodForSelector:@selector(setFilled:)];
for ( i = 0 ; i < 1000 ; i++ )
setter(targetList[i], @selector(setFilled:), YES);

动态方法解析

我们可以动态的提供一个方法的实现。如可以用@dynamic关键字修饰类的属性:

1
@dynamic propertyName;

这告诉编译器不自动生成 getter/setter 方法,由我们自己实现或者运行时动态绑定。
我们可以通过分别重载resolveInstanceMethod:resolveClassMethod:方法分别添加实例方法实现和类方法实现。当 Runtime 系统在 Cache 和方法列表中(包括超类)找不到要执行的方法时,Runtime 在消息转发之前会调用resolveInstanceMethod:resolveClassMethod:来查询是否支持该方法。我们可以用class_addMethod函数完成方法的添加:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
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

如果要对特定方法进行消息转发,resolveInstanceMethod:resolveClassMethod:对该方法的返回值是NO。
v@:对应返回值void(v),参数self(@)、_cmd(:)

消息转发

Runtime Forwarding

重定向

这一步,可以把消息转发给其他对象,若forwardingTargetForSelector:返回 nil 或 self 则进入后面的转发流程:

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

转发

转发需要实现两个方法:

1
2
- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector ;
- (void)forwardInvocation:(NSInvocation *)anInvocation ;

methodSignatureForSelector:用来返回方法签名,用于生成 NSInvocation ,forwardInvocation:方法比forwardingTargetForSelector:更为灵活,它也可以将消息转给其他对象,还能够对消息进行修改或者“吃掉”消息,并且可以转发给多个对象

转发和多继承

Multiple Inderitance
消息转发弥补了 Objective-C 不支持多继承的性质,也避免了因为多继承导致单个类变得臃肿复杂。

转发和继承

尽管转发很像继承,但是 NSObject 类不会将两者混淆。像respondsToSelector:isKindOfClass:这类方法只会考虑继承体系,不会考虑转发链

Associated Objects

使用 Associated 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 );

举例:
NSObject+AssociatedObject.h

1
2
3
@interface NSObject (AssociatedObject)
@property (nonatomic, strong) id associatedObject;
@end

NSObject+AssociatedObject.m

1
2
3
4
5
6
7
8
9
10
11
@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));
}

Method Swizzling

我们在拥有源码修改权限的时候可以使用消息转发,若我们无法触碰类的源码,就可以使用 Method Swizzling:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
#import <objc/runtime.h>

@implementation UIViewController (Tracking)

+ (void)load {
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
Class class = [self class];

SEL originalSelector = @selector(viewWillAppear:);
SEL swizzledSelector = @selector(xxx_viewWillAppear:);

Method originalMethod = class_getInstanceMethod(class, originalSelector);
Method swizzledMethod = class_getInstanceMethod(class, swizzledSelector);

// When swizzling a class method, use the following:
// Class class = object_getClass((id)self);
// ...
// Method originalMethod = class_getClassMethod(class, originalSelector);
// Method swizzledMethod = class_getClassMethod(class, swizzledSelector);

BOOL didAddMethod =
class_addMethod(class,
originalSelector,
method_getImplementation(swizzledMethod),
method_getTypeEncoding(swizzledMethod));

if (didAddMethod) {
class_replaceMethod(class,
swizzledSelector,
method_getImplementation(originalMethod),
method_getTypeEncoding(originalMethod));
} else {
method_exchangeImplementations(originalMethod, swizzledMethod);
}
});
}

#pragma mark - Method Swizzling

- (void)xxx_viewWillAppear:(BOOL)animated {
[self xxx_viewWillAppear:animated];
NSLog(@"viewWillAppear: %@", self);
}

@end

最后xxx_viewWillAppear:方法的定义看似是递归调用引发死循环,其实不会。因为[self xxx_viewWillAppear:animated]消息会动态找到xxx_viewWillAppear:方法的实现,而它的实现已经被我们与viewWillAppear:方法实现进行了互换,所以这段代码不仅不会死循环。

为保证安全性:

Swizzling should always be done in +load.

Swizzling should always be done in a dispatch_once.

[NSObject class][object class]的区别:

1
2
3
4
5
6
7
+ (Class)class {
return self;
}

- (Class)class {
return object_getClass(self);
}

所以Swizzling一个类方法需要用object_getClass((id)self)来获取元类

+load+ initialize

类最初加载的时候会调用+load,而+ initialize只在系统第一次调用该类的方法是才会调用。

方法替换

如果类中不存在要替换的方法,那就先用class_addMethodclass_replaceMethod函数添加和替换两个方法的实现;如果类中已经有了想要替换的方法,那么就调用method_exchangeImplementations函数交换了两个方法的 IMP。类中没有想被替换实现的原方法时,class_replaceMethod相当于直接调用class_addMethod向类中添加该方法的实现。

Selectors,Methods,Implementations

  • Selector (typedef struct objc_selector *SEL):其实就是个映射到方法的C字符串
  • Method (typedef struct objc_method *Method):代表类中的某个方法
  • Implementation (typedef id (*IMP)(id, SEL, …)):指向了方法的实现,本质上是一个函数指针

KVO

KVO 的实现也依赖于 Objective-C 强大的 Runtime 。Apple 的文档有简单提到过 KVO 的实现:

Automatic key-value observing is implemented using a technique called isa-swizzling… When an observer is registered for an attribute of an object the isa pointer of the observed object is modified, pointing to an intermediate class rather than at the true class …

添加KVO之前的isa指针:

1
ViewController

添加之后变成了:

1
NSKVONotifying_ViewController

但是[self class]不变。

成员变量和属性

成员变量和属性就是下面两个结构体,同时Runtime提供了接口对变量和属性进行查询、修改、获取名称等一系列操作。接口可参考官方文档Objective-C Runtime Reference

成员变量

1
typedef struct objc_ivar *Ivar;

属性

1
typedef struct objc_property *objc_property_t;

参考链接

Cotin Yang wechat
欢迎订阅我的微信公众号 CotinDev
小小地鼓励一下吧~😘