美文网首页
iOS runtime2-成员变量、属性、方法、消息、分类、协议

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

作者: 小秀秀耶 | 来源:发表于2018-12-18 22:18 被阅读0次

    本文是参考南峰子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-成员变量、属性、方法、消息、分类、协议

          本文链接:https://www.haomeiwen.com/subject/afpghqtx.html