动态语言缓存
OC是一门彻彻底底的动态语言,因此它的不少机制都是动态运行时决定的。这点和C语言不同,C语言是静态绑定,也就是编译后全部的一切都已经决定了。这一点和C语言的函数指针有些相似,不少时候函数指针在编译的时候并不知道会指向哪一个函数,因此此时就是动态绑定。安全
举几个OC动态类型的例子,最为直接的就是id类型了、还有关联对象、动态绑定、消息转发、方法调配、这些技术都是动态类型很好的证实。
函数
OC对象结构优化
在介绍动动态性以前,咱们先来看看OC对象的一些结构。ui
#import<objc/runtime>这是OC运行时函数库,里面定义了不少结构体。this
首先看对象的结构:spa
typdef struct objc_object { Class isa; } *id;
对象结构中很是简单,只有一个isa指针,isa指针后面咱们会介绍。设计
接下来咱们看类的结构体指针
struct objc_class { Class isa #if !__OBJC2__ Class super_class const char *name long version long info long instance_size struct objc_ivar_list *ivars struct objc_method_list **methodLists struct objc_cache *cache struct objc_protocol_list *protocols #endif } typedef struct objc_class *Class;
能够看到不少信息都在Class中定义着,里面信息以下调试
字段 | 含义 |
isa | isa指针 |
super_class | 父类指针 |
name | 类名 |
version | 类的版本信息,默认为0 |
info | 供运行期使用的一些位标识 |
instance_size | 实例的大小 |
ivars | 实例变量列表 |
methodLists | 方法列表 |
cache | 指向最近调用的方法,用于优化调用方法的速度 |
protocols | 协议列表 |
接下来咱们逐个介绍一下:
isa指针和super_class
在OC中,严格意义上讲是没有类这种概念的,每个类都是一个对象,只不过类对象是一个单例。
isa指针存在于每个对象中,类普通实例的isa指针指向类,类的isa指针指向它的元类(类方法所有都在元类中存放)。元类的isa指针指向根元类,也就是NSObject的isa所指向的元类。
super_class只有类和元类才有,它们分别指向本身的父类和父元类,而为了让NSObject成为全部类的根类,让NSObject的元类的父类指针也指向了NSObject。
这样说可能也不是很好理解,看下面这张图应该就很快理解了。
等下咱们说到消息传递的时候还会在说到isa和super_class。
ivars属性列表
struct objc_ivar_list { int ivar_count #ifdef __LP64__ int space #endif /* variable length structure */ struct objc_ivar ivar_list[1] }
space做用还不太清楚...求指教啊。
下面是实例变量结构
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_offset,这个是实例变量在编译时的偏移量,是由编译时决定的。
methodLists方法列表
struct objc_method_list { struct objc_method_list *obsolete int method_count #ifdef __LP64__ int space #endif /* variable length structure */ struct objc_method method_list[1] }
该结构有方法链表和方法总数。
struct objc_method { SEL method_name OBJC2_UNAVAILABLE; char *method_types OBJC2_UNAVAILABLE; IMP method_imp OBJC2_UNAVAILABLE; }
里面有函数名,返回值类型和函数实现,接下来看看SEL和IMP定义
typedef struct objc_selector *SEL; typedef id (*IMP)(id, SEL, ...);
能够看到SEL是objc_selector,(*IMP)(id,SEL,...)是id,我没有找到objc_selector的结构因此这里也无法说什么...
cache缓存列表
typedef struct objc_cache *Cache #define CACHE_BUCKET_NAME(B) ((B)->method_name) #define CACHE_BUCKET_IMP(B) ((B)->method_imp) #define CACHE_BUCKET_VALID(B) (B) #ifndef __LP64__ #define CACHE_HASH(sel, mask) (((uintptr_t)(sel)>>2) & (mask)) #else #define CACHE_HASH(sel, mask) (((unsigned int)((uintptr_t)(sel)>>3)) & (mask)) #endif struct objc_cache { unsigned int mask /* total = mask + 1 */ unsigned int occupied Method buckets[1] };
缓存列表里面包含了已缓存的方法,用于快速的调用,不须要在去方法列表里面查询了。
protocol协议列表
struct objc_protocol_list { struct objc_protocol_list *next; long count; Protocol *list[1]; };
协议列表包含了协议数量和协议的指针。
id
id类型实际上就是一个objc_object的typedef,是一个对象实例,并且仍是一个指针,因此用id来定义对象的时候就不须要加*号了。
id类型每每须要咱们使用”自省“机制来保证使用安全,所谓自省其实就是看看这个对象是否是某个类的实例,或者是否是其子类。
自省用一下两个方法:
isKindOfClass:(Class)class 判断是否是其类族对象
isMemberOfClass:(Class)class 判断是否是类自己对象
关联对象
关联对象在我以前的博客中已经有介绍了,这里就再也不说了。
OC消息机制和消息转发
别的语言调用函数,OC则叫作发送消息,这是由于全部OC的方法调用实际上底层都是经过
objc_msgSend(id self , SEL cmd,...)来发送的。
该函数的做用就是传递给一个对象某个方法,后面的不定参数列表是方法所须要的参数。
这里说一下OC的消息传递机制,首先对一个对象发送消息,它会先检查本身的类中有没有该方法,若是没有就找他的父类中有没有,若是尚未则会进行消息转发。
在看例子以前先说一下,Xcode6貌似默认行为不让咱们使用objc_msgSend了,因此须要先设置一下
把这一项设置为No就能够了。
这里看个例子,
EqualObject *object1 = [EqualObject new]; EqualObject *object2 = [EqualObject new]; object1.name = @"xiaoming"; object2.name = @"xiaoming"; BOOL isEqual = objc_msgSend(object1,@selector(isEqualToEqualObject:),object2); if(isEqual) { NSLog(@"equal"); }
EqualObject是咱们本身实现的类,它有一个判断是否相等的方法isEqualToEqualObject:,若是name相等就人为两个对象相等。
这里咱们直接传递消息,不经过OC语法,运行程序能够看到equal被打印了出来。
而后咱们再看看消息转发机制。
当该对象包括其父类都没有这个方法的时候会启动,消息转发机制分为两大阶段。
第一阶段先看对象所属类是否有能力动态添加方法,已处理这个位置的选择子,这叫作动态解析(dynamic method resolution)。
第二阶段设计“完整的消息转发机制”。若是运行期系统已经把第一阶段执行完了,那么接受者本身就无法再以动态新增方法的手段来处理与消息相关的方法调用。这又分为两个小步。
首先,请接受者看看有没有其余对象能处理这条消息。如有,则在运行时转给那个对象,因而消息转发过程结束。若没有“备用的接受者”,则启动完成的消息转发机制,运行起系统会把与消息有关的所有细节都封装到NSInvocation对象中,再给接受者最后一次机会,令其设法解决当前还未处理的这条消息。
动态方法解析
遇到没法解析的信息后,首先将调用其所属类的下列类方法:
+(BOOL)resolveInstanceMethod:(SEL)selector
该方法参数就是未知的选择子,返回BOOL类型那个,表示这个类是否能新增一个实力方法已处理这个选择子。假如是类方法,那么会调用
+(BOOL)resolveClassMethod:(SEL)selector
使用这种方法的前提是相关的实现已经写好了,只等运行时动态的插入就行,好比CoreData中NSManagedObjects对象的属性时就能够这么作,由于实现这些属性所需的存取方法在编译期就能肯定。
下面咱们看个例子
void showMessage(id self, SEL _cmd, id value) { if([value isKindOfClass:[NSString class]]) { NSLog(@"%@",(NSString *)value); } } +(BOOL)resolveInstanceMethod:(SEL)sel { NSString *selString = NSStringFromSelector(sel); if([selString isEqualToString:@"showMessage:"]) { class_addMethod(self, sel, (IMP)showMessage, "v@:@"); return YES; } else { return [super resolveInstanceMethod:sel]; } }
向刚才EqualObject添加以上实现带代码,而后在客户端调用:
objc_msgSend(object1,@selector(showMessage:),@"Hello");
可以看到程序并无报错,并且还打印出了Hello!
备援接受者
当没有使用动态方法解析后,仍是出发备用接受者,该步骤会触发该方法
-(id)forwardingTargetForSelector:(SEL)selector
方法参数是未知的选择子,若是找到备用对象返回对象,不然返回nil。
能够利用该方法来模拟多重继承机制(实际为组合),由于外部看不到,因此感受上就像是自己处理该消息。
下面看一个例子:
把刚才添加的代码注释掉,添加一个新类OtherObject,添加showMessage方法,而后在EqualObject中添加如下代码
-(id)forwardingTargetForSelector:(SEL)aSelector { NSString *selectString = NSStringFromSelector(aSelector); if([selectString isEqualToString:@"showMessage:"]) { return other; } return nil; }
会发现Hello依旧出现了!,并且若是你在OtherObject中的showMessage方法中打上断点,会发现方法执行到了OtherObject中...
完整的消息转发
若是消息没有转发,那么回来到这一步,首先建立NSInvocation对象,而后把未处理的信息细节所有都封装于其中。
此对象包含选择子、目标(target)和参数。在触发NSInvocation对象时,“消息派发系统”会把消息派给目标。
此步骤会调用如下方法:
-(void)forwardInvocation:(NSInvocation *)invocation
这个方法实现简单,只要改变调用目标,使消息在新目标上得以调用便可。可是这样和备用接受者实现就同样了,因此通常都不会这样写。
比较有用的实现是再出发消息前,先以某种方式改变消息内容,好比追加另外一个参数,或是改装选择子。
若是发现不应由该类执行,那么须要调用超类的该方法,继承体系中每一个类都有机会处理此调用请求,若是到NSObject还不能处理会调用doesNotRecognizerSelector抛出异常。
实现上面那个方法的时候须要同时实现。
- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector;
须要先对NSInvocation签名而后才能使用NSInvocation
- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector { NSMethodSignature *sig; sig = [other methodSignatureForSelector:aSelector]; return sig; } -(void)forwardInvocation:(NSInvocation *)anInvocation { [anInvocation invokeWithTarget:other]; }
方法调配
在运行时,咱们还可使用方法调配技术来改变SEL指向的IMP,好比说目前方法名和对应IMP以下
方法 | IMP |
methodA | IMPA |
methodB | IMPB |
可是当咱们使用方法调配后,就能够出现一下状况
方法 | IMP |
methodA | IMPB |
methodB | IMPA |
其实咱们修改的本质是方发表的映射,修改了选择子的指向
该技术主要用到的方法以下
void method_exchangeImplementations(Method m1, Method m2)
该函数的两个参数表示待交换的两个方法实现,而方法实现则可经过下列函数得到:
Method class_getInstanceMethod(Class aClass , SEL aSelector)
如今咱们向EqualObject中添加methodA和methodB
-(void)methodA { NSLog(@"methodA"); } -(void)methodB { NSLog(@"methodB"); }
而后客户端这样写
EqualObject *object1 = [EqualObject new]; Method methodA = class_getInstanceMethod([EqualObject class], @selector(methodA)); Method methodB = class_getInstanceMethod([EqualObject class], @selector(methodB)); method_exchangeImplementations(methodA, methodB); [object1 methodA];
运行后就会打印出methodB。
利用该技术进行黑盒调试
要进行黑盒调试,主要用到的就是该技术和category
下面来看看具体该如何编写:
首先添加一个EqualObject的category,而后添加一个新方法plusMethodA
-(void)plusMethodA { [self plusMethodA]; NSLog(@"this is plus version"); }
这里看着像是会无限递归,可是实际上plusMethodA选择子已经指向了methodA的IMP,因此并不会出现无限调用的状况。
客户端代码
EqualObject *object1 = [EqualObject new]; Method methodA = class_getInstanceMethod([EqualObject class], @selector(methodA)); Method methodB = class_getInstanceMethod([EqualObject class], @selector(plusMethodA)); method_exchangeImplementations(methodA, methodB); [object1 methodA]
输出结果为
2015-08-13 09:06:01.672 Equal[8282:5413229] methodA 2015-08-13 09:06:01.673 Equal[8282:5413229] this is plus version
能够看到咱们没有继承一个类就作到了扩展某个方法,用于调试打印一些输出信息会颇有用!
经常使用Runtime总结
关联对象:
设置一个关联对象
void objc_setAssociatedObject(id object, void *key ,id value, objc_AssociationPolicy policy)
获取关联对象
void objc_getAssociatedObject(id object, void *key)
删除该对象全部的关联对象
void objc_removeAssociatedObjects(id object)
消息传递
向某个对象/父类 发送消息
objc_msgSend(Super)
方法调配
交换两个方法的实现
void method_exchangeImplementation(Method m1, Method m2)
获得该法的指针
Method class_getInstanceMethod(Class aClass, SEL aSelector)
动态建立对象
建立新的类
Class objc_allocateClassPair(Class superclass, const char *name, size_t extraBytes)
给类增长新的方法
BOOL class_addMethod(Class cls, SEL name, IMP imp, const char *types)
注册新的类
void objc_registerClassPair(Class cls)
得到对象的isa指针所指向的对象
Class object_getClass(id obj)