(轉(zhuǎn))Objective-C Runtime 運(yùn)行時之二:成員變量與屬性?

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

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

類型編碼(Type Encoding)

作為對Runtime的補(bǔ)充,編譯器將每個方法的返回值和參數(shù)類型編碼為一個字符串宴卖,并將其與方法的selector關(guān)聯(lián)在一起。這種編碼方案在其它情況下也是非常有用的邻悬,因此我們可以使用@encode編譯器指令來獲取它症昏。當(dāng)給定一個類型時,@encode返回這個類型的字符串編碼父丰。這些類型可以是諸如int肝谭、指針這樣的基本類型,也可以是結(jié)構(gòu)體蛾扇、類等類型攘烛。事實(shí)上,任何可以作為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()栅炒,在此不細(xì)說。

另外,還有些編碼類型赢赊,@encode雖然不會直接返回它們乙漓,但它們可以作為協(xié)議中聲明的方法的類型限定符∮蛐可以參考Type Encoding簇秒。

對于屬性而言,還會有一些特殊的類型編碼秀鞭,以表明屬性是只讀趋观、拷貝、retain等等锋边,詳情可以參考Property Type String皱坛。

成員變量、屬性

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

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

Ivar

Ivar是表示實(shí)例變量的類型萍膛,其實(shí)際是一個指向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聲明的屬性的類型,其實(shí)際是指向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中一個非常實(shí)用的特性串塑,不過可能很容易被忽視沼琉。

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

我們可能希望通過使用(甚至是濫用)全局變量來解決這個問題葛碧。但這些都不是Ivar,因?yàn)樗麄儾粫B接到一個單獨(dú)的實(shí)例过吻。因此进泼,這種方法很少使用蔗衡。

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

我們可以把關(guān)聯(lián)對象想象成一個Objective-C對象(如字典)绞惦,這個對象通過給定的key連接到類的一個實(shí)例上。不過由于使用的是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

當(dāng)宿主對象被釋放時王滤,會根據(jù)指定的內(nèi)存管理策略來處理關(guān)聯(lián)對象。如果指定的策略是assign滓鸠,則宿主釋放時雁乡,關(guān)聯(lián)對象不會被釋放;而如果指定的是retain或者是copy糜俗,則宿主釋放時踱稍,關(guān)聯(lián)對象會被釋放。我們甚至可以選擇是否是自動retain/copy悠抹。當(dāng)我們需要在多個線程中處理訪問關(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)對象,當(dāng)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杨何。

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

假定我們想要動態(tài)地將一個Tap手勢操作連接到任何UIView中沥邻,并且根據(jù)需要指定點(diǎn)擊后的實(shí)際操作危虱。這時候我們就可以將一個手勢對象及操作的block對象關(guān)聯(lián)到我們的UIView對象中。這項(xiàng)任務(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`厕吉,所以接下來我們定義處理方法:

objc

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

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

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

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

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

ivar_getOffset函數(shù),對于類型id或其它對象類型的實(shí)例變量寄纵,可以調(diào)用object_getIvarobject_setIvar來直接訪問成員變量鳖敷,而不使用偏移量。

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

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

// 設(shè)置關(guān)聯(lián)對象
void objc_setAssociatedObject ( id object, const void *key, id value, objc_AssociationPolicy policy );
// 獲取關(guān)聯(lián)對象
id objc_getAssociatedObject ( id object, const void *key );
// 移除關(guān)聯(lián)對象
void objc_removeAssociatedObjects ( id object );

關(guān)聯(lián)對象及相關(guān)實(shí)例已經(jīng)在前面討論過了程拭,在此不再重復(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, const char *attributeName );
// 獲取屬性的特性列表
objc_property_attribute_t * property_copyAttributeList ( objc_property_t property, unsigned int *outCount );
  • property_copyAttributeValue函數(shù),返回的char *在使用完后需要調(diào)用free()釋放恃鞋。
  • property_copyAttributeList函數(shù)崖媚,返回值在使用完后需要調(diào)用free()釋放。
實(shí)例

假定這樣一個場景恤浪,我們從服務(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)換泥张,不過如果能靈活地運(yùn)用Runtime的話,可以只實(shí)現(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];
        }
    }];
}

當(dāng)然筝野,一個屬性能否通過上面這種方式來處理的前提是其支持KVC晌姚。

小結(jié)

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

參考

Objective-C Runtime Programming Guide

Associated Objects

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市焕议,隨后出現(xiàn)的幾起案子宝磨,更是在濱河造成了極大的恐慌,老刑警劉巖盅安,帶你破解...
    沈念sama閱讀 218,451評論 6 506
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件唤锉,死亡現(xiàn)場離奇詭異,居然都是意外死亡别瞭,警方通過查閱死者的電腦和手機(jī)窿祥,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,172評論 3 394
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來蝙寨,“玉大人晒衩,你說我怎么就攤上這事∏酵幔” “怎么了听系?”我有些...
    開封第一講書人閱讀 164,782評論 0 354
  • 文/不壞的土叔 我叫張陵,是天一觀的道長虹菲。 經(jīng)常有香客問我靠胜,道長,這世上最難降的妖魔是什么毕源? 我笑而不...
    開封第一講書人閱讀 58,709評論 1 294
  • 正文 為了忘掉前任浪漠,我火速辦了婚禮,結(jié)果婚禮上霎褐,老公的妹妹穿的比我還像新娘郑藏。我一直安慰自己,他們只是感情好瘩欺,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,733評論 6 392
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著拌牲,像睡著了一般俱饿。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上塌忽,一...
    開封第一講書人閱讀 51,578評論 1 305
  • 那天拍埠,我揣著相機(jī)與錄音,去河邊找鬼土居。 笑死枣购,一個胖子當(dāng)著我的面吹牛嬉探,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播棉圈,決...
    沈念sama閱讀 40,320評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼涩堤,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了分瘾?” 一聲冷哼從身側(cè)響起胎围,我...
    開封第一講書人閱讀 39,241評論 0 276
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎德召,沒想到半個月后白魂,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,686評論 1 314
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡上岗,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,878評論 3 336
  • 正文 我和宋清朗相戀三年福荸,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片肴掷。...
    茶點(diǎn)故事閱讀 39,992評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡敬锐,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出捆等,到底是詐尸還是另有隱情滞造,我是刑警寧澤,帶...
    沈念sama閱讀 35,715評論 5 346
  • 正文 年R本政府宣布栋烤,位于F島的核電站谒养,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏明郭。R本人自食惡果不足惜买窟,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,336評論 3 330
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望薯定。 院中可真熱鬧始绍,春花似錦、人聲如沸话侄。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,912評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽年堆。三九已至吞杭,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間变丧,已是汗流浹背芽狗。 一陣腳步聲響...
    開封第一講書人閱讀 33,040評論 1 270
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留痒蓬,地道東北人童擎。 一個月前我還...
    沈念sama閱讀 48,173評論 3 370
  • 正文 我出身青樓滴劲,卻偏偏與公主長得像,于是被迫代替她去往敵國和親顾复。 傳聞我的和親對象是個殘疾皇子班挖,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,947評論 2 355

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