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是同样的。 针对数组的类型编码,返回字符串会包括:数组元素的个数以及元素的类型,具体以下所示:ide

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。ui

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

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

在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_getIvar和object_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"

//一、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);
 
//二、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);
 
//三、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);
 
//四、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
 
//五、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;

最后咱们销毁了 panda 和 Panda。

这里有几点须要注意的:

咱们不能用 class_addIvar() 函数为一个已经存在的类添加Ivar,而且 class_addIvar() 只能在 objc_allocateClassPair() 和 objc_registerClassPair() 之间调用;

添加属性特性时的各类类型字符能够参考:Property Type String。

添加一个属性及对应的成员变量后,咱们还能经过 [obj valueForKey:@"propertyName"];得到属性值。

小结 本文主要讲解了成员变量与属性相关使用,尤为是关联对象的使用。但愿阅读完本文,能对成员变量和属性的理解更深刻。

文章来源于网络 若有侵权请及时联系本人删除 小编这里推荐一个群:691040931 里面有大量的书籍和面试资料,不少的iOS开发者都在里面交流技术

相关文章
相关标签/搜索