概述
在上篇文章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。
成員變量與屬性
成員變量與屬性這一部分有三個方面需要注意:Ivar
、objc_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_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"
//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; - 最后我們銷毀了
panda
和Panda
。
這里有幾點需要注意的:
- 我們不能用
class_addIvar()
函數為一個已經存在的類添加Ivar
,并且class_addIvar()
只能在objc_allocateClassPair()
和objc_registerClassPair()
之間調用; - 添加屬性特性時的各種類型字符可以參考:Property Type String。
- 添加一個屬性及對應的成員變量后,我們還能通過
[obj valueForKey:@"propertyName"];
獲得屬性值。
小結
本文主要講解了成員變量與屬性相關使用,尤其是關聯對象的使用。希望閱讀完本文,能對成員變量和屬性的理解更深入。