iOS runtime2-成员变量、属性、方法、消息、分类、协议

本文是参考南峰子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@:@ : 没有返回值,参数类型依次为,idSELid

定义一个属性如下:就是参考Property Type String
官方文档写了,T为开始,V为结束,中间参考表

The string starts with a T followed by the @encode type and a comma, and finishes with a V 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 );
  1. 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_copyAttributeValueproperty_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,则宿主释放时,关联对象不会被释放;
如果指定的是retaincopy,则宿主释放时,关联对象会被释放

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;

  1. method_name : 方法名
  2. method_imp : 方法实现

objc_method 结构体中包含了一个SELIMP,实际相当于在SELIMP之间做了一个映射,有了SEL,我们便能找到对应的IMP

objc_msgSend有两个隐藏参数:

  1. 消息接收对象
  2. 方法的selector

这两个参数为方法的实现提供了调用者的信息。之所以说是隐藏的,是因为它们在定义方法的源代码中没有声明。它们是在编译期被插入实现代码的。

虽然这些参数没有显示声明,但在代码中仍然可以引用它们。我们可以使用self来引用接收者对象,使用_cmd来引用选择器。

5.1、SEL(方法名)

SEL又叫选择器,是表示一个方法的selector的指针,其定义如下:

typedef struct objc_selector *SEL;

Objective-C在编译时,会根据每个方法的名字、参数、生成一个唯一的标识(Int类型地址),这个标识就是SEL,根据一个方法名hash化了的key值,能唯一代表一个方法,它的存在只是为了加快了方法的查询速度

我们可以在运行时添加新的selector,也可以在运行时获取已存在的selector,我们可以通过下面三种方法来获取SEL:

  1. sel_registerName函数
  2. Objective-C编译器提供的@selector()
  3. 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 );
  1. method_invoke : 返回的是实际实现的返回值。参数receiver不能为空。这个方法的效率会比method_getImplementationmethod_getName更快
  2. method_getName : 返回的是一个SEL。如果想获取方法名的C字符串,可以使用sel_getName(method_getName(method))
  3. method_getReturnType : 类型字符串会被拷贝到dst
  4. method_setImplementation : 注意该函数返回值是方法之前的实现

6、消息

给一个对象发送消息:如[person run];
会被编译成objc_msgSend(person, @selector(run));
objc_msgSend具体查找流程如下

  1. 通过isa指针找到对象所属的类
  2. 查找类的cache列表,成功,跳转到第4步,失败,跳转到第3步
  3. 查找类的methodLists列表
  4. 查找名字相同的方法,成功跳转到代码实现,失败,跳转到第5步
  5. 沿着集成体系向上查找,成功跳转到代码实现,失败,消息转发

6.1、消息转发

  1. 动态方法解析:询问是否有动态方法来处理这个未知消息,有则消息转发结束
  2. 备用接收者:询问其他接收者是否能处理这条消息,有,则将这条消息转发给该接收者,消息转发结束
  3. 完整转发:将这个未知消息所有细节封装到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 );
  1. objc_getProtocol : 如果仅仅是声明了一个协议,而未在任何类中实现这个协议,则该函数返回的是nil。
  2. objc_copyProtocolList : 获取到的数组需要使用free()来释放
  3. objc_allocateProtocol : 如果同名的协议已经存在,则返回nil
  4. objc_registerProtocol : 创建一个新的协议后,必须调用该函数以在运行时中注册新的协议。协议注册后便可以使用,但不能再做修改,即注册完后不能再向协议添加方法或协议

注意: 协议一旦注册后就不可再修改,即无法再通过调用protocol_addMethodDescriptionprotocol_addProtocolprotocol_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 *指针类型的作用。

你可能感兴趣的:(iOS runtime2-成员变量、属性、方法、消息、分类、协议)