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

櫻花盛開.jpg

概述

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

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

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

類型編碼

Runtime中,編譯器將每個方法的返回值和參數(shù)類型編碼為一個字符串,并將其與方法的selector關聯(lián)在一起。

由于該編碼方案具有一定的通用性,系統(tǒng)提供了編譯器指令@encode來獲取特定編碼后的字符串。

當給定一個類型時,@encode返回這個類型的字符串編碼。這些類型可以是諸如int、指針等基本類型,也可以是結構體、類等類型。

事實上,任何可以作為sizeof()操作參數(shù)的類型都可以執(zhí)行@encode()指令。

Objective-C Runtime Programming Guide中的Type Encoding一節(jié)中,列出了Objective-C中所有的類型編碼。需要注意的是這些類型很多是與我們用于存檔和分發(fā)的編碼類型是相同的。但有一些不能在存檔時使用,如下所示:

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

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

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基本數(shù)據(jù)結構和關聯(lián)對象(Associated Object)。其中,關于關聯(lián)對象的相關內(nèi)容在之前的文章中詳細闡述過。

基礎數(shù)據(jù)結構

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

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

typedef struct objc_ivar *Ivar;

結構體struct objc_ivar的數(shù)據(jù)結構如下所示:

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
} 

屬性的數(shù)據(jù)結構

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

typedef struct objc_property *objc_property_t;

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

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;

成員變量與屬性的聯(lián)系

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

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

成員變量

成員變量的相關函數(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_getIvarobject_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 );
屬性

屬性相關函數(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()釋放。

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

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
  • 上面的代碼中,我們在運行時動態(tài)創(chuàng)建了Animal 的一個子類 Panda;
  • 然后為它動態(tài)添加了 Ivar:_runtimeProperty、對應的 Property:runtimeProperty、對應的 Getter/Setter方法:runtimeProperty``setRuntimeProperty;
  • 接著我們遍歷和打印了Panda 的 Ivar 列表和 Property 列表;
  • 然后創(chuàng)建了 Panda 的一個實例 panda,并使用了 Property;
  • 最后我們銷毀了 pandaPanda

這里有幾點需要注意的:

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

小結

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

參考

最后編輯于
?著作權歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務。

相關閱讀更多精彩內(nèi)容

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

友情鏈接更多精彩內(nèi)容