runtime-分類為什么不生成setter和getter

前言

前幾天有人問我一個問題:為什么分類不能自動創(chuàng)建get set方法。老實說,筆者從來沒有去思考過這個問題。于是這次通過代碼實踐跟runtime源碼來探究這個問題。

準備工作

為了能減少輸出類數(shù)據(jù)的代碼工作停巷,筆者基于NSObject的分類封裝了一套代碼


其中輸出類實例變量的具體代碼:
- (void)logIvarsWithExpReg: (NSString *)expReg customed: (BOOL)customed {
[NSObject kRecordOBJ];
unsigned int ivarCount;
Ivar * ivars = class_copyIvarList([self class], &ivarCount);
for (int idx = 0; idx < ivarCount; idx++) {
Ivar ivar = ivars[idx];
NSString * ivarName = [NSString stringWithUTF8String: ivar_getName(ivar)];
if (customed && [kOBJIvarNames containsObject: ivarName]) {
continue;
}
if (expReg && !kValidExpReg(ivarName, expReg)) {
continue;
}
printf("ivar: %s --- %s\n", NSStringFromClass([self class]).UTF8String, ivarName.UTF8String);
}
free(ivars);
}
+(void)kRecordOBJ采用dispatch_once的方式將NSObject存在的數(shù)據(jù)存儲到三個數(shù)組中,用來排除父類的數(shù)據(jù)輸出

類的屬性

  • 正常創(chuàng)建類
    @interface Person: NSObject {
    int _pId;
    }

    @property (nonatomic, copy) NSString * name;
    @property (nonatomic, assign) NSUInteger age;
    
    @end
    
    int main(int argc, char * argv[]) {
        @autoreleasepool {
            Person * p = [[Person alloc] init];
            [p logCustomIvars];
            [p logCustomMethods];
            [p logCustomProperties];
            return UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class]));
        }
    }
    

運行結(jié)果:屬性nameage生成了對應(yīng)的_propertyName的實例變量以及settergetter

  • 動態(tài)生成屬性age
    @implementation Person
    @dynamic age;

    @end
    

運行結(jié)果:缺少了_age變量以及對應(yīng)的setAge:age方法

  • 手動實現(xiàn)setter/getter
    @implemetation Person
    @dynamic age;

    - (void)setAge: (NSUInteger)age {}
    - (NSUInteger)age { return 18; }
    
    @end
    

輸出結(jié)果:未生成_age實例變量

  • 手動實現(xiàn)_pIdsetter/getter
    @implemetation Person
    @dynamic age;

    - (void)setAge: (NSUInteger)age {}
    - (NSUInteger)age { return 18; }
    
    - (void)setPId: (int)pId { _pId = pId; }
    - (int)pId { return _pId; }      
    
    @end
    
    [p setValueForKey: @"pId"];
    

運行結(jié)果:KVC的訪問會觸發(fā)setter方法榕栏,_pId除了無法通過點語法訪問外畔勤,其他表現(xiàn)與@property無異

通過上面的幾段試驗,可以得出@property的公式:

分類屬性

  • 分類中添加weighheight屬性
    @interface Person (category)

    @property (nonatomic, assign) CGFloat weigh;
    @property (nonatomic, assign) CGFloat height;
    
    @end
    

運行結(jié)果:weighheight未生成實例變量以及對應(yīng)的setter/getter扒磁,與@dynamic修飾的age表現(xiàn)一致

  • 使用@synthesize自動合成setter/getter方法時編譯報錯

  • 手動實現(xiàn)setter/getter
    @implemetation Person (category)

    - (void)setWeigh: (CGFloat)weigh {}
    - (CGFloat)weigh { return 150; }
    
    @end
    

運行結(jié)果:與@dynamic age后重寫其setter/getter表現(xiàn)一致

  • 動態(tài)綁定屬性來實現(xiàn)setter/getter
    void * kHeightKey = &kHeightKey;
    @implemetation Person (category)

    - (void)setWeigh: (CGFloat)weigh {}
    - (CGFloat)weigh { return 150; }
    
    - (void)setHeight: (CGFloat)height {
        objc_setAssociatedObject(self, kHeightKey, @(height), OBJC_ASSOCIATION_RETAIN_NONATOMIC);
    }
    - (CGFloat)height { 
        return return [objc_getAssociatedObject(self, kHeightKey) doubleValue];;
    }
    
    @end
    
    [p logCustomIvars]
    [p logCustomMethods];
    [p logCustomProperties];
    
    CGFloat height = 180;
    p.height = 180;
    height = p.height;
    
    [p logCustomIvars]
    [p logCustomMethods];
    [p logCustomProperties];
    

運行結(jié)果:動態(tài)綁定前后ivar沒有發(fā)生任何變化

通過代碼實驗庆揪,可以得出下面兩個結(jié)論:

  • 分類屬性相當于@dynamic property
  • 缺少ivar的情況下無法使用@synthesize自動合成屬性

以及一個猜想:

  • 在類完成加載后無法繼續(xù)添加ivar

通過runtime動態(tài)創(chuàng)建類驗證猜想:

int main(int argc, char * argv[]) {

    NSString * className = @"Custom";
    Class customClass = objc_allocateClassPair([NSObject class], className.UTF8String, 0);
    class_addIvar(customClass, @"ivar1".UTF8String, sizeof(NSString *), 0, "@");
    objc_property_attribute_t type1 = { "T", "@\"NSString\"" };
    objc_property_attribute_t ownership1 = { "C", "N" };
    objc_property_attribute_t atts1[] = { type1, ownership1 };
    class_addProperty(customClass, "property1", atts1, 2);

    objc_registerClassPair(customClass);
    id instance = [[customClass alloc] init];
    NSLog(@"\nLog Ivars ===================");
    [instance logCustomIvars];
    NSLog(@"\nLog methods ===================");
    [instance logCustomMethods];
    NSLog(@"\nLog properties ===================");
    [instance logCustomProperties];

    class_addIvar(customClass, @"ivar2".UTF8String, sizeof(NSString *), 0, "@");
    objc_property_attribute_t type2 = { "T", "@\"NSString\"" };
    objc_property_attribute_t ownership2 = { "C", "N" };
    objc_property_attribute_t atts2[] = { type2, ownership2 };
    class_addProperty(customClass, "property2", atts2, 2);
    instance = [[customClass alloc] init];
    NSLog(@"\nLog Ivars ===================");
    [instance logCustomIvars];
    NSLog(@"\nLog methods ===================");
    [instance logCustomMethods];
    NSLog(@"\nLog properties ===================");
    [instance logCustomProperties];
}

運行結(jié)果:在調(diào)用class_registerClassPair后,添加ivar失敗

從源碼解析

objc_class的結(jié)構(gòu)體定義如下:

struct objc_class : objc_object {
    Class superclass;
    const char *name;
    uint32_t version;
    uint32_t info;
    uint32_t instance_size;
    struct old_ivar_list *ivars;
    struct old_method_list **methodLists;
    Cache cache;
    struct old_protocol_list *protocols;
    // CLS_EXT only
    const uint8_t *ivar_layout;
    struct old_class_ext *ext;
}

ps: 在新版本中結(jié)構(gòu)體內(nèi)部已經(jīng)發(fā)生了大改妨托,但是內(nèi)部的屬性大致上仍是這些

這里面有個重要的屬性ivar_layout缸榛,顧名思義存放的是變量的位置屬性,與之對應(yīng)的還有一個weakIvarLayout變量兰伤,不過在默認結(jié)構(gòu)中沒有出現(xiàn)内颗。這兩個屬性用來記錄ivar哪些是strong或者weak,而這個記錄操作在runtime階段已經(jīng)被確定好敦腔。正由于如此均澳,這極有可能是ivar無法在類被加載后繼續(xù)添加的原因之一。ivar_layout的更多了解可以參照Objective-C Class Ivar layout一文

import操作幫助編譯檢查和鏈接過程符衔,但是在category的加載過程中找前,不會將擴展的內(nèi)容添加到原始的類結(jié)構(gòu)中。runtime對于category的加載過程可以簡單的分成下面幾步(摘自objc category的密碼):

  • objc runtime的加載入口是一個叫_objc_init的方法判族,在library加載前由libSystem dyld調(diào)用躺盛,進行初始化操作
  • 調(diào)用map_images方法將文件中的image map到內(nèi)存
  • 調(diào)用_read_images方法初始化map后的image,這里面干了很多的事情五嫂,像load所有的類、協(xié)議和category,著名的+ load方法就是這一步調(diào)用的
    -仔細看category的初始化沃缘,循環(huán)調(diào)用了_getObjc2CategoryList方法躯枢,這個方法拿出來看看:
  • .…

這一切的過程發(fā)生在_objc_init函數(shù)中,函數(shù)實現(xiàn)如下


簡單來說在load_images函數(shù)中最終會走到下面的代碼調(diào)用來加載所有的類以及類的分類

根據(jù)上面的代碼加上runtime的加載順序槐臀,可以繼續(xù)推出:

  • @dynamic實際上是將屬性的加載推遲到類加載完成后

另外锄蹂,前面也說過在缺少ivar的情況下無法自動合成setter/getter,除了category本身是不被添加到類結(jié)構(gòu)中的水慨,所以無法使用類結(jié)構(gòu)的ivar合成屬性外得糜,還有分類自身結(jié)構(gòu)的問題

struct category_t {
    const char *name;    ///  類名
    classref_t cls;  ///  類指針
    struct method_list_t *instanceMethods;  ///  實例方法
    struct method_list_t *classMethods;  ///  類方法
    struct protocol_list_t *protocols;  ///  擴展的協(xié)議
    struct property_list_t *instanceProperties;  ///  擴展屬性

    method_list_t *methodsForMeta(bool isMeta) { ... }
    property_list_t *propertiesForMeta(bool isMeta) { ... }
};

可以看到分類結(jié)構(gòu)本身是不存在ivar的容器的,因此缺少了自動合成屬性的條件晰洒。最后還有一個問題朝抖,我們在使用objc_associate系列函數(shù)綁定屬性的時候這些變量存儲在了哪里?

總結(jié)

首先谍珊,iOS的分類在runtime實現(xiàn)的結(jié)構(gòu)體中并不存在Ivar類型的容器治宣,缺少了自動合成setter以及getter的必要條件,因此在分類中聲明的屬性默認為@dynamic修飾砌滞。

其次侮邀,OC本身是一門原型語言,對象和類原型很像贝润。類對象執(zhí)行alloc方法就像是原型模式中的copy操作一樣绊茧,類保存了copy所需的實例信息,這些信息內(nèi)存信息在runtime加載時就被固定了打掘,沒有擴充Ivar的條件华畏。(感謝大表哥的科普)

最后,在runtime中存在一個類型為AssociationHashMap的哈希映射表保存著對象動態(tài)添加的屬性胧卤,每個對象以自身地址為key維護著一個綁定屬性表唯绍,我們動態(tài)添加的屬性就都存儲在這個表里,這也是動態(tài)添加property能成功的基礎(chǔ)枝誊。

上一篇:閑聊內(nèi)存管理

轉(zhuǎn)載請注明原文地址及作者

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末况芒,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子叶撒,更是在濱河造成了極大的恐慌绝骚,老刑警劉巖,帶你破解...
    沈念sama閱讀 221,695評論 6 515
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件祠够,死亡現(xiàn)場離奇詭異压汪,居然都是意外死亡,警方通過查閱死者的電腦和手機古瓤,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,569評論 3 399
  • 文/潘曉璐 我一進店門止剖,熙熙樓的掌柜王于貴愁眉苦臉地迎上來腺阳,“玉大人,你說我怎么就攤上這事穿香⊥ひ” “怎么了?”我有些...
    開封第一講書人閱讀 168,130評論 0 360
  • 文/不壞的土叔 我叫張陵皮获,是天一觀的道長焙蚓。 經(jīng)常有香客問我,道長洒宝,這世上最難降的妖魔是什么购公? 我笑而不...
    開封第一講書人閱讀 59,648評論 1 297
  • 正文 為了忘掉前任,我火速辦了婚禮雁歌,結(jié)果婚禮上宏浩,老公的妹妹穿的比我還像新娘。我一直安慰自己将宪,他們只是感情好绘闷,可當我...
    茶點故事閱讀 68,655評論 6 397
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著较坛,像睡著了一般印蔗。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上丑勤,一...
    開封第一講書人閱讀 52,268評論 1 309
  • 那天华嘹,我揣著相機與錄音,去河邊找鬼法竞。 笑死耙厚,一個胖子當著我的面吹牛,可吹牛的內(nèi)容都是我干的岔霸。 我是一名探鬼主播薛躬,決...
    沈念sama閱讀 40,835評論 3 421
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼呆细!你這毒婦竟也來了型宝?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 39,740評論 0 276
  • 序言:老撾萬榮一對情侶失蹤絮爷,失蹤者是張志新(化名)和其女友劉穎趴酣,沒想到半個月后,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體坑夯,經(jīng)...
    沈念sama閱讀 46,286評論 1 318
  • 正文 獨居荒郊野嶺守林人離奇死亡岖寞,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 38,375評論 3 340
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了柜蜈。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片仗谆。...
    茶點故事閱讀 40,505評論 1 352
  • 序言:一個原本活蹦亂跳的男人離奇死亡指巡,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出隶垮,到底是詐尸還是另有隱情厌处,我是刑警寧澤,帶...
    沈念sama閱讀 36,185評論 5 350
  • 正文 年R本政府宣布岁疼,位于F島的核電站,受9級特大地震影響缆娃,放射性物質(zhì)發(fā)生泄漏捷绒。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 41,873評論 3 333
  • 文/蒙蒙 一贯要、第九天 我趴在偏房一處隱蔽的房頂上張望暖侨。 院中可真熱鬧,春花似錦崇渗、人聲如沸字逗。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,357評論 0 24
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽葫掉。三九已至,卻和暖如春跟狱,著一層夾襖步出監(jiān)牢的瞬間俭厚,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 33,466評論 1 272
  • 我被黑心中介騙來泰國打工驶臊, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留挪挤,地道東北人。 一個月前我還...
    沈念sama閱讀 48,921評論 3 376
  • 正文 我出身青樓关翎,卻偏偏與公主長得像扛门,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子纵寝,可洞房花燭夜當晚...
    茶點故事閱讀 45,515評論 2 359

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

  • 出題者簡介: 孫源(sunnyxx)论寨,目前就職于百度,負責百度知道 iOS 客戶端的開發(fā)工作店雅,對技術(shù)喜歡刨根問底和...
    戈多_于勒閱讀 1,798評論 0 5
  • 夜幕降臨政基,下班的時間終于到了。我飛快地收拾好東西闹啦,疾步走出辦公室沮明。半小時前,酒友來電說她已經(jīng)出發(fā)啦窍奋。 地面的車堵得...
    南琵琶閱讀 499評論 2 2