Objective-C Runtime 運(yùn)行時(shí)之二:成員變量與屬性

本文轉(zhuǎn)載自:http://southpeak.github.io/2014/10/30/objective-c-runtime-2/

在前面一篇文章中,我們介紹了Runtime中與類(lèi)和對(duì)象相關(guān)的內(nèi)容,從這章開(kāi)始,我們將討論類(lèi)實(shí)現(xiàn)細(xì)節(jié)相關(guān)的內(nèi)容,主要包括類(lèi)中成員變量,屬性,方法,協(xié)議與分類(lèi)的實(shí)現(xiàn)。

本章的主要內(nèi)容將聚集在Runtime對(duì)成員變量與屬性的處理。在討論之前,我們先介紹一個(gè)重要的概念:類(lèi)型編碼。

類(lèi)型編碼(Type Encoding)

作為對(duì)Runtime的補(bǔ)充,編譯器將每個(gè)方法的返回值和參數(shù)類(lèi)型編碼為一個(gè)字符串,并將其與方法的selector關(guān)聯(lián)在一起。這種編碼方案在其它情況下也是非常有用的,因此我們可以使用@encode編譯器指令來(lái)獲取它。當(dāng)給定一個(gè)類(lèi)型時(shí),@encode返回這個(gè)類(lèi)型的字符串編碼。這些類(lèi)型可以是諸如int、指針這樣的基本類(lèi)型,也可以是結(jié)構(gòu)體、類(lèi)等類(lèi)型。事實(shí)上,任何可以作為sizeof()操作參數(shù)的類(lèi)型都可以用于@encode()。

在Objective-C Runtime Programming Guide中的Type Encoding一節(jié)中,列出了Objective-C中所有的類(lèi)型編碼。需要注意的是這些類(lèi)型很多是與我們用于存檔和分發(fā)的編碼類(lèi)型是相同的。但有一些不能在存檔時(shí)使用。

注:Objective-C不支持long double類(lèi)型。@encode(long double)返回d,與double是一樣的。

一個(gè)數(shù)組的類(lèi)型編碼位于方括號(hào)中;其中包含數(shù)組元素的個(gè)數(shù)及元素類(lèi)型。如以下示例:

float a[] = {1.0,2.0,3.0};

NSLog(@"array encoding type: %s",@encode(typeof(a)));

輸出是:

2014-10-2811:44:54.731RuntimeTest[942:50791] array encoding type: [3f]

其它類(lèi)型可參考Type Encoding,在此不細(xì)說(shuō)。

另外,還有些編碼類(lèi)型,@encode雖然不會(huì)直接返回它們,但它們可以作為協(xié)議中聲明的方法的類(lèi)型限定符。可以參考Type Encoding。

對(duì)于屬性而言,還會(huì)有一些特殊的類(lèi)型編碼,以表明屬性是只讀、拷貝、retain等等,詳情可以參考Property Type String

成員變量、屬性

Runtime中關(guān)于成員變量和屬性的相關(guān)數(shù)據(jù)結(jié)構(gòu)并不多,只有三個(gè),并且都很簡(jiǎn)單。不過(guò)還有個(gè)非常實(shí)用但可能經(jīng)常被忽視的特性,即關(guān)聯(lián)對(duì)象,我們將在這小節(jié)中詳細(xì)討論。

基礎(chǔ)數(shù)據(jù)類(lèi)型

Ivar

Ivar是表示實(shí)例變量的類(lèi)型,其實(shí)際是一個(gè)指向objc_ivar結(jié)構(gòu)體的指針,其定義如下:

typedef struct objc_ivar *Ivar;

structobjc_ivar {

char*ivar_name? ? ? ? ? ? ? OBJC2_UNAVAILABLE;// 變量名

char*ivar_type? ? ? ? ? ? OBJC2_UNAVAILABLE;// 變量類(lèi)型

intivar_offset? ? ? ? ? ? OBJC2_UNAVAILABLE;// 基地址偏移字節(jié)

#ifdef __LP64__

intspace? ? ? ? ? ? ? ? OBJC2_UNAVAILABLE;

#endif

}

objc_property_t

objc_property_t是表示Objective-C聲明的屬性的類(lèi)型,其實(shí)際是指向objc_property結(jié)構(gòu)體的指針,其定義如下:

typedef struct objc_property *objc_property_t;

objc_property_attribute_t

objc_property_attribute_t定義了屬性的特性(attribute),它是一個(gè)結(jié)構(gòu)體,定義如下:

typedef struct{

constchar*name;// 特性名

constchar*value;// 特性值

} objc_property_attribute_t;

關(guān)聯(lián)對(duì)象(Associated Object)

關(guān)聯(lián)對(duì)象是Runtime中一個(gè)非常實(shí)用的特性,不過(guò)可能很容易被忽視。

關(guān)聯(lián)對(duì)象類(lèi)似于成員變量,不過(guò)是在運(yùn)行時(shí)添加的。我們通常會(huì)把成員變量(Ivar)放在類(lèi)聲明的頭文件中,或者放在類(lèi)實(shí)現(xiàn)的@implementation后面。但這有一個(gè)缺點(diǎn),我們不能在分類(lèi)中添加成員變量。如果我們嘗試在分類(lèi)中添加新的成員變量,編譯器會(huì)報(bào)錯(cuò)。

我們可能希望通過(guò)使用(甚至是濫用)全局變量來(lái)解決這個(gè)問(wèn)題。但這些都不是Ivar,因?yàn)樗麄儾粫?huì)連接到一個(gè)單獨(dú)的實(shí)例。因此,這種方法很少使用。

Objective-C針對(duì)這一問(wèn)題,提供了一個(gè)解決方案:即關(guān)聯(lián)對(duì)象(Associated Object)。

我們可以把關(guān)聯(lián)對(duì)象想象成一個(gè)Objective-C對(duì)象(如字典),這個(gè)對(duì)象通過(guò)給定的key連接到類(lèi)的一個(gè)實(shí)例上。不過(guò)由于使用的是C接口,所以key是一個(gè)void指針(const void *)。我們還需要指定一個(gè)內(nèi)存管理策略,以告訴Runtime如何管理這個(gè)對(duì)象的內(nèi)存。這個(gè)內(nèi)存管理的策略可以由以下值指定:

OBJC_ASSOCIATION_ASSIGN

OBJC_ASSOCIATION_RETAIN_NONATOMIC

OBJC_ASSOCIATION_COPY_NONATOMIC

OBJC_ASSOCIATION_RETAIN

OBJC_ASSOCIATION_COPY

當(dāng)宿主對(duì)象被釋放時(shí),會(huì)根據(jù)指定的內(nèi)存管理策略來(lái)處理關(guān)聯(lián)對(duì)象。如果指定的策略是assign,則宿主釋放時(shí),關(guān)聯(lián)對(duì)象不會(huì)被釋放;而如果指定的是retain或者是copy,則宿主釋放時(shí),關(guān)聯(lián)對(duì)象會(huì)被釋放。我們甚至可以選擇是否是自動(dòng)retain/copy。當(dāng)我們需要在多個(gè)線(xiàn)程中處理訪(fǎng)問(wèn)關(guān)聯(lián)對(duì)象的多線(xiàn)程代碼時(shí),這就非常有用了。

我們將一個(gè)對(duì)象連接到其它對(duì)象所需要做的就是下面兩行代碼:

static char myKey;

objc_setAssociatedObject(self, &myKey, anObject, OBJC_ASSOCIATION_RETAIN);

在這種情況下,self對(duì)象將獲取一個(gè)新的關(guān)聯(lián)的對(duì)象anObject,且內(nèi)存管理策略是自動(dòng)retain關(guān)聯(lián)對(duì)象,當(dāng)self對(duì)象釋放時(shí),會(huì)自動(dòng)release關(guān)聯(lián)對(duì)象。另外,如果我們使用同一個(gè)key來(lái)關(guān)聯(lián)另外一個(gè)對(duì)象時(shí),也會(huì)自動(dòng)釋放之前關(guān)聯(lián)的對(duì)象,這種情況下,先前的關(guān)聯(lián)對(duì)象會(huì)被妥善地處理掉,并且新的對(duì)象會(huì)使用它的內(nèi)存。

idanObject = objc_getAssociatedObject(self, &myKey);

我們可以使用objc_removeAssociatedObjects函數(shù)來(lái)移除一個(gè)關(guān)聯(lián)對(duì)象,或者使用objc_setAssociatedObject函數(shù)將key指定的關(guān)聯(lián)對(duì)象設(shè)置為nil。

我們下面來(lái)用實(shí)例演示一下關(guān)聯(lián)對(duì)象的使用方法。

假定我們想要?jiǎng)討B(tài)地將一個(gè)Tap手勢(shì)操作連接到任何UIView中,并且根據(jù)需要指定點(diǎn)擊后的實(shí)際操作。這時(shí)候我們就可以將一個(gè)手勢(shì)對(duì)象及操作的block對(duì)象關(guān)聯(lián)到我們的UIView對(duì)象中。這項(xiàng)任務(wù)分兩部分。首先,如果需要,我們要?jiǎng)?chuàng)建一個(gè)手勢(shì)識(shí)別對(duì)象并將它及block做為關(guān)聯(lián)對(duì)象。如下代碼所示:

- (void)setTapActionWithBlock:(void (^)(void))block

{

UITapGestureRecognizer *gesture = objc_getAssociatedObject(self, &kDTActionHandlerTapGestureKey);

if (!gesture)

{

gesture = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(__handleActionForTapGesture:)];

[self addGestureRecognizer:gesture];

objc_setAssociatedObject(self, &kDTActionHandlerTapGestureKey, gesture, OBJC_ASSOCIATION_RETAIN);

}

objc_setAssociatedObject(self, &kDTActionHandlerTapBlockKey, block, OBJC_ASSOCIATION_COPY);

}

```

這段代碼檢測(cè)了手勢(shì)識(shí)別的關(guān)聯(lián)對(duì)象。如果沒(méi)有,則創(chuàng)建并建立關(guān)聯(lián)關(guān)系。同時(shí),將傳入的塊對(duì)象連接到指定的key上。注意`block`對(duì)象的關(guān)聯(lián)內(nèi)存管理策略。

手勢(shì)識(shí)別對(duì)象需要一個(gè)`target`和`action`,所以接下來(lái)我們定義處理方法:

```objc

- (void)__handleActionForTapGesture:(UITapGestureRecognizer *)gesture

{

if (gesture.state == UIGestureRecognizerStateRecognized)

{

void(^action)(void) = objc_getAssociatedObject(self, &kDTActionHandlerTapBlockKey);

if (action)

{

action();

}

}

}

我們需要檢測(cè)手勢(shì)識(shí)別對(duì)象的狀態(tài),因?yàn)槲覀冎恍枰邳c(diǎn)擊手勢(shì)被識(shí)別出來(lái)時(shí)才執(zhí)行操作。

從上面的例子我們可以看到,關(guān)聯(lián)對(duì)象使用起來(lái)并不復(fù)雜。它讓我們可以動(dòng)態(tài)地增強(qiáng)類(lèi)現(xiàn)有的功能。我們可以在實(shí)際編碼中靈活地運(yùn)用這一特性。

成員變量、屬性的操作方法

成員變量

成員變量操作包含以下函數(shù):

// 獲取成員變量名

const char* ivar_getName ( Ivar v );

// 獲取成員變量類(lèi)型編碼

const char* ivar_getTypeEncoding ( Ivar v );

// 獲取成員變量的偏移量

ptrdiff_t ivar_getOffset ( Ivar v );

ivar_getOffset函數(shù),對(duì)于類(lèi)型id或其它對(duì)象類(lèi)型的實(shí)例變量,可以調(diào)用object_getIvar和object_setIvar來(lái)直接訪(fǎng)問(wèn)成員變量,而不使用偏移量。

關(guān)聯(lián)對(duì)象

關(guān)聯(lián)對(duì)象操作函數(shù)包括以下:

// 設(shè)置關(guān)聯(lián)對(duì)象

void objc_setAssociatedObject (id object,const void *key,id value, objc_AssociationPolicy policy );

// 獲取關(guān)聯(lián)對(duì)象

id objc_getAssociatedObject (id object,const void*key );

// 移除關(guān)聯(lián)對(duì)象

void objc_removeAssociatedObjects (id object );

關(guān)聯(lián)對(duì)象及相關(guān)實(shí)例已經(jīng)在前面討論過(guò)了,在此不再重復(fù)。

屬性

屬性操作相關(guān)函數(shù)包括以下:

// 獲取屬性名

const char* property_getName ( objc_property_t property );

// 獲取屬性特性描述字符串

const char* property_getAttributes ( objc_property_t property );

// 獲取屬性中指定的特性

char* property_copyAttributeValue ( objc_property_t property,constchar*attributeName );

// 獲取屬性的特性列表

objc_property_attribute_t * property_copyAttributeList ( objc_property_t property,unsignedint*outCount );

property_copyAttributeValue函數(shù),返回的char *在使用完后需要調(diào)用free()釋放。

property_copyAttributeList函數(shù),返回值在使用完后需要調(diào)用free()釋放。

實(shí)例

假定這樣一個(gè)場(chǎng)景,我們從服務(wù)端兩個(gè)不同的接口獲取相同的字典數(shù)據(jù),但這兩個(gè)接口是由兩個(gè)人寫(xiě)的,相同的信息使用了不同的字段表示。我們?cè)诮邮盏綌?shù)據(jù)時(shí),可將這些數(shù)據(jù)保存在相同的對(duì)象中。對(duì)象類(lèi)如下定義:

@interfaceMyObject:NSObject

@property(nonatomic,copy)NSString*? name;

@property(nonatomic,copy)NSString*? status;

@end

接口A、B返回的字典數(shù)據(jù)如下所示:

@{@"name1":"張三",@"status1":@"start"}

@{@"name2":"張三",@"status2":@"end"}

通常的方法是寫(xiě)兩個(gè)方法分別做轉(zhuǎn)換,不過(guò)如果能靈活地運(yùn)用Runtime的話(huà),可以只實(shí)現(xiàn)一個(gè)轉(zhuǎn)換方法,為此,我們需要先定義一個(gè)映射字典(全局變量)

static NSMutableDictionary*map =nil;

@implementationMyObject

+ (void)load

{

map = [NSMutableDictionarydictionary];

map[@"name1"]? ? ? ? ? ? ? ? =@"name";

map[@"status1"]? ? ? ? ? ? ? =@"status";

map[@"name2"]? ? ? ? ? ? ? ? =@"name";

map[@"status2"]? ? ? ? ? ? ? =@"status";

}

@end

上面的代碼將兩個(gè)字典中不同的字段映射到MyObject中相同的屬性上,這樣,轉(zhuǎn)換方法可如下處理:

- (void)setDataWithDic:(NSDictionary*)dic

{

[dic enumerateKeysAndObjectsUsingBlock:^(NSString*key,idobj,BOOL*stop) {

NSString*propertyKey = [selfpropertyForKey:key];

if(propertyKey)

{

objc_property_t property = class_getProperty([selfclass], [propertyKey UTF8String]);

//TODO:針對(duì)特殊數(shù)據(jù)類(lèi)型做處理

NSString*attributeString = [NSStringstringWithCString:property_getAttributes(property) encoding:NSUTF8StringEncoding];

...

[selfsetValue:obj forKey:propertyKey];

}

}];

}

當(dāng)然,一個(gè)屬性能否通過(guò)上面這種方式來(lái)處理的前提是其支持KVC。

小結(jié)

本章中我們討論了Runtime中與成員變量和屬性相關(guān)的內(nèi)容。成員變量與屬性是類(lèi)的數(shù)據(jù)基礎(chǔ),合理地使用Runtime中的相關(guān)操作能讓我們更加靈活地來(lái)處理與類(lèi)數(shù)據(jù)相關(guān)的工作。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀(guān)點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容