动态的Objective-C——关于消息机制与运行时的探讨 一、引言 Objective-C是一种很优美的语言,至少在我使用其进行编程的过程中,是很享受他那近乎自然语言的函数命名、灵活多样的方法调用方式以及配合IDE流顺畅快编写体验。Objective-C是扩展与C面向对象的编程语言,然而其方法的调用方式又和大多面向对象语言大有不同,其采用的是消息传递、转发的方式进行方法的调用。因此在Objective-C中对象的真正行为往往是在运行时确定而非在编译时确定,所以Objective-C又被称为是一种运行时的动态语言。
本篇博客既不介绍iOS开发,也不提及MacOS开发,只对Objective-C语言的这种消息机制与运行时动态进行探讨,所提及的内容也都是我开发中的个人积累与经验,如果偏颇之处,欢迎讨论指正。
二、消息发送与转发机制 1.初窥消息发送机制 许多面向对象语言中方法的调用都是采用obj.function这样的方式,在Objective-C语言中却是采用中括号包裹的方式进行方法调用,例如\[obj function\]。实际上,Objective-C中的每一句方法调用最后都会转换成一条消息进行发送。一条消息包含3部分内容:方法选择器、接收消息的对象以及参数。objc_msgSend函数就是用来发送这种消息。例如,创建一个Xcode命令行工程,我们创建一个类,命名为MyObject,如下:
MyObject.h文件:
1 2 3 4 5 #import <Foundation/Foundation.h> @interface MyObject : NSObject @end
MyObject.m文件:
1 2 3 4 5 6 7 8 9 #import "MyObject.h" @implementation MyObject -(void )showSelf{ NSLog (@"MyObject" ); } @end
首先在MyObject.h文件中并没有暴漏任何方法,MyObject.m文件中添加了一个showSelf方法,这个方法只是做了简单的打印操作。
将main.m文件修改如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 #import <Foundation/Foundation.h> #import "MyObject.h" #import <objc/message.h> int main(int argc, const char * argv[]) { @autoreleasepool { MyObject * obj = [[MyObject alloc]init]; [obj class ]; #pragma clang diagnostic push #pragma clang diagnostic ignored"-Wundeclared-selector" ((void (*)(id ,SEL))objc_msgSend)(obj,@selector (showSelf)); #pragma clang diagnostic pop } return 0 ; }
运行工程,可以看到控制台执行了MyObject类的示例方法showSelf。如果要进行传参,在objc_msgSend方法中继续添加参数,并且指定对应的函数类型即可,例如:
MyObject.m文件:
1 2 3 4 5 6 7 8 9 #import "MyObject.h" @implementation MyObject -(void )showSelf:(NSString *)name age:(int )age{ NSLog (@"MyObject:%@,%d" ,name,age); } @end
main.m文件:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 #import <Foundation/Foundation.h> #import "MyObject.h" #import <objc/message.h> int main(int argc, const char * argv[]) { @autoreleasepool { MyObject * obj = [[MyObject alloc]init]; [obj class ]; #pragma clang diagnostic push #pragma clang diagnostic ignored"-Wundeclared-selector" ((void (*)(id ,SEL,NSString *,int ))objc_msgSend)(obj,@selector (showSelf:age:),@"珲少" ,25 ); #pragma clang diagnostic pop } return 0 ; }
运行工程可以看到方法被调用,参数被正确传入。
2.消息传递是基于继承链的 上面代码只是简单演示了消息发送的效果,下面我们来剖析下消息发送的过程与原理,明白了这个原理,对Objective-C中许多神奇的现象你将会豁然开朗,后面我会再具体向你介绍这些现象。
在介绍消息机制之前,我还是要再啰嗦一点,关于@selector()我们还需要深入理解一下,通过@selector(方法名)可以获取到一个SEL类型的对象,SEL实际上是objc\_selector结构体指针,在Objective-C库头文件中没有找到objc\_selector结构体的定义,但我们可以合理猜测,其中很有可能包含的是一个函数指针。因此SEL也可以理解为函数签名,在程序的编译阶段,我们定义类中所有所发会生成一个方法签名列表,这个列表时类直接关联的(原则上来说,类的本质也是对象,它是一个单例对象),在运行时通过方法签名表来找到具体要执行的函数。
我们再来看objc_msgSend()函数,前面说过,它的第一个参数为接收消息的对象,第2个参数为方法签名,之后为传递的参数。那么Objective-C运行时是如何根据一个对象实例来找到方法签名表,再找到要执行的方法呢,看似麻烦的事情其实原理也非常简单,细心观察,你会发现所有的NSObject子类对象中都包含一个isa成员变量,请看NSObject类的定义:
1 2 3 @interface NSObject <NSObject > { Class isa OBJC_ISA_AVAILABILITY; }
这个isa变量是Class类型,我们的主角终于来了,Class顾名思义就是“类”类型,其实质是objc_class结构体指针:
1 typedef struct objc_class *Class;
有些蒙圈了吧,不用着急,拨开层层迷雾,你就会发现Objective-C中类本质上只是结构体而已,下面是objc_class结构体的定义:
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 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;
每一个“类”对象是也有一个isa指针,这个指针指向的类实际上是元类,即构造“类”的类。现在你无须纠结这些概念,举一个例子你就能明白,在Objective-C开发中有加方法与减方法,减方法是实例对象调用的方法,每一个“类”中都包含一个函数列表,就是上面的objc_method_list结构体数组指针,同样如果调用加方法,实际上是从类的元类中找到对应的方法列表,这个列表就是我们前面提到的方法签名列表,进行方法的执行。关于实例对象,“类”对象和元类,下图很好的表现了他们之间的关系:
需要注意,使用LLDB调试器我们是可以拿到对象的isa指针的,并且可以看出它的确为Class类型,但是我们缺无法通过isa指针继续向下取抓取更多类的信息,其所在的内存是禁止我们访问的。但是Objective-C运行时提供了一些方法可以获取到这些信息,后面我们会一一介绍。
上面我们介绍的消息发送机制其实十分不完整,首先Objective-C是支持继承的,因此如果在当前对象的类的方法列表中没有找到此消息对应的方法签名,系统会通过super_class一层层继续向上,直到找到相应的方法或者到达继承链的顶端。
有了上面的理论知识作为基础,我们就可以更深入的分析消息传递的过程了,首先,如果消息的接收对象刚好可以处理这个消息,即其isa指针对应的类中可以查找到这个方法,那么万事大吉,找到对应方法直接执行就大功告成,可以如果接收对象无法处理,其父类,父父类...等都无法处理,那么该怎么办呢,Objective-C为了增强语言的动态性,如果真的出现了这种情况,程序并不会马上crash,在crash前,有3次机会可以挽救本条消息的命运。
3.拯救未知消息的3根救命稻草 第一根救命稻草:
如上所说,如果对象整个继承链都无法处理当前消息,那么首先会调用接收对象所属类的resolveInstanceMethod方法(这个对应实例方法,如果是无法处理的类方法消息,则会调用resolveClassMethod方法),在这个方法中,开发者有机会为类动态添加方法,如果动态添加了方法,可以在这个方法中返回YES,那么此条消息依然会被成功处理。例如我们将main.m文件修改如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 #import <Foundation/Foundation.h> #import "MyObject.h" #import <objc/message.h> int main(int argc, const char * argv[]) { @autoreleasepool { MyObject * obj = [[MyObject alloc]init]; [obj class ]; #pragma clang diagnostic push #pragma clang diagnostic ignored"-Wundeclared-selector" ((void (*)(id ,SEL))objc_msgSend)(obj,@selector (showSelf)); #pragma clang diagnostic pop } return 0 ; }
MyObject类不做任何修改,当我们运行程序,程序会直接crash掉,现在我们在MyObject类中添加如下方法:
1 2 3 4 5 6 7 +(BOOL )resolveInstanceMethod:(SEL)sel{ NSLog (@"resolveInstanceMethod" ); if ([NSStringFromSelector (sel) isEqualToString:@"showSelf" ]) { class_addMethod(self , sel, newFunc, "v@:" ); } return [super resolveInstanceMethod:sel]; }
其中class_addMethod函数用来向类中动态添加方法,第一个参数为Class对象,第二个参数为方法选择器,第三个参数为IMP类型的函数指针,第四个参数为指定方法的返回值和参数类型。这个参数采用的是C字符串的形式来指定返回值和参数的类型,第1个字符为返回值类型,其后都为参数类型,需要注意,使用这种方式添加方法的时候系统会默认传入两个参数,分别是调用此方法的实例对象和方法选择器,上面示例代码中的”@”表示第1个id类型的参数,”:”表示第2个选择器类型的参数,后面我会把字符所表示的参数类型映射表提供给大家。
抽丝剥茧一下,IMP和SEL并不同,SEL可以理解为函数签名,其与函数名相关联,而IMP是函数所在地址的指针,其定义如下:
1 typedef void (*IMP)(void );
简单理解,通过IMP我们可以直接拿到函数的地址,后面会对函数做更深入的剖析,到时候你能就能豁然你开朗。
运行工程,根据打印信息可以看到showSelf方法被添加并正常执行了。
第二根救命稻草:
抛开运行时添加方法这一手段,将resolveInstanceMethod方法删去,是不是我们的程序就必然走进crash的深渊了,其实不然,上帝还会给你另一根救命稻草,当通过运行时添加方法被否定后,系统会接着调用forwardingTargetForSelector方法,这个方法用来对消息进行转发,没错,重点来了,Objective-C中强大的消息转发机制的奥妙就在这里。forwardingTargetForSelector方法需要返回一个id类型的对象,系统会将当前对象服务处理的消息转发给这个方法返回的对象,如果这个返回的对象可以处理,那么程序依然可以很好的执行下去。
例如,在我们的命令行工程中新添加一个类,命名为SubObject,实现如下:
SubObject.h文件:
1 2 3 4 5 #import <Foundation/Foundation.h> @interface SubObject : NSObject @end
SubObject.m文件:
1 2 3 4 5 6 7 #import "SubObject.h" @implementation SubObject -(void )showSelf{ NSLog (@"subObject" ); } @end
在MyObject类中实现如下方法:
1 2 3 4 5 6 7 -(id )forwardingTargetForSelector:(SEL)aSelector{ NSLog (@"forwardingTargetForSelector" ); if ([NSStringFromSelector (aSelector) isEqualToString:@"showSelf" ]) { return [SubObject new]; } return [super forwardingTargetForSelector:aSelector]; }
forwardingTargetForSelector方法可以返回一个对象,Objective-C会将当前对象无法处理的消息转发给这个方法返回的对象,如果返回nil,则表示不进行消息转发,这时你如果还想挽救此次crash,你就需要用到第三根救命稻草了。我们可以这种消息转发的机制来模拟Objective-C中的多继承。
第三根救命稻草:
如果你不幸错过了前两次拯救未知消息的机会,那么你还有最后一次机会(中国有句古话,事不过三,世间万事也果真如此...)。当消息转发策略也被否定后,系统会调用methodSignatureForSelector方法,这个方法的主要用途是询问这个选择器是否是有效的,我们需要返回一个NSMethodSignature,顾名思义,这个对象是函数签名的抽象。如果我们返回了有效的函数签名,那么接着系统会调用forwardInvocation方法,这里是拯救应用程序的最后一根稻草了,这个函数会直接将消息包装成NSInvocation对象传入,我们直接将其发送给可以处理此消息的对象即可(当然你也可以直接抛弃,不理会这条未知的消息)。
例如,在MyObject类中将forwardingTargetForSelector方法删去,实现如下两个方法:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 -(NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector{ NSLog (@"methodSignatureForSelector" ); if ([NSStringFromSelector (aSelector) isEqualToString:@"showSelf" ]) { return [[SubObject new] methodSignatureForSelector:aSelector]; } return [super methodSignatureForSelector:aSelector]; } -(void )forwardInvocation:(NSInvocation *)anInvocation{ NSLog (@"forwardInvocation" ); if ([NSStringFromSelector (anInvocation.selector) isEqualToString:@"showSelf" ]) { [anInvocation invokeWithTarget:[SubObject new]]; }else { [super forwardInvocation:anInvocation]; } }
再次运行工程,程序又被你挽救了一次。
你真的需要救命稻草么?
通过上面的三根救命稻草,我相信你一定对Objective-C消息机制有了全面而深入的了解,上面的代码也只是为了示例所用,正常情况下,你都不会使用到这些函数(毕竟如果你需要救命稻草,说明你已经落水了)。除非某些特殊需求或者做一些调试框架的开发,否则尽量不要介入消息的发送机制,就像生病就医,发现问题总比逃避治疗要好。顺便说一下,如果你没有使用任何救命稻草,当向某个对象发送了无法处理的消息时,系统会最终调用到NSObject类的doesNotRecognizeSelector方法,这个方法会抛出异常信息,正因如此,你在Xcode的控制台会经常看到如下图所示的crash信息:
你也可以重写这个方法来自定义输出信息,例如:
1 2 3 4 5 6 7 8 -(void )doesNotRecognizeSelector:(SEL)aSelector{ NSLog (@"doesNotRecognizeSelector" ); if ([NSStringFromSelector (aSelector) isEqualToString:@"showSelf" ]) { NSLog (@"not have a method named showSelf" ); return ; } [super doesNotRecognizeSelector:aSelector]; }
下图完整展示了Objective-C整个消息发送与转发机制:
三、发送消息的几个函数 1.最重要的两个发送消息函数 既然Objective-C函数最终的调用都是要转换成消息发送,那么了解下面这些消息发送函数是十分必要的,这些方法都定义在objc/message.h文件中,其中最重要的两个方法是:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 id objc_msgSend(id self , SEL op, ...);id objc_msgSendSuper(struct objc_super *super , SEL op, ...);
objc_msgSend函数前面已经有过介绍,objc_msgSendSuper函数则是从父类中找方法的实现进行执行。需要注意,这个函数非常重要,理解了这个这个函数进行消息发送的原理,你就明白super关键字的某些令人疑惑的行为了。
2.super关键字到底做了什么 做了这么久的Objective-C开发,你是否真的理解super关键字的含义?你一定会说,这很简单啊,self调用本类的方法,super调用父类的方法。那么我们来看一个小案例:
在前面创建的命令行工程中新建一个类,使其继承于MyObject类,命名为MyObjectSon,在其中提供两个方法,如下:
MyObjectSon.h文件:
1 2 3 4 5 #import "MyObject.h" @interface MyObjectSon : MyObject -(void )showClass; -(void )showSuperClass; @end
MyObjectSon.m文件:
1 2 3 4 5 6 7 8 9 10 #import "MyObjectSon.h" @implementation MyObjectSon -(void )showClass{ NSLog (@"%@" ,[self className]); } -(void )showSuperClass{ NSLog (@"%@" ,[super className]); } @end
分别调用两个方法,你会惊奇的发现,打印结构都是“MyObjectSon”,super关键字失效了么?非也非也,下面我们来用消息发送机制重新模拟这两个方法的调用。
首先\[self className\]在调用时会采用前面介绍的消息发送机制先从当前类中找className函数,当前类中并没有提供className函数,所以消息会随着继承链向上传递,找到MyObject类中也没有className函数的实现,会继续向上,最终在NSObject类中找到这个方法,记住,这条消息处理的两个要素是:当前MyObjectSon实例对象作为接收者,NSObject类中的className方法作为调用函数。
当调用\[super className\]时,首先会使用objc_msgSendSuper方法进行消息的发送,等价于如下代码:
1 2 3 4 5 6 -(void )showSuperClass{ struct objc_super superObj = {self , object_getClass([MyObject new])}; NSString * name = ((id (*)(struct objc_super*,SEL))objc_msgSendSuper)(&superObj,@selector (className)); NSLog (@"%@" ,name); }
objc_msgSendSuper函数第一个参数为一个父类接收者结构体指针,objc_super结构体定义如下:
1 2 3 4 5 6 struct objc_super { __unsafe_unretained id receiver; __unsafe_unretained Class super_class; };
在构造objc_super这个结构体时,receive为接收消息的对象,super_class为从哪个类中查方法。如此来看一些都清楚了,系统首先从MyObject类中找className方法,没有相应的实现,会继续向上直到找到NSObject类中的className方法,之后进行执行。这条消息处理的两个要素是:当前MyObjectSon实例对象作为接收者,NSObject类中的className方法作为调用函数。这样分析下来,无论是使用self执行的className方法还是使用super执行的className方法,行为实质上是完全一致的!
3.一些辅助的消息发送函数 特殊返回值类型对应不同的发送消息函数:
1 2 3 4 5 void objc_msgSend_stret(id self , SEL op, ...);void objc_msgSendSuper_stret(struct objc_super *super , SEL op, ...);double objc_msgSend_fpret(id self , SEL op, ...);
除了使用SEL方法选择器来发送消息,也可以直接使用Method来发送消息:
1 2 3 4 5 6 7 8 9 id method_invoke(id receiver, Method m, ...);void method_invoke_stret(id receiver, Method m, ...);
Method也是一种结构体指针,其定义如下:
1 2 3 4 5 6 7 8 struct objc_method { SEL method_name OBJC2_UNAVAILABLE; char *method_types OBJC2_UNAVAILABLE; IMP method_imp OBJC2_UNAVAILABLE; }
示例代码如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 int main(int argc, const char * argv[]) { @autoreleasepool { MyObject * obj = [[MyObject alloc]init]; [obj class ]; #pragma clang diagnostic push #pragma clang diagnostic ignored"-Wundeclared-selector" Method method = class_getInstanceMethod([MyObject class ], @selector (showSelf:age:)); ((void (*)(id ,Method,NSString *,int ))method_invoke)(obj,method,@"珲少" ,25 ); #pragma clang diagnostic pop } return 0 ; }
下面这些方法可以跳过当前对象,直接进行消息转发:
1 2 3 id _objc_msgForward(id receiver, SEL sel, ...);void _objc_msgForward_stret(id receiver, SEL sel, ...);
一点建议,上面两个方法都是以下划线开头,这也表明设计者并不想让你直接调用这个方法,确实如此,这两个方法会直接出发对象的消息转发流程,即便当前对象类已经实现了相应的方法也不会进行查找。
四、是时候来重温下Runtime了 所谓运行时是针对于编译时而言的,本篇文章的开头,我们就说过Objective-C是一种极动态的运行时语言。对象的行为是在运行时被决定的,我们前边也了解了有关isa指针即Class的内容,虽然我们并不能直接访问isa指针,但是我们可以通过objc/runtime.h文件中定义的运行时方法来获取或改变类与对象的行为。
1.类相关操作函数 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 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 id object_copy(id obj, size_t size);id object_dispose(id obj);Class object_getClass(id obj); Class object_setClass(id obj, Class cls); BOOL object_isClass(id obj);const char *object_getClassName(id obj);Class objc_getClass(const char *name); Class objc_getMetaClass(const char *name); Class objc_lookUpClass(const char *name); Class objc_getRequiredClass(const char *name); int objc_getClassList(Class *buffer, int bufferCount);Class *objc_copyClassList(unsigned int *outCount); const char *class_getName(Class cls);BOOL class_isMetaClass(Class cls);Class class_getSuperclass(Class cls); Class class_setSuperclass(Class cls, Class newSuper); int class_getVersion(Class cls);void class_setVersion(Class cls, int version);size_t class_getInstanceSize(Class cls);
上面列举的方法都和类相关,你没看错,通过object_setClass()动态改变对象所属的类,但是需要注意,对象的成员变量并不会受到影响,方法则全部替换为新类的方法。如果你喜欢,你甚至可以运行时动态修改类的父类,这十分酷吧。下面这些方法则与类中的变量有关:
2.变量属性相关操作函数 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 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 void *object_getIndexedIvars(id obj);Ivar class_getInstanceVariable(Class cls, const char *name); Ivar class_getClassVariable(Class cls, const char *name); Ivar *class_copyIvarList(Class cls, unsigned int *outCount); id object_getIvar(id obj, Ivar ivar);void object_setIvar(id obj, Ivar ivar, id value);void object_setIvarWithStrongDefault(id obj, Ivar ivar, id value);Ivar object_setInstanceVariable(id obj, const char *name, void *value); Ivar object_setInstanceVariableWithStrongDefault(id obj, const char *name, void *value); Ivar object_getInstanceVariable(id obj, const char *name, void **outValue); const uint8_t *class_getIvarLayout(Class cls);void class_setIvarLayout(Class cls, const uint8_t *layout);const uint8_t *class_getWeakIvarLayout(Class cls);void class_setWeakIvarLayout(Class cls, const uint8_t *layout); const char *value; } objc_property_attribute_t; */ objc_property_t class_getProperty(Class cls, const char *name); objc_property_t *class_copyPropertyList(Class cls, unsigned int *outCount); BOOL class_addIvar(Class cls, const char *name, size_t size, uint8_t alignment, const char *types); BOOL class_addProperty(Class cls, const char *name, const objc_property_attribute_t *attributes, unsigned int attributeCount);void class_replaceProperty(Class cls, const char *name, const objc_property_attribute_t *attributes, unsigned int attributeCount);const char *ivar_getName(Ivar v);const char *ivar_getTypeEncoding(Ivar v);const char *property_getName(objc_property_t property);const char *property_getAttributes(objc_property_t property);char *property_copyAttributeValue(objc_property_t property, const char *attributeName);objc_property_attribute_t *property_copyAttributeList(objc_property_t property, unsigned int *outCount); OBJC_ASSOCIATION_RETAIN_NONATOMIC = 1 , OBJC_ASSOCIATION_COPY_NONATOMIC = 3 , OBJC_ASSOCIATION_RETAIN = 01401 , OBJC_ASSOCIATION_COPY = 01403 }; */ 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);
3.方法操作相关函数 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 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 Method class_getInstanceMethod(Class cls, SEL name); Method class_getClassMethod(Class cls, SEL name); IMP class_getMethodImplementation(Class cls, SEL name); IMP class_getMethodImplementation_stret(Class cls, SEL name); BOOL class_respondsToSelector(Class cls, SEL sel);Method *class_copyMethodList(Class cls, unsigned int *outCount); BOOL class_addMethod(Class cls, SEL name, IMP imp, const char *types); IMP class_replaceMethod(Class cls, SEL name, IMP imp, const char *types); SEL method_getName(Method m); IMP method_getImplementation(Method m); const char *method_getTypeEncoding(Method m);unsigned int method_getNumberOfArguments(Method m);char *method_copyReturnType(Method m);char *method_copyArgumentType(Method m, unsigned int index);void method_getReturnType(Method m, char *dst, size_t dst_len) ;void method_getArgumentType(Method m, unsigned int index, char *dst, size_t dst_len); char *types; }; */ struct objc_method_description *method_getDescription(Method m);IMP method_setImplementation(Method m, IMP imp); void method_exchangeImplementations(Method m1, Method m2);const char *sel_getName(SEL sel);SEL sel_getUid(const char *str); SEL sel_registerName(const char *str); BOOL sel_isEqual(SEL lhs, SEL rhs);IMP imp_implementationWithBlock(id block); id imp_getBlock(IMP anImp);BOOL imp_removeBlock(IMP anImp);
上面列举的函数中很多都用到参数类型的指定,types需要设置为C风格的字符数组,即C字符串,其中第1个字符表示返回值类型,其余字符依次表示参数类型,参数类型与字符的映射表如下:
4.协议相关操作函数 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 BOOL class_conformsToProtocol(Class cls, Protocol *protocol);Protocol * __unsafe_unretained *class_copyProtocolList(Class cls, unsigned int *outCount); BOOL class_addProtocol(Class cls, Protocol *protocol);Protocol *objc_getProtocol(const char *name); Protocol * __unsafe_unretained *objc_copyProtocolList(unsigned int *outCount); BOOL protocol_conformsToProtocol(Protocol *proto, Protocol *other);BOOL protocol_isEqual(Protocol *proto, Protocol *other);const char *protocol_getName(Protocol *p);struct objc_method_description protocol_getMethodDescription(Protocol *p, SEL aSel, BOOL isRequiredMethod, BOOL isInstanceMethod);struct objc_method_description *protocol_copyMethodDescriptionList(Protocol *p, BOOL isRequiredMethod, BOOL isInstanceMethod, unsigned int *outCount);objc_property_t protocol_getProperty(Protocol *proto, const char *name, BOOL isRequiredProperty, BOOL isInstanceProperty); objc_property_t *protocol_copyPropertyList(Protocol *proto, unsigned int *outCount); objc_property_t *protocol_copyPropertyList2(Protocol *proto, unsigned int *outCount, BOOL isRequiredProperty, BOOL isInstanceProperty); Protocol * __unsafe_unretained *protocol_copyProtocolList(Protocol *proto, unsigned int *outCount); Protocol *objc_allocateProtocol(const char *name); void objc_registerProtocol(Protocol *proto);void protocol_addMethodDescription(Protocol *proto, SEL name, const char *types, BOOL isRequiredMethod, BOOL isInstanceMethod);void protocol_addProtocol(Protocol *proto, Protocol *addition);void protocol_addProperty(Protocol *proto, const char *name, const objc_property_attribute_t *attributes, unsigned int attributeCount, BOOL isRequiredProperty, BOOL isInstanceProperty);
协议实质也是一个Objective-C对象。
5.动态构建类实例相关函数 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 id class_createInstance(Class cls, size_t extraBytes);id objc_constructInstance(Class cls, void *bytes);void *objc_destructInstance(id obj);Class objc_allocateClassPair(Class superclass, const char *name,size_t extraBytes); void objc_registerClassPair(Class cls);void objc_disposeClassPair(Class cls);
五、运行时的几点应用扩展 到此本篇文章终于要告一段落了,相信你如果能看到这里,你一定有超凡的耐心。但是切记Objective-C的消息机制配合运行时是可以给开发者极大的元编程自由,但是不适当的使用也会造成破坏性的后果。下面几篇博客从一些方面介绍了Runtime的几点应用,你可以从中管中窥豹,可见一斑。
1.runtime基础应用:https://my.oschina.net/u/2340880/blog/489072
2.使用runtime全局修改UILabel字体:https://my.oschina.net/u/2340880/blog/538356
3.使用runtime自动化归档:https://my.oschina.net/u/2340880/blog/514330
4.代码调试框架的设计:https://my.oschina.net/u/2340880/blog/504675