Effective Objective-C 2.0 總結(jié)與筆記(第二章)—— 對象构订、消息侮叮、運(yùn)行期

第二章:對象、消息悼瘾、運(yùn)行期

? “對象”就是“基本構(gòu)造單元”囊榜,開發(fā)者可以通過對象來存儲并傳遞數(shù)據(jù)。對象之間傳遞數(shù)據(jù)并執(zhí)行任務(wù)的過程就是“消息傳遞”亥宿。程序運(yùn)行起來后卸勺,為其提供相關(guān)支持的代碼就是“Objective-C運(yùn)行期環(huán)境”,它提供了一些使得對象之間能夠傳遞消息的重要函數(shù)烫扼,并且包括創(chuàng)建類實(shí)例所用的全部邏輯曙求。

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

  • ”屬性“ (property) 是Objective-C的一項(xiàng)特性,用于封裝對象中的數(shù)據(jù)映企。實(shí)例變量一般通過“存取方法”來訪問悟狱,其中g(shù)etter用于讀取變量值,setter用于寫入變量值卑吭。開發(fā)者可以令編譯器自動編寫與屬性有關(guān)的存取方法芽淡。
  • Objective-C語言很少將實(shí)例變量放在類接口的public區(qū)段內(nèi),更多的是使用@property的方式來聲明豆赏。這是因?yàn)槿绻苯邮褂胮ublic的區(qū)段,當(dāng)實(shí)例變量的偏移量改變(原有的實(shí)例變量里插入了新的實(shí)例變量)的時候富稻,需要重新編譯掷邦,假如代碼庫中某份代碼使用了舊的類定義,就會出現(xiàn)不兼容的情況椭赋。
//public區(qū)段內(nèi)聲明實(shí)例變量
@interface EOCPerson : NSObject {
@public
    NSData *_dataOfBirth;
    NSString *_firstName;
    NSString *_lastName;
}

//使用@property方式聲明
@interface EOCPerson : NSObject

@property(nonatomic, copy) NSString *firstName;
@property(nonatomic, copy) NSString *lastName;

@end
    
//@property方式等效于
@interface EOCPerson : NSObject

- (NSString *)firstName;
- (void)setFirstName;
- (NSString *)lastName;
- (void)setLastName;

@end

如果要訪問屬性抚岗,可以使用“點(diǎn)語法”,編譯器會把“點(diǎn)語法”轉(zhuǎn)換成對存取方法的調(diào)用哪怔。

EOCPerson *aPerson = [EOCPerson new];
aPerson.firstName = @"GDGD";//same as
[aPerson setFirstName:@"GDGD"];

NSString *lastNameOfGD = aPerson.lastName;//same as
NSString *lastNameOfGD = [aPerson lastName];

使用屬性的方式進(jìn)行聲明實(shí)例變量宣蔚,編譯器會自動生成存取方法向抢,雖然這個時候在編譯器上看不到生成的方法,這個方法默認(rèn)的名字是:getter—就是屬性的名字胚委,setter—屬性名字前面加set挟鸠。

如果想要修改“合成方法”的名字,可以使用@synthesize語法來指定關(guān)鍵字的名字亩冬。

//在實(shí)現(xiàn)文件中
@implementation EOCPerson
@synthesize firstName = _myFirstName;
@synthesize lastName = _myLastName;
@end

如果不想要編譯器自動生成的方法艘希,可以使用@dynamic語法來阻止編譯器的生成。

@implementation EOCPerson
@dynamic firstName, lastName;
@end
  • 屬性具有四種特質(zhì)硅急,不同的特質(zhì)也會影響屬性生成的存取方法覆享。

(1)、原子性:默認(rèn)情況下营袜,編譯器所合成的方法會通過鎖定機(jī)制確保其原子性 (atomicity) 撒顿。如果屬性具備nonatomic特質(zhì),則不使用同步鎖荚板。在iOS開發(fā)的程序里凤壁,由于同步鎖開銷較大,如果使用原子性的屬性會導(dǎo)致性能問題啸驯,所以屬性都是nonatomic的特質(zhì)客扎。

(2)、讀寫權(quán)限:

? readwrite (讀寫) :擁有前面說的獲取方法 (getter) 和設(shè)置方法 (setter)罚斗,默認(rèn)情況徙鱼。

? readonly (只讀) :僅擁有獲取方法≌胱耍可以對外暴露為只讀屬性袱吆,然后在“class-continuation分類”中重新定義為讀寫屬性。(27條有說)

(3)距淫、內(nèi)存管理語義:這是最難理解的部分了绞绒,雖然這個特質(zhì)僅會影響“設(shè)置方法”,但是其中涉及到內(nèi)存管理部分榕暇,所以還是非常重要蓬衡。

? assign:“設(shè)置方法”只會針對“純量類型”(scalar type,例如CGFloat彤枢,NSInteger等)的簡單賦值狰晚。一般在之前分配在棧里的數(shù)據(jù)類型就是使用assign修飾。

? strong:此特質(zhì)表示一種擁有關(guān)系缴啡,當(dāng)給這種屬性設(shè)置新值的時候壁晒,會先保留新值,再釋放舊值业栅,然后設(shè)置新值上去秒咐,一般Objective-C對象使用這個修飾詞谬晕。

? weak:表示非擁有關(guān)系,為這種屬性設(shè)置新值的時候携取,不保留新值攒钳,也不釋放舊值,當(dāng)屬性所指的對象被銷毀的時候歹茶,屬性值也會被清空夕玩。這就是一種弱持有,一般可以用來打破循環(huán)引用的情況(后面會介紹)惊豺。

? unsafe_unretained :語義和assign相同燎孟,但是適用于對象類型(object type),表示一種非擁有關(guān)系尸昧,但是當(dāng)目標(biāo)對象被銷毀的時候揩页,屬性值不會被清空。

? copy:和strong類似烹俗,但是設(shè)置方法并不保留新值爆侣,而是將其拷貝。一般NSString *就使用這個特質(zhì)來修飾幢妄。這是因?yàn)镹SString *有可能指向一個可變的NSMutableString 實(shí)例兔仰,如果使用的不是copy的話,那么當(dāng)可變字符串被篡改后蕉鸳,會影響到你不可變的字符串乎赴,所以要拷貝一個不可變的字符串。

(4)潮尝、方法名:可以用來指定存取方法的方法名榕吼。

? getter=<name>:指定獲取方法的方法名,如果某屬性是Boolean勉失,一般會用這種方式來在獲取方法名上加上is前綴羹蚣。

? setter=<name>:指定設(shè)置方法的方法名,較少使用乱凿。

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

  • 在對象之外訪問實(shí)例變量是通過屬性來實(shí)現(xiàn)顽素,但是當(dāng)在對象內(nèi)部訪問實(shí)例變量的話最好采用直接訪問的形式,而在設(shè)置實(shí)例變量的時候才通過屬性來做徒蟆。
//使用屬性訪問
- (NSString *)fullName {
    return [NSString stringWithFormat:@"%@ %@", self.firstName, self.lastName];
}

- (void)setFullName {
    NSArray *components = [[self fullName] componentsSeparatedByString:@" "];
    self.firstName = components[0];
    self.lastName = components[1];
}

//使用實(shí)例變量訪問
- (NSString *)fullName {
    return [NSString stringWithFormat:@"%@ %@", _firstName, _lastName];
}

- (void)setFullName {
    NSArray *components = [[self fullName] componentsSeparatedByString:@" "];
    _firstName = components[0];
    _lastName = components[1];
}
  • 使用實(shí)例變量和屬性訪問的區(qū)別如下:

    • 使用實(shí)例變量直接訪問由于不經(jīng)過方法派發(fā)戈抄,直接訪問保存實(shí)例變量的那塊內(nèi)存,所以速度更快后专。
    • 直接訪問實(shí)例變量可以繞過內(nèi)存管理語義。
    • 直接訪問實(shí)例變量不會出發(fā)KVO (Key-Value Observing)输莺。
    • 通過屬性可以幫助自己debug的時候排查與之相關(guān)的錯誤戚哎。

    知道以上的區(qū)別了之后裸诽,一般采用一種折中的方案,設(shè)置屬性使用“設(shè)置方法“型凳,讀取實(shí)例變量則直接訪問丈冬。

  • 在初始化的時候,如果需要設(shè)置實(shí)例變量甘畅,應(yīng)當(dāng)直接訪問埂蕊,因?yàn)樽宇惪赡軙备矊憽霸O(shè)置方法,導(dǎo)致調(diào)用到子類的設(shè)置方法的時候疏唾,出現(xiàn)意想不到的結(jié)果蓄氧。

  • 如果使用了懶加載的方式,就需要使用屬性的方式來訪問實(shí)例變量槐脏。

//lazy initialization
- (NSString *)firstName {
    if (!_firstName) {
        _firstName = @"GDGD";
    }
    return _firstName;
}

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

  • 由于==操作符比較出來的結(jié)果未必是我們想要的喉童,因?yàn)樵摬僮鞣莾蓚€指針本身,而不是指針?biāo)傅膶ο蠖偬臁K砸话闶褂肗SObject協(xié)議中聲明的”isEqual“方法來判斷兩個對象的等同性堂氯。如果是自定義的類,則需要重寫該方法以實(shí)現(xiàn)等同性判斷牌废。
    NSString *foo = @"hhh123";
    NSString *bar = [NSString stringWithFormat:@"hhh%i",123];
    Boolean equalA = (foo == bar); // NO
    Boolean equalB = [foo isEqual:bar];//YES
    Boolean equalC = [foo isEqualToString:bar];//YES
  • NSObject協(xié)議中判斷對象等同性主要是兩個方法:
- (BOOL)isEqual:(id)object;
- (NSUInteger)hash;

NSObject對這兩個方法的默認(rèn)實(shí)現(xiàn)是:指針值完全相等的時候咽白,兩個對象才相等。如果isEqual方法為Yes鸟缕,那么兩個對象的hash值相等晶框,反之不成立。理解這個意義是自定義實(shí)現(xiàn)isEqual:的關(guān)鍵叁扫。

設(shè)計hash方法的時候三妈,需要盡量減少對象的碰撞,防止出現(xiàn)運(yùn)算復(fù)雜度過大的情況莫绣。

  • 特定類所具有的等同性判定方法:由于Objective-C在編譯期沒有做強(qiáng)類型轉(zhuǎn)換畴蒲,為了增強(qiáng)代碼的魯棒性,應(yīng)該保證所傳對象的類型正確对室,且有異常處理邏輯模燥。
  • 等同性判定的執(zhí)行深度:如果是對數(shù)組進(jìn)行等同性判定,往往需要比較兩個數(shù)組的對應(yīng)位置的所有對象掩宜,這樣叫做“深度等同性判定”蔫骂,如果可以通過identifier來標(biāo)示等同性,就能大大節(jié)省計算牺汤。
  • 容器中可變類的等同性:在容器加入可變類對象的時候辽旋,把某個對象放入容器之后,就不應(yīng)該改變其hash值了。由于容器會根據(jù)其hash值放在不同的“箱子數(shù)組”里补胚,如果放入箱子之后hash值又改變了码耐,那么證明容器放錯了箱子,可能會出現(xiàn)隱患溶其。所以需要保證hash值不是根據(jù)對象的可變部分來實(shí)現(xiàn)的骚腥,或者是在其放入容器后就不再改變hash值。
    NSMutableSet *set = [NSMutableSet new];
    
    NSMutableArray *arrayA = [@[@1, @2] mutableCopy];
    [set addObject:arrayA];
    //set = {((1,2))}
    
    NSMutableArray *arrayB = [@[@1] mutableCopy];
    [set addObject:arrayB];
    //set = {((1),(1,2))}
    
    [arrayB addObject:@2];
    //set = {((1,2),(1,2))} 打破了set的語義
    
    NSSet *copySet = [set copy];
    //copySet = {((1,2))}

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

  • 類族模式可以把實(shí)現(xiàn)細(xì)節(jié)隱藏在一套簡單的公共接口后面。Objective-C的系統(tǒng)框架中普遍使用該模式,用戶無需自己創(chuàng)建子類實(shí)例谜叹,只需要調(diào)用基類方法來創(chuàng)建即可乾忱。
  • 一般采用工廠模式來創(chuàng)建類族。由于Objective-C這門語言沒辦法指明某個基類是”抽象“的,所以如果使用了類族模式需要寫上相應(yīng)的注釋。
  • Cocoa里的類族:大部分collection類都是類族,如果要子類化這些類族埠褪,需要遵守一些規(guī)則:
    • 子類應(yīng)到繼承自類族中的抽象基類:如果要編寫NSArray類族的子類,需要繼承自不可變數(shù)組的基類或者可變數(shù)組的基類挤庇。
    • 子類應(yīng)該定義自己的數(shù)據(jù)存儲方式钞速。
    • 子類應(yīng)當(dāng)覆寫超類文檔中指明需要覆寫的方法。

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

  • 有時候類的實(shí)例可能是由某種機(jī)制所創(chuàng)建的嫡秕,那么也就是無法創(chuàng)建出自己所寫的子類實(shí)例渴语,那么也就是如果你需要存放對象到類中,就不能通過創(chuàng)建子類的方式實(shí)現(xiàn)了昆咽。而Objective-C的“關(guān)聯(lián)對象” (associated object) 可以解決這個問題驾凶。

    • 可以給某個對象關(guān)聯(lián)多個對象,通過“鍵”來區(qū)分掷酗,同時在創(chuàng)建關(guān)聯(lián)對象的時候调违,也有相應(yīng)的“內(nèi)存管理語義”,由名為objc_AssociationPolicy的枚舉所定義泻轰。

      關(guān)聯(lián)類型 等效的@property屬性
      OBJC_ASSOCIATION_ASSIGN assign
      OBJC_ASSOCIATION_COPY_NONATOMIC nonatomic技肩、copy
      OBJC_ASSOCIATION_RETAIN_NONATOMIC nonatomic、retain
      OBJC_ASSOCIATION_COPY copy
      OBJC_ASSOCIATION_RETAIN retain
  • 管理關(guān)聯(lián)對象的方法:

//此方法以給定的鍵和策略為某對象設(shè)置關(guān)聯(lián)對象
- void objc_setAssociatedObject(id object, const void *key, id value, objc_AssociationPolicy policy);

//此方法根據(jù)給定的鍵從某對象中獲取相應(yīng)的關(guān)聯(lián)對象值
id objc_getAssociatedObject(id object, const void *key);

//此方法移除指定對象的全部關(guān)聯(lián)對象
void objc_removeAssociatedObjects(id object);

可以把對象想象成NSDictionary浮声,把關(guān)聯(lián)到該對象的值理解為字典的條目虚婿,本質(zhì)的區(qū)別在于設(shè)置關(guān)聯(lián)對象的key是個不透明的指針,在NSDictionary里如果兩個鍵值相等那么isEqual:方法的返回值就是YES泳挥,但是關(guān)聯(lián)對象必須是兩個指針相同才行然痊,在設(shè)置關(guān)聯(lián)對象的時候通常使用靜態(tài)全局變量做鍵。

第11條:理解objc_msgSend的作用

  • Objective-C的方法調(diào)用方式是消息結(jié)構(gòu)屉符,這種傳遞消息需要有“名稱”或“選擇子”剧浸,可以接受參數(shù)锹引,而且可能還有返回值。在Objective-C中辛蚊,如果向某對象傳遞消息粤蝎,那么就會使用動態(tài)綁定機(jī)制來決定需要調(diào)用的方法,當(dāng)對象收到消息之后袋马,究竟該調(diào)用那個方法完全由運(yùn)行期決定,同時可以在程序運(yùn)行時改變秸应。
  • 給對象發(fā)送消息的例子和過程如下:
/**
someObject —— 接受者
messageName —— 選擇子
parameter —— 參數(shù)
**/
id returnValue = [someObject messageName:parameter];

//編譯器看到這個消息之后會轉(zhuǎn)換成一條標(biāo)準(zhǔn)的C語言函數(shù)調(diào)用虑凛,調(diào)用的是消息中心的核心函數(shù),objc_msgSend
void objc_msgSend(id self, SEL cmd, ...);

//所以上面那個函數(shù)調(diào)用經(jīng)過編譯器會轉(zhuǎn)換成如下函數(shù)-->
id returnValue = objc_msgSend(someObject,
                              @selector(messageName:),
                              parameter);

//obj_msgSend函數(shù)會依據(jù)接受者和選擇子的類型來調(diào)用適當(dāng)?shù)姆椒ㄈ硖洌瑸榱送瓿纱瞬僮鞯姆椒ㄐ枰诮邮苷咚鶎兕愔兴褜し椒斜砩5绻芯吞狡鋱?zhí)行的代碼,如果沒找到就沿著體系繼續(xù)往上查找祸挪,找到合適的方法再跳轉(zhuǎn)锣披。如果還沒有,后面就會涉及到消息轉(zhuǎn)發(fā)機(jī)制贿条。
//obj_msgSend會將匹配結(jié)果換存在”快速映射表“里雹仿,這樣每個類其實(shí)都有這樣一塊緩存,所以執(zhí)行起來很快整以,但是還是不如”靜態(tài)綁定的函數(shù)調(diào)用操作“那樣迅速胧辽。
  • 除了上述的部分消息的調(diào)用過程,還有一些”邊界情況“公黑,則需要交由Objective-C運(yùn)行環(huán)境中的一些函數(shù)來處理:
    • objc_msgSend_stret:如果待發(fā)送的消息要返回結(jié)構(gòu)體邑商,那么可以交由此函數(shù)處理。需要CPU的寄存器能夠容納這個消息返回的結(jié)構(gòu)體凡蚜,如果無法容納就會由另一個函數(shù)進(jìn)行派發(fā)人断,那個函數(shù)會通過分配在棧上的某個變量來處理返回的結(jié)構(gòu)體。
    • objc_msgSend_fpret:如果消息返回的是浮點(diǎn)數(shù)朝蜘,那么需要交由此函數(shù)處理恶迈。這個函數(shù)是為了處理x86等架構(gòu)CPU中某些奇怪狀況。
    • objc_msgSendSuper:如果要給超類發(fā)消息芹务,就給這個函數(shù)處理蝉绷。
  • 如果某函數(shù)的最后一項(xiàng)操作是調(diào)用另外一個函數(shù),就可以使用“尾調(diào)用優(yōu)化”技術(shù)枣抱。編譯器會生成跳轉(zhuǎn)到另一函數(shù)所需要的指令碼熔吗,而不用向調(diào)用堆棧里推入新的”棧幀“。當(dāng)某函數(shù)的最后一個操作僅僅是調(diào)用其他函數(shù)而不會將其返回值另作他用的時候佳晶,才能執(zhí)行”尾調(diào)用優(yōu)化“桅狠。

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

  • 在編譯期向類發(fā)送了其無法解讀的消息并不會報錯,因?yàn)檫\(yùn)行期可以向類中添加方法,所以編譯器在編譯時無法確定類中到底會不會有某個方法實(shí)現(xiàn)中跌。當(dāng)對象接收到無法解讀的消息后咨堤,就會啟動”消息轉(zhuǎn)發(fā)“機(jī)制。

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

    • 第一階段先征詢接受者所屬的類漩符,看其是否能動態(tài)添加方法一喘,以處理當(dāng)前這個”未知的選擇子“,這部分叫”動態(tài)方法解析“嗜暴。注意這是一個類方法凸克,因?yàn)槭窍蚪邮照咚鶎俚念愡M(jìn)行請求。
    • 第二階段涉及”完整的消息轉(zhuǎn)發(fā)機(jī)制“闷沥,這里細(xì)分為兩小步:
      • 當(dāng)對象所屬類不能動態(tài)添加方法后萎战,runtime就會詢問當(dāng)前的接受者是否有其他對象可以處理這個未知的selector
      • 當(dāng)沒有備援接收者時舆逃,就只剩下最后一次機(jī)會蚂维,那就是消息重定向。這個時候runtime會將未知消息的所有細(xì)節(jié)都封裝為NSInvocation對象路狮,給接受者最后一次機(jī)會虫啥,令其設(shè)法解決當(dāng)前還未處理的這條消息。
  • 動態(tài)方法解析:

    • 對象收到無法解讀的消息后览祖,首先調(diào)用其所屬類的類方法:
    + (BOOL)resolveInstanceMethod:(SEL)sel;
    

    這種方法的前提是:相關(guān)方法的實(shí)現(xiàn)代碼已經(jīng)寫好孝鹊,只等著運(yùn)行的時候動態(tài)插在類里面就可以了。此方案常用來實(shí)現(xiàn)@dynamic屬性展蒂。

    Example:

    //假設(shè)這兩個方法已經(jīng)實(shí)現(xiàn)
    id autoDictionaryGetter(id self,SEL _cmd);
    void autoDictionarySetter(id self, SEL _cmd, id value);
    
    //使用這個類方法進(jìn)行消息轉(zhuǎn)發(fā)
    + (BOOL)resolveInstanceMethod:(SEL)sel {
        NSString *selectorString = NSStringFromSelector(sel);
        if (/* selector is from a @dynamic property */) {
            if ([selectorString hasPrefix:@"set"]) {
                class_addMethod(self,
                                sel,
                                (IMP)autoDictionarySetter,
                                "v@:@");
            }
            else {
                class_addMethod(self,
                                sel,
                                (IMP)autoDictionaryGetter,
                                "@@:");
            }
            return YES;
        }
        return [super resolveInstanceMethod:sel];
    }
    
  • 備援接收者:

    • 當(dāng)前接受者還有第二次機(jī)會能處理未知的選擇子又活,在這一步中,系統(tǒng)會問該選擇子能不能轉(zhuǎn)發(fā)給其他接受者锰悼。相關(guān)的方法為:
    - (id)forwardingTargetForSelector:(SEL)aSelector;
    

    雖然Objective-C不支持多重繼承柳骄,但是通過這個函數(shù)的組合我們可以模擬出多次繼承的某些特性。

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

    • 首先創(chuàng)建NSInvocation對象箕般,把尚未處理的那條消息的所有細(xì)節(jié)都封在其中耐薯,包括選擇子、目標(biāo)和參數(shù)丝里,這個步驟會調(diào)用下列方法來轉(zhuǎn)發(fā)消息:
    - (void)forwardInvocation:(NSInvocation *)invocation;
    

    實(shí)現(xiàn)此方法的時候曲初,如果發(fā)現(xiàn)某調(diào)用操作不應(yīng)由本類處理,則需調(diào)用超類的同名方法杯聚,這樣繼承體系中的每個類都有機(jī)會處理此調(diào)用請求臼婆,直到NSObject。

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

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

  • 與給定的選擇子名稱相對應(yīng)的方法也可以在運(yùn)行期改變幌绍,不需要知道源代碼颁褂,也不需要通過繼承子類來覆寫方法就能夠改變這個類本身的功能故响,新功能會在本類的所有實(shí)例中生效,而不僅限于覆寫了相關(guān)方法的子類實(shí)例颁独,這個方案成為“方法調(diào)配” (method swizzling)彩届。方法以函數(shù)指針的形式來表示,為IMP指針誓酒,原型如下:

    id (*IMP)(id, SEL, ...);
    
  • 為了互換2個已經(jīng)寫好的方法實(shí)現(xiàn)樟蠕,可以使用下列函數(shù):

//此函數(shù)的兩個參數(shù)表示待交換的兩個方法實(shí)現(xiàn)
void method_exchangeImplementations(Method m1, Method m2);

//方法實(shí)現(xiàn)可以通過下列函數(shù)獲得。
Method class_getInstanceMethod(Class cls, SEL name);

//example
//將uppercaseString和lowercaseString兩個方法利用方法調(diào)配進(jìn)行調(diào)換
Method originalMethod = class_getClassMethod([NSString class], @selector(lowercaseString));
    Method swappedMethod = class_getClassMethod([NSString class], @selector(uppercaseString));
    method_exchangeImplementations(originalMethod, swappedMethod);

除了上面說的兩個系統(tǒng)方法的替換丰捷,還可以使用自定義的方法和系統(tǒng)方法進(jìn)行替換坯墨,這樣的話就能夠?yàn)槟切┫到y(tǒng)的黑盒方法增加日志記錄功能,這個非常有助于程序調(diào)試病往。很少人會在調(diào)試程序之外的場合使用上述方法來永久改變某個類的功能

第14條:理解“類對象”的用意

  • Objective-C有個特殊的類型叫id骄瓣,他能指代任意類型的Objective-C對象類型停巷。編譯器假定它能響應(yīng)所有消息。id類型本身的定義如下:
/// Represents an instance of a class.
struct objc_object {
    Class _Nonnull isa  OBJC_ISA_AVAILABILITY;
};

/// A pointer to an instance of a class.
typedef struct objc_object *id;

每個對象結(jié)構(gòu)體的首個成員是Class類的變量榕栏,該變量定義了對象所屬的類畔勤,稱為"is a"指針。

Class的定義如下:

struct objc_class {
    Class _Nonnull isa  OBJC_ISA_AVAILABILITY;

#if !__OBJC2__
    Class _Nullable super_class                              OBJC2_UNAVAILABLE;
    const char * _Nonnull name                               OBJC2_UNAVAILABLE;
    long version                                             OBJC2_UNAVAILABLE;
    long info                                                OBJC2_UNAVAILABLE;
    long instance_size                                       OBJC2_UNAVAILABLE;
    struct objc_ivar_list * _Nullable ivars                  OBJC2_UNAVAILABLE;
    struct objc_method_list * _Nullable * _Nullable methodLists                    OBJC2_UNAVAILABLE;
    struct objc_cache * _Nonnull cache                       OBJC2_UNAVAILABLE;
    struct objc_protocol_list * _Nullable protocols          OBJC2_UNAVAILABLE;
#endif

} OBJC2_UNAVAILABLE;

此類結(jié)構(gòu)體存放類的元數(shù)據(jù)扒磁,例如類的實(shí)例實(shí)現(xiàn)了幾個方法庆揪,具備多少個實(shí)例變量等信息,首個變量也是isa指針妨托,說明Class本身也是Objective-C對象缸榛。結(jié)構(gòu)體有個叫做super_class的變量,是本類的超類兰伤。類對象所屬的類型是另一個類内颗,叫做“元類”,用來表示類對象本身所具備的元數(shù)據(jù)敦腔。每個類僅有一個類對象均澳,每個類對象僅有一個與之相關(guān)的元類。

example:

假設(shè)有個名為someClass的子類從NSObject繼承而來符衔,則既成體系如下所示:


super_class指針確立了繼承關(guān)系找前,而isa指針描述了實(shí)例所屬的類。

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

    • isMemberOfClass:能夠判斷出對象是否為某個特定類的實(shí)例判族。
    • isKindOfClass:能夠判斷出對象是否為某類或者其派生類的實(shí)例躺盛。
    NSMutableDictionary *dict = [NSMutableDictionary new];
    [dict isMemberOfClass:[NSDictionary class]];// NO
    [dict isMemberOfClass:[NSMutableDictionary class]];// YES
    [dict isKindOfClass:[NSDictionary class]];// YES
    [dict isKindOfClass:[NSArray class]];// NO
    

    如果要比較類對象是否等同的話需要使用==操作符,而不是isEqual:五嫂,因?yàn)轭悓ο笫莻€單例颗品,在應(yīng)用程序范圍內(nèi)肯尺,每個類的Class僅有一個實(shí)例。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末躯枢,一起剝皮案震驚了整個濱河市则吟,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌锄蹂,老刑警劉巖氓仲,帶你破解...
    沈念sama閱讀 219,188評論 6 508
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異得糜,居然都是意外死亡敬扛,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,464評論 3 395
  • 文/潘曉璐 我一進(jìn)店門朝抖,熙熙樓的掌柜王于貴愁眉苦臉地迎上來啥箭,“玉大人,你說我怎么就攤上這事治宣〖苯模” “怎么了?”我有些...
    開封第一講書人閱讀 165,562評論 0 356
  • 文/不壞的土叔 我叫張陵侮邀,是天一觀的道長坏怪。 經(jīng)常有香客問我,道長绊茧,這世上最難降的妖魔是什么铝宵? 我笑而不...
    開封第一講書人閱讀 58,893評論 1 295
  • 正文 為了忘掉前任,我火速辦了婚禮华畏,結(jié)果婚禮上鹏秋,老公的妹妹穿的比我還像新娘。我一直安慰自己唯绍,他們只是感情好拼岳,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,917評論 6 392
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著况芒,像睡著了一般惜纸。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上绝骚,一...
    開封第一講書人閱讀 51,708評論 1 305
  • 那天耐版,我揣著相機(jī)與錄音,去河邊找鬼压汪。 笑死粪牲,一個胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的止剖。 我是一名探鬼主播腺阳,決...
    沈念sama閱讀 40,430評論 3 420
  • 文/蒼蘭香墨 我猛地睜開眼落君,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了亭引?” 一聲冷哼從身側(cè)響起绎速,我...
    開封第一講書人閱讀 39,342評論 0 276
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎焙蚓,沒想到半個月后纹冤,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,801評論 1 317
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡购公,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,976評論 3 337
  • 正文 我和宋清朗相戀三年萌京,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片宏浩。...
    茶點(diǎn)故事閱讀 40,115評論 1 351
  • 序言:一個原本活蹦亂跳的男人離奇死亡知残,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出比庄,到底是詐尸還是另有隱情橡庞,我是刑警寧澤,帶...
    沈念sama閱讀 35,804評論 5 346
  • 正文 年R本政府宣布印蔗,位于F島的核電站,受9級特大地震影響丑勤,放射性物質(zhì)發(fā)生泄漏华嘹。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,458評論 3 331
  • 文/蒙蒙 一法竞、第九天 我趴在偏房一處隱蔽的房頂上張望耙厚。 院中可真熱鬧,春花似錦岔霸、人聲如沸薛躬。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,008評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽型宝。三九已至,卻和暖如春絮爷,著一層夾襖步出監(jiān)牢的瞬間趴酣,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 33,135評論 1 272
  • 我被黑心中介騙來泰國打工坑夯, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留岖寞,地道東北人。 一個月前我還...
    沈念sama閱讀 48,365評論 3 373
  • 正文 我出身青樓柜蜈,卻偏偏與公主長得像仗谆,于是被迫代替她去往敵國和親指巡。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,055評論 2 355

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