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

南峰子的技術博客
本章的主要內(nèi)容將聚集在Runtime對成員變量與屬性的處理烟勋。在討論之前,我們先介紹一個重要的概念:類型編碼筐付。

類型編碼(Type Encoding)

作為對Runtime的補充卵惦,編譯器將每個方法的返回值和參數(shù)類型編碼為一個字符串,并將其與方法的selector關聯(lián)在一起瓦戚。這種編碼方案在其它情況下也是非常有用的沮尿,因此我們可以使用@encode編譯器指令來獲取它。當給定一個類型時较解,@encode返回這個類型的字符串編碼畜疾。這些類型可以是諸如int、指針這樣的基本類型印衔,也可以是結構體啡捶、類等類型。事實上当编,任何可以作為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中關于成員變量和屬性的相關數(shù)據(jù)結構并不多胸完,只有三個,并且都很簡單儡循。不過還有個非常實用但可能經(jīng)常被忽視的特性舶吗,即關聯(lián)對象征冷,我們將在這小節(jié)中詳細討論择膝。

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

Ivar

Ivar是表示實例變量的類型,其實際是一個指向objc_ivar結構體的指針检激,其定義如下:

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結構體的指針,其定義如下:

typedef  struct  objc_property *objc_property_t;

objc_property_attribute_t

objc_property_attribute_t定義了屬性的特性(attribute)叔收,它是一個結構體齿穗,定義如下:

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

關聯(lián)對象(Associated Object)

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

關聯(lián)對象類似于成員變量窃页,不過是在運行時添加的。我們通常會把成員變量(Ivar)放在類聲明的頭文件中复濒,或者放在類實現(xiàn)的@implementation后面脖卖。但這有一個缺點,我們不能在分類中添加成員變量巧颈。如果我們嘗試在分類中添加新的成員變量畦木,編譯器會報錯。

我們可能希望通過使用(甚至是濫用)全局變量來解決這個問題砸泛。但這些都不是Ivar十籍,因為他們不會連接到一個單獨的實例。因此唇礁,這種方法很少使用勾栗。

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

我們可以把關聯(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)存管理策略來處理關聯(lián)對象陶耍。如果指定的策略是assign,則宿主釋放時她混,關聯(lián)對象不會被釋放烈钞;而如果指定的是retain或者是copy,則宿主釋放時坤按,關聯(lián)對象會被釋放毯欣。我們甚至可以選擇是否是自動retain/copy。當我們需要在多個線程中處理訪問關聯(lián)對象的多線程代碼時臭脓,這就非常有用了酗钞。

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

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

在這種情況下,self對象將獲取一個新的關聯(lián)的對象anObject来累,且內(nèi)存管理策略是自動retain關聯(lián)對象砚作,當self對象釋放時,會自動release關聯(lián)對象嘹锁。另外葫录,如果我們使用同一個key來關聯(lián)另外一個對象時,也會自動釋放之前關聯(lián)的對象领猾,這種情況下米同,先前的關聯(lián)對象會被妥善地處理掉,并且新的對象會使用它的內(nèi)存摔竿。

id anObject  =  objc_getAssociatedObject(self,  &myKey);

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

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

假定我們想要動態(tài)地將一個Tap手勢操作連接到任何UIView中但金,并且根據(jù)需要指定點擊后的實際操作。這時候我們就可以將一個手勢對象及操作的block對象關聯(lián)到我們的UIView對象中郁季。這項任務分兩部分冷溃。首先,如果需要梦裂,我們要創(chuàng)建一個手勢識別對象并將它及block做為關聯(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);
}

這段代碼檢測了手勢識別的關聯(lián)對象。如果沒有年柠,則創(chuàng)建并建立關聯(lián)關系凿歼。同時,將傳入的塊對象連接到指定的key上。注意block對象的關聯(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í)行操作虐拓。

從上面的例子我們可以看到心俗,關聯(lián)對象使用起來并不復雜。它讓我們可以動態(tài)地增強類現(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來直接訪問成員變量,而不使用偏移量瞻润。

關聯(lián)對象

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

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

關聯(lián)對象及相關實例已經(jīng)在前面討論過了喘垂,在此不再重復。

屬性

屬性操作相關函數(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ù)據(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"}

通常的方法是寫兩個方法分別做轉換丧荐,不過如果能靈活地運用Runtime的話缆瓣,可以只實現(xià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中相同的屬性上弓坞,這樣,轉換方法可如下處理:

-  (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渡冻。

小結

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

?著作權歸作者所有,轉載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子超歌,更是在濱河造成了極大的恐慌砍艾,老刑警劉巖,帶你破解...
    沈念sama閱讀 212,718評論 6 492
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件巍举,死亡現(xiàn)場離奇詭異辐董,居然都是意外死亡,警方通過查閱死者的電腦和手機禀综,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,683評論 3 385
  • 文/潘曉璐 我一進店門简烘,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人定枷,你說我怎么就攤上這事孤澎。” “怎么了欠窒?”我有些...
    開封第一講書人閱讀 158,207評論 0 348
  • 文/不壞的土叔 我叫張陵覆旭,是天一觀的道長。 經(jīng)常有香客問我岖妄,道長型将,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 56,755評論 1 284
  • 正文 為了忘掉前任荐虐,我火速辦了婚禮七兜,結果婚禮上,老公的妹妹穿的比我還像新娘福扬。我一直安慰自己腕铸,他們只是感情好,可當我...
    茶點故事閱讀 65,862評論 6 386
  • 文/花漫 我一把揭開白布铛碑。 她就那樣靜靜地躺著狠裹,像睡著了一般。 火紅的嫁衣襯著肌膚如雪汽烦。 梳的紋絲不亂的頭發(fā)上涛菠,一...
    開封第一講書人閱讀 50,050評論 1 291
  • 那天,我揣著相機與錄音撇吞,去河邊找鬼俗冻。 笑死,一個胖子當著我的面吹牛梢夯,可吹牛的內(nèi)容都是我干的言疗。 我是一名探鬼主播,決...
    沈念sama閱讀 39,136評論 3 410
  • 文/蒼蘭香墨 我猛地睜開眼颂砸,長吁一口氣:“原來是場噩夢啊……” “哼噪奄!你這毒婦竟也來了死姚?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 37,882評論 0 268
  • 序言:老撾萬榮一對情侶失蹤勤篮,失蹤者是張志新(化名)和其女友劉穎都毒,沒想到半個月后,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體碰缔,經(jīng)...
    沈念sama閱讀 44,330評論 1 303
  • 正文 獨居荒郊野嶺守林人離奇死亡账劲,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 36,651評論 2 327
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了金抡。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片瀑焦。...
    茶點故事閱讀 38,789評論 1 341
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖梗肝,靈堂內(nèi)的尸體忽然破棺而出榛瓮,到底是詐尸還是另有隱情,我是刑警寧澤巫击,帶...
    沈念sama閱讀 34,477評論 4 333
  • 正文 年R本政府宣布禀晓,位于F島的核電站,受9級特大地震影響坝锰,放射性物質發(fā)生泄漏粹懒。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 40,135評論 3 317
  • 文/蒙蒙 一顷级、第九天 我趴在偏房一處隱蔽的房頂上張望凫乖。 院中可真熱鬧,春花似錦愕把、人聲如沸拣凹。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,864評論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至爬迟,卻和暖如春橘蜜,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背付呕。 一陣腳步聲響...
    開封第一講書人閱讀 32,099評論 1 267
  • 我被黑心中介騙來泰國打工计福, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人徽职。 一個月前我還...
    沈念sama閱讀 46,598評論 2 362
  • 正文 我出身青樓象颖,卻偏偏與公主長得像,于是被迫代替她去往敵國和親姆钉。 傳聞我的和親對象是個殘疾皇子说订,可洞房花燭夜當晚...
    茶點故事閱讀 43,697評論 2 351

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