對(duì)象、消息丝里、運(yùn)行期

Objective-C作為面向?qū)ο缶幊糖酰皩?duì)象”(object)就是“基本構(gòu)造單元”(building block),開發(fā)者可以通過對(duì)象來存儲(chǔ)并傳遞數(shù)據(jù)杯聚。

在對(duì)象之間傳遞數(shù)據(jù)并執(zhí)行任務(wù)的過程就叫做“消息傳遞”(Messaging)臼婆。

第6條:理解“屬性”這一概念

  • “屬性”(property)是Objective-C的一項(xiàng)特性,用于封裝對(duì)象中的數(shù)據(jù)幌绍。

    通過使用屬性這一寫法颁褂,我們可以從中獲取很多的優(yōu)勢(shì)故响,其中就包括編譯器會(huì)自動(dòng)編寫這些屬性所需的方法,此過程叫做“自動(dòng)合成”(autosynthesis)颁独。而且需要強(qiáng)調(diào)的是彩届,這個(gè)過程由編譯器在編譯期執(zhí)行抗俄,所以編輯器里是看不到這些“合成方法”(syntheiszed method)的源代碼霉翔。

    除了生成方法代碼以外,編譯器還要自動(dòng)向類中添加適當(dāng)類型的實(shí)例變量柬唯,并且在屬性名前面加下劃線靠柑,以此作為實(shí)例變量的名字寨辩。而一般情況下,不太建議大家去修改默認(rèn)的實(shí)例變量名歼冰,但如果由于個(gè)人原因不能接受下劃線命名方案的話靡狞,也可以改為自己想要的寫法,如下:

    @synthesize firstName = myFirstName;
    @synthesize lastName = myLastName;
    
  • 若不想令編譯器自動(dòng)合成存取方法隔嫡,則可以自己實(shí)現(xiàn)甸怕。如果你只實(shí)現(xiàn)了其中一個(gè)存取方法,那么另外一個(gè)還是會(huì)由編譯器來合成腮恩,除非你使用@dynamic關(guān)鍵字來告訴編譯器:不要自動(dòng)創(chuàng)建實(shí)現(xiàn)屬性所用的實(shí)例變量梢杭,也不要為其創(chuàng)建存取方法。而且庆揪,在編譯器訪問屬性的代碼時(shí)式曲,及時(shí)編譯器發(fā)現(xiàn)沒有定義存取方法,也不會(huì)報(bào)錯(cuò)缸榛,因?yàn)樗嘈胚@些方法會(huì)在運(yùn)行期找到吝羞。

    @interface MarkAnimatedView ()
    
    @property NSString *firstName;
    @property NSString *lastName;
    
    @end
    
    @implementation MarkAnimatedView
    @dynamic firstName, lastName;
    

    編譯器不會(huì)為上面這個(gè)類自動(dòng)合成存取方法或?qū)嵗兞浚褂么a訪問其中的屬性内颗,編譯器也不會(huì)發(fā)出警示信息钧排。

  • 內(nèi)存管理語義

    屬性用于封裝數(shù)據(jù),而數(shù)據(jù)則要有“具體的所有權(quán)語義”(concrete ownership semantic)均澳。如果自己編寫存取方法恨溜,那么就必須與有關(guān)屬性所具備的特質(zhì)相符。

    • assign “設(shè)置方法”只會(huì)執(zhí)行針對(duì)“純量類型”(scalar type找前,例如CGFloat或NSInteger)的簡(jiǎn)單賦值操作糟袁。
    • strong 此特征表明該屬性定義了一種“擁有關(guān)系”(owning relationship)。為這種屬性設(shè)置新值時(shí)躺盛,設(shè)置方法會(huì)先保留新值项戴,并釋放舊值,然后再把新值設(shè)置上去槽惫。
    • weak 此特質(zhì)表明該屬性定義了一種“非擁有關(guān)系”(nonowning relationship)周叮。為這種屬性設(shè)置新值時(shí)辩撑,設(shè)置方法既不保留新值仿耽,也不釋放舊值合冀。在屬相所指對(duì)象遭到摧毀時(shí),屬性值會(huì)清空项贺。
    • unsafe_unretained 此特質(zhì)的語義與assign相同君躺,但是它適用于“對(duì)象類型”(object type),該特質(zhì)表達(dá)了一種“非擁有關(guān)系”(“不保留”敬扛,unretained)晰洒,當(dāng)目標(biāo)對(duì)象遭到摧毀朝抖,屬性值不會(huì)自動(dòng)清空(“不安全”啥箭,unsafe),這一點(diǎn)與weak有區(qū)別治宣。
    • copy 此特質(zhì)所表達(dá)的所屬關(guān)系與strong類似急侥,然而設(shè)置方法并不保留新值,而是將其“拷貝”(copy)侮邀。當(dāng)屬性類型為NSString*時(shí)坏怪,經(jīng)常使用此特質(zhì)保護(hù)其封裝性。因?yàn)閭鬟f給設(shè)置方法的新值有可能指向一個(gè)NSMutableString類的實(shí)例绊茧,那么設(shè)置完屬性之后铝宵,字符串的值就可能會(huì)在對(duì)象不知情的情況下遭人更改,所以华畏,這時(shí)就要拷貝一份“不可變”(immutable)的字符串鹏秋,來確保對(duì)象中的字符串值不會(huì)無意間變動(dòng)。
  • 方法名
    可以通過如下特質(zhì)來指定存取方法的方法名:

    @property (nonatomic, getter=isOn) BOOL on;
    
  • atomic與nonatomic的區(qū)別

    具備atomic特質(zhì)的獲取方法會(huì)通過鎖定機(jī)制來確保其操作的原子性亡笑,這也就是說侣夷,如果兩個(gè)線程讀寫同一個(gè)屬性,那么無論何時(shí)仑乌,總能看到有效的屬性值百拓。而若是使用nonatomic語義的話,那么當(dāng)其中一個(gè)線程正在改寫某屬性值時(shí)晰甚,另一個(gè)線程也許會(huì)突然闖入衙传,把尚未修改好的屬性值讀取出來。發(fā)生這種情況時(shí)厕九,線程讀到的屬性值可能不對(duì)蓖捶。

    但是在iOS開發(fā)中,基本所有的屬性都聲明為nonatomic止剖,這樣做的歷史原因就是:在iOS中使用同步鎖的開銷較大腺阳,這會(huì)帶來性能問題落君。但是在Mac OS程序開發(fā)時(shí),使用atomic屬性通常都不會(huì)有性能瓶頸亭引。

第7條:在對(duì)象內(nèi)部盡量直接訪問實(shí)例變量

  • 首先绎速,我們需要先清楚,什么是【通過屬性訪問】焙蚓?什么又是【直接訪問】纹冤?

    其實(shí)用最簡(jiǎn)單的話來說就是:使用點(diǎn)語法來訪問的就是【通過屬性訪問】,如: self.firstName = [components objectAtIndex: 0];
    而【直接訪問】就是使用下劃線語法訪問购公,如: _firstName = [components objectAtIndex: 0];

    這兩種寫法有幾個(gè)區(qū)別:

    1. 由于不經(jīng)過Objective-C的“方法派發(fā)”(method dispatch)步驟萌京,所以直接訪問實(shí)例變量的速度當(dāng)然會(huì)比較快。在這種情況下宏浩,編譯器所生成的代碼會(huì)直接訪問保存對(duì)象實(shí)例變量的那塊內(nèi)存知残。
    2. 直接訪問實(shí)例變量時(shí),不會(huì)調(diào)用其“設(shè)置方法”比庄,這就繞過了為相關(guān)屬性所定義的“內(nèi)存管理語義”求妹。(如:在ARC下直接訪問一個(gè)聲明為copy的屬性,那么并不會(huì)拷貝該屬性佳窑,只會(huì)保留新值并釋放舊值制恍。)
    3. 如果直接訪問實(shí)例變量,那就不會(huì)觸發(fā)“鍵值觀察”(Key-Value Observing神凑,KVO)通知净神。(所以是否使用需要根據(jù)實(shí)際的項(xiàng)目需求而定)
    4. 通過屬性來訪問則有助于排查與之相關(guān)的錯(cuò)誤,因?yàn)榭梢越o“獲取方法”和/或“設(shè)置方法”中新增“斷點(diǎn)”(breakpoint)溉委。

    所以總的來說鹃唯,在對(duì)象內(nèi)部讀取數(shù)據(jù)的時(shí)候,我們更推薦通過直接訪問實(shí)例變量的方式進(jìn)行薛躬,而在寫入數(shù)據(jù)的時(shí)候俯渤,則應(yīng)通過屬性訪問的方式來寫。在初始化方法及delloc方法中型宝,就只應(yīng)該使用【直接訪問】來讀寫數(shù)據(jù)了八匠!

第8條:理解“對(duì)象等同性”這一概念

  • 按照==操作符比較出來的結(jié)果未必是我們想要的,因?yàn)樵摬僮鞅容^的是兩個(gè)指針本身趴酣,而不是其所指的對(duì)象梨树。應(yīng)該使用NSObject協(xié)議中聲明的“isEqual:”方法來判斷兩個(gè)對(duì)象的等同性。

    NSString *foo = @"Badger 123";
    NSString *bar = [NSString stringWithFormat:@"Badger %i",123];
    BOOL equalA = (foo == bar);
    BOOL equalB = [foo isEqual:bar];
    BOOL equalC = [foo isEqualToString:bar];
    NSLog(@"equalA : %d\nequalB : %d\nequalC : %d",equalA,equalB,equalC);
    

    Output:
    equalA : 0
    equalB : 1
    equalC : 1

    大家這樣就可以看到==與等同性判斷方法之間的差別了岖寞。

    注意 : NSString類實(shí)現(xiàn)了一個(gè)自己獨(dú)有的等同性判斷方法抡四,名叫“isEqualToString:”,傳遞給該方法的對(duì)象必須是NSString,否則結(jié)果未定義指巡。調(diào)用該方法比調(diào)用“isEqual:”方法快淑履,后者還要執(zhí)行額外的步驟,因?yàn)樗恢朗軠y(cè)對(duì)象的類型藻雪。

  • 若想在自定義的對(duì)象中正確覆寫判斷等同性的方法秘噪,就必須先理解其約定。但是勉耀,如果兩個(gè)對(duì)象的hash方法返回同一個(gè)值指煎,那么“isEqual:”方法未必會(huì)認(rèn)為兩者想等。

==大概的判斷邏輯思路如下:==

  1. 首先便斥,直接判斷兩個(gè)指針是否想等(若相等至壤,則其均指向同一個(gè)對(duì)象,所以受測(cè)的對(duì)象也必定相等枢纠。)像街;
  2. 接下來,比較兩對(duì)象所屬的類(若不屬于同一個(gè)類京郑,則兩對(duì)象不相等宅广。)葫掉;
  3. 但是在繼承體系中判斷等同性時(shí)些举,經(jīng)常會(huì)遭遇A實(shí)例與其子類實(shí)例想等,所以實(shí)現(xiàn)“isEqual:”方法要考慮到這種情況俭厚!
  4. 最后户魏,檢測(cè)每個(gè)屬性是否相等。(只要其中有不相等的屬性挪挤,就判定兩對(duì)象不等叼丑,否則兩對(duì)象相等。)
??接下來該實(shí)現(xiàn)hash方法了扛门,根據(jù)等同性約定:若兩對(duì)象相等鸠信,則其哈希碼也相等,但是兩個(gè)哈希碼相同的對(duì)象卻未必相等论寨。這就是能否正確覆寫“isEqual:”方法的關(guān)鍵所在星立。下面這種寫法完全可行:

```
- (NSUInteger)hash{
        return 1337;
    }
```

不過若是這么寫的話,在collection中使用這種對(duì)象將會(huì)產(chǎn)生性能問題葬凳,因?yàn)閏ollection在檢索哈希表時(shí)绰垂,會(huì)用對(duì)象的哈希碼做引索。假如某個(gè)collection是用set集合實(shí)現(xiàn)的火焰,那么set可能會(huì)根據(jù)哈希碼把對(duì)象分裝到不同的數(shù)組中劲装。在向set中添加新對(duì)象的時(shí)候,要根據(jù)其哈希碼找到與之相關(guān)的那個(gè)數(shù)組,依次檢查其中各個(gè)元素占业,看數(shù)組中已有的對(duì)象是否和將要添加的新對(duì)象相等绒怨。

由此可知,如果令每個(gè)對(duì)象都返回相同的哈希碼谦疾,那么在set中已有1 000 000個(gè)對(duì)象的情況下窖逗,若是繼續(xù)向其中添加對(duì)象,則需將這1 000 000個(gè)對(duì)象全部掃描一遍餐蔬。

??下面給大家推薦一種技能保持較高效率碎紊,又能使生成的哈希碼至少位于一定范圍內(nèi),而不會(huì)過于頻繁地重復(fù)樊诺。當(dāng)然仗考,此算法生成的哈希碼還是會(huì)碰撞,不過至少可以保證哈希碼有多種可能的取值词爬。編寫hash方法時(shí)秃嗜,應(yīng)該用當(dāng)前的對(duì)象做做實(shí)驗(yàn),以便在減少碰撞頻度與降低運(yùn)算復(fù)雜程度之間取舍顿膨。

```
- (NSUInteger)hash{
    
    NSUInteger firstNameHash = [_firstName hash];
    NSUInteger lastNameHash = [_lastName hash];
    NSUInteger ageHash = _age;
    
    return firstNameHash ^ lastNameHash ^ ageHash;
}
```
  • 特定類所具有的等同性判定方法

    除了剛剛上面提到的NSString之外锅锨,NSArray與NSDictionary類也具有特殊的等同性判定方法,由于使用這些特殊的等同性判定方法在編譯期是不做強(qiáng)類型檢查(strong type checking)恋沃,所以判定的速度會(huì)比較快必搞,但是開發(fā)者要保證所傳對(duì)象的類型是正確的。

  • 等同性判定的執(zhí)行深度

    NSArray的檢測(cè)方式為先看兩個(gè)數(shù)組所含對(duì)象個(gè)數(shù)是否相同囊咏,若相同恕洲,則在每個(gè)對(duì)應(yīng)位置的兩個(gè)對(duì)象身上調(diào)用其“isEqual:”方法。如果對(duì)應(yīng)位置上的對(duì)象均相等梅割,那么這兩個(gè)數(shù)組就相等霜第,這叫做“深度等同性判定”(deep equality)。
    ??不過有時(shí)候無須將所有數(shù)據(jù)逐個(gè)比較户辞,只根據(jù)其中部分?jǐn)?shù)據(jù)即可判明二者是否等同泌类。(如:A類的實(shí)例是根據(jù)數(shù)據(jù)庫里面的數(shù)據(jù)創(chuàng)建而來,那么其中就可能含有另外一個(gè)屬性底燎,此屬性是“唯一標(biāo)識(shí)符”刃榨,在這種情況下,我們也許只會(huì)根據(jù)標(biāo)識(shí)符來判斷等同性书蚪,尤其是在此屬性聲明為readonly時(shí)更應(yīng)該如此喇澡。)

  • 容器中可變類的等同性

    現(xiàn)在跟大家分享一種情況:用一個(gè)NSMutableSet與幾個(gè)NSMutableArray對(duì)象測(cè)試一下,就能讓大家發(fā)現(xiàn)問題了殊校。

    NSMutableSet *set = [NSMutableSet new];
    NSMutableArray *arrayA = [@[@1, @2] mutableCopy];
    [set addObject:arrayA];
    
    NSLog(@"set = %@", set);
    //Output:set = {((1,2))}
    

    現(xiàn)在set里含有一個(gè)數(shù)組對(duì)象晴玖,數(shù)組包含兩個(gè)對(duì)象。如果再向set中加入一個(gè)與現(xiàn)有數(shù)組一模一樣的數(shù)組對(duì)象的話,那set里面的對(duì)象將不會(huì)有變化呕屎,所以我們接下來添加一個(gè)和set已有對(duì)象不同的數(shù)組:

    NSMutableArray *arrayC = [@[@1] mutableCopy];
    [set addObject:arrayC];
    //Output:set = {((1),(1,2))}
    

    正如大家所料让簿,由于arrayC與set里已有的對(duì)象不相等,所以現(xiàn)在set里有兩個(gè)數(shù)組了秀睛。最后尔当,我們來改變一下arrayC的內(nèi)容,令到其和arrayA相等

    [arrayC addObject:@2];
    NSLog(@"set = %@", set);
    //Output:set = {((1, 2),(1, 2))}
    

set中居然可以包含兩個(gè)彼此相等的數(shù)組u灏病M钟!根據(jù)set語義是不允許出現(xiàn)這種情況的田盈,然而現(xiàn)在卻無法保證這一點(diǎn)了畜号,因?yàn)槲覀冃薷牧藄et中的已有對(duì)象。所以允瞧,我們得出一個(gè)結(jié)論:==如果把某對(duì)象放入set之后简软,又修改其內(nèi)容,那么后面的行為將很難預(yù)料==

當(dāng)然述暂,舉這個(gè)例子只是為了提醒大家痹升,把某對(duì)象加入collection之后,改變其內(nèi)容將會(huì)帶來的后果畦韭,并沒有說絕對(duì)不要這么做疼蛾,而是讓大家注意在這樣做的時(shí)候,要用相應(yīng)的代碼去處理可能出現(xiàn)的問題廊驼。

==**要點(diǎn)概括**==
1. 若想檢測(cè)對(duì)象的等同性据过,請(qǐng)?zhí)峁癷sEqual:”與hash方法;
2. 相同的對(duì)象必須具有相同的哈希碼妒挎,但是兩個(gè)哈希碼相同的對(duì)象卻未必相同;
3. 不要盲目地逐個(gè)檢測(cè)每天屬性西饵,而是應(yīng)該依照具體需求來制定檢測(cè)方法酝掩;
4. 編寫hash方法時(shí),應(yīng)該使用計(jì)算速度快且哈希碼碰撞幾率低的算法眷柔;

第9條:以“類族模式”隱藏實(shí)現(xiàn)細(xì)節(jié)

  • 類族模式

    “類族”(class cluster)是一種模式(pattern)期虾。該模式可以靈活應(yīng)對(duì)多個(gè)類,將它們的實(shí)現(xiàn)細(xì)節(jié)隱藏在“抽象基類”(abstract base class)后面驯嘱,以保持接口簡(jiǎn)潔镶苞。開發(fā)者無須自己創(chuàng)建子類實(shí)例,只需調(diào)用基類方法來創(chuàng)建即可鞠评。

  • 創(chuàng)建類族

    MarkEmployee頭文件:

    //
    //  MarkEmployee.h
    #import <Foundation/Foundation.h>
    /* 抽象基類  */
    typedef NS_ENUM(NSUInteger, MarkEmployeeType) {
    MarkEmployeeTypeDeveloper,
    MarkEmployeeTypeDesigner,
    MarkEmployeeTypeFinance,};
    
    @interface MarkEmployee : NSObject
    /** 名字 */
    @property(nonatomic,copy) NSString *name;
    /** 薪水 */
    @property(nonatomic,assign) NSUInteger salary;
    
    // 工廠方法(類方法):創(chuàng)建雇員對(duì)象
    + (MarkEmployee *)employeeWithType:(MarkEmployeeType)type;;
    
    // 職員干自己的工作
    - (void)doADaysWork;
    
    @end
    

    MarkEmployee實(shí)現(xiàn)文件:

    //
    //  MarkEmployee.m
    //
    
    #import "MarkEmployee.h"
    
    @implementation MarkEmployee
    
    /*  將子類的實(shí)例的創(chuàng)建隱藏在基類的實(shí)現(xiàn)方法中 */
    // 根據(jù)職員類型創(chuàng)建子類各自的實(shí)例
    +(MarkEmployee *)employeeWithType:(MarkEmployeeType)type
    {
        switch (type) {
            case MarkEmployeeTypeDeveloper:
                return [MarkEmployeeDeveloper new];
                break;
                
            case MarkEmployeeTypeDesigner:
                return [MarkEmployeeDesigner new];
                break;
                
            case MarkEmployeeTypeFinance:
                return [MarkEmployeeFinance new];
                break;
                
            default:
                break;
        }
    }
    
    //Make Employees do their respective day's work
    -(void)doADaysWork
    {
        // 在子類的實(shí)現(xiàn)文件中各自實(shí)現(xiàn)其工作
    }
    
    @end
    

    MarkEmployee的子類MarkEmployeeDeveloper的實(shí)現(xiàn)文件:

    #import "MarkEmployeeDeveloper.h"
    
    @implementation MarkEmployeeDeveloper
    
    - (void)doADaysWork{
        // 子類其工作的實(shí)現(xiàn)細(xì)節(jié)
        [self writeCode];
    }
    
    - (void)writeCode{
        NSLog(@"writeCode");
    }
    
    @end
    

    main函數(shù):

    #import "MarkEmployee.h"
    
    int main(int argc, const char * argv[]) {
        @autoreleasepool {
            MarkEmployee *developer = [MarkEmployee emplyeeWithType:MarkEmployeeTypeDeveloper];
            NSLog(@"%@",[developer class]);// output MarkEmployeeDeveloper
    
            /*
             * 總結(jié):
             * 工廠模式
             * 通過MarkEmployee類的工廠方法創(chuàng)建出來的實(shí)例是MarkEmployee類的子類的實(shí)例
             */
        }
        return 0;
    }
    
  • Cocoa里的族類

    系統(tǒng)框架中有許多類族茂蚓。大部分collection類都是某個(gè)類族中的抽象基類。NSArray與NSMutableArray實(shí)際上有兩個(gè)抽象基類,但是仍然算是一個(gè)類族聋涨,意味著兩者在實(shí)現(xiàn)各自類型的數(shù)組時(shí)可以共用實(shí)現(xiàn)代碼晾浴,此外,還能把可變數(shù)組復(fù)制為不可變數(shù)組牍白,反之亦然脊凰。

    id maybeAnArray = /* ... */;
    if ([maybeAnArray class] == [NSArray class]){// 永遠(yuǎn)不可為真
        // will nerver be hit
    }
    

    注意 : [maybeAnArray class] 所返回的類絕不可能是NSArray類本身,因?yàn)橛蒒SArray的初始化方法所返回的那個(gè)實(shí)例所屬的類型是“隱藏在類族公共接口后面的那個(gè)內(nèi)部類型”茂腥。要判斷出某個(gè)實(shí)例所屬的類是否位于類族之中需要用類型信息查詢方法狸涌。

    id maybeAnArray = /* ... */;
    if (maybeAnArray isKindOfClass:[NSArray class]){
        // will be hit
    }
    

    向Cocoa中NSArray這樣的類族新增子類所要遵守的規(guī)則

    • 子類應(yīng)該繼承自類族中的抽象基類。
    • 子類應(yīng)該定義自己的數(shù)據(jù)存儲(chǔ)方式最岗。
      子類必須用一個(gè)實(shí)例變量來存放數(shù)組中的對(duì)象杈抢,而NSArray本身只不過是包在其他隱藏對(duì)象外面的殼,它僅僅定義了所有數(shù)組都需具備的一些接口仑性。對(duì)于這個(gè)自定義的數(shù)組子類來說惶楼,可以用NSArray來保存其實(shí)例。
    • 子類應(yīng)當(dāng)覆寫超類文檔中指明需要覆寫的方法诊杆。
      在類族中實(shí)現(xiàn)子類時(shí)所需遵循的規(guī)范一般都會(huì)定義基類的文檔之中歼捐,編碼前應(yīng)該先看看。

    要點(diǎn)

    1. 類族模式可以把實(shí)現(xiàn)細(xì)節(jié)隱藏在一套簡(jiǎn)單的公共接口(抽象基類)后面晨汹。
    2. 系統(tǒng)框架中經(jīng)常使用類族豹储。
    3. 從類族的公共抽象基類中繼承子類時(shí)要當(dāng)心,若有開發(fā)文檔淘这,則應(yīng)首先閱讀剥扣。

第10條:在既有類中使用關(guān)聯(lián)對(duì)象存放自定義數(shù)據(jù)

  • 有時(shí)候我們需要在對(duì)象中存放相關(guān)的信息,此時(shí)铝穷,我們通常的做法是會(huì)從對(duì)象所屬的類中去繼承一個(gè)子類钠怯,然后改用這個(gè)子類對(duì)象。然而曙聂,并非所有情況下都能這么做晦炊,有時(shí)候類的實(shí)例可能是由某種機(jī)制所創(chuàng)建的,而開發(fā)者無法令這種機(jī)制創(chuàng)建出自己所寫的子類實(shí)例宁脊。
  • 但是断国,Objective-C中有一項(xiàng)強(qiáng)大的特性可以解決此問題,這就是“關(guān)聯(lián)對(duì)象(Associated Object)”榆苞。
  • 可以給某對(duì)象關(guān)聯(lián)許多其他對(duì)象稳衬,這些對(duì)象通過“鍵”來區(qū)分,這就是關(guān)聯(lián)對(duì)象坐漏。存儲(chǔ)對(duì)象值的時(shí)候薄疚,可以指明“存儲(chǔ)策略”(storage policy)碧信,用以維護(hù)相應(yīng)的“內(nèi)存管理語義”
| 關(guān)聯(lián)類型 | 等效的@property屬性 |
| --- | --- |
| OBJC_ASSOCIATION_ASSIGN | assign |
| OBJC_ASSOCIATION_RETAIN_NONATOMIC | nonatomic,retain |
| OBJC_ASSOCIATION_COPY_NONATOMIC | nonatomic, copy |
| OBJC_ASSOCIATION_RETAIN | retain |
| OBJC_ASSOCIATION_COPY | copy |

**下列方法可以管理管理對(duì)象:**
  • void objc_setAssociatedObject(id object, void*key, id value, objc_AssociationPolicy)

    此方法以給定的鍵和策略為某對(duì)象設(shè)置關(guān)聯(lián)對(duì)象值输涕。

  • id objc_getAssociatedObject(id object, void*key)

    此方法根據(jù)給定的鍵從某對(duì)象中獲取相應(yīng)的關(guān)聯(lián)對(duì)象值音婶。

  • void objc_removeAssociatedObjects(id object)

    此方法移除指定對(duì)象的全部關(guān)聯(lián)對(duì)象。

    由于設(shè)置關(guān)聯(lián)對(duì)象時(shí)所用的鍵是個(gè)“不透明的指針”(opaque pointer)莱坎,所以在設(shè)置關(guān)聯(lián)對(duì)象值時(shí)衣式,若想令兩個(gè)鍵匹配到同一個(gè)值,則兩者必須是完全相同的指針才行檐什。鑒于此碴卧,在設(shè)置關(guān)聯(lián)對(duì)象值時(shí),通常使用靜態(tài)全局變量做鍵乃正。

  • 關(guān)聯(lián)對(duì)象用法舉例

    以我們最常用的UIAlertView為例住册,給大家講解一下關(guān)聯(lián)對(duì)象的用法:

    這是我們常用于處理一些向用戶提示某些消息,并根據(jù)用戶點(diǎn)擊的按鈕來處理下一步動(dòng)作的代碼:

    - (void)askUserAQuestion{
        
        UIAlertView *alert = [[UIAlertView alloc]
                              initWithTitle:@"Question"
                              message:@"What do you want to do ?"
                              delegate:self
                              cancelButtonTitle:@"Cancel"
                              otherButtonTitles:@"Continue", nil];
        [alert show];
    }
    
    //UIAlertView Delegate
    -(void)alertView:(UIAlertView *)alertView clickedButtonAtIndex:(NSInteger)buttonIndex
    {
        if (buttonIndex == 0) {
            [self doCancel];
        }else{
            [self doContinue];
        }
    }
    

    如果想在同一個(gè)類里處理多個(gè)AlertView的話瓮具,代碼就會(huì)變得更為復(fù)雜荧飞,我們必須在delegate方法中檢查傳入的alertView參數(shù),并據(jù)此選用相應(yīng)對(duì)的邏輯名党。要是能在創(chuàng)建AlertView的時(shí)候直接把處理每個(gè)按鈕的邏輯都寫好叹阔,那豈不是簡(jiǎn)單多了!
    ??接下來我們就用關(guān)聯(lián)對(duì)象的方式來實(shí)現(xiàn)同樣的功能:

    #import <objc/runtime.h>
        
    static void *MarkMyAlertViewKey = "MarkMyAlertViewKey";
        
    - (void)askUserAQuestion{
       
       UIAlertView *alert = [[UIAlertView alloc]
                             initWithTitle:@"Question"
                             message:@"What do you want to do ?"
                             delegate:self
                             cancelButtonTitle:@"Cancel"
                             otherButtonTitles:@"Continue", nil];
       
       void(^block)(NSInteger) = ^(NSInteger buttonIndex){
           
           if (buttonIndex == 0) {
               [self doCancel];
           }else{
               [self doContinue];
           }
       };
       
       objc_setAssociatedObject(alert,
                            MarkMyAlertViewKey,
                            block,
                            OBJC_ASSOCIATION_COPY);
       [alert show];
    }
        
       //UIAlertView Delegate
    -(void)alertView:(UIAlertView *)alertView clickedButtonAtIndex:(NSInteger)buttonIndex
    {
       void (^block)(NSInteger) = objc_getAssociatedObject(alertView, MarkMyAlertViewKey);
       block(buttonIndex);
    }
    

    在一個(gè)類里創(chuàng)建一個(gè)警告視圖后传睹,設(shè)定一個(gè)與之關(guān)聯(lián)的“塊”并將它們放在同一作用域里耳幢,等到執(zhí)行delegate方法時(shí)再將其讀出來。這種方式創(chuàng)建的UIAlertView與處理操作結(jié)果的代碼都放在一起欧啤,更易讀懂睛藻。==但是,由于塊可能要捕獲某些變量邢隧,也會(huì)造成“保留環(huán)”==店印。
    ??所以,這種做法只應(yīng)該在其他辦法行不通時(shí)才去考慮用它府框。而對(duì)于多次用到UIAlertView視圖吱窝,有個(gè)更好的辦法,那就是從中繼承子類迫靖,把塊保存為子類中的屬性。

    要點(diǎn)

    • 可以通過“關(guān)聯(lián)對(duì)象”機(jī)制來把兩個(gè)對(duì)象連起來兴使。
    • 定義關(guān)聯(lián)對(duì)象時(shí)可指定內(nèi)存管理語義系宜,用以模仿定義屬性時(shí)所采用的“擁有關(guān)系”與“非擁有關(guān)系”。
    • 只有在其他做法不可行時(shí)才應(yīng)選用關(guān)聯(lián)對(duì)象发魄,因?yàn)檫@種做法通常會(huì)引起難于查找的bug盹牧。

第11條:理解objc_msgSend的作用

在Objective-C中俩垃,如果向某對(duì)象傳遞消息,那就會(huì)使用動(dòng)態(tài)綁定機(jī)制來決定需要調(diào)用的方法汰寓。在底層口柳,所有方法都是普通的C語言函數(shù),然而對(duì)象收到消息之后有滑,究竟該調(diào)用哪個(gè)方法則完全于運(yùn)行期決定跃闹,甚至可以在程序運(yùn)行時(shí)改變,這些特性使得Objective-C成為一門真正的動(dòng)態(tài)語言毛好。

  • 消息派發(fā)的一般過程

    id returnValue  = [someObject messageName:parameter];
    

    解釋:someObject是“接收者”望艺,messageName是“選擇器”,選擇器與參數(shù)合起來成為“消息”肌访。

    編譯器在看到此消息后找默,將其轉(zhuǎn)換為一條標(biāo)準(zhǔn)的C語言函數(shù)調(diào)用objc_msgSend,其原型如下:

    void objc_msgSend(id self, SEL cmd, ...)
    

    解釋:這是個(gè)參數(shù)可變的函數(shù)吼驶。第一個(gè)參數(shù)代表接收者惩激,第二個(gè)參數(shù)代表選擇器,后續(xù)參數(shù)就是消息中的那些參數(shù)蟹演,其順序不變风钻。

    對(duì)先前例子中的消息進(jìn)行轉(zhuǎn)換,如下:

    id returnValue  = objc_msgSend(someObject, @selector(messageName:), parameter);
    

    解釋:objc_msgSend函數(shù)會(huì)依據(jù)接收者與選擇器的類型來調(diào)用適當(dāng)?shù)姆椒ü熘摹榱送瓿纱瞬僮髌枪荆摲椒ㄐ枰诮邮照咚鶎俚念愔兴褜て洹胺椒斜怼保绻苷业脚c選擇器名稱相符的方法蚌父,就跳至其實(shí)現(xiàn)代碼哮兰。若是找不到,那就沿著繼承體系繼續(xù)向上查找苟弛,等找到合適的方法之后再跳轉(zhuǎn)喝滞。如果最終還是找不到相符的方法,那就執(zhí)行“消息轉(zhuǎn)發(fā)”(message forwarding)操作膏秫。

  • 消息派發(fā)的特殊情況

    • objc_msgSend_stret:如果待發(fā)送的消息要返回結(jié)構(gòu)體右遭,那么可交由此函數(shù)處理。
    • objc_msgSend_fpret:如果消息返回的是浮點(diǎn)數(shù)缤削,那么交由此函數(shù)處理窘哈。
    • objc_msgSendSuper:如果要給超類發(fā)消息,例如[super message:parameter]亭敢,那么就交由此函數(shù)處理滚婉。
  • 尾調(diào)用優(yōu)化

    objc_msgSend等函數(shù)一旦找到應(yīng)該調(diào)用的方法實(shí)現(xiàn)之后,就會(huì)“跳轉(zhuǎn)過去”帅刀。之所以能這么做让腹,是因?yàn)镺bjective-C對(duì)象的每個(gè)方法都可以視為簡(jiǎn)單的C函數(shù)远剩,其原型如下:

    <return_type> Class_selector(id self, SEL _cmd, ...)
    

    解釋:每個(gè)類里都有一張表格,其中的指針都會(huì)指向這種函數(shù)骇窍,而選擇器的名稱則是查表時(shí)所用的“鍵”瓜晤。objc_msgSend等函數(shù)正是通過這張表格來尋找應(yīng)該執(zhí)行的方法并跳至其實(shí)現(xiàn)的。其中腹纳,所使用的“尾調(diào)用優(yōu)化”(tail-call optimization)技術(shù)痢掠,可以讓“跳至方法實(shí)現(xiàn)”這一操作變得更簡(jiǎn)單些。但是只估,只有當(dāng)某函數(shù)的最后一個(gè)操作僅僅是調(diào)用其他函數(shù)而不會(huì)將其返回值另作他用時(shí)志群,才能執(zhí)行“尾調(diào)用優(yōu)化”。

    要點(diǎn)

    • 消息由接收者蛔钙、選擇器參數(shù)構(gòu)成锌云。給某對(duì)象“發(fā)送消息”(invoke a message)也就相當(dāng)于在該對(duì)象上“調(diào)用方法”(call a method)。
    • 發(fā)給某對(duì)象的全部信息都要由“動(dòng)態(tài)消息派發(fā)系統(tǒng)”(dynamic message dispatch system)來處理吁脱,該系統(tǒng)會(huì)查出對(duì)應(yīng)的方法桑涎,并執(zhí)行其代碼。

第12條:理解消息轉(zhuǎn)發(fā)機(jī)制

  • 當(dāng)對(duì)象接收到無法解讀的消息后兼贡,就會(huì)啟動(dòng)“消息轉(zhuǎn)發(fā)”機(jī)制攻冷,開發(fā)者可經(jīng)由此過程告訴對(duì)象應(yīng)該如何處理未知消息。

  • 消息轉(zhuǎn)發(fā)分為兩大階段

    第一階段:先征詢接受者所屬的類遍希,看其是否能動(dòng)態(tài)添加方法等曼,以處理當(dāng)前這個(gè)“未知的選擇子”,這叫做“動(dòng)態(tài)方法解析”凿蒜。

    第二階段:涉及“完整的消息轉(zhuǎn)發(fā)機(jī)制”禁谦。
    運(yùn)行時(shí)系統(tǒng)會(huì)請(qǐng)求接收者以“動(dòng)態(tài)新增方法”之外的手段來處理與消息相關(guān)的方法調(diào)用,這又細(xì)分為兩小步废封。
    ??首先州泊,請(qǐng)接收者看看有沒有其他對(duì)象能處理這條消息。若有漂洋,則運(yùn)行期系統(tǒng)會(huì)把消息轉(zhuǎn)給那個(gè)對(duì)象遥皂,于是消息轉(zhuǎn)發(fā)過程結(jié)束。
    ??若沒有“備援的接收者”刽漂,則啟動(dòng)完整的消息轉(zhuǎn)發(fā)機(jī)制演训,運(yùn)行時(shí)系統(tǒng)會(huì)把與消息有關(guān)的全部細(xì)節(jié)都封裝到NSInvocation對(duì)象中,再給接收者最后一次機(jī)會(huì)贝咙,令其設(shè)法解決當(dāng)前還沒處理的這條消息仇祭。

  • 動(dòng)態(tài)方法解析
    對(duì)象在收到無法解讀的消息后,首先將調(diào)用其所屬類的下列類方法:

    + (BOOL)resolveInstanceMethod:(SEL)selector
    

    解釋:selector是未知的選擇器颈畸,返回值為Boolean類型乌奇,表示這個(gè)類是否能新增一個(gè)實(shí)例方法用以處理此選擇器。

    在繼續(xù)往下執(zhí)行轉(zhuǎn)發(fā)之前眯娱,本類有機(jī)會(huì)新增一個(gè)處理未知選擇器的方法礁苗,便是通過調(diào)用“resolveInstanceMethod:”或“resolveClassMethod:”方法來實(shí)現(xiàn)的。

    但是徙缴,使用這種辦法有個(gè)前提:相關(guān)方法的實(shí)現(xiàn)代碼已經(jīng)寫好试伙,只等著運(yùn)行的時(shí)候動(dòng)態(tài)插在類里面就可以了。此方案常用來實(shí)現(xiàn)@dynamic屬性于样。

    id autoDictionaryGetter(id self, SEL _cmd);
    void autoDictionarySetter(id self, SEL _cmd, id value);
    
    + (BOOL)resolveInstanceMethod:(SEL)selector{
        NSString *selectorString = NSStringFromSelector(selector);// 將選擇器轉(zhuǎn)換為字符串
        if(/* selector is from a @dynamic property */){// 使用了@dynamic屬性
            if([selectorString hasPrefix:@"set"]){
                class_addMethod(self,
                                selector,
                                (IMP)autoDictionarySetter,
                                "V@:@");
            }else{
                class_addMethod(self,
                                selector,
                                (IMP)autoDictionaryGetter,
                                "@@:");
            }
            return YES;
        }
        return [super resolveInstanceMethod:selector];
    }
    
    
  • 備援接受者

    在第二階段的第一小步中疏叨,運(yùn)行期系統(tǒng)會(huì)問未知的選擇器能不能把這條消息轉(zhuǎn)發(fā)給其他接收者來處理。與該步驟對(duì)應(yīng)的處理方法如下:

    - (id)forwardingTargetForSelector:(SEL)selector
    

    解釋:selector代表未知的選擇器穿剖,若當(dāng)前接收者能找到備援對(duì)象蚤蔓,則將其返回礁哄,若找不到讽坏,就返回nil撬陵。
    ??通過此方案慢味,可以用“組合”(composition)來模擬出“多重繼承”(multiple inheritance)的某些特性菲茬。
    ??注意開發(fā)者無法操作經(jīng)由這一步所轉(zhuǎn)發(fā)的消息赶促。若是想在發(fā)送給備援接收者之前先修改消息內(nèi)容妒蔚,那就得通過完整的消息轉(zhuǎn)發(fā)機(jī)制來做了疮鲫。

  • 完整的消息轉(zhuǎn)發(fā)

    若沒有“備援的接收者”蘸劈,則啟動(dòng)完整的消息轉(zhuǎn)發(fā)機(jī)制昏苏,運(yùn)行時(shí)系統(tǒng)會(huì)把與尚未處理的那條消息有關(guān)的全部細(xì)節(jié)都封裝到NSInvocation對(duì)象中。

    在觸發(fā)NSInvocation對(duì)象時(shí)威沫,“消息派發(fā)系統(tǒng)”(message-dispatch system)將親自出馬贤惯,把消息指派給目標(biāo)對(duì)象。如下:

    - (void)forwardInvocation:(NSInvocation*)invacation
    

    此方法比較有用的實(shí)現(xiàn)方式為:在觸發(fā)消息前壹甥,先以某種方式改變消息內(nèi)容救巷,比如追加另外一個(gè)參數(shù),或是改換選擇器句柠,等等浦译。

  • 消息轉(zhuǎn)發(fā)全流程

  • 以完整的例子演示動(dòng)態(tài)方法解析
    MarkAutoDictionary頭文件

    #import <Foundation/Foundation.h>
    
    @interface MarkAutoDictionary : NSObject
    
    @property(nonatomic,strong) NSString *string;
    @property(nonatomic,strong) NSNumber *number;
    @property(nonatomic,strong) NSDate *date;
    @property(nonatomic,strong) id opaqueObject;
    
    @end
    

    MarkAutoDictionary實(shí)現(xiàn)文件

    #import "MarkAutoDictionary.h"
    #import <objc/runtime.h>
    
    @interface MarkAutoDictionary ()
    @property(nonatomic,strong) NSMutableDictionary *backingStore;
    @end
    
    @implementation MarkAutoDictionary
    
    // @dynamic會(huì)阻止編譯器自動(dòng)生成相關(guān)的存取方法,而由開發(fā)者自己創(chuàng)建存取方法
    @dynamic string, number, date, opaqueObject;
    
    - (id)init{
        if (self = [super init]) {
            _backingStore = [NSMutableDictionary new]; // 延遲加載
        }
        return self;
    }
    
    // 動(dòng)態(tài)添加新方法
    + (BOOL)resolveInstanceMethod:(SEL)sel{
        NSString *selectorString = NSStringFromSelector(sel);
        if ([selectorString hasPrefix:@"set"]) {
            class_addMethod(self,
                            sel,
                            (IMP)autoDictionarySetter,
                            "v@:@");
        }else{
            class_addMethod(self,
                            sel,
                            (IMP)autoDictionaryGetter,
                            "@@:");
        }
        return YES;
    }
    
    // getter函數(shù)
    id autoDictionaryGetter(id self, SEL _cmd){
        // 從MarkAutoDictionary對(duì)象獲取backingStore字典
        EOCAutoDictionary *typedSelf = (MarkAutoDictionary*)self;
        NSMutableDictionary *backingStore = typedSelf.backingStore;
    
        // 將選擇器轉(zhuǎn)換為字符串溯职,并將其設(shè)為key
        NSString *key = NSStringFromSelector(_cmd);
    
        // 返回backingStore字典中key所對(duì)應(yīng)的值
        return [backingStore objectForKey:key];
    }
    
    // setter函數(shù)
    void autoDictionarySetter(id self, SEL _cmd, id value){
        // 從MarkAutoDictionary對(duì)象獲取backingStore字典
        MarkAutoDictionary *typedSelf = (MarkAutoDictionary*)self;
        NSMutableDictionary *backingStore = typedSelf.backingStore;
    
        // 將選擇器轉(zhuǎn)換為字符串精盅,并將其拷貝為可變字符串
        NSString *selectorString = NSStringFromSelector(_cmd);
        NSMutableString *key = [selectorString mutableCopy];
    
        // 移除key中尾部的“:”
        [key deleteCharactersInRange:NSMakeRange(key.length - 1, 1)];
    
        // 移除key中前面的“set”
        [key deleteCharactersInRange:NSMakeRange(0, 3)];
    
        // 取出現(xiàn)有的key中的首字母,將其小寫化并替代掉原來的首字母
        NSString *lowercaseFirstChar = [[key substringToIndex:1] lowercaseString];
        [key replaceCharactersInRange:NSMakeRange(0, 1) withString:lowercaseFirstChar];
    
        // 根據(jù)key給backingStore存儲(chǔ)相關(guān)的值
        if (value) {
            [backingStore setObject:value forKey:key];
        }else{
            [backingStore removeObjectForKey:key];
        }
    
    }
    @end
    

    main函數(shù):

    int main(int argc, const char * argv[]) {
        @autoreleasepool {
    
            EOCAutoDictionary *autoDict = [EOCAutoDictionary new];
            // autoDict.date == [autoDict setDate]
            // 由于接收者沒有相應(yīng)的方法可調(diào)用谜酒,因?yàn)锧dynamic特性叹俏,所以可以動(dòng)態(tài)新增方法
            autoDict.date = [NSDate dateWithTimeIntervalSince1970:3140907998];
            NSLog(@"%@",autoDict.date);
        }
        return 0;
    }
    

    輸出結(jié)果為:

    2016-03-09 20:29:25.552 第12條.演示動(dòng)態(tài)方法解析[7000:347059] 2069-07-13 02:26:38 +0000
    Program ended with exit code: 0
    

    總結(jié):要想添加新屬性,只需要用@property來定義僻族,并將其聲明為@dynamic即可粘驰。

    要點(diǎn)

    • 若對(duì)象無法響應(yīng)某個(gè)選擇器屡谐,則進(jìn)入消息轉(zhuǎn)發(fā)流程
    • 通過運(yùn)行期的動(dòng)態(tài)方法解析功能蝌数,我們可以在需要用到某個(gè)方法時(shí)再將其加入類中愕掏。
    • 對(duì)象可以把其無法解讀的某些選擇器轉(zhuǎn)交給其他對(duì)象(備援接收者)來處理。
    • 經(jīng)過上述兩步之后顶伞,如果還是沒辦法處理選擇器饵撑,那就啟動(dòng)完整的消息轉(zhuǎn)發(fā)機(jī)制

第13條:用“方法調(diào)配技術(shù)”調(diào)試“黑盒方法”

  • 在給定的選擇子名稱相對(duì)應(yīng)的方法也可以在運(yùn)行期改變唆貌,若能善用此特性滑潘,則可發(fā)揮巨大優(yōu)勢(shì),因?yàn)槲覀?strong>既不需要源代碼锨咙,也不需要通過繼承子類來覆寫方法就能改變這個(gè)類本身的功能语卤。這么一來,新功能將在本類的所有實(shí)例中生效蓖租,而不是僅限于覆寫了相關(guān)方法的那些子類實(shí)例粱侣。此方案經(jīng)常稱為“方法調(diào)配”(method swizzling)。

  • 實(shí)現(xiàn)原理
    類的方法列表會(huì)把選擇器的名稱映射到相關(guān)的方法實(shí)現(xiàn)之上蓖宦,使得“動(dòng)態(tài)消息派發(fā)系統(tǒng)”能夠據(jù)此找到應(yīng)該調(diào)用的方法齐婴。這些方法均以函數(shù)指針(IMP)的形式來表示,其原型如下:

    id (*IMP)(id, SEL, ...)
    

    以NSString類的選擇器映射表為例 圖例如下:

  • Objective-C運(yùn)行時(shí)系統(tǒng)提供的幾個(gè)方法都能夠用來操作這種表。開發(fā)者可以向其中新增選擇器稠茂,也可以改變某選擇器所對(duì)應(yīng)的方法實(shí)現(xiàn)柠偶,還可以交換兩個(gè)選擇器所映射的指針。

  • Method class_getInstanceMethod(Class aClass, SEL aSelector) :根據(jù)給定的選擇器從類中取出與之相關(guān)的方法睬关。

  • void method_exchangeImplementations( Method m1, Method m2):可以交換兩個(gè)方法實(shí)現(xiàn)诱担。

  • 應(yīng)用案例

    編寫一個(gè)方法,在此方法中實(shí)現(xiàn)所需的附加功能电爹,并調(diào)用原有實(shí)現(xiàn)蔫仙。
    分類 NSString (MarkMyAdditions)頭文件:

    @interface NSString (MarkMyAdditions)
    - (NSString*)mark_myLowercaseString;
    @end
    

    分類 NSString (MarkMyAdditions)實(shí)現(xiàn)文件:

    @implementation NSString (MarkMyAdditions)
    // 新增一個(gè)方法,實(shí)現(xiàn)附加功能
    - (NSString *)mark_myLowercaseString{
        NSString *lowercase = [self eoc_myLowercaseString];
        NSLog(@"%@ => %@", self, lowercase);
        return lowercase;
    }
    @end
    

    main函數(shù):

    #import <Foundation/Foundation.h>
    #import <objc/runtime.h>
    #import "NSString+MarkMyAdditions.h"
    
    int main(int argc, const char * argv[]) {
        @autoreleasepool {
            // 1.交換方法
            Method originalMethod = class_getInstanceMethod([NSString class], @selector(lowercaseString));
            Method swappedMethod = class_getInstanceMethod([NSString class], @selector(mark_myLowercaseString));
            // 2.交換方法
            method_exchangeImplementations(originalMethod, swappedMethod);
    
            // 3.調(diào)用lowercaseString方法丐箩,但實(shí)現(xiàn)的功能卻是分類中新增方法的附加功能
            NSString *string = @"THIs is tHe StRiNg";
            NSString *lowercaseString = [string lowercaseString];
    
        }
        return 0;
    }
    

    輸出結(jié)果:

     THIs is tHe StRiNg => this is the string
    

    總結(jié):通過此方案摇邦,開發(fā)者可以為那些“完全不知道其具體實(shí)現(xiàn)的”黑盒方法增加日志記錄功能,這非常有助于程序調(diào)試屎勘。然而施籍,此做法只在調(diào)試程序時(shí)有用,禁止濫用概漱。

    要點(diǎn)

    1. 運(yùn)行期丑慎,可以向類中新增或替換選擇器所對(duì)應(yīng)的方法實(shí)現(xiàn)。
    2. 使用另一份實(shí)現(xiàn)來替換原有的方法實(shí)現(xiàn),這道工序叫做“方法調(diào)配”竿裂,開發(fā)者常用此技術(shù)向原有實(shí)現(xiàn)中添加新功能玉吁。
    3. 一般來說,只有調(diào)試程序的時(shí)候才需要在運(yùn)行期修改方法實(shí)現(xiàn)铛绰,這種做法不宜濫用诈茧。

第14條:理解“類對(duì)象”的用意

  • 運(yùn)行期檢視對(duì)象類型的操作叫做“類型信息查詢”(introspection,內(nèi)饰骊)。在程序中不要直接比較對(duì)象所屬的類曾沈,明智的做法是調(diào)用“類型信息查詢方法”这嚣。

    typedef struct objc_object *id;
    struct objc_object { Class isa; };
    
    // 等價(jià)于
    typedef sturct objc_object {
        Class isa;
    } *id;
    

    解釋:該結(jié)構(gòu)體描述了Objective-C對(duì)象所用的數(shù)據(jù)結(jié)構(gòu)。其中塞俱,isa指針定義了對(duì)象所屬的類姐帚。

    typedef struct objc_class *Class;
    struct objc_class {            
        Class isa;    
        Class super_class;    
        const char *name;        
        long version;
        long info;
        long instance_size;
        struct objc_ivar_list *ivars;
    
    #if defined(Release3CompatibilityBuild)
        struct objc_method_list *methods;
    #else
        struct objc_method_list **methodLists;
    #endif
    
        struct objc_cache *cache;
        struct objc_protocol_list *protocols;
    };
    

    解釋:該結(jié)構(gòu)體存放類的“元數(shù)據(jù)”。其中障涯,isa指針定義了另外一個(gè)類——元類(metaclass)罐旗,用來表述類對(duì)象本身所具備的元數(shù)據(jù)。super_class定義了本類的超類唯蝶。
    類方法 :類方法可以理解成類對(duì)象的實(shí)例方法九秀,每個(gè)類僅有一個(gè)“類對(duì)象”,而每個(gè)“類對(duì)象”僅有一個(gè)與之相關(guān)的“元類”粘我。

  • 類繼承體系 圖例

    總結(jié):通過這張布局關(guān)系圖即可執(zhí)行“類型信息查詢”鼓蜒。開發(fā)者可以查出對(duì)象是否能響應(yīng)某個(gè)選擇器,是否遵從某項(xiàng)協(xié)議征字,并且能看出此對(duì)象位于“類繼承體系”(class hierarchy)的哪一部分都弹。

  • 在類繼承體系中查詢類型信息

    1. 類型信息查詢方法

      可以用類型信息查詢方法來檢視類繼承體系。
      isMemberOfClass:”能夠判斷出對(duì)象是否為某個(gè)特定類的實(shí)例匙姜,而“isKindOfClass:”則能夠判斷出對(duì)象是否為某類或其派生類的實(shí)例畅厢。

    2. 等同性判斷方法

      使用“==”操作符來比較類對(duì)象是否等同。原因在于氮昧,類對(duì)象是“單例”(singleton)框杜,在應(yīng)用程序范圍內(nèi),每個(gè)類的Class僅有一個(gè)實(shí)例郭计,也就是說霸琴,借助“==”操作符可以精確判斷出對(duì)象是否為某類實(shí)例。如:

      id object = /* ... */;
      if ([object class] == [EOCSomeClass class]){
          // 'object' is an instance of EOCSomeClass
      }
      

      總結(jié):應(yīng)該盡量使用類型信息查詢方法昭伸,而不應(yīng)該直接比較兩個(gè)類對(duì)象是否等同梧乘。

    要點(diǎn)

    • 每個(gè)實(shí)例都有一個(gè)指向Class對(duì)象的指針,用以表明其類型,而這些Class對(duì)象則構(gòu)成了類的繼承體系选调。
    • 如果對(duì)象類型無法在編譯期確定夹供,那么就應(yīng)該使用類型信息查詢方法(內(nèi)省)來探知仁堪。
    • 盡量使用類型信息查詢方法來確定對(duì)象類型哮洽,而不要直接比較類對(duì)象,因?yàn)槟承?duì)象可能實(shí)現(xiàn)了消息轉(zhuǎn)發(fā)功能弦聂。
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末鸟辅,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子莺葫,更是在濱河造成了極大的恐慌匪凉,老刑警劉巖,帶你破解...
    沈念sama閱讀 206,311評(píng)論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件捺檬,死亡現(xiàn)場(chǎng)離奇詭異再层,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)堡纬,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,339評(píng)論 2 382
  • 文/潘曉璐 我一進(jìn)店門聂受,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人烤镐,你說我怎么就攤上這事蛋济。” “怎么了职车?”我有些...
    開封第一講書人閱讀 152,671評(píng)論 0 342
  • 文/不壞的土叔 我叫張陵瘫俊,是天一觀的道長(zhǎng)。 經(jīng)常有香客問我悴灵,道長(zhǎng)扛芽,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 55,252評(píng)論 1 279
  • 正文 為了忘掉前任积瞒,我火速辦了婚禮川尖,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘茫孔。我一直安慰自己叮喳,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,253評(píng)論 5 371
  • 文/花漫 我一把揭開白布缰贝。 她就那樣靜靜地躺著馍悟,像睡著了一般。 火紅的嫁衣襯著肌膚如雪剩晴。 梳的紋絲不亂的頭發(fā)上锣咒,一...
    開封第一講書人閱讀 49,031評(píng)論 1 285
  • 那天侵状,我揣著相機(jī)與錄音,去河邊找鬼毅整。 笑死趣兄,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的悼嫉。 我是一名探鬼主播艇潭,決...
    沈念sama閱讀 38,340評(píng)論 3 399
  • 文/蒼蘭香墨 我猛地睜開眼,長(zhǎng)吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼戏蔑!你這毒婦竟也來了蹋凝?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 36,973評(píng)論 0 259
  • 序言:老撾萬榮一對(duì)情侶失蹤辛臊,失蹤者是張志新(化名)和其女友劉穎仙粱,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體彻舰,經(jīng)...
    沈念sama閱讀 43,466評(píng)論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 35,937評(píng)論 2 323
  • 正文 我和宋清朗相戀三年候味,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了刃唤。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 38,039評(píng)論 1 333
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡白群,死狀恐怖尚胞,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情帜慢,我是刑警寧澤笼裳,帶...
    沈念sama閱讀 33,701評(píng)論 4 323
  • 正文 年R本政府宣布,位于F島的核電站粱玲,受9級(jí)特大地震影響躬柬,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜抽减,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,254評(píng)論 3 307
  • 文/蒙蒙 一允青、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧卵沉,春花似錦颠锉、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,259評(píng)論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至停撞,卻和暖如春瓷蛙,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 31,485評(píng)論 1 262
  • 我被黑心中介騙來泰國打工速挑, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留谤牡,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 45,497評(píng)論 2 354
  • 正文 我出身青樓姥宝,卻偏偏與公主長(zhǎng)得像翅萤,于是被迫代替她去往敵國和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子腊满,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,786評(píng)論 2 345

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