专栏首页攻城狮的动态[Objective-C Runtime] 成员变量与属性

[Objective-C Runtime] 成员变量与属性

在上篇文章[Objective-C Runtime] 类与对象详细讲解了Runtime机制对于类和对象相关处理,今天继续讲解一下Runtime在成员变量和属性上的处理方法和策略。

成员变量(Ivar)的数据结构

在Objective-C中,成员变量即Ivar类型,是指向结构体struct objc_ivar的指针,在Objc/runtime.h 中查到,如下所示:

typedef struct objc_ivar *Ivar;

结构体struct objc_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} 
属性的数据结构

属性(property)数据结构如下所示:

typedef struct objc_property *objc_property_t;

属性特性(Attribute)的数据结构如下所示:

typedef struct {
    const char * _Nonnull name;           /**< The name of the attribute */
    const char * _Nonnull value;       
                   /**< The value of the atribute (usually empty) */
} objc_property_attribute_t;
成员变量与属性的联系
  • 本质上,一个属性一定对应一个成员变量,但是属性又不仅仅是一个成员变量,属性还会根据自己对应的属性特性的定义来对这个成员变量进行一系列的封装:提供 Getter/Setter 方法、内存管理策略、线程安全机制等等;
相关函数

Runtime 中与成员变量和属性相关的函数有很多,这里罗列出一些常用的方法:

  • Ivar class_getClassVariable(Class cls, const char *name),返回指定类的指定名字的成员变量;
  • Ivar *class_copyIvarList(Class cls, unsigned int *outCount),返回指定类的成员变量列表。调用后需要自己 free();
  • BOOL class_addIvar(Class cls, const char *name, size_t size, uint8_t alignment, const char *types),给指定的类添加成员变量。这个函数只能在objc_allocateClassPair()objc_registerClassPair() 之间调用,并且不能为一个已经存在的类添加成员变量;
  • id object_getIvar(id obj, Ivar ivar),获得对象的指定成员变量的值。速度比 object_getInstanceVariable() 快;
  • void object_setIvar(id obj, Ivar ivar, id value),设置对象指定成员变量的值。速度比object_setInstanceVariable() 快;
  • Ivar object_getInstanceVariable(id obj, const char *name, void **outValue),获取指定名字的成员变量的值;
  • Ivar object_setInstanceVariable(id obj, const char *name, void *value),设置指定名字成员变量的值;
  • const char *ivar_getName(Ivar v),获取成员变量名;
  • const char *ivar_getTypeEncoding(Ivar v),获取成员变量的类型编码;
  • ptrdiff_t ivar_getOffset(Ivar v),获取成员变量的偏移量;
  • ``objc_property_t class_getProperty(Class cls, const char *name)`, 获取指定类指定名字的属性;
  • objc_property_t *class_copyPropertyList(Class cls, unsigned int *outCount), 获取指定类的属性列表。调用后需要自己 free();
  • BOOL class_addProperty(Class cls, const char *name, const objc_property_attribute_t *attributes, unsigned int attributeCount), 给指定的类添加属性;
  • void class_replaceProperty(Class cls, const char *name, const objc_property_attribute_t *attributes, unsigned int attributeCount),替代指定类的属性;
  • const char *property_getName(objc_property_t property),获取属性名;
  • const char *property_getAttributes(objc_property_t property),获取属性特性描述;
  • objc_property_attribute_t *property_copyAttributeList(objc_property_t property, unsigned int *outCount),获取属性特性列表。调用后需要自己 free();
  • char *property_copyAttributeValue(objc_property_t property, const char *attributeName),获取属性特性值。调用后需要自己 free();
运行时操作成员变量和属性的示例代码
NSString * runtimePropertyGetterIMP(id self, SEL _cmd){
    Ivar ivar = class_getInstanceVariable([self class], "_runtimeProperty");    
    return object_getIvar(self, ivar);
}

void runtimePropertySetterIMP(id self, SEL _cmd, NSString *value){
    Ivar ivar = class_getInstanceVariable([self class], "_runtimeProperty");    NSString *aValue = (NSString *)object_getIvar(self, ivar);    if (![aValue isEqualToString:value]) {
        object_setIvar(self, ivar, value);
    }
}

- (void)verifyPropertyAndIvar{    

#pragma clang diagnostic push#pragma clang diagnostic ignored "-Wundeclared-selector"
    
    //1、Add property and getter/setter method
    Class cls = objc_allocateClassPair([Animal class], "Panda", 0);    
    //add instance variable
    BOOL isSuccess = class_addIvar(cls, "_runtimeProperty", sizeof(cls), log2(sizeof(cls)), @encode(NSString));    NSLog(@"%@", isSuccess ? @"成功" : @"失败");//print 成功
    
    //add attributes
    objc_property_attribute_t type = {"T", "@\"NSString\""};
    objc_property_attribute_t ownership = {"C", ""};//C = Copy
    objc_property_attribute_t isAutomic = {"N", ""};// N = nonatomic
    objc_property_attribute_t backingVar = {"V", "_runtimeProperty"};
    objc_property_attribute_t attrubutes[] = {type, ownership, isAutomic, backingVar};
    class_addProperty(cls, "runtimeProperty", attrubutes, 4);
    class_addMethod(cls, @selector(runtimeProperty), (IMP)runtimePropertyGetterIMP, "@@:");
    class_addMethod(cls, @selector(setRuntimeProperty), (IMP)runtimePropertySetterIMP, "V@:");
    
    objc_registerClassPair(cls);    
    //2、print all properties
    unsigned int count = 0;
    objc_property_t *properties = class_copyPropertyList(cls, &count);    for (int32_t i = 0; i < count; i ++) {
        objc_property_t property = properties[i];    
    
        NSLog(@"%s, %s\n", property_getName(property), property_getAttributes(property)); 
       //print: _runtimeProperty, T@"NSString",C,N,V_runtimeProperty
    }
    free(properties); 
          
    //3、print all Ivar
    unsigned int outCount = 0;
    Ivar *ivars = class_copyIvarList(cls, &outCount);   
          
     for (int32_t i = 0; i < outCount; i ++) {
        Ivar ivar = ivars[i];       
        NSLog(@"%s, %s\n", ivar_getName(ivar), ivar_getTypeEncoding(ivar));        
      //print:_runtimeProperty, {NSString=#}
       
    }
    free(ivars); 
          
        
    //4、use property
    id panda = [[cls alloc] init];
    [panda performSelector:@selector(setRuntimeProperty) withObject:@"set-property"];    
          
    NSString *propertyValue = [panda performSelector:@selector(runtimeProperty)];   
    NSLog(@"return value = %@", propertyValue);    
    //print: return value = set-property
    
    //5、destory
    panda = nil;
    objc_disposeClassPair(cls);    
    #pragma clang diagnostic pop
    }

上述代码打印信息:

成功
runtimeProperty, T@"NSString",C,N,V_runtimeProperty
_runtimeProperty, {NSString=#}return value = set-property
  • 上面的代码中,我们在运行时动态创建了Animal 的一个子类 Panda
  • 然后为它动态添加了 Ivar:_runtimeProperty、对应的 Property:runtimeProperty、对应的 Getter/Setter方法:runtimeProperty``setRuntimeProperty
  • 接着我们遍历和打印了Panda 的 Ivar 列表和 Property 列表;
  • 然后创建了 Panda 的一个实例 panda,并使用了 Property;
  • 最后我们销毁了 pandaPanda

这里有几点需要注意的:

  • 我们不能用 class_addIvar() 函数为一个已经存在的类添加Ivar,并且 class_addIvar() 只能在 objc_allocateClassPair()objc_registerClassPair() 之间调用;
  • 添加属性特性时的各种类型字符可以参考:Property Type String(https://developer.apple.com/library/content/documentation/Cocoa/Conceptual/ObjCRuntimeGuide/Articles/ocrtPropertyIntrospection.html#//apple_ref/doc/uid/TP40008048-CH101-SW6)。
  • 添加一个属性及对应的成员变量后,我们还能通过 [obj valueForKey:@"propertyName"];获得属性值。

小结

本文主要讨论了Runtime中成员变量与属性相关的内容。成员变量与属性是类的数据基础,合理使用Runtime中的相关操作能使我们更加灵活地处理与类数据相关开发工作。

本文分享自微信公众号 - 攻城狮的动态(iOSDevSkills),作者:Jacklin

原文出处及转载信息见文内详细说明,如有侵权,请联系 yunjia_community@tencent.com 删除。

原始发表时间:2018-03-16

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

我来说两句

0 条评论
登录 后参与评论

相关文章

  • 「类与对象」如何准确获取对象的内存大小?

    在上篇文章「类与对象」揭秘本质的第一步中,揭秘NSObject类的底层数据结构,如下所示:

    Jacklin
  • [Objective-C Runtime] 类与对象

    Jacklin
  • iOS视图编程指南(View Programming Guide for iOS)(译)

    Jacklin
  • Objective-C Runtime详解

    BY
  • Nebula Graph 在大规模数据量级下的实践和定制化开发

    图数据在社交推荐、多跳实时计算、风控和安全等领域有可期待的前景。如何用图数据库高效存储和查询大规模异构图数据,是一个重大挑战。本文描述了开源分布式图数据库 Ne...

    NebulaGraph
  • C++:二阶构造函数

    类的构造函数是类的一种特殊的成员函数,它会在每次创建类的新对象时执行。最常见的操作是在构造函数中为类的成员变量进行赋值,如果还想再构造函数中进行一些其他操作,可...

    王强
  • Flink Back Pressure(背压)是怎么实现的?有什么绝妙之处?

    场景描述:如果看到任务的背压警告(如 High 级别),这意味着 生成数据的速度比下游算子消费的的速度快。以一个简单的 Source -> Sink 作业为例。...

    暴走大数据
  • 在WordPress 文章未尾自动添加一个作者信息框

    如果想在WordPress文章的未尾,添加文章作者的相关信息,下面一段代码可以帮助方便在文章中添加一个作者的信息框。

    赵帆同学GXUZF.COM
  • AtomicIntegerFieldUpdater

    对于volatile变量,写的时候会将线程本地内存的数据刷新到主内存上,读的时候会将主内存的数据加载到本地内存里,所以可以保证可见行和单个读/写操作的原子性。但...

    yiduwangkai
  • 震惊! 被一个简单的 SQL 查询难住

    问题大概是, 我有两个表 TableA, TableB, 其中 TableA 表大概百万行级别(存量业务数据), TableB 表几行(新业务场景, 数据还未膨...

    程序猿石头

扫码关注云+社区

领取腾讯云代金券