Objective-C Runtime Tricks

说到oc的动态特性尝尝会牵扯到objc 的动态运行时(runtime),在日常的iOS开发中很少和深层次runtime打交道,除非你是个越狱开发者。在一些特定情况下为了完成一些特定功能(如绕过appStore更新app功能)就完全需要了解或者说深入研究一下Objective-C的runtime特性了。

iOS和C语言中关于对象,消息,方法,函数的称谓或有歧义,本文采用如下称谓:

消息:Message
对象:Object
方法;Method
函数:Function
类: Class

##The Runtime
Objective-C是一门非常简单的语言,它95%都是C,只是在C语言上添加了一些关键词和语法(其实C语言真的很复杂). 除了C语言的一些特性,Objective-C的真正灵魂在于它的runtime,而runtime的核心在于message sending,下面慢慢道来。

###Messages
当想要执行一段代码的时候脑海里立马想到的就是调用对象的方法,一些语言允许编译器在调用着和被调用着之间进行一些类型检查和错误处理等工作。Messages和Method并不是一对一的关系。比如在Objective-C中对象持有很多Messages,可能这些Messages只是用一个C语言的Method来实现的。

在Objective-C中发送消息是由objc_msgSend()来完成的,实际上OC编译的时候会把所有的消息发送代码转化为objc_msgSend(). 这个C类型的方法是OC runtime 的核心,参数包含一个target,一个selector 和arguments列表。举个例子:

1
2
array insertObject:foo atIndex:5];
objc_msgSend(array, @selector(insertObject:atIndex:), foo, 5);

###Objects, Classes & Metaclasses

这个知识点大神的一篇文章已经写的很详细了,参考 Objective-C对象模型及应用 (2013.11.15修改)

###Methods, Selectors and IMPs

我们知道runtime给object发送message,而object的class持有一个method列表,messages和methods是怎么对应的呢?method有事怎么具体实现的呢?

回答第一个问题非常简单,method list 实际上是一字典的形式来存储的,selectors 是keys,IMPs 是values.一个IMP指向一个内存地址,而这个地址是method具体实现的物理位置。重要的的是,哪个selector和哪个IMP进行连接是程序运行的时候决定的,而不是编译的时候。其实这个特性留给了iOS hacker很大的可玩空间。

IMP 是个指向C函数的特殊指针,这个函数在编译时由OC 的消息自动转化而来:

1
2
- (id)doSomethingWithInt:(int)aInt {}
id doSomethingWithInt(id self, SEL _cmd, int aInt) {}

###Other Runtime Functionality
现在我们知道了objects, classes, selectors, IMPs and message sending,那么runtime真正能够干什么呢?其实它有两个用处:

1.用于object,class的创建、修改和自省

2.发送消息

上文已经讨论了消息发送的内容,但它只是runtime的一小部分 ,下面是runtime其他一些有趣的功能:

####class
可以通过class_addIvar, class_addMethod, class_addProperty 和class_addProtocol 这些方法来构建一个class. class_copyIvarList, class_copyMethodList, class_copyProtocolList class_copyProperty可以用来获取class一些内在属性。还有一些方法经常用到,如class_conformsToProtocol,class_respondsToSelector ,class_getSuperclass,class_createInstance等。至于用法需要的时候查看官方文档便知

####method
runtime在method层级上应用最广的就是Method Swizzling,上文博客链接也有详细描述,不再赘述。(2013.11.15修改)

####objc
objc是runtime最底层。例如runtime中最核心的功能objc_msgSend。此外还有 objc_getAssociatedObject, objc_setAssociatedObject 和objc_removeAssociatedObjects, objc_copyProtocolList, objc_getClassList, objc_getProtocol, objc_getClass等。这几个方法在实际编程中也经常用到。

####other
property_getAttributes可以获得对象属性的一些特征,如返回类型,是否是atomic,内存管理方式,是否是weak reference 等等

###Dynamic Method Resolution
如果向一个对象发送一个并没有声明的消息,或者由于各种原因消息并没有响应。这个时候其实还是有一些技巧可以进一步来处理的。

第一步想到的应该就是Dynamic Method Resolution。有些时候你不想在编译的时候创建方法或者只在编译时候声明一个方法并且在运行时来实现。这时候可以用过+resolveInstanceMethod: 和 t+resolveClassMethod:来实现。 需要注意的是替换的方法必须返回YES。一个例子:

1
2
3
4
5
6
7
+ (BOOL)resolveInstanceMethod:(SEL)aSelector {
if (aSelector == @selector(myDynamicMethod)) {
class_addMethod(self, aSelector, (IMP)myDynamicIMP, "v@:");
return YES;
}
return [super resolveInstanceMethod:aSelector];
}

这个知识点在CoreData的NSManagedObjects会经常用到,来动态的给model增加属性和关系。

###Using Blocks As Method IMPs
iOS4.3 以后 runtime添加了很多新特性,比如IMPs block,直接上例子一目了然。

IMP myIMP = imp_implementationWithBlock(^(id _self, NSString *string) {
    NSLog(@"Hello %@", string);
});
class_addMethod([MYclass class], @selector(sayHello:), myIMP, "v@:@");

objc 的runtime深不可测,有兴趣的同学可以继续深入学习。

参考 pilky