Runtime理解
动态语言
OC是一门不折不扣的动态语言,所以它的很多机制都是动态运行时决定的。这点和C语言不一样,C语言是静态绑定,也就是编译后所有的一切都已经决定了。这一点和C语言的函数指针有些类似,很多时候函数指针在编译的时候并不知道会指向哪个函数,所以此时就是动态绑定。
举几个OC动态类型的例子,最为直接的就是id类型了、还有关联对象、动态绑定、消息转发、方法调配、这些技术都是动态类型很好的证明。
OC对象结构
在介绍动动态性之前,我们先来看看OC对象的一些结构。
#import<objc/runtime>这是OC运行时函数库,里面定义了很多结构体。
首先看对象的结构:
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对象中,再给接受者最后一次机会,令其设法解决当前还未处理的这条消息。
动态方法解析
void showLog(id self, SEL _cmd, id value) { if([value isKindOfClass:[NSString class]]) { NSLog(@"%@",(NSString *)value); } } +(BOOL)resolveInstanceLog:(SEL)sel { NSString *selString = NSStringFromSelector(sel); if([selString isEqualToString:@"showMessage:"]) { class_addMethod(self, sel, (IMP)showMessage, "v@:@"); return YES; } else { return [super resolveInstanceMethod:sel]; } }
遇到无法解析的信息后,首先将调用其所属类的下列类方法:
+(BOOL)resolveInstanceMethod:(SEL)selector
该方法参数就是未知的选择子,返回BOOL类型那个,表示这个类是否能新增一个实力方法已处理这个选择子。假如是类方法,那么会调用
+(BOOL)resolveClassMethod:(SEL)selector
使用这种方法的前提是相关的实现已经写好了,只等运行时动态的插入就行,比如CoreData中NSManagedObjects对象的属性时就可以这么做,因为实现这些属性所需的存取方法在编译期就能确定。
下面我们看个例子