Objective-C Runtime:深入理解成員變量與屬性

櫻花盛開.jpg

概述

在上篇文章Objective-C Runtime:深入理解類與對象中,講解了類與對象的相關內容。

在本文中,著重講解一下類實現細節的先關內容,主要包括類中的成員變量、屬性、方法以及協議與分類的實現。

在講解成員變量與屬性之前,需要了解一下類型編碼相關知識。

類型編碼

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

針對數組的類型編碼,返回字符串會包括:數組元素的個數以及元素的類型,具體如下所示:

int a[] = {1, 2};
NSLog(@"type Coding = %s", @encode(typeof(a)));

打印結果如下:

2018-03-28 22:46:28.253495+0800 RuntimeUsage[48760:1909814] type Coding = [2i]

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

成員變量與屬性

成員變量與屬性這一部分有三個方面需要注意:Ivarobjc_property_t基本數據結構和關聯對象(Associated Object)。其中,關于關聯對象的相關內容在之前的文章中詳細闡述過。

基礎數據結構

成員變量(Ivar)的數據結構

在Objective-C中,成員變量即Ivar類型,是指向結構體struct objc_ivar的指針,在Objc/runtime.h 中查到,如下所示:

typedef struct objc_ivar *Ivar;

結構體struct objc_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
} 

屬性的數據結構

屬性(property)數據結構如下所示:

typedef struct objc_property *objc_property_t;

屬性特性(Attribute)的數據結構如下所示:

typedef struct {
    const char * _Nonnull name;           /**< The name of the attribute */
    const char * _Nonnull value;          /**< The value of the attribute (usually empty) */
} objc_property_attribute_t;

成員變量與屬性的聯系

  • 本質上,一個屬性一定對應一個成員變量,但是屬性又不僅僅是一個成員變量,屬性還會根據自己對應的屬性特性的定義來對這個成員變量進行一系列的封裝:提供 Getter/Setter 方法、內存管理策略、線程安全機制等等。

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

成員變量

成員變量的相關函數如下:

// 獲取成員變量名
const char * ivar_getName ( Ivar v );
// 獲取成員變量類型編碼
const char * ivar_getTypeEncoding ( Ivar v );
// 獲取成員變量的偏移量
ptrdiff_t ivar_getOffset ( Ivar v );
  • ivar_getOffset函數,對于類型id或其它對象類型的實例變量,可以調用object_getIvarobject_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()釋放。

運行時操作成員變量和屬性的示例代碼

NSString * runtimePropertyGetterIMP(id self, SEL _cmd){
    Ivar ivar = class_getInstanceVariable([self class], "_runtimeProperty");
    
    return object_getIvar(self, ivar);
}

void runtimePropertySetterIMP(id self, SEL _cmd, NSString *value){
    Ivar ivar = class_getInstanceVariable([self class], "_runtimeProperty");
    NSString *aValue = (NSString *)object_getIvar(self, ivar);
    if (![aValue isEqualToString:value]) {
        object_setIvar(self, ivar, value);
    }
}

- (void)verifyPropertyAndIvar{
    
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wundeclared-selector"
    
    //1、Add property and getter/setter method
    Class cls = objc_allocateClassPair([Animal class], "Panda", 0);
    
    //add instance variable
    BOOL isSuccess = class_addIvar(cls, "_runtimeProperty", sizeof(cls), log2(sizeof(cls)), @encode(NSString));
    NSLog(@"%@", isSuccess ? @"成功" : @"失敗");//print 成功
    
    //add attributes
    objc_property_attribute_t type = {"T", "@\"NSString\""};
    objc_property_attribute_t ownership = {"C", ""};//C = Copy
    objc_property_attribute_t isAutomic = {"N", ""};// N = nonatomic
    objc_property_attribute_t backingVar = {"V", "_runtimeProperty"};
    objc_property_attribute_t attrubutes[] = {type, ownership, isAutomic, backingVar};
    class_addProperty(cls, "runtimeProperty", attrubutes, 4);
    class_addMethod(cls, @selector(runtimeProperty), (IMP)runtimePropertyGetterIMP, "@@:");
    class_addMethod(cls, @selector(setRuntimeProperty), (IMP)runtimePropertySetterIMP, "V@:");
    
    objc_registerClassPair(cls);
    
    //2、print all properties
    unsigned int count = 0;
    objc_property_t *properties = class_copyPropertyList(cls, &count);
    for (int32_t i = 0; i < count; i ++) {
        objc_property_t property = properties[I];
        NSLog(@"%s, %s\n", property_getName(property), property_getAttributes(property));
        //print: _runtimeProperty, T@"NSString",C,N,V_runtimeProperty
    }
    free(properties);
    
    //3、print all Ivar
    unsigned int outCount = 0;
    Ivar *ivars = class_copyIvarList(cls, &outCount);
    for (int32_t i = 0; i < outCount; i ++) {
        Ivar ivar = ivars[I];
        NSLog(@"%s, %s\n", ivar_getName(ivar), ivar_getTypeEncoding(ivar));
        //print:_runtimeProperty, {NSString=#}
       
    }
    free(ivars);
    
    //4、use property
    id panda = [[cls alloc] init];
    [panda performSelector:@selector(setRuntimeProperty) withObject:@"set-property"];
    NSString *propertyValue = [panda performSelector:@selector(runtimeProperty)];
    NSLog(@"return value = %@", propertyValue);
    //print: return value = set-property
    
    //5、destory
    panda = nil;
    objc_disposeClassPair(cls);
    
    
#pragma clang diagnostic pop
    
}

上述代碼打印信息:

成功
runtimeProperty, T@"NSString",C,N,V_runtimeProperty
_runtimeProperty, {NSString=#}
return value = set-property
  • 上面的代碼中,我們在運行時動態創建了Animal 的一個子類 Panda
  • 然后為它動態添加了 Ivar:_runtimeProperty、對應的 Property:runtimeProperty、對應的 Getter/Setter方法:runtimeProperty``setRuntimeProperty
  • 接著我們遍歷和打印了Panda 的 Ivar 列表和 Property 列表;
  • 然后創建了 Panda 的一個實例 panda,并使用了 Property;
  • 最后我們銷毀了 pandaPanda

這里有幾點需要注意的:

  • 我們不能用 class_addIvar() 函數為一個已經存在的類添加Ivar,并且 class_addIvar() 只能在 objc_allocateClassPair()objc_registerClassPair() 之間調用;
  • 添加屬性特性時的各種類型字符可以參考:Property Type String
  • 添加一個屬性及對應的成員變量后,我們還能通過 [obj valueForKey:@"propertyName"];獲得屬性值。

小結

本文主要講解了成員變量與屬性相關使用,尤其是關聯對象的使用。希望閱讀完本文,能對成員變量和屬性的理解更深入。

參考

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

推薦閱讀更多精彩內容

  • 1.@synthesize是系統自動生成getter和setter屬性聲明 2.@dynamic就是屬性的獲取和賦...
    凌嘯寒閱讀 319評論 0 0
  • 幾周前我在圖書館借閱了一系列劉墉的書,這本名叫《螢窗小語》的書,里面是由一篇篇短的故事構成,每個小故事都能以小見大...
    冰冰小書屋閱讀 825評論 0 2
  • 雨 定格在凌晨窗前 漆黑的深夜駐足在凄慘四邊 寂靜把落紅風干 沒有等到綻放在天山 揮了幾行信箋 揉了幾團青煙 這難...
    肅默閱讀 166評論 0 1
  • 最近和媳婦聊天,談到朋友的話題,媳婦問了我一句:你說你有朋友嗎?我思考了片刻,竟然無言以對。 細細算來,我真的有朋...
    煜言閱讀 381評論 2 1
  • 和男朋友一起,準備找工作,租的房子,一人一把客廳鑰匙,前兩天我的那把鑰匙被我弄丟了,找遍了房間幾乎所有角落,搜遍...
    綠由由閱讀 437評論 0 0