Runtime運行時類與對象(二)

在前面一篇文章中,我們介紹了Runtime中與類和對象相關的內容,從這章開始,我們將討論類實現細節相關的內容,主要包括類中成員變量,屬性,方法,協議與分類的實現。

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

類型編碼(Type Encoding)

作為對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是一樣的。

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

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

1

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雖然不會直接返回它們,但它們可以作為協議中聲明的方法的類型限定符。可以參考Type Encoding。

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

成員變量、屬性

Runtime中關于成員變量和屬性的相關數據結構并不多,只有三個,并且都很簡單。不過還有個非常實用但可能經常被忽視的特性,即關聯對象,我們將在這小節中詳細討論。

基礎數據類型

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;? // 基地址偏移字節

#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;

關聯對象(Associated Object)

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

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

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

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

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

OBJC_ASSOCIATION_ASSIGN

OBJC_ASSOCIATION_RETAIN_NONATOMIC

OBJC_ASSOCIATION_COPY_NONATOMIC

OBJC_ASSOCIATION_RETAIN

OBJC_ASSOCIATION_COPY

當宿主對象被釋放時,會根據指定的內存管理策略來處理關聯對象。如果指定的策略是assign,則宿主釋放時,關聯對象不會被釋放;而如果指定的是retain或者是copy,則宿主釋放時,關聯對象會被釋放。我們甚至可以選擇是否是自動retain/copy。當我們需要在多個線程中處理訪問關聯對象的多線程代碼時,這就非常有用了。

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

static char myKey;

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

在這種情況下,self對象將獲取一個新的關聯的對象anObject,且內存管理策略是自動retain關聯對象,當self對象釋放時,會自動release關聯對象。另外,如果我們使用同一個key來關聯另外一個對象時,也會自動釋放之前關聯的對象,這種情況下,先前的關聯對象會被妥善地處理掉,并且新的對象會使用它的內存。

id anObject = objc_getAssociatedObject(self, &myKey);

我們可以使用objc_removeAssociatedObjects函數來移除一個關聯對象,或者使用objc_setAssociatedObject函數將key指定的關聯對象設置為nil。

我們下面來用實例演示一下關聯對象的使用方法。

假定我們想要動態地將一個Tap手勢操作連接到任何UIView中,并且根據需要指定點擊后的實際操作。這時候我們就可以將一個手勢對象及操作的block對象關聯到我們的UIView對象中。這項任務分兩部分。首先,如果需要,我們要創建一個手勢識別對象并將它及block做為關聯對象。如下代碼所示:

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

}

這段代碼檢測了手勢識別的關聯對象。如果沒有,則創建并建立關聯關系。同時,將傳入的塊對象連接到指定的key上。注意block對象的關聯內存管理策略。

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

- (void)__handleActionForTapGesture:(UITapGestureRecognizer *)gesture

{

if (gesture.state == UIGestureRecognizerStateRecognized)

{

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

if (action)

{

action();

}

}

}

我們需要檢測手勢識別對象的狀態,因為我們只需要在點擊手勢被識別出來時才執行操作。

從上面的例子我們可以看到,關聯對象使用起來并不復雜。它讓我們可以動態地增強類現有的功能。我們可以在實際編碼中靈活地運用這一特性。

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

成員變量

成員變量操作包含以下函數:

// 獲取成員變量名

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()釋放。

實例

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

@interface MyObject: NSObject

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

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

@end

接口A、B返回的字典數據如下所示:

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

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

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

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: 針對特殊數據類型做處理

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

...

[self setValue:obj forKey:propertyKey];

}

}];

}

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


來自http://southpeak.github.io/blog/2014/10/30/objective-c-runtime-yun-xing-shi-zhi-er-:cheng-yuan-bian-liang-yu-shu-xing/

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 227,283評論 6 530
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 97,947評論 3 413
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 175,094評論 0 373
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 62,485評論 1 308
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 71,268評論 6 405
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 54,817評論 1 321
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 42,906評論 3 440
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 42,039評論 0 285
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 48,551評論 1 331
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 40,502評論 3 354
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 42,662評論 1 366
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,188評論 5 356
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 43,907評論 3 345
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,304評論 0 25
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 35,563評論 1 281
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 51,255評論 3 389
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 47,637評論 2 370

推薦閱讀更多精彩內容