• Runtime之成员变量&属性&关联对象


    上篇介绍了Runtime类和对象的相关知识点,在4.5和4.6小节,也介绍了成员变量和属性的一些方法应用。本篇将讨论实现细节的相关内容。

    在讨论之前,我们先来介绍一个很冷僻但又很有用的一个关键字:@encode

    1.类型编码

    为了协助运行时系统,编译器用字符串为每个方法的返回值、参数类型和方法选择器编码,使用的编码方案在其他情况下也很有用。在 Objective-C 运行时的消息发送机制中,传递参数时,由于类型信息的缺失,需要类型编码进行辅助以保证类型信息也能够被传递。在实际的应用开发中,使用案例比较少:某些 API 中 Apple 建议使用 NSValue 的 valueWithBytes:objCType: 来获取值 (比如 CIAffineClamp 的文档里) ,这时的 objCType就需要类型的编码值;另外就是在类型信息丢失时我们可能需要用到这个特性。在上篇的函数介绍过程中,有几个函数用到了类型编码,types参数需要用 Objective-C 的编译器指令 @encode() 来创建,@encode() 返回的是 Objective-C 类型编码,这是一种内部表示的字符串(例如,@encode(int)→ i),类似于 ANSI C 的 typeof 操作,苹果的 Objective-C 运行时库内部利用类型编码帮助加快消息分发

    //添加方法
    OBJC_EXPORT BOOL class_addMethod(Class cls, SEL name, IMP imp, 
                                     const char *types) 
        OBJC_AVAILABLE(10.5, 2.0, 9.0, 1.0);
    //替代方法的实现
    OBJC_EXPORT IMP class_replaceMethod(Class cls, SEL name, IMP imp, 
                                        const char *types) 
        OBJC_AVAILABLE(10.5, 2.0, 9.0, 1.0);
    //添加成员变量
    OBJC_EXPORT BOOL class_addIvar(Class cls, const char *name, size_t size, 
                                   uint8_t alignment, const char *types) 
        OBJC_AVAILABLE(10.5, 2.0, 9.0, 1.0);
    //为协议添加方法
    OBJC_EXPORT void protocol_addMethodDescription(Protocol *proto, SEL name, const char *types, BOOL isRequiredMethod, BOOL isInstanceMethod) 
        OBJC_AVAILABLE(10.7, 4.3, 9.0, 1.0);

    那么常用的类型编码都有哪些呢?

        NSLog(@"基本数据类型:");
        NSLog(@"short               		 %s", @encode(short));
        NSLog(@"int                 		 %s", @encode(int));
        NSLog(@"long                		 %s", @encode(long));
        NSLog(@"ong long            		 %s", @encode(long long));
        NSLog(@"float               		 %s", @encode(float));
        NSLog(@"double              		 %s", @encode(double));
        NSLog(@"char                		 %s", @encode(char));
        
        NSLog(@"
    ");
        
        NSLog(@"指针和数组类型:");
        NSLog(@"int *               		 %s", @encode(int *));
        NSLog(@"int **              		 %s", @encode(int **));
        NSLog(@"int ***             		 %s", @encode(int ***));
        NSLog(@"int []              		 %s", @encode(int []));
        NSLog(@"int [2]             		 %s", @encode(int [2]));
        NSLog(@"int [][3]           		 %s", @encode(int [][3]));
        NSLog(@"int [3][3]          		 %s", @encode(int [3][3]));
        NSLog(@"int [][4][4]        		 %s", @encode(int [][4][4]));
        NSLog(@"int [4][4][4]       		 %s", @encode(int [4][4][4]));
        
        NSLog(@"
    ");
        
        NSLog(@"空类型:");
        NSLog(@"void                		 %s", @encode(void));
        NSLog(@"void *              		 %s", @encode(void *));
        NSLog(@"void **             		 %s", @encode(void **));
        NSLog(@"void ***            		 %s", @encode(void ***));
        
        NSLog(@"
    ");
        
        NSLog(@"结构体类型:");
        struct Person {
            char *anme;
            int age;
            char *birthday;
        };
        NSLog(@"struct Person       		 %s", @encode(struct Person));
        NSLog(@"CGPoint             		 %s", @encode(CGPoint));
        NSLog(@"CGRect              		 %s", @encode(CGRect));
        
        NSLog(@"
    ");
        
        NSLog(@"OC类型:");
        NSLog(@"BOOL                   		 %s", @encode(BOOL));
        NSLog(@"SEL                    		 %s", @encode(SEL));
        NSLog(@"id                     		 %s", @encode(id));
        NSLog(@"Class                  		 %s", @encode(Class));
        NSLog(@"Class *                		 %s", @encode(Class *));
        NSLog(@"NSObject class         		 %s", @encode(typeof([NSObject class])));
        NSLog(@"[NSObject class] *     		 %s", @encode(typeof([NSObject class]) *));
        NSLog(@"NSObject               		 %s", @encode(NSObject));
        NSLog(@"NSObject *             		 %s", @encode(NSObject *));
        NSLog(@"NSArray                		 %s", @encode(NSArray));
        NSLog(@"NSArray *              		 %s", @encode(NSArray *));
        NSLog(@"NSMutableArray         		 %s", @encode(NSMutableArray));
        NSLog(@"NSMutableArray *       		 %s", @encode(NSMutableArray *));
        NSLog(@"UIView                 		 %s", @encode(UIView));
        NSLog(@"UIView *               		 %s", @encode(UIView *));
        NSLog(@"UIImage                		 %s", @encode(UIImage));
        NSLog(@"UIImage *              		 %s", @encode(UIImage *)); 

    打印结果:

    其他类型可参考Type Encoding,在此不细说。

    对于属性而言,还会有一些特殊的类型编码,以表明属性是只读、拷贝、retain等等,详情可以参考Property Type String

    2.成员变量 

    Ivar是表示实例变量的类型,其实际是指向objc_ivar结构体的指针,其定义如下:

    typedef struct objc_ivar *Ivar;
    
    struct objc_ivar {
        char *ivar_name                                          OBJC2_UNAVAILABLE;  //变量名
        char *ivar_type                                          OBJC2_UNAVAILABLE;  //变量类型
        int ivar_offset                                          OBJC2_UNAVAILABLE;  //基地址偏移字节
    #ifdef __LP64__
        int space                                                OBJC2_UNAVAILABLE;
    #endif
    }  

    对应的操作函数有如下几个:

    //获取成员变量名
    OBJC_EXPORT const char *ivar_getName(Ivar v) 
        OBJC_AVAILABLE(10.5, 2.0, 9.0, 1.0);
    //设置成员变量值
    OBJC_EXPORT void object_setIvar(id obj, Ivar ivar, id value) 
        OBJC_AVAILABLE(10.5, 2.0, 9.0, 1.0);
    //获取成员变量值
    OBJC_EXPORT id object_getIvar(id obj, Ivar ivar) 
        OBJC_AVAILABLE(10.5, 2.0, 9.0, 1.0);
    // 获取成员变量类型编码
    OBJC_EXPORT const char *ivar_getTypeEncoding(Ivar v) 
        OBJC_AVAILABLE(10.5, 2.0, 9.0, 1.0);
    // 获取成员变量的偏移量 对于类型id或其它对象类型的实例变量,可以调用object_getIvar和object_setIvar来直接访问成员变量,而不使用偏移量
    OBJC_EXPORT ptrdiff_t ivar_getOffset(Ivar v) 
        OBJC_AVAILABLE(10.5, 2.0, 9.0, 1.0);

    这几个方法在上篇中已经使用,这里就不提供示例了。

    3.属性

    objc_property_t是表示属性的类型,其实际是指向objc_property结构体的指针,其定义如下:

    typedef struct objc_property *objc_property_t;
    
    //来自objc-private.h
    struct objc_property {
        const char *name;
        const char *attributes;
    };

    objc_property_attribute_t定义了属性的特性(attribute),它也是一个结构体,定义如下:

    typedef struct {
        const char *name;  //特性名
        const char *value;  //特性值
    } objc_property_attribute_t;

    对应的操作函数有如下几个:

    //获取属性名
    OBJC_EXPORT const char *property_getName(objc_property_t property) 
        OBJC_AVAILABLE(10.5, 2.0, 9.0, 1.0);
    // 获取属性特性描述字符串
    OBJC_EXPORT const char *property_getAttributes(objc_property_t property) 
        OBJC_AVAILABLE(10.5, 2.0, 9.0, 1.0);
    // 获取属性中指定的特性
    OBJC_EXPORT char *property_copyAttributeValue(objc_property_t property, const char *attributeName)
        OBJC_AVAILABLE(10.7, 4.3, 9.0, 1.0);
    // 获取属性的特性列表
    OBJC_EXPORT objc_property_attribute_t *property_copyAttributeList(objc_property_t property, unsigned int *outCount)
        OBJC_AVAILABLE(10.7, 4.3, 9.0, 1.0);

    这几个方法在上篇中已经使用,这里就不提供示例了。

    4.关联对象

    我们知道,在 Objective-C 中可以通过 Category 给一个现有的类添加属性,但是却不能添加实例变量。Objective-C针对这一问题,提供了一个解决方案:即关联对象。

    关联对象类似于成员变量,不过是在运行时添加的。我们通常会把成员变量(Ivar)放在类声明的头文件中,或者放在类实现的@implementation后面。但这有一个缺点,我们不能再分类中添加成员变量。如果我们尝试在分类中添加新的成员变量,编译器会报错。

    与关联对象相关的函数有如下三个:

    //给对象添加关联对象,传入 nil 则可以移除已有的关联对象
    OBJC_EXPORT void objc_setAssociatedObject(id object, const void *key, id value, objc_AssociationPolicy policy)
        OBJC_AVAILABLE(10.6, 3.1, 9.0, 1.0);
    //获取关联对象
    OBJC_EXPORT id objc_getAssociatedObject(id object, const void *key)
        OBJC_AVAILABLE(10.6, 3.1, 9.0, 1.0);
    //移除一个对象的所有关联对象
    OBJC_EXPORT void objc_removeAssociatedObjects(id object)
        OBJC_AVAILABLE(10.6, 3.1, 9.0, 1.0);

    【注意】:objc_removeAssociatedObjects 函数我们一般是用不上的,因为这个函数会移除一个对象的所有关联对象,将该对象恢复成“原始”状态。这样做就很有可能把别人添加的关联对象也一并移除,这并不是我们所希望的。所以一般的做法是通过给 objc_setAssociatedObject 函数传入 nil 来移除某个已有的关联对象。

    在给一个对象添加关联对象时有五种关联策略可供选择:

    关联策略 等价属性 说明
    OBJC_ASSOCIATION_ASSIGN

    @property (assign)

    @property (unsafe_unretained)

    弱引用关联对象
    OBJC_ASSOCIATION_RETAIN_NONATOMIC @property (strong, nonatomic) 强引用关联对象,非原子操作
    OBJC_ASSOCIATION_COPY_NONATOMIC @property (copy, nonatomic) 复制关联对象,非原子操作
    OBJC_ASSOCIATION_RETAIN @property (strong, atomic) 强引用关联对象,原子操作
    OBJC_ASSOCIATION_COPY @property (copy, atomic) 复制关联对象,原子操作

    5.应用讲解

    5.1归档解档

    首先我们先来思考一下我们常规的归档解档方案:

    //第一步:实现协议NSCoding
    @interface GofUser : NSObject<NSCoding>
    
    @property (nonatomic, strong) NSString *name;  //!<姓名
    @property (nonatomic, strong) NSString *phone;  //!<电话
    
    @end
    
    @implementation GofUser
    //第二步:实现协议的两个方法
    - (instancetype)initWithCoder:(NSCoder *)aDecoder {
        if (self = [super init]) {
         //需要解码的属性 self.name
    = [aDecoder decodeObjectForKey:@"name"]; self.phone = [aDecoder decodeObjectForKey:@"phone"]; } return self; } - (void)encodeWithCoder:(NSCoder *)aCoder {
    //需要编码的属性 [aCoder encodeObject:self.name forKey:
    @"name"]; [aCoder encodeObject:self.phone forKey:@"phone"]; } @end //第三步:归档 GofUser *user = [[GofUser alloc] init]; user.name = @"LeeGof"; user.phone = @"13800138000"; [NSKeyedArchiver archiveRootObject:user toFile:[GofUser cacheMetadataFilePath]]; //第四步:解档 GofUser *user1 = [NSKeyedUnarchiver unarchiveObjectWithFile:[GofUser cacheMetadataFilePath]]; NSLog(@"name : %@ phone : %@", user1.name, user1.phone);

    这里的GofUser类只有两个属性,如果要归档的类有100个属性怎么办?难道在NSCoding协议的两个方法各写100次吗?答案是否定的,我们可以用runtime的成员变量来实现。

    //核心代码
    - (instancetype)initWithCoder:(NSCoder *)aDecoder {
        if (self = [super init]) {
            unsigned int count = 0;
            Ivar *ivars = class_copyIvarList([self class], &count);
            
            for (int i = 0; i < count; i++) {
                //取出成员变量
                Ivar ivar = ivars[i];
                const char *name = ivar_getName(ivar);
                //获取KEY
                NSString *key = [NSString stringWithUTF8String:name];
                //解档
                id value = [aDecoder decodeObjectForKey:key];
                //KVC赋值
                [self setValue:value forKey:key];
            }
            free(ivars);
        }
        return self;
    }
    
    - (void)encodeWithCoder:(NSCoder *)aCoder {
        unsigned int count = 0;
        Ivar *ivars = class_copyIvarList([self class], &count);
        
        for (int i = 0; i < count; i++) {
            //取出成员变量
            Ivar ivar = ivars[i];
            const char *name = ivar_getName(ivar);
            //获取KEY
            NSString *key = [NSString stringWithUTF8String:name];
            //归档
            [aCoder encodeObject:[self valueForKey:key] forKey:key];
        }
        free(ivars);
    }

    【思考】:使用这种方式,成员变量能否正常的归档和解档?

    5.2关联对象

    假设现在有这么一个应用场景:需要动态的给一个GofPerson类添加属性workSpace。

    @interface GofPerson (GofWork)
    
    @property (nonatomic, strong) NSString *workSpace;  //!<工作空间
    
    @end
    
    static const void *s_WorkSpace = "s_WorkSpace";
    @implementation GofPerson (GofWork)
    
    - (void)setWorkSpace:(NSString *)workSpace {
        objc_setAssociatedObject(self, s_WorkSpace, workSpace, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
    }
    
    - (NSString *)workSpace {
        return objc_getAssociatedObject(self, s_WorkSpace);
    }
    
    @end

    6.小结

    本篇讨论了Runtime中成员变量、属性、关联对象相关的内容。成员变量与属性是类的数据基础,合理地使用Runtime中的相关操作能让我们更加灵活地来处理与类数据相关的工作。

  • 相关阅读:
    把C语言的指针按在地上摩擦!
    组合索引相关介绍
    ConcurrentModificationException异常
    chat和varchar的区别?
    二进制部署K8S集群(二十三)addons之安装部署dashboard
    二进制部署K8S集群(二十二)addons之安装部署ingress
    二进制部署K8S集群(二十一)addons之flanneld优化SNAT规则
    二进制部署K8S集群(二十)K8s服务暴露之NodePort型Service
    二进制部署K8S集群(十九)addons之安装部署coredns
    二进制部署K8S集群(十八)addons之flannel三种模型安装部署详解
  • 原文地址:https://www.cnblogs.com/LeeGof/p/6674949.html
Copyright © 2020-2023  润新知