Objective-C Runtime 運行時之二:成員變量與屬性

一椎侠、類型編碼

作為對Runtime的補充怨绣,編譯器將每個方法的返回值和參數(shù)類型編碼為一個字符串,并將其與方法的selector關(guān)聯(lián)在一起睬愤。這種編碼方案在其它情況下也是非常有用的句伶,因此我們可以使用@encode編譯器指令來獲取它劲蜻。當給定一個類型時,@encode返回這個類型的字符串編碼考余。這些類型可以是諸如int先嬉、指針這樣的基本類型,也可以是結(jié)構(gòu)體楚堤、類等類型疫蔓。事實上,任何可以作為sizeof()操作參數(shù)的類型都可以用于@encode()身冬。

在Objective-C Runtime Programming Guide中的Type Encoding一節(jié)中衅胀,列出了Objective-C中所有的類型編碼。

需要注意的是這些類型很多是與我們用于存檔和分發(fā)的編碼類型是相同的酥筝。但有一些不能在存檔時使用滚躯。

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

一個數(shù)組的類型編碼位于方括號中茁影;其中包含數(shù)組元素的個數(shù)及元素類型。如以下示例:

float a[] = {1.0, 2.0, 3.0}; 
NSLog(@"array encoding type: %s", @encode(typeof(a))); 

輸出是:

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

其它類型可參考Type Encoding丧凤,在此不細說呼胚。

另外,還有些編碼類型息裸,@encode雖然不會直接返回它們,但它們可以作為協(xié)議中聲明的方法的類型限定符沪编『襞瑁可以參考Type Encoding。

對于屬性而言蚁廓,還會有一些特殊的類型編碼访圃,以表明屬性是只讀、拷貝相嵌、retain等等腿时,詳情可以參考Property Type String。

二饭宾、成員變量批糟、屬性

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

  • Ivar

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

typedef struct objc_ivar *Ivar; 
 
struct objc_ivar { 
    char *ivar_name                 OBJC2_UNAVAILABLE;  // 變量名 
    char *ivar_type                 OBJC2_UNAVAILABLE;  // 變量類型 
    int ivar_offset                 OBJC2_UNAVAILABLE;  // 基地址偏移字節(jié) 
#ifdef __LP64__ 
    int space                       OBJC2_UNAVAILABLE; 
#endif 
}  
  • objc_property_t

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

typedef struct objc_property *objc_property_t; 
  • objc_property_attribute_t

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

typedef struct { 
    const char *name;           // 特性名 
    const char *value;          // 特性值 
} objc_property_attribute_t; 

三、關(guān)聯(lián)對象(Associated Object)

關(guān)聯(lián)對象是Runtime中一個非常實用的特性报咳,不過可能很容易被忽視侠讯。

關(guān)聯(lián)對象類似于成員變量,不過是在運行時添加的暑刃。我們通常會把成員變量(Ivar)放在類聲明的頭文件中厢漩,或者放在類實現(xiàn)的@implementation后面。但這有一個缺點岩臣,我們不能在分類中添加成員變量溜嗜。如果我們嘗試在分類中添加新的成員變量宵膨,編譯器會報錯。

我們可能希望通過使用(甚至是濫用)全局變量來解決這個問題炸宵。但這些都不是Ivar辟躏,因為他們不會連接到一個單獨的實例。因此土全,這種方法很少使用捎琐。

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

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

OBJC_ASSOCIATION_ASSIGN 
OBJC_ASSOCIATION_RETAIN_NONATOMIC 
OBJC_ASSOCIATION_COPY_NONATOMIC 
OBJC_ASSOCIATION_RETAIN 
OBJC_ASSOCIATION_COPY 

當宿主對象被釋放時技掏,會根據(jù)指定的內(nèi)存管理策略來處理關(guān)聯(lián)對象。如果指定的策略是assign项鬼,則宿主釋放時哑梳,關(guān)聯(lián)對象不會被釋放;而如果指定的是retain或者是copy绘盟,則宿主釋放時涧衙,關(guān)聯(lián)對象會被釋放。我們甚至可以選擇是否是自動retain/copy奥此。當我們需要在多個線程中處理訪問關(guān)聯(lián)對象的多線程代碼時弧哎,這就非常有用了。

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

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

在這種情況下稚虎,self對象將獲取一個新的關(guān)聯(lián)的對象anObject撤嫩,且內(nèi)存管理策略是自動retain關(guān)聯(lián)對象,當self對象釋放時蠢终,會自動release關(guān)聯(lián)對象序攘。另外,如果我們使用同一個key來關(guān)聯(lián)另外一個對象時寻拂,也會自動釋放之前關(guān)聯(lián)的對象程奠,這種情況下,先前的關(guān)聯(lián)對象會被妥善地處理掉祭钉,并且新的對象會使用它的內(nèi)存瞄沙。

id anObject = objc_getAssociatedObject(self, &myKey); 

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

我們下面來用實例演示一下關(guān)聯(lián)對象的使用方法距境。

假定我們想要動態(tài)地將一個Tap手勢操作連接到任何UIView中申尼,并且根據(jù)需要指定點擊后的實際操作。這時候我們就可以將一個手勢對象及操作的block對象關(guān)聯(lián)到我們的UIView對象中垫桂。這項任務(wù)分兩部分师幕。首先,如果需要诬滩,我們要創(chuàng)建一個手勢識別對象并將它及block做為關(guān)聯(lián)對象霹粥。如下代碼所示:

- (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); 
} 

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

手勢識別對象需要一個target和action矾利,所以接下來我們定義處理方法:

- (void)__handleActionForTapGesture:(UITapGestureRecognizer *)gesture 
{ 
    if (gesture.state == UIGestureRecognizerStateRecognized) 
    { 
        void(^action)(void) = objc_getAssociatedObject(self, &kDTActionHandlerTapBlockKey); 
 
        if (action) 
        { 
            action(); 
        } 
    } 
} 

我們需要檢測手勢識別對象的狀態(tài)姑裂,因為我們只需要在點擊手勢被識別出來時才執(zhí)行操作。

從上面的例子我們可以看到男旗,關(guān)聯(lián)對象使用起來并不復(fù)雜舶斧。它讓我們可以動態(tài)地增強類現(xiàn)有的功能。我們可以在實際編碼中靈活地運用這一特性察皇。

這里有一篇關(guān)于關(guān)聯(lián)對象底層實現(xiàn)講解茴厉,感興趣可以看看。關(guān)聯(lián)對象底層實現(xiàn)

三什荣、成員變量矾缓、屬性的操作方法

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

//獲取成員變量名
const char * ivar_getName ( Ivar v ); 
 
// 獲取成員變量類型編碼 
const char * ivar_getTypeEncoding ( Ivar v ); 
 
// 獲取成員變量的偏移量 
ptrdiff_t ivar_getOffset ( Ivar v ); 

ivar_getOffset函數(shù),對于類型id或其它對象類型的實例變量稻爬,可以調(diào)用object_getIvar和object_setIvar來直接訪問成員變量嗜闻,而不使用偏移量。

  • 屬性
屬性操作相關(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, const char *attributeName ); 
 
// 獲取屬性的特性列表 
objc_property_attribute_t * property_copyAttributeList ( objc_property_t property, unsigned int *outCount ); 

四桅锄、 實例

假定這樣一個場景琉雳,我們從服務(wù)端兩個不同的接口獲取相同的字典數(shù)據(jù),但這兩個接口是由兩個人寫的友瘤,相同的信息使用了不同的字段表示翠肘。我們在接收到數(shù)據(jù)時,可將這些數(shù)據(jù)保存在相同的對象中辫秧。對象類如下定義:

@interface MyObject: NSObject 
 
@property (nonatomic, copy) NSString * name;  
@property (nonatomic, copy) NSString * status; 
 
@end 

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

@{@"name1": "張三", @"status1": @"start"} 
 
@{@"name2": "張三", @"status2": @"end"} 

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

static NSMutableDictionary *map = nil; 
 
@implementation MyObject 
 
+ (void)load 
{ 
    map = [NSMutableDictionary dictionary]; 
 
    map[@"name1"]                = @"name"; 
    map[@"status1"]              = @"status"; 
    map[@"name2"]                = @"name"; 
    map[@"status2"]              = @"status"; 
} 
 
@end 

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

- (void)setDataWithDic:(NSDictionary *)dic 
{ 
    [dic enumerateKeysAndObjectsUsingBlock:^(NSString *key, id obj, BOOL *stop) { 
 
        NSString *propertyKey = [self propertyForKey:key]; 
 
        if (propertyKey) 
        { 
            objc_property_t property = class_getProperty([self class], [propertyKey UTF8String]); 
 
            // TODO: 針對特殊數(shù)據(jù)類型做處理 
            NSString *attributeString = [NSString stringWithCString:property_getAttributes(property) encoding:NSUTF8StringEncoding]; 
 
            ... 
 
            [self setValue:obj forKey:propertyKey]; 
        } 
    }]; 
} 

當然格嘁,一個屬性能否通過上面這種方式來處理的前提是其支持KVC。

五廊移、小結(jié)

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

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末懂诗,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子苗膝,更是在濱河造成了極大的恐慌殃恒,老刑警劉巖,帶你破解...
    沈念sama閱讀 221,695評論 6 515
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件辱揭,死亡現(xiàn)場離奇詭異离唐,居然都是意外死亡,警方通過查閱死者的電腦和手機问窃,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,569評論 3 399
  • 文/潘曉璐 我一進店門亥鬓,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人域庇,你說我怎么就攤上這事嵌戈。” “怎么了听皿?”我有些...
    開封第一講書人閱讀 168,130評論 0 360
  • 文/不壞的土叔 我叫張陵熟呛,是天一觀的道長。 經(jīng)常有香客問我尉姨,道長惰拱,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 59,648評論 1 297
  • 正文 為了忘掉前任啊送,我火速辦了婚禮偿短,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘馋没。我一直安慰自己昔逗,他們只是感情好,可當我...
    茶點故事閱讀 68,655評論 6 397
  • 文/花漫 我一把揭開白布篷朵。 她就那樣靜靜地躺著勾怒,像睡著了一般婆排。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上笔链,一...
    開封第一講書人閱讀 52,268評論 1 309
  • 那天段只,我揣著相機與錄音,去河邊找鬼鉴扫。 笑死赞枕,一個胖子當著我的面吹牛,可吹牛的內(nèi)容都是我干的坪创。 我是一名探鬼主播炕婶,決...
    沈念sama閱讀 40,835評論 3 421
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼莱预!你這毒婦竟也來了柠掂?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 39,740評論 0 276
  • 序言:老撾萬榮一對情侶失蹤依沮,失蹤者是張志新(化名)和其女友劉穎涯贞,沒想到半個月后,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體危喉,經(jīng)...
    沈念sama閱讀 46,286評論 1 318
  • 正文 獨居荒郊野嶺守林人離奇死亡宋渔,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 38,375評論 3 340
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了姥饰。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 40,505評論 1 352
  • 序言:一個原本活蹦亂跳的男人離奇死亡孝治,死狀恐怖列粪,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情谈飒,我是刑警寧澤岂座,帶...
    沈念sama閱讀 36,185評論 5 350
  • 正文 年R本政府宣布,位于F島的核電站杭措,受9級特大地震影響费什,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜手素,卻給世界環(huán)境...
    茶點故事閱讀 41,873評論 3 333
  • 文/蒙蒙 一鸳址、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧泉懦,春花似錦稿黍、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,357評論 0 24
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽言沐。三九已至,卻和暖如春酣栈,著一層夾襖步出監(jiān)牢的瞬間险胰,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 33,466評論 1 272
  • 我被黑心中介騙來泰國打工矿筝, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留起便,地道東北人。 一個月前我還...
    沈念sama閱讀 48,921評論 3 376
  • 正文 我出身青樓跋涣,卻偏偏與公主長得像缨睡,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子陈辱,可洞房花燭夜當晚...
    茶點故事閱讀 45,515評論 2 359

推薦閱讀更多精彩內(nèi)容