Objective-C是一种很优美的语言,至少在我使用其进行编程的过程当中,是很享受他那近乎天然语言的函数命名、灵活多样的方法调用方式以及配合IDE流顺畅快编写体验。Objective-C是扩展与C面向对象的编程语言,然而其方法的调用方式又和大多面向对象语言大有不一样,其采用的是消息传递、转发的方式进行方法的调用。所以在Objective-C中对象的真正行为每每是在运行时肯定而非在编译时肯定,因此Objective-C又被称为是一种运行时的动态语言。编程
本篇博客既不介绍iOS开发,也不说起MacOS开发,只对Objective-C语言的这种消息机制与运行时动态进行探讨,所说起的内容也都是我开发中的我的积累与经验,若是偏颇之处,欢迎讨论指正。数组
许多面向对象语言中方法的调用都是采用obj.function这样的方式,在Objective-C语言中倒是采用中括号包裹的方式进行方法调用,例如[obj function]。实际上,Objective-C中的每一句方法调用最后都会转换成一条消息进行发送。一条消息包含3部份内容:方法选择器、接收消息的对象以及参数。objc_msgSend函数就是用来发送这种消息。例如,建立一个Xcode命令行工程,咱们建立一个类,命名为MyObject,以下:缓存
MyObject.h文件:框架
#import <Foundation/Foundation.h> @interface MyObject : NSObject @end
MyObject.m文件:编程语言
#import "MyObject.h" @implementation MyObject -(void)showSelf{ NSLog(@"MyObject"); } @end
首先在MyObject.h文件中并无暴漏任何方法,MyObject.m文件中添加了一个showSelf方法,这个方法只是作了简单的打印操做。函数
将main.m文件修改以下:布局
#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文件:ui
#import "MyObject.h" @implementation MyObject -(void)showSelf:(NSString*)name age:(int)age{ NSLog(@"MyObject:%@,%d",name,age); } @end
main.m文件:编码
#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; }
运行工程能够看到方法被调用,参数被正确传入。
上面代码只是简单演示了消息发送的效果,下面咱们来剖析下消息发送的过程与原理,明白了这个原理,对Objective-C中许多神奇的现象你将会豁然开朗,后面我会再具体向你介绍这些现象。
在介绍消息机制以前,我仍是要再啰嗦一点,关于@selector()咱们还须要深刻理解一下,经过@selector(方法名)能够获取到一个SEL类型的对象,SEL其实是objc_selector结构体指针,在Objective-C库头文件中没有找到objc_selector结构体的定义,但咱们能够合理猜想,其中颇有可能包含的是一个函数指针。所以SEL也能够理解为函数签名,在程序的编译阶段,咱们定义类中全部所发会生成一个方法签名列表,这个列表时类直接关联的(原则上来讲,类的本质也是对象,它是一个单例对象),在运行时经过方法签名表来找到具体要执行的函数。
咱们再来看objc_msgSend()函数,前面说过,它的第一个参数为接收消息的对象,第2个参数为方法签名,以后为传递的参数。那么Objective-C运行时是如何根据一个对象实例来找到方法签名表,再找到要执行的方法呢,看似麻烦的事情其实原理也很是简单,细心观察,你会发现全部的NSObject子类对象中都包含一个isa成员变量,请看NSObject类的定义:
@interface NSObject <NSObject> { Class isa OBJC_ISA_AVAILABILITY; }
这个isa变量是Class类型,咱们的主角终于来了,Class顾名思义就是“类”类型,其实质是objc_class结构体指针:
typedef struct objc_class *Class;
有些蒙圈了吧,不用着急,拨开层层迷雾,你就会发现Objective-C中类本质上只是结构体而已,下面是objc_class结构体的定义:
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次机会能够挽救本条消息的命运。
第一根救命稻草:
如上所说,若是对象整个继承链都没法处理当前消息,那么首先会调用接收对象所属类的resolveInstanceMethod方法(这个对应实例方法,若是是没法处理的类方法消息,则会调用resolveClassMethod方法),在这个方法中,开发者有机会为类动态添加方法,若是动态添加了方法,能够在这个方法中返回YES,那么此条消息依然会被成功处理。例如咱们将main.m文件修改以下:
#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类中添加以下方法:
+(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是函数所在地址的指针,其定义以下:
typedef void (*IMP)(void /* id, SEL, ... */ );
简单理解,经过IMP咱们能够直接拿到函数的地址,后面会对函数作更深刻的剖析,到时候你能就能豁然你开朗。
运行工程,根据打印信息能够看到showSelf方法被添加并正常执行了。
第二根救命稻草:
抛开运行时添加方法这一手段,将resolveInstanceMethod方法删去,是否是咱们的程序就必然走进crash的深渊了,其实否则,上帝还会给你另外一根救命稻草,当经过运行时添加方法被否认后,系统会接着调用forwardingTargetForSelector方法,这个方法用来对消息进行转发,没错,重点来了,Objective-C中强大的消息转发机制的奥妙就在这里。forwardingTargetForSelector方法须要返回一个id类型的对象,系统会将当前对象服务处理的消息转发给这个方法返回的对象,若是这个返回的对象能够处理,那么程序依然能够很好的执行下去。
例如,在咱们的命令行工程中新添加一个类,命名为SubObject,实现以下:
SubObject.h文件:
#import <Foundation/Foundation.h> @interface SubObject : NSObject @end
SubObject.m文件:
#import "SubObject.h" @implementation SubObject -(void)showSelf{ NSLog(@"subObject"); } @end
在MyObject类中实现以下方法:
-(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方法删去,实现以下两个方法:
//询问此选择器是不是有效的 -(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信息:
你也能够重写这个方法来自定义输出信息,例如:
-(void)doesNotRecognizeSelector:(SEL)aSelector{ NSLog(@"doesNotRecognizeSelector"); if ([NSStringFromSelector(aSelector) isEqualToString:@"showSelf"]) { NSLog(@"not have a method named showSelf"); return; } [super doesNotRecognizeSelector:aSelector]; }
下图完整展现了Objective-C整个消息发送与转发机制:
既然Objective-C函数最终的调用都是要转换成消息发送,那么了解下面这些消息发送函数是十分必要的,这些方法都定义在objc/message.h文件中,其中最重要的两个方法是:
//发送消息的函数 /* self:消息的接收对象 op:方法选择器 ...:参数 */ id objc_msgSend(id self, SEL op, ...); //发送消息给父类 /* super:父类对象结构体 op:方法选择器 ...:参数 */ id objc_msgSendSuper(struct objc_super *super, SEL op, ...);
objc_msgSend函数前面已经有过介绍,objc_msgSendSuper函数则是从父类中找方法的实现进行执行。须要注意,这个函数很是重要,理解了这个这个函数进行消息发送的原理,你就明白super关键字的某些使人疑惑的行为了。
作了这么久的Objective-C开发,你是否真的理解super关键字的含义?你必定会说,这很简单啊,self调用本类的方法,super调用父类的方法。那么咱们来看一个小案例:
在前面建立的命令行工程中新建一个类,使其继承于MyObject类,命名为MyObjectSon,在其中提供两个方法,以下:
MyObjectSon.h文件:
#import "MyObject.h" @interface MyObjectSon : MyObject -(void)showClass; -(void)showSuperClass; @end
MyObjectSon.m文件:
#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方法进行消息的发送,等价于以下代码:
-(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结构体定义以下:
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方法,行为实质上是彻底一致的!
特殊返回值类型对应不一样的发送消息函数:
//返回值为结构体时使用此方法发送消息 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来发送消息:
//进行函数的调用 /* receiver:接收者 m:函数 ...:参数 */ id method_invoke(id receiver, Method m, ...); //返回结构体数据的函数调用 void method_invoke_stret(id receiver, Method m, ...);
Method也是一种结构体指针,其定义以下:
struct objc_method { //选择器 SEL method_name OBJC2_UNAVAILABLE; //参数类型 char *method_types OBJC2_UNAVAILABLE; //函数地址 IMP method_imp OBJC2_UNAVAILABLE; }
示例代码以下:
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; }
下面这些方法能够跳过当前对象,直接进行消息转发:
//跳过当前对象直接进行消息转发机制 id _objc_msgForward(id receiver, SEL sel, ...); void _objc_msgForward_stret(id receiver, SEL sel, ...);
一点建议,上面两个方法都是如下划线开头,这也代表设计者并不想让你直接调用这个方法,确实如此,这两个方法会直接出发对象的消息转发流程,即使当前对象类已经实现了相应的方法也不会进行查找。
所谓运行时是针对于编译时而言的,本篇文章的开头,咱们就说过Objective-C是一种极动态的运行时语言。对象的行为是在运行时被决定的,咱们前边也了解了有关isa指针即Class的内容,虽然咱们并不能直接访问isa指针,可是咱们能够经过objc/runtime.h文件中定义的运行时方法来获取或改变类与对象的行为。
//对OC对象进行内存拷贝 在ARC环境下不可用 /* obj:要拷贝的对象 size:内存大小 */ id object_copy(id obj, size_t size); //进行OC对象内存的释放 在ARC环境下不可用 id object_dispose(id obj); //获取OC对象的类 注意 这个返回值和isa指针并非同一个指针 Class object_getClass(id obj); //重建对象的类 Class object_setClass(id obj, Class cls); //判断一个OC对象是不是类或元类(前面说过类实际上也是对象) BOOL object_isClass(id obj); //获取OC对象的类名 const char *object_getClassName(id obj); //经过类名获取“类”对象 Class objc_getClass(const char *name); //经过类名获取元类对象 Class objc_getMetaClass(const char *name); //这个方法也是返回类的定义 只是若是是未注册的 会返回nil Class objc_lookUpClass(const char *name); //这个方法也是返回类的定义 只是若是是未注册的 会直接杀死进程 Class objc_getRequiredClass(const char *name); /* 获取全部已经注册的类 会返回已经注册的类的个数 一般使用以下示例代码: int numClasses; Class * classes = NULL; numClasses = objc_getClassList(NULL, 0); if (numClasses > 0) { classes = (Class*)malloc(sizeof(Class) * numClasses); numClasses = objc_getClassList(classes, numClasses); NSLog(@"number of classes: %d", numClasses); for (int i = 0; i < numClasses; i++) { Class cls = classes[i]; NSLog(@"class name: %s", class_getName(cls)); } free(classes); } */ int objc_getClassList(Class *buffer, int bufferCount); //拷贝全部注册过的类列表 参数为输出类的个数 Class *objc_copyClassList(unsigned int *outCount); //获取Class类名字符串 const char *class_getName(Class cls); //判断一个Class是否为元类 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()动态改变对象所属的类,可是须要注意,对象的成员变量并不会受到影响,方法则所有替换为新类的方法。若是你喜欢,你甚至能够运行时动态修改类的父类,这十分酷吧。下面这些方法则与类中的变量有关:
//获取类额外分配内存的指针 void *object_getIndexedIvars(id obj); //根据变量名获取实例变量指针 /* Ivar实质上是一个结构体指针,存放变量信息,以下: 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 } */ Ivar class_getInstanceVariable(Class cls, const char *name); //根据变量名获取类变量指针 Ivar class_getClassVariable(Class cls, const char *name); //获取全部实例变量指针 outCount输出实例变量个数 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); //经过属性名获取属性 /* 属性特指使用@prototype定义的 objc_property_t是一个结构体指针,其描述的是属性的信息,以下: typedef struct { const char *name; /**属性名 */ 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); //向类添加一个实例变量 /* 须要注意,已经注册存在的类是不能经过这个方法追加实例变量的 这个方法只能在objc_allocateClassPair函数执行后而且objc_registerClassPair执行前进行调用 即这个函数是用来动态生成类的 */ 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); //获取属性attribute const char *property_getAttributes(objc_property_t property); char *property_copyAttributeValue(objc_property_t property, const char *attributeName); //获取属性attribute列表 objc_property_attribute_t *property_copyAttributeList(objc_property_t property, unsigned int *outCount); //进行属性关联 这种方式能够为已经存在的类的实例扩展属性 /* object:要添加属性的对象 key:关联的键 value:添加的属性的值 policy:添加属性的策略 typedef OBJC_ENUM(uintptr_t, objc_AssociationPolicy) { //assign OBJC_ASSOCIATION_ASSIGN = 0, /**< Specifies a weak reference to the associated object. */ //retain nonatimic OBJC_ASSOCIATION_RETAIN_NONATOMIC = 1, /**< Specifies a strong reference to the associated object. * The association is not made atomically. */ //copy nonatomic OBJC_ASSOCIATION_COPY_NONATOMIC = 3, /**< Specifies that the associated object is copied. * The association is not made atomically. */ //retain OBJC_ASSOCIATION_RETAIN = 01401, /**< Specifies a strong reference to the associated object. * The association is made atomically. */ //copy OBJC_ASSOCIATION_COPY = 01403 /**< Specifies that the associated object is copied. * The association is made atomically. */ }; */ 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);
//经过选择器获取某个类的实例方法 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); //为某个类动态添加一个实例方法 /* cls:添加方法的类 SEL:添加的方法选择器 IMP:方法实现 types:参数类型字符串 */ BOOL class_addMethod(Class cls, SEL name, IMP imp, const char *types); //替换一个方法的实现 /* cls:类 SEL:要替换实现的选择器 IMP:实现 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); //获取函数描述信息 /* objc_method_description结构体描述函数信息 以下: struct objc_method_description { //函数名 SEL name; /**< The name of the method */ //参数类型 char *types; /**< The types of the method arguments */ }; */ 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); //将block做为IMP的实现 IMP imp_implementationWithBlock(id block); //获取IMP的实现block id imp_getBlock(IMP anImp); //删除IMP的block实现 BOOL imp_removeBlock(IMP anImp);
上面列举的函数中不少都用到参数类型的指定,types须要设置为C风格的字符数组,即C字符串,其中第1个字符表示返回值类型,其他字符依次表示参数类型,参数类型与字符的映射表以下:
//判断某个类是否遵照某个协议 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); //获取协议中某个函数的描述 /* p:协议指针 aSel:方法选择器 isRequiredMethod:是不是必实现的 isInstanceMehod:是不是实例方法 */ 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对象。
//动态建立一个类实例 /* cls:类名 size_t:分配额外的内存 用来存放类定义以外的变量 */ id class_createInstance(Class cls, size_t extraBytes); //同上 id objc_constructInstance(Class cls, void *bytes); //销毁一个实例 void *objc_destructInstance(id obj); //动态定义一个类 /* superClass:指定父类 name:类名 extraBytes:额外的内存空间 */ 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基础应用:http://www.javashuo.com/article/p-dlbbjtue-cc.html
2.使用runtime全局修改UILabel字体:http://www.javashuo.com/article/p-yxowtpff-be.html
3.使用runtime自动化归档:http://www.javashuo.com/article/p-amqfxthi-ms.html
4.代码调试框架的设计:http://www.javashuo.com/article/p-yeekvfpt-hv.html