Objective-C——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[]
}
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[]
}
该结构有方法链表和方法总数。
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[]
};
缓存列表里面包含了已缓存的方法,用于快速的调用,不需要在去方法列表里面查询了。
protocol协议列表
struct objc_protocol_list {
struct objc_protocol_list *next;
long count;
Protocol *list[];
};
协议列表包含了协议数量和协议的指针。
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]
输出结果为
-- ::01.672 Equal[:] methodA
-- ::01.673 Equal[:] 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)
Objective-C——Runtime理解的更多相关文章
- iOS --runtime理解
iOS~runtime理解 Runtime是想要做好iOS开发,或者说是真正的深刻的掌握OC这门语言所必需理解的东西.最近在学习Runtime,有自己的一些心得,整理如下,一为 查阅方便二为 或许能给 ...
- Objective C Runtime 开发介绍
简介 Objective c 语言尽可能的把决定从编译推迟到链接到运行时.只要可能,它就会动态的处理事情.这就意味着它不仅仅需要一个编译器,也需要一个运行时系统来执行变异好的代码.运行时系统就好像是O ...
- Runtime理解
动态语言 OC是一门不折不扣的动态语言,所以它的很多机制都是动态运行时决定的.这点和C语言不一样,C语言是静态绑定,也就是编译后所有的一切都已经决定了.这一点和C语言的函数指针有些类似,很多时候函数指 ...
- runtime 理解笔记
runtime 简称运行时,是系统运行的一种机制,在oc中通过c语言编写一个运行系统库.考进行一些非常底层的操作(oc无法完成的). 1.利用runtime,在程序运行过程中,动态创建一个类(比如KV ...
- 函数调用和给对象发消息(Runtime理解)
在写代码的时候这个差距其实是不打看的出得,很多时候也就无所谓叫什么,很多人为了便于理解,干脆就叫函数调用.这个其实应该是oc的一个特色,消息发送.具体的类typedef struct objc_cla ...
- 刨根问底Objective-C Runtime(4)- 成员变量与属性
http://chun.tips/blog/2014/11/08/bao-gen-wen-di-objective[nil]c-runtime(4)[nil]-cheng-yuan-bian-lian ...
- Objective-C Runtime(一)预备知识
很早就知道了Objective-C Runtime这个概念,「Objective-C奇技淫巧」「iOS黑魔法」各种看起来很屌的主题中总会有它的身影:但一直没有深入去学习,一来觉得目前在实际项目中还没有 ...
- Runtime系列(二)--Runtime的使用场景
Runtime 理解介绍的文章非常多,我只想讲讲Runtime 可以用在哪里,而我在项目里哪些地方用到了runtime.多以实际使用过程为主,来介绍runtime的使用. * 那么runtime 怎么 ...
- iOS 面试基础题目
转载: iOS 面试基础题目 题目来自博客:面试百度的记录,有些问题我能回答一下,不能回答的或有更好的回答我放个相关链接供参考. 1面 Objective C runtime library:Obje ...
随机推荐
- 【02】Node.js 安装配置(OK)
[02] Node.js 安装配置 本章节我们将向大家介绍在window和Linux上安装Node.js的方法. Node.js安装包及源码下载地址为:http://www.nodejs.org/do ...
- spring-quartz定时器简单用法
基本配置: 1.quartz.properties #============================================================== #Configure ...
- nyoj_123_士兵杀敌(四)_201404131143
士兵杀敌(四) 时间限制:2000 ms | 内存限制:65535 KB 难度:5 描述 南将军麾下有百万精兵,现已知共有M个士兵,编号为1~M,每次有任务的时候,总会有一批编号连在一起人请战 ...
- 2102 石子归并 2codevs
2102 石子归并 2codevs 题目描述 Description 在一个园形操场的四周摆放N堆石子,现要将石子有次序地合并成一堆.规定每次只能选相邻的2堆合并成新的一堆,并将新的一堆的石子数,记为 ...
- Ubuntu 16.04下Markdown编辑器Haroopad
1.下载deb包 地址:https://bitbucket.org/rhiokim/haroopad-download/downloads/haroopad-v0.13.2-x64.deb 这里是历史 ...
- sql sever 等待事件
http://blog.csdn.net/dba_huangzj/article/details/7607844
- MVC WebApi 将返回值改为JSON格式
新增一个类: public class BrowserJsonFormatter : JsonMediaTypeFormatter { public BrowserJsonFormatter() { ...
- 交换机是干嘛的!!交换机如何学习MAC地址过程?
1.它收到一个帧的时候,先检查源MAC地址,看看自己维护的一个地址表中有没有这个地址.如果有,则2:如果没有,则将这个MAC地址.进入的端口.进入的时间放入这个表中: 2.检查目的MAC地址,然后到该 ...
- 第5章 Cisco测试命令和TCP/IP连接故障处理
第5章 Cisco测试命令和TCP/IP连接故障处理 一.故障处理命令 1.show命令: 1) 全局命令: show version :显示系统硬件和软件版本.DRAM.Flash show sta ...
- windows bat命令 开启关闭Oracle服务
0.吐槽 单位发的ThinkPad T61.太弱小了. 问题是我去百度下T61,发现它好贵好贵.真心无力吐槽.还不如给我发台外星人,廉价点的. . Oracle一开就内存就不够了.所以绝对不能让它开机 ...