参考:
Objective-C Runtime Programming Guide
深入Objective-C的动态特性
Objective-C Runtime 运行时之一六:类与对象拾遗
刨根问底Objective-C Runtime 系列
Objective-C Runtime 1小时入门教程
Objective-C 与 Runtime
Objective-C Runtime源码传送门
一个剖析runtime的利器:
$ clang -rewrite-objc 源文件名(比如test.m)
引子
Runtime到底是什么?
Objective-C,顾名思义,和C++一样都是在C的基础上加入面向对象的特性扩充而成的程序设计语言,但二者实现的机制差异很大(C++是基于静态编译时类型,而Objective-C是基于动态运行时类型)。关键就在于OC的runtime, runtime是一个用C和汇编编写的动态库,它将OC和C紧密关联并提供动态特性(动态类型、动态绑定函数实现、动态加载资源),这个系统主要做两件事 :
1、封装C语言的结构体和函数,让开发者在运行时创建、检查或者修改类、对象和方法等等。
◈ Class ( objc_class * ) => objc_object、id ( objc_object * )、objc_super
◈ Ivar、objc_property_t ( objc_property_attribute_t )
◈ objc_method_description ( 即SEL + types ) + IMP + => Method ( objc_method * )
◈ ……
◈ Objective-C Runtime Reference
2、传递消息,找出方法的最终执行代码。
◈ 静态类型编程语言(比如C++)的函数调用和OC的方法调用(消息发送)的区别在于前者在编译期就确定了(函数的地址),而后者是运行时动态确定(代价是性能下降,objc_class中的objc_cache就是用来补偿这种性能下降的);
◈ 类层次体系查找 ( isa+objc_method_list ) + 消息转发 ( 动态解析 => 备用接收者 => ( 签名+打包+完整转发 )
动态加载:NSBundle类提供了许多面向对象的便捷接口用于动态加载;比如Retina设备自动加载@2x的图片。
应用场景举例
1、遍历对象的成员变量/属性
比如:自动实现<NSCoping>、<NSMutableCopying>、<NSCoding>协议(参考 Mantle 的源码实现)
2、动态添加/修改属性或成员变量,动态添加/修改/替换方法
3、动态创建类/对象/协议等等
比如:Model与Json Dictionary的相互转换(还涉及对象成员变量/属性的遍历),参考 MJExtension 的实现
4、方法拦截调用
比如:拦截imageNamed:、viewDidLoad、viewWillAppear:(统计页面展现次数等)、alloc。
5、为分类添加的属性提供动态的存取方法实现
传送门:RunTime源码
1. 基础数据类型
#类、对象、id以及协议
关注Class定义和isa、super_class、cache和version字段
// 类
struct objc_class {
Class isa OBJC_ISA_AVAILABILITY; //指向metaClass(元类)
#if !__OBJC2__
Class super_class OBJC2_UNAVAILABLE; // 父类
const char *name OBJC2_UNAVAILABLE; // 类名
long version OBJC2_UNAVAILABLE; // 类的版本信息,默认为0 => 序列化支持
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; // 方法缓存;优化methodLists查找
struct objc_protocol_list *protocols OBJC2_UNAVAILABLE; // 协议链表
#endif
} OBJC2_UNAVAILABLE;
typedef struct objc_class *Class;
// 对象
struct objc_object {
Class isa OBJC_ISA_AVAILABILITY;
};
// id
typedef struct objc_object *id;
// 协议
typedef struct objc_object Protocol;
#分类
typedef struct objc_category *Category;
#元类(Meta Class)
类自身也是一个对象(元类的对象),调用类方法其实就是向类对象发送消息;区别:
◈ 向一个对象发送消息时,runtime会在对象所属类的方法列表中查找方法;
◈ 向一个类发送消息时,会在这个类的meta-class的方法列表中查找方法。
#成员变量、属性
- 成员变量与属性的联系 - 属性声明的本质是根据指定规则生成对应的成员变量及其存取方法
- 成员变量名:默认是_xxx;当然你也可以用语法糖@synthesize自行指定
- 存取方法:包括原子性(锁机制)、读写权限、内存管理策略、存取方法名设定
- 编码
-
纯类型编码 -
c、i、s、l、q、C、I、S、L、Q或f、d、B、V、*、@、#、:、[]、{}、()、b、^、?
^{example=@*i} //example类型表示包含一个id对象、一个char *、一个int的结构体的指针
^^{example} //一个example指针的指针
- 方法类型编码
对 - (BOOL)danceWith:(NSString *)people at:(char)place on:(int)count;
调用const char * method_getTypeEncoding ( Method m );
=> 返回值(BOOL) + 隐藏参数self(id) + 隐藏参数_cmd(SEL) + 第一个参数(NSString *) + 第二个参数(char) + 第三个参数(int)
=> ("B"+32) + ("@"+0) + (":"+8) + ("@"+16) + ("c"+24) + ("i"+28)
=> "B32@0:8@16c24i28"
- 属性类型编码 - T<纯类型编码>、V<s>、R、C、&、N、G<s>、S<s>、D、W、P、t<s>
对 @property (nonatomic, readonly, retain) NSString* vName;
调用const char * property_getAttributes ( objc_property_t property );
=> 属性编码 + nonatomic + readonly + retain + 属性对应的成员变量
=> ("T" + "@\"NSString\"") + ("N" + "") + ("R" + "") + ("&" + "") + ("V" + "_vName")
=> "T@\"NSString\",R,&,N,V_vName"
- 基础数据类型
// 成员变量 - objc_ivar的结构在iOS 9.0已经被隐藏,其封装了成员变量的名字和类型。
typedef struct objc_ivar *Ivar;
// 属性
typedef struct objc_property *objc_property_t;
// 属性的特性 - 注意:特性V对应的是成员变量名(可被@synthesize指定或自动生成加前缀‘_’),不是属性名
typedef struct {
const char *name; // 特性名 - 必选:T、V 可选:R、C、&、N、G、S、D、W、P、t
const char *value; // 特性值 - 只有name为TVGSt时value不为nil,且T用纯类型编码表示
} objc_property_attribute_t;
#方法
- SEL - 本质上是一个根据方法名hash化了的KEY值,它的存在只是为了加快方法的查询速度。
- IMP - IMP实际上是一个函数指针,指向方法实现的首地址。
// 方法选择器
typedef struct objc_selector *SEL;
// 方法实现 - 第一个参数是指向self的指针(如果是实例方法,则是类实例的内存地址;如果是类方法,则是指向元类的指针)
id (*IMP)(id, SEL, ...)
// 方法 - 将SEL和对应的实现IMP关联起来
struct objc_method {
SEL method_name OBJC2_UNAVAILABLE; // 方法名
char *method_types OBJC2_UNAVAILABLE; // 返回值和参数类型编码; 可用+signatureWithObjCTypes:封装成方法签名
IMP method_imp OBJC2_UNAVAILABLE; // 方法实现
}
typedef struct objc_method *Method;
#图形化
OC对象的内存布局.png OC的类继承体系结构附:健壮的实例变量
由于超类实例变量的变更(增删改)导致OC对象的内存布局变化,将由系统通过Ivar对象(封装了基地址和偏移量)管理,我们需要做的仅仅是通过名字直接获取或通过Ivar间接获取实例变量的值,复杂的事就交给系统吧。
自动偏移
2. 操作函数
注:增改类的方法放在系列二中。
#类
// 获取所有已注册类 - 前6个函数的搜索范围都是已注册到运行时系统的类(objc_registerClassPair)。
int objc_getClassList ( Class *buffer, int bufferCount );
Class * objc_copyClassList ( unsigned int *outCount );
------------------------------------------------ // 功能类似,区别在于未找到Class的情况:
Class objc_getClass ( const char *name ) // 会调class handler callback???,并再次检查Class是否注册
Class objc_lookUpClass ( const char *name ) // 直接返回nil
Class objc_getRequiredClass ( const char *name ) // 会kill进程,程序crash
Class objc_getMetaClass ( const char *name )
Class class_getSuperclass ( Class cls ); // cls不需注册
-------------------------------------------
const char * class_getName ( Class cls ); // cls不需注册
size_t class_getInstanceSize ( Class cls ); // cls不需注册
BOOL class_isMetaClass( Class cls ); // 不注册就没有元类
#分类
Runtime并没有在 <objc/runtime.h>
头文件中提供针对分类的操作函数。因为分类中的信息最终会被合并到对应类中(也即对应的objc_class中),所以我们可以通过针对objc_class的操作函数来获取分类的信息。
#协议
注意:从未被任何类实现的协议不会被自动注册到运行时系统,当然你也可以objc_registerProtocol手动注册。
// 获取已注册的协议
Protocol * objc_getProtocol ( const char *name );
Protocol ** objc_copyProtocolList ( 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 );
// 方法 - 方法描述包含了方法名(比如"init")、返回值和参数的类型编码信息(比如"@16@0:8")
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 );
/* 与其他协议的关系 */
Protocol ** protocol_copyProtocolList ( Protocol *proto, unsigned int *outCount );
BOOL protocol_conformsToProtocol ( Protocol *proto, Protocol *other );
-------------------------------------------
const char * protocol_getName ( Protocol *p );
BOOL protocol_isEqual ( Protocol *proto, Protocol *other );
-------------------------------------------
BOOL class_conformsToProtocol ( Class cls, Protocol *protocol );
Protocol * class_copyProtocolList ( Class cls, unsigned int *outCount );
#对象
/* 根据成员变量的类型(OC对象/其他)用不同的方法获取其信息*/
id object_getIvar ( id obj, Ivar ivar );
Ivar object_getInstanceVariable ( id obj, const char *name, void **outValue );
void * object_getIndexedIvars ( id obj ); // 给定实例obj的extra bytes的指针,详见上文图形化
/* 针对对象的类进行操作 */
const char * object_getClassName ( id obj );
Class object_getClass ( id obj );
#版本
int class_getVersion ( Class cls );
void class_setVersion ( Class cls, int version );
#成员变量
// 获取信息
const char * ivar_getName ( Ivar v );
const char * ivar_getTypeEncoding ( Ivar v ); // 比如"@\"NSString\""
ptrdiff_t ivar_getOffset ( Ivar v );
// 获取
Ivar class_getInstanceVariable ( Class cls, const char *name ); // 实例成员变量
Ivar class_getClassVariable ( Class cls, const char *name ); // 类变量,如何用OC代码声明一个类变量???
Ivar * class_copyIvarList ( Class cls, unsigned int *outCount ); // 实例/类的成员变量列表
#属性 - 属性可以replace,纯成员变量不行。
const char * property_getName ( objc_property_t property ); // 返回属性名(区别于成员变量名)
const char * property_getAttributes ( objc_property_t property );
char * property_copyAttributeValue ( objc_property_t property, const char *attributeName );
objc_property_attribute_t * property_copyAttributeList ( objc_property_t property, unsigned int *outCount );
// 获取
objc_property_t class_getProperty ( Class cls, const char *name );
objc_property_t protocol_getProperty( Protocol *proto, const char *name, BOOL isRequiredProperty, BOOL isInstanceProperty )
=> objc_property_t * class_copyPropertyList ( Class cls, unsigned int *outCount );
=> objc_property_t * protocol_copyPropertyList( Protocol *proto, unsigned int *outCount );
// 属性列表
objc_property_t *class_copyPropertyList(Class cls, unsigned int *outCount)
#SEL
SEL sel_registerName ( const char *str ); // 也可使用更方便的语法糖 @selector(…)
SEL sel_getUid ( const char *str ); // 实现等价于sel_registerName
const char * sel_getName ( SEL sel );
BOOL sel_isEqual ( SEL lhs, SEL rhs ); // 只要方法名相同就返回YES
#IMP
// 获取实现 - 注意:涵盖了消息转发机制!!!
IMP method_getImplementation ( Method m );
IMP class_getMethodImplementation ( Class cls, SEL name );
IMP class_getMethodImplementation_stret ( Class cls, SEL name );
// 设置、交换实现
=> IMP method_setImplementation ( Method m, IMP imp );
=> void method_exchangeImplementations ( Method m1, Method m2 );
// 使用方便的块语法
=> IMP imp_implementationWithBlock(id block)
=> id imp_getBlock( IMP anImp)
=> BOOL imp_removeBlock( IMP anImp)
#Method
注:要获取类方法列表,用class_copyMethodList(object_getClass(cls), &count)
实现。
// 查
Method class_getInstanceMethod ( Class cls, SEL name ); // 搜索父类
Method class_getClassMethod ( Class cls, SEL name ); // 搜索父类
Method * class_copyMethodList ( Class cls, unsigned int *outCount ); // 不搜索父类, 只返回实例方法
SEL method_getName ( Method m );
unsigned int method_getNumberOfArguments ( Method m );
struct objc_method_description * method_getDescription ( Method m );
// 获取参数或/和返回值的类型编码,
const char * method_getTypeEncoding ( Method m ); // 比如"B32@0:8@16c24i28"
char * method_copyArgumentType ( Method m, unsigned int index ); // 0->"@", 1->":", 2->"c", 3->"i"
void method_getArgumentType ( Method m, unsigned int index, char *dst, size_t dst_len );
char * method_copyReturnType ( Method m ); // "B"
void method_getReturnType ( Method m, char *dst, size_t dst_len );
--------------------------------------------------------------------------------
// 方法调用
=> id method_invoke ( id receiver, Method m, ... );
=> void method_invoke_stret ( id receiver, Method m, ... ); // 调用返回一个数据结构的方法的实现
#关联对象
=> 解决了category不能添加成员变量()的问题 => 引申:为指定对象动态添加功能的解决方案
注意:
1、在category中可以声明属性,但是不会自动生成成员变量和对应的存取方法;
2、关联对象并不会自动生成属性或成员变量,通常做法是声明一个属性,并利用关联对象动态实现其存取方法,使其看起来就像一个正常的属性一样。
// 5种内存管理策略: OBJC_ASSOCIATION_[ASSIGN/RETAIN/RETAIN_NONATOMIC/COPY/COPY_NONATOMIC]
// 也可以通过设置value为nil来移除关联
=> void objc_setAssociatedObject(id object, void *key, id value, objc_AssociationPolicy policy)
=> id objc_getAssociatedObject(id object, void *key)
=> void objc_removeAssociatedObjects ( id object );
网友评论