Objective-C Runtime:深入理解成员变量与属性

概述

在上篇文章Objective-C Runtime:深入理解类与对象中,讲解了类与对象的相关内容。

在本文中,着重讲解一下类实现细节的先关内容,主要包括类中的成员变量、属性、方法以及协议与分类的实现。

在讲解成员变量与属性之前,需要了解一下类型编码相关知识。

类型编码

Runtime中,编译器将每个方法的返回值和参数类型编码为一个字符串,并将其与方法的selector关联在一起。

由于该编码方案具有一定的通用性,系统提供了编译器指令@encode来获取特定编码后的字符串。

当给定一个类型时,@encode返回这个类型的字符串编码。这些类型可以是诸如int、指针等基本类型,也可以是结构体、类等类型。

事实上,任何可以作为sizeof()操作参数的类型都可以执行@encode()指令。

Objective-C Runtime Programming Guide中的Type Encoding一节中,列出了Objective-C中所有的类型编码。需要注意的是这些类型很多是与我们用于存档和分发的编码类型是相同的。但有一些不能在存档时使用,如下所示:

注意:Objective-C不支持long double类型。@encode(long double)返回d,与double是一样的。

针对数组的类型编码,返回字符串会包括:数组元素的个数以及元素的类型,具体如下所示:

int a[] = {1, 2};
NSLog(@"type Coding = %s", @encode(typeof(a)));

打印结果如下:

2018-03-28 22:46:28.253495+0800 RuntimeUsage[48760:1909814] type Coding = [2i]

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

成员变量与属性

成员变量与属性这一部分有三个方面需要注意:Ivarobjc_property_t基本数据结构和关联对象(Associated Object)。其中,关于关联对象的相关内容在之前的文章中详细阐述过。

基础数据结构

成员变量(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 attribute (usually empty) */
} objc_property_attribute_t;

成员变量与属性的联系

  • 本质上,一个属性一定对应一个成员变量,但是属性又不仅仅是一个成员变量,属性还会根据自己对应的属性特性的定义来对这个成员变量进行一系列的封装:提供 Getter/Setter 方法、内存管理策略、线程安全机制等等。

成员变量、属性的操作方法

成员变量

成员变量的相关函数如下:

// 获取成员变量名
const char * ivar_getName ( Ivar v );
// 获取成员变量类型编码
const char * ivar_getTypeEncoding ( Ivar v );
// 获取成员变量的偏移量
ptrdiff_t ivar_getOffset ( Ivar v );
  • ivar_getOffset函数,对于类型id或其它对象类型的实例变量,可以调用object_getIvarobject_setIvar来直接访问成员变量,而不使用偏移量。
关联对象

关联对象函数如下:

// 设置关联对象
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 );
属性

属性相关函数如下:

// 获取属性名
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函数,返回的char *在使用完后需要调用free()释放。
  • property_copyAttributeList函数,返回值在使用完后需要调用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
  • 添加一个属性及对应的成员变量后,我们还能通过 [obj valueForKey:@"propertyName"];获得属性值。

小结

本文主要讲解了成员变量与属性相关使用,尤其是关联对象的使用。希望阅读完本文,能对成员变量和属性的理解更深入。

参考

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

发表于

我来说两句

0 条评论
登录 后参与评论

相关文章

来自专栏华仔的技术笔记

iOS中的正则表达式,一篇就够了

38110
来自专栏代码散人

ios开发 Runtime 详解part2(动态方法解析)

在 ios开发 Runtime 详解part1中我已经解释了Introspection,接下来介绍Runtime的其它特性。

881
来自专栏诸葛青云的专栏

C语言位运算的妙用你知道多少?

位运算在驱动开发中是经常遇到的,尤其是置0和置1。既要指定的位数发生变化,又不能改变其它位的值,还要高效率的编写代码,这时候技巧就很重要了。在位运算中有几个符号...

1654
来自专栏cmazxiaoma的架构师之路

你应该会的一道多线程笔试题

2623
来自专栏青玉伏案

Objective-C中把URL请求的参数转换为字典

上一篇博客中是把URL转换为字典,那么我们如何把URL请求中的参数封装成字典,然后再封装成数组呢?对OC中字符串操作熟练的小伙伴们应该觉得这是一个a+b的问题...

2006
来自专栏xx_Cc的学习总结专栏

iOS底层原理总结 - 探寻Runtime本质(四)

首先来看一道面试题。 下列代码中Person继承自NSObject,Student继承自Person,写出下列代码输出内容。

2154
来自专栏二进制文集

LeetCode 473 Matchsticks to Square

Remember the story of Little Match Girl? By now, you know exactly what matchstic...

1123
来自专栏一个会写诗的程序员的博客

《Kotlin极简教程》第3章 Kotlin语言基础第3章 Kotlin语言基础《Kotlin极简教程》正式上架:参考资料

学习任何东西,都是一个由表及里的过程。学习一门编程语言也一样。对于一门编程语言来说,“表” 就是基本词汇(关键字、标识符等)、句子(表达式)和语法。

1412
来自专栏Objective-C

Swift 基本语法05-"String"

2779
来自专栏分布式系统和大数据处理

编写高质量代码:改善C#程序的157个建议

这本书汇集了C#编写中的一些建议、注意事项和小技巧,为了温故知新,又拿出来翻看了一遍。很多主题要阐述的内容是显而易见、不言自明的,就没有再写摘要。部分主题的名称...

1163

扫码关注云+社区

领取腾讯云代金券