本文是参考南峰子Objective-C Runtime系列文章
做一个自己的总结,强烈推荐查看原文
1、类型编码(Type Encoding)
南峰子关于Type Encoding的翻译
苹果官方文档,Type Encodings
苹果官方文档,Property Type String
在定义一个方法如下:第四个参数就是参考Type Encoding
class_addMethod([self class],@selector(eat:),(IMP) eatIMP, "v@:@");
v@:@
: 没有返回值,参数类型依次为,id
、SEL
、id
定义一个属性如下:就是参考Property Type String
官方文档写了,T
为开始,V
为结束,中间参考表
The string starts with a
T
followed by the@encode
type and a comma, and finishes with aV
followed by the name of the backing instance variable. Between these, the attributes are specified by the following descriptors, separated by commas:
objc_property_attribute_t type = { "T", "@\"NSString\"" };//类型
objc_property_attribute_t ownership = { "C", "" }; // C = copy
objc_property_attribute_t nonatomic = { "N", "" }; //nonatomic
objc_property_attribute_t backingivar = { "V", "_property1" };//V 实例变量
objc_property_attribute_t attrs[] = { type, ownership,nonatomic, backingivar };
class_addProperty(cls, "property1", attrs, 4);
2、Ivar
Ivar
表示实例成员变量类型,其实际是指向objc_ivar
结构体的指针,其定义如下:
typedef struct objc_ivar *Ivar;
struct objc_ivar {
char * _Nullable ivar_name // 变量名 OBJC2_UNAVAILABLE;
char * _Nullable ivar_type //变量类型 OBJC2_UNAVAILABLE;
int ivar_offset // 基地址偏移字节 OBJC2_UNAVAILABLE;
#ifdef __LP64__
int space OBJC2_UNAVAILABLE;
#endif
}
成员变量操作函数
// 获取成员变量名
const char * ivar_getName ( Ivar v );
// 获取成员变量类型编码
const char * ivar_getTypeEncoding ( Ivar v );
// 获取成员变量的偏移量
ptrdiff_t ivar_getOffset ( Ivar v );
-
ivar_getOffset
: 对于类型id
或其它对象类型的实例变量,可以调用id object_getIvar(id _Nullable obj, Ivar _Nonnull ivar)
和void object_setIvar(id _Nullable obj, Ivar _Nonnull ivar, id _Nullable value)
来直接访问成员变量,而不使用偏移量
3、objc_property_t
objc_property_t
是表示属性类型,其实际是指向objc_property
结构体的指针,其定义如下:
typedef struct objc_property *objc_property_t;
3.1、objc_property_attribute_t
objc_property_attribute_t
定义了属性的特性(attribute
),它是一个结构体,定义如下:
typedef struct {
const char *name; // 特性名
const char *value; // 特性值
} objc_property_attribute_t;
属性操作函数如下:
// 获取属性名
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 );
注意:property_copyAttributeValue
和 property_copyAttributeList
使用完后需要调用free()释放
4、关联对象(Associated Object)
关联对象类似于成员变量,不过是在运行时添加的。我们通常会把成员变量(Ivar
)放在类声明的头文件中,或者放在类实现的@implementation
后面。但这有一个缺点,我们不能在分类中添加成员变量。如果我们尝试在分类中添加新的成员变量,编译器会报错。
我们可能希望通过使用(甚至是滥用)全局变量来解决这个问题。但这些都不是Ivar
,因为他们不会连接到一个单独的实例。
Objective-C
针对这一问题,提供了一个解决方案:即关联对象(Associated Object
)
关联对象操作函数如下:
// 设置关联对象
void objc_setAssociatedObject ( id object, const void *key, id value, objc_AssociationPolicy policy );
// 获取关联对象
id objc_getAssociatedObject ( id object, const void *key );
// 移除关联对象
void objc_removeAssociatedObjects ( id object );
objc_AssociationPolicy
如下:
BJC_ASSOCIATION_ASSIGN
OBJC_ASSOCIATION_RETAIN_NONATOMIC
OBJC_ASSOCIATION_COPY_NONATOMIC
OBJC_ASSOCIATION_RETAIN
OBJC_ASSOCIATION_COPY
当宿主对象被释放时,会根据指定的内存管理策略来处理关联对象。
如果指定的策略是assign
,则宿主释放时,关联对象不会被释放;
如果指定的是retain
、copy
,则宿主释放时,关联对象会被释放
5、Method
typedef struct objc_method *Method;
struct objc_method {
SEL _Nonnull method_name OBJC2_UNAVAILABLE;
char * _Nullable method_types OBJC2_UNAVAILABLE;
IMP _Nonnull method_imp OBJC2_UNAVAILABLE;
} OBJC2_UNAVAILABLE;
-
method_name
: 方法名 -
method_imp
: 方法实现
objc_method
结构体中包含了一个SEL
和IMP
,实际相当于在SEL
和IMP
之间做了一个映射,有了SEL
,我们便能找到对应的IMP
objc_msgSend
有两个隐藏参数:
- 消息接收对象
- 方法的
selector
这两个参数为方法的实现提供了调用者的信息。之所以说是隐藏的,是因为它们在定义方法的源代码中没有声明。它们是在编译期被插入实现代码的。
虽然这些参数没有显示声明,但在代码中仍然可以引用它们。我们可以使用self
来引用接收者对象,使用_cmd
来引用选择器。
5.1、SEL(方法名)
SEL
又叫选择器,是表示一个方法的selector
的指针,其定义如下:
typedef struct objc_selector *SEL;
Objective-C
在编译时,会根据每个方法的名字、参数、生成一个唯一的标识(Int
类型地址),这个标识就是SEL
,根据一个方法名hash
化了的key
值,能唯一代表一个方法,它的存在只是为了加快了方法的查询速度
我们可以在运行时添加新的selector
,也可以在运行时获取已存在的selector
,我们可以通过下面三种方法来获取SEL
:
-
sel_registerName
函数 -
Objective-C
编译器提供的@selector()
-
NSSelectorFromString()
方法
选择器相关操作如下:
// 返回给定选择器指定的方法的名称
const char * sel_getName ( SEL sel );
// 在Objective-C Runtime系统中注册一个方法,将方法名映射到一个选择器,并返回这个选择器
SEL sel_registerName ( const char *str );
// 在Objective-C Runtime系统中注册一个方法
SEL sel_getUid ( const char *str );
// 比较两个选择器
BOOL sel_isEqual ( SEL lhs, SEL rhs );
1.sel_registerName
:在我们将一个方法添加到类定义时,我们必须在Objective-C Runtime
系统中注册一个方法名以获取方法的选择器
5.2、IMP(方法实现)
IMP
实际上是一个函数指针,指向方法实现的首地址,其定义如下:
typedef void (*IMP)(void /* id, SEL, ... */ );
第一个参数是指向self
的指针 (如果是对象方法,则指向对象所属类,如果是类方法,则指向元类)
第二参数是方法选择器selector
第三个参数起,就是方法参数
方法名SEL
的查找就是为了查找方法的实现IMP
。由于每个方法对应唯一的SEL
,因此我们可以通过SEL
快速准确地获取它所以对应的IMP
。获取IMP
后,我们就获得了执行这个方法代码的入口点,就可以像调用普通C函数
来使用这个函数了。
通过取得IMP
,我们可以跳过Runtime
的消息传递机制,直接执行IMP
指向的函数实现,这样省去了Runtime
消息传递过程中所做的一系列查找操作,会比直接向对象发送消息高效一些。
5.2.1、objc_method_description
方法描述包括:方法名和方法类型(类方法、实例方法)
struct objc_method_description {
SEL _Nullable name; /**< The name of the method */
char * _Nullable types; /**< The types of the method arguments */
};
方法操作相关函数如下:
// 调用指定方法的实现
id method_invoke ( id receiver, Method m, ... );
// 调用返回一个数据结构的方法的实现
void method_invoke_stret ( id receiver, Method m, ... );
// 获取方法名
SEL method_getName ( Method m );
// 返回方法的实现
IMP method_getImplementation ( Method m );
// 获取描述方法参数和返回值类型的字符串
const char * method_getTypeEncoding ( Method m );
// 获取方法的返回值类型的字符串
char * method_copyReturnType ( Method m );
// 获取方法的指定位置参数的类型字符串
char * method_copyArgumentType ( Method m, unsigned int index );
// 通过引用返回方法的返回值类型字符串
void method_getReturnType ( Method m, char *dst, size_t dst_len );
// 返回方法的参数的个数
unsigned int method_getNumberOfArguments ( Method m );
// 通过引用返回方法指定位置参数的类型字符串
void method_getArgumentType ( Method m, unsigned int index, char *dst, size_t dst_len );
// 返回指定方法的方法描述结构体
struct objc_method_description * method_getDescription ( Method m );
// 设置方法的实现
IMP method_setImplementation ( Method m, IMP imp );
// 交换两个方法的实现
void method_exchangeImplementations ( Method m1, Method m2 );
-
method_invoke
: 返回的是实际实现的返回值。参数receiver
不能为空。这个方法的效率会比method_getImplementation
和method_getName
更快 -
method_getName
: 返回的是一个SEL
。如果想获取方法名的C
字符串,可以使用sel_getName(method_getName(method))
。 -
method_getReturnType
: 类型字符串会被拷贝到dst
中 -
method_setImplementation
: 注意该函数返回值是方法之前的实现
6、消息
给一个对象发送消息:如[person run];
会被编译成objc_msgSend(person, @selector(run));
objc_msgSend
具体查找流程如下
- 通过
isa
指针找到对象所属的类 - 查找类的
cache
列表,成功,跳转到第4步,失败,跳转到第3步 - 查找类的
methodLists
列表 - 查找名字相同的方法,成功跳转到代码实现,失败,跳转到第5步
- 沿着集成体系向上查找,成功跳转到代码实现,失败,消息转发
6.1、消息转发
- 动态方法解析:询问是否有动态方法来处理这个
未知消息
,有则消息转发结束 - 备用接收者:询问其他接收者是否能处理这条消息,有,则将这条消息转发给该接收者,消息转发结束
- 完整转发:将这个
未知消息
所有细节封装到NSInvocation
对象中,再次询问,如果此时还未能处理,则消息处理失败
+ (BOOL)resolveInstanceMethod:(SEL)sel{}
- (id)forwardingTargetForSelector:(SEL)aSelector{}
- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector{}
- (void)forwardInvocation:(NSInvocation *)anInvocation{}
7、Protocol
Protocol
定义如下,可以看出Protocol
就是一个对象结构体
typedef struct objc_object Protocol;
Protocol相关操作函数如下:
// 返回指定的协议
Protocol * objc_getProtocol ( const char *name );
// 获取运行时所知道的所有协议的数组
Protocol ** objc_copyProtocolList ( unsigned int *outCount );
// 创建新的协议实例
Protocol * objc_allocateProtocol ( const char *name );
// 在运行时中注册新创建的协议
void objc_registerProtocol ( Protocol *proto );
// 为协议添加方法
void protocol_addMethodDescription ( Protocol *proto, SEL name, const char *types, BOOL isRequiredMethod, BOOL isInstanceMethod );
// 添加一个已注册的协议到协议中
void protocol_addProtocol ( Protocol *proto, Protocol *addition );
// 为协议添加属性
void protocol_addProperty ( Protocol *proto, const char *name, const objc_property_attribute_t *attributes, unsigned int attributeCount, BOOL isRequiredProperty, BOOL isInstanceProperty );
// 返回协议名
const char * protocol_getName ( Protocol *p );
// 测试两个协议是否相等
BOOL protocol_isEqual ( Protocol *proto, Protocol *other );
// 获取协议中指定条件的方法的方法描述数组
struct objc_method_description * protocol_copyMethodDescriptionList ( Protocol *p, BOOL isRequiredMethod, BOOL isInstanceMethod, unsigned int *outCount );
// 获取协议中指定方法的方法描述
struct objc_method_description protocol_getMethodDescription ( Protocol *p, SEL aSel, BOOL isRequiredMethod, BOOL isInstanceMethod );
// 获取协议中的属性列表
objc_property_t * protocol_copyPropertyList ( Protocol *proto, unsigned int *outCount );
// 获取协议的指定属性
objc_property_t protocol_getProperty ( Protocol *proto, const char *name, BOOL isRequiredProperty, BOOL isInstanceProperty );
// 获取协议采用的协议
Protocol ** protocol_copyProtocolList ( Protocol *proto, unsigned int *outCount );
// 查看协议是否采用了另一个协议
BOOL protocol_conformsToProtocol ( Protocol *proto, Protocol *other );
-
objc_getProtocol
: 如果仅仅是声明了一个协议,而未在任何类中实现这个协议,则该函数返回的是nil。 -
objc_copyProtocolList
: 获取到的数组需要使用free()
来释放 -
objc_allocateProtocol
: 如果同名的协议已经存在,则返回nil -
objc_registerProtocol
: 创建一个新的协议后,必须调用该函数以在运行时中注册新的协议。协议注册后便可以使用,但不能再做修改,即注册完后不能再向协议添加方法或协议
注意: 协议一旦注册后就不可再修改,即无法再通过调用protocol_addMethodDescription
、protocol_addProtocol
和protocol_addProperty
往协议中添加方法属性等
8、 Category
Category定义如下,表示指向分类结构体的指针
typedef struct objc_category *Category;
struct objc_category {
char *category_name OBJC2_UNAVAILABLE; // 分类名
char *class_name OBJC2_UNAVAILABLE; // 分类所属的类名
struct objc_method_list *instance_methods OBJC2_UNAVAILABLE; // 实例方法列表
struct objc_method_list *class_methods OBJC2_UNAVAILABLE; // 类方法列表
struct objc_protocol_list *protocols OBJC2_UNAVAILABLE; // 分类所实现的协议列表
}
runtime
并没有提供过多的函数来处理分类
9、id
typedef struct objc_object *id;
它是一个objc_object
结构类型的指针。它的存在可以让我们实现类似于C++
中泛型的一些操作。该类型的对象可以转换为任何一种对象,有点类似于C语言
中void *
指针类型的作用。