Objective-C Runtime
来源:互联网 发布:室内分布设计软件 编辑:程序博客网 时间:2024/06/16 21:16
由消息发送开始: [receiver message]
在编译的时候会被转化为objc_msgSend(receiver, selector)
。objc_msgSend
的签名是:objc_msgSend(id self, SEL op, …)
对象模型
id是什么?
typedef struct objc_object *id;struct objc_object{ Class isa };typedef struct objc_class *Class;
即id是一个objc_object的指针,这个objc_object结构中有一个objc_class指针。
id -> objc_object { -> objc_class}
objc_class又是啥?
struct objc_class { Class isa; Class super_class; const char *name; long version; long instance_size; struct objc_ivar_list *ivars; struct objc_method_list **methodLists; struct objc_cache *cache; struct objc_protocol_list *protocols;}
objc_class是描述类的类,叫做类对象。其中isa字段是一个递归定义,指向一个描述这个类对象的类,叫做元类对象。
- 元类对象的isa指向root class的元类对象,root class可以认为是NSObject。
- root class的元类对象的isa指向自己。
- isa的根节点就是root class的元类对象。
super_class还是一个Class,这里有几种情况:
- 如果这个类对象描述的是一个自定义的类,那么这个类对象的super_class指针指向的是这 个自定义类的基类的类对象;
- 如果这个类对象描述的是root class,那么super_class指向nil;
- 如果这是一个元类对象,super_class指针指向的是它描述的类对象对所描述的类的基类的元类对象。
- 如果这是一个root class的元类对象,super_class指向的root class的类对象。
- super_class的根节点是nil。
isa和super_class的关系可以用下图表示:
name字段就是描述的类的名字。
version是类的版本号,暂时不知道有啥用,应该是运行时自己用。
info类的信息,暂时不知道有啥用。
instance_size 类的大小。
ivars用于保存类对象所描述的类的实例变量的描述。objc_ivar_list这个结构不展开了,里边保存了变量的个数和一个数组,数组中的每一个元素就是一个实例变量的描述。
描述实例变量的结构叫做objc_ivar
,typedef struct objc_ivar *Ivar
:
struct objc_method { SEL method_name char *method_types IMP method_imp}
第一个字段是SEL,是方法选择器,这就解释了如何从一个对象中通过方法选择器找到方法。
第二个字段保存了方法的参数类型和返回值,
第三个字段是方法的函数指针,指向真正的代码地址
cache主要的作用是将找到的方法地址缓存起来,提高效率。
objc_protocol_list保存了所描述对象遵从的协议的类对象。
消息机制
对象模型了解到这里,接下来可以表述下消息发送的路由:
- 检查接受者是不是nil,如果是,返回nil。
- 从接受者的类对象的cache中查找selector,找到就执行。
- 找接受者的类对象的methodLists.应该是比较SEL。
- 找接受者的基类的类对象的methodLists,直到NSObject的类对象。
- 进入动态方法解析。
可以使用NSObject的methodForSelector方法可以找到Selector的函数指针,直接调用。
动态方法解析
首先如何构造动态方法解析?如果向一个实例发送它没有实现的方法,编译器就不会通过,是不能运行的,也就无法触发动态方法解析。
一种方法是将实例赋值给id,然后在id指向的对象上调用。
另外一种是只声明实例方法而不提供实现。动态属性@dynamic
就是这种类型。
NSObject
的类方法resolveInstanceMethod
和resolveClassMethod
会在上文中的第5步被调用,这是一个提供动态方法的时机。如果在这里给类动态的添加了方法来响应这个selector
,返回YES,否则返回NO。 responseToSelector
也会触发resolveInstanceMethod
和resolveClassMethod
。
如果resolveInstanceMethod
或者resolveClassMethod
返回了NO。则进入消息转发的环节。
消息转发环节有两个钩子,第一个是forwardingTargetForSelector
,可以将这个selector
发给其他对象。第二个是forwardInvokation
,可以调用其他对象的其他方法,而不仅仅限于这个selector
。
最后都没有机会处理消息,程序Crash。
Category
先说一个category和extension的区别,因为extension看起来就是一个没有名字的category,实际上完全不是那么回事。extentsion是编译期决议的,因此必须有源码,所以可以添加实例变量。category是运行时决议的,不需要有源码。所以也不能添加实例变量。
category是一个category_t的结构体。
typedef struct category_t { const char *name; classref_t *cls; struct method_list_t *instanceMethods; struct method_list_t *classMethods; struct protocol_list _t *protocols; struct property_list_t *instanceProperties;}category_t;
category的cls字段建立起了和对应的Class的联系。编译期间并不对原来的类做任何改变。在运行时,runtime根据cls找到对应的类对象,将category的实例方法加载到类对象的实例方法列表中,把category的类方法加载到元类对象上。
这里有两个问题:
1. category的方法是在什么时候加载到类中去的?
2. 如果不引入category头文件,category的方法还会加载么
这两个问题用同一个答案能解释,category是在类加载的时候就加载了,所以无论是否引入了Category头文件,Category中相应的方法都会被添加进主类。可以通过-performSelector等方式对Category中的方法进行调用.
Category中的方法被整合到类对象中以后,和主类中的方法没有区别。如果有重复的方法,category中的方法会在前边。这样很容易导致难以调试的bug,可以通过在写category的时候,给所有方法加上自己的前缀来解决这个问题。
那么这里有一个偏门的问题,我们常常在category中写一个+load方法,用于在类的初始化期间做一些事情,如果method swizzling,既然category中的方法在主类的方法前边,那么在category中的+load方法和主类中的+load为什么会分别执行?
答案是runtime对+load的方法调用是直接使用函数指针,而不是通过方法列表查找。runtime会使用指针分别调用主类和category的+load方法。
+load
& +initialize
+load
是类被加载的时候就会被调用,+initialize
是类或者子类第一次收到消息的时候被调用。可以将一些可以延迟加载的代码放到+initialize
中优化启动速度。
+load
只会被调用一次,并且,基类的+load
会在子类的+load
方法之前调用,子类的+load
会在category的+load
方法之前调用。+load
方法不走方法列表,直接调用函数指针,所以子类的实现也不能覆盖父类的实现。
+initialize
不能保证只被加载一次,子类如果实现+initialize
,就会调用父类的,这是因为+initialize
就是一个普通的消息发送,会走方法列表。所以一个类的+initialize
函数应该是有dispatch_once
来保证代码只执行一次。
动态创建类
- 首先要查一下这个类名是不是已经存在了:
objc_getClass(className);
- 如果不存在才继续创建:
objc_allocteClassPair(superClass, className, 0);
- 添加成员变量:
class_addIvar(kClass, “expression”, alignment,“”)
,最后一个“”是typeEncoding表示方式。 - 添加方法:
class_addMethod(kClass, @selector(setExpression:),(IMP)setExpression, “v@:@“)
- 注册类到运行时:
objc_registerClassPair(kClass)
- 实例化:
id instance = [[kClass alloc] init];
- 给变量赋值:
object_setInstanceVariable(instance, “expression”, “1+1”);
- 调用函数:
[instance performSelector:@selector(getExpression)];
给变量赋值和调用函的形式和在编译时定义的类是不同的,是为了绕过编译器检查,如果使用[instance getExpression]是不能通过编译的。
注意类方法要添加到kClass的元类中。kClass = [kClass class]
;
在objc_registerClassPair
之后不能再添加变量。
- Runtime of Objective-C
- Runtime of Objective-C
- objective-c runtime
- Objective-C Runtime
- 详解Objective-C runtime
- 详解Objective-C runtime
- 详解Objective-C runtime
- 详解Objective-C runtime
- objective-c runtime
- 详解Objective-C runtime
- Objective-C Runtime
- 详解Objective-C runtime
- Runtime of Objective-C
- Runtime of Objective-C
- 理解 Objective-C Runtime
- 理解 Objective-C Runtime
- 理解 Objective-C Runtime
- 理解 Objective-C Runtime
- basename命令和函数
- 手机号和用户名验证
- 如何判断一个正整数是否是2的乘方
- Scrapy框架安装遇到的问题
- c++操作符重载的两种类型
- Objective-C Runtime
- hashmap和hashtable的区别
- HDU
- recv函数返回值总结
- Unity|ShaderLab笔记整理-三(逐像素漫反射+环境光)
- Hive将txt、csv等文本文件导入hive表
- /etc/apt/sources.list 详解
- MySQL数据库性能优化之缓存参数优化
- iOS远程真机之OS X EI Captian 编译 libimobiledevice 错误记录以及解决方法!!