iOS 類命锄、元類、Block (基于 OC 2.0)

講述 objective-c 2.0 對(duì) class 的定義偏化,類脐恩、元類的關(guān)系,一些面試題侦讨,并且對(duì) block 簡(jiǎn)單說(shuō)明驶冒。

網(wǎng)上很多文章對(duì) class 的定義還是 OC 1.0 的,因此自己寫(xiě)一篇記錄一下韵卤。

iOS 運(yùn)行時(shí)代碼 objc4 的下載地址:
https://opensource.apple.com/tarballs/objc4/

打開(kāi)源代碼骗污,Xcode 同時(shí)按 cmd + shift + o,輸入 Class 可以搜到相關(guān)定義沈条。


1需忿、簡(jiǎn)化定義

// Class
typedef struct objc_class *Class;
// id
typedef struct objc_object *id;

// 類
struct objc_class : objc_object {
    // 省略部分成員變量以及方法...
}

// 對(duì)象
struct objc_object {
    // 省略部分成員變量以及方法...
}

// Object
@interface Object {
    Class isa;
}
@end

// NSObject
@interface NSObject <NSObject> {
    Class isa;
}

可見(jiàn),類和對(duì)象是結(jié)構(gòu)體。

類也是對(duì)象屋厘,因?yàn)閷?duì)象和類都是結(jié)構(gòu)體 objc_object涕烧。


2、詳細(xì)定義

2.1 對(duì)象

結(jié)構(gòu)體 objc_object:

struct objc_object {
private:
    isa_t isa;
    // 省略部分成員變量以及方法...
}

聯(lián)合體 isa_t:

union isa_t {
    Class cls;
    uintptr_t bits;
    // 省略部分成員變量以及方法...
};

對(duì)象有個(gè) isa_t 汗洒,isa_t 有個(gè) Class 指向?qū)ο蟮念悺?br> 類也是對(duì)象议纯,類的 isa_t 的 Class 指向元類。
元類保存類方法仲翎,類保存實(shí)例方法痹扇。

2.2 類

結(jié)構(gòu)體 objc_class:

struct objc_class : objc_object {
    Class superclass;
    cache_t cache;             // formerly cache pointer and vtable
    class_data_bits_t bits;    // class_rw_t * plus custom rr/alloc flags

    // class_rw_t 有類的方法和屬性列表。
    class_rw_t *data() { 
        return bits.data();
    }
    // 省略部分成員變量以及方法...
}

superclass 是父類溯香,
cache_t 是方法緩存鲫构,
class_data_bits_t 包含 class_rw_t,
class_rw_t 里面有類的方法和屬性列表玫坛。

結(jié)構(gòu)體 class_rw_t:

struct class_rw_t {
    method_array_t methods;
    property_array_t properties;
    protocol_array_t protocols;

    const class_ro_t *ro;
    Class firstSubclass;
    Class nextSiblingClass;

    char *demangledName;
    // 省略部分成員變量以及方法...
};

method_array_t 是個(gè)類结笨,保存方法列表,
property_array_t 保存屬性列表湿镀,
protocol_array_t 保存協(xié)議列表炕吸。

objc_object 可以簡(jiǎn)單理解為,有個(gè) isa 指向?qū)ο蟮念悾?br> objc_class 比 objc_object 多了方法列表勉痴、屬性列表赫模。
然后類、元類也是對(duì)象蒸矛。


3瀑罗、對(duì)象、類雏掠、元類的關(guān)系

類斩祭、元類,也是對(duì)象乡话,他們的關(guān)系如下圖所示:


對(duì)象摧玫、類、元類關(guān)系圖

Root class 是 NSObject绑青。

  1. 對(duì)于 instance诬像,沒(méi)有 superclass 線,isa 都指向自己的類闸婴。
  2. 對(duì)于 class颅停,superclass 指向父類,isa 指向元類掠拳。
  3. 對(duì)于 meta癞揉,superclass 指向父類,isa 指向同一個(gè)元類,即 NSObject meta class喊熟。
  4. NSObject 沒(méi)有父類柏肪,所以 superclass 指向 nil。
  5. NSObject meta class 的 isa 指向 NSObject 類芥牌,形成閉環(huán)烦味。

為什么所有元類的 isa 都指向 NSObject meta class?
為什么 NSObject meta class 的 superclass 要指向 NSObject壁拉?
有沒(méi)有大神解答一下??谬俄。

查找對(duì)象方法時(shí),根據(jù)對(duì)象的 isa 找到對(duì)象的類弃理,如果子類沒(méi)有找到方法溃论,就通過(guò) superclass 找到父類,在父類查找方法痘昌。

查找類方法時(shí)钥勋,根據(jù)類的 isa 找到元類,如果子元類沒(méi)有找到辆苔,就通過(guò) superclass 找到父元類算灸,在父元類查找方法。

如果根元類驻啤,即 NSObject meta class 也沒(méi)找到菲驴,就會(huì)去 superclass,也就是 NSObject 類查找骑冗。

如果想深入了解赊瞬,可以看這篇文章:《神經(jīng)病院Objective-C Runtime入院第一天——isa和Class》。


4沐旨、一些題目

4.1

類調(diào)用實(shí)例方法森逮,是否會(huì)崩潰榨婆?

// 分類
@interface NSObject (Test)
+ (void)ioo;
@end

@implementation NSObject (Test)
- (void)ioo {
    NSLog(@"%@", NSStringFromSelector(_cmd));
}
@end

測(cè)試用例:

- (void)test1 {
    [NSObject ioo];     // 1磁携、
    [NSString ioo];     // 2、
}

1 和 2良风,哪個(gè)會(huì)崩潰谊迄?

答案是都不會(huì)崩潰。

對(duì)于 [NSObject ioo] 烟央,先在元類 NSObject meta class 里面找不到 ioo 方法统诺,然后在元類的父類 NSObject class 里找到了。元類 NSObject meta class 的 superclass 指向類 NSObject class 疑俭。

對(duì)于 [NSString ioo]粮呢,先在 NSString meta class 里面找不到,然后在 superclass 指向的 NSObject meta class 里也找不到,最后在 NSObject meta class 的 superclass 指向的 NSObject class 里找到了啄寡。


4.2

下面的代碼是否會(huì)崩潰

// 分類
@interface NSString (Test)
+ (void)sioo;
@end

@implementation NSString (Test)
- (void)sioo {
    NSLog(@"%@", NSStringFromSelector(_cmd));
}
@end


- (void)test2 {
    [NSString ioo];    // 1豪硅、
    [NSString sioo];    // 2、
}

1 不會(huì)崩潰挺物,2 會(huì)崩潰懒浮。

對(duì)于 [NSString sioo],尋找的過(guò)程是 NSString meta class识藤、NSObject meta class砚著、NSObject,而方法 sioo 是定義在 NSString 里面痴昧,所以崩潰了稽穆。

另外,假設(shè)只聲明但沒(méi)有實(shí)現(xiàn)方法 - (void)fun剪个,
對(duì)于 [[NSObject new] fun]秧骑,在 NSObject 里找不到,而 NSObject 的父類是 nil扣囊,因此崩潰了乎折。


4.3

下面代碼輸出什么

- (void)test3 {
    BOOL b1 = [(id)[NSObject class] isKindOfClass:[NSObject class]];
    BOOL b2 = [(id)[NSObject class] isMemberOfClass:[NSObject class]];
    BOOL b3 = [(id)[Son class] isKindOfClass:[Son class]];
    BOOL b4 = [(id)[Son class] isMemberOfClass:[Son class]];
    NSLog(@"%d %d %d %d", b1, b2, b3, b4);

    BOOL b5 = [[Son new] isKindOfClass:[Son class]]; 
    BOOL b6 = [[Son new] isMemberOfClass:[Son class]]; 
    NSLog(@"%d %d", b5, b6); 
    
    BOOL b7 = class_isMetaClass([Son class]); // 要 #import <objc/runtime.h>
    BOOL b8 = [Son new].class == [Son class]; 
    BOOL b9 = class_isMetaClass([[Son class] class]);
    NSLog(@"%d %d %d", b7, b8, b9);
}

b1-4 輸出 1 0 0 0,
b5-6 輸出 1 1侵歇,
b7-9 輸出 0 1 0骂澄。

先看 class 方法:

+ (Class)class {
    return self;
}

- (Class)class {
    return object_getClass(self);
}

Class object_getClass(id obj)
{
    if (obj) return obj->getIsa();
    else return Nil;
}

方法 object_getClass 是獲取對(duì)象的 isa 指向的 class。
類和對(duì)象的 class 方法都返回類惕虑,不是元類坟冲。
類的 class 方法返回 self,對(duì)象的返回所屬類溃蔫。

舉個(gè)栗子:

po [Son class]
po [[[[Son class] class] class] class]

上面都是輸出 Son健提,不管調(diào)用多少次 class,返回的都是 self

然后看下 isMemberOfClass 方法:

+ (BOOL)isMemberOfClass:(Class)cls {
    return object_getClass((id)self) == cls;
}

- (BOOL)isMemberOfClass:(Class)cls {
    return [self class] == cls;
}

- (Class)class {
    return object_getClass(self);
}

Class object_getClass(id obj)
{
    if (obj) return obj->getIsa();
    else return Nil;
}

方法 object_getClass 是獲取對(duì)象的 isa 指向的 class伟叛,如果傳入的參數(shù)是類對(duì)象私痹,獲取的就是元類了。
方法 isMemberOfClass 會(huì)取一次 isa 指向的類统刮,然后進(jìn)行比較紊遵。
如果是對(duì)象調(diào)用 isMemberOfClass,就是類與類比較侥蒙。
如果是類調(diào)用 isMemberOfClass暗膜,就是元類與類比較。

舉個(gè)栗子:

[(id)[A class] isMemberOfClass:[B class]];

上面代碼是 A meta class鞭衩,與 B class 進(jìn)行比較学搜,
而不是 A class 與 B class 比較娃善。

最后看下 isKindOfClass :

+ (BOOL)isKindOfClass:(Class)cls {
    for (Class tcls = object_getClass((id)self); tcls; tcls = tcls->superclass) {
        if (tcls == cls) return YES;
    }
    return NO;
}

- (BOOL)isKindOfClass:(Class)cls {
    for (Class tcls = [self class]; tcls; tcls = tcls->superclass) {
        if (tcls == cls) return YES;
    }
    return NO;
}

- (Class)class {
    return object_getClass(self);
}

Class object_getClass(id obj)
{
    if (obj) return obj->getIsa();
    else return Nil;
}

可見(jiàn) isKindOfClass 就是 isMemberOfClass 的循環(huán)版,會(huì)通過(guò) superclass 沿著繼承鏈進(jìn)行判斷瑞佩。
如果是對(duì)象調(diào)用 isMemberOfClass会放,會(huì)沿著 class 繼承鏈判斷。
如果是類調(diào)用 isMemberOfClass钉凌,會(huì)沿著 meta class 繼承鏈判斷咧最。
需要注意的是,NSObject meta class 的 superclass 是 NSObject class御雕,isa 指向自己矢沿。
NSObject class 的 superclass 是 nil,isa 指向 NSObject meta class酸纲。

對(duì)于
BOOL b1 = [(id)[NSObject class] isKindOfClass:[NSObject class]]捣鲸,
先是 NSObject meta class 與 NSObject class 比較,
然后是 NSObject class 與 NSObject class 比較闽坡,所以結(jié)果是 1栽惶。

總結(jié)一下:

- (void)test3 {
    // 類和對(duì)象的 class 方法會(huì)返回類,不是元類疾嗅。
    // 類的 class 方法返回 self外厂,對(duì)象的返回所屬類。
    // isMemberOfClass 會(huì)取 isa 指向的類代承,與參數(shù)的類就行比較
    // 如果是對(duì)象調(diào)用 isMemberOfClass汁蝶,就是類與類比較
    // 如果是類調(diào)用 isMemberOfClass,就是元類與類比較
    // isKindOfClass 會(huì)通過(guò) superclass 沿著繼承鏈循環(huán)判斷
    
    BOOL b1 = [(id)[NSObject class] isKindOfClass:[NSObject class]]; // 1论悴,循環(huán)到 NSObject == NSObject
    BOOL b2 = [(id)[NSObject class] isMemberOfClass:[NSObject class]]; // 0掖棉,NSObject meta != NSObject
    BOOL b3 = [(id)[Son class] isKindOfClass:[Son class]]; // 0,從 Son meta class 循環(huán)到 NSObject 到 nil膀估,都 != Son
    BOOL b4 = [(id)[Son class] isMemberOfClass:[Son class]]; // 0幔亥,Son meta != Son
    b3 = [Son isKindOfClass:[Son class]]; // 0,和上面的兩句是一樣的
    b4 = [Son isMemberOfClass:[Son class]]; // 0察纯,Son meta != Son
    NSLog(@"%d %d %d %d", b1, b2, b3, b4);// 1 0 0 0
    
    BOOL b5 = [[Son new] isKindOfClass:[Son class]]; // 1
    BOOL b6 = [[Son new] isMemberOfClass:[Son class]]; // 1帕棉,Son == Son
    NSLog(@"%d %d", b5, b6); // 1 1
    
    
    BOOL b7 = class_isMetaClass([Son class]); // 0,要 #import <objc/runtime.h>
    BOOL b8 = [Son new].class == [Son class]; // 1捐寥,Son == Son
    BOOL b9 = class_isMetaClass([[Son class] class]); // 0笤昨,[[Son class] class] == Son
    NSLog(@"%d %d %d", b7, b8, b9);// 0 1 0
}



5祖驱、Block

最后簡(jiǎn)單說(shuō)一下 block握恳。block 也是對(duì)象。

定義一個(gè)繼承自 NSObject 的 Test 類捺僻,有個(gè) test 方法:

@implementation Test

- (void)test {
    void (^blc)(void) = ^ {
        printf("哈哈哈");
    };
    
    blc();
}

@end

代碼轉(zhuǎn)換后乡洼, block 語(yǔ)法轉(zhuǎn)換的函數(shù):

// block 的函數(shù)崇裁,參數(shù)是一個(gè) block 指針
static void __Test__test_block_func_0(struct __Test__test_block_impl_0 *__cself) {
    printf("哈哈哈");
}

block 轉(zhuǎn)換成結(jié)構(gòu)體:

// 自定義 block 的結(jié)構(gòu)體
struct __Test__test_block_impl_0 {
    struct __block_impl impl; // block 的基本定義
    struct __Test__test_block_desc_0* Desc; // block 的數(shù)據(jù)
    
    // 構(gòu)造函數(shù)。
    // fp 是 block 的函數(shù)的指針束昵,desc 是 block 的數(shù)據(jù)拔稳。
    __Test__test_block_impl_0(void *fp, struct __Test__test_block_desc_0 *desc, int flags=0) {
        // 省略部分代碼
    }
};

再看 block 的基本定義:

// block 的基本定義
struct __block_impl {
    void *isa; // block 的類
    int Flags;
    int Reserved;
    void *FuncPtr; // 指向 block 的函數(shù)
};

里面有個(gè) isa 指針,指向 block 的類锹雏,可以是堆巴比、棧、全局 block礁遵。

打印輸出 block 的繼承鏈:

(lldb) po [blc class]
__NSGlobalBlock__

(lldb) po [blc superclass]
__NSGlobalBlock

(lldb) po [[blc superclass] superclass]
NSBlock

(lldb) po [[[blc superclass] superclass] superclass]
NSObject

(lldb) po [[[[blc superclass] superclass] superclass] superclass]
nil

具體可以看我的另一篇文章 筆記-《Objective-C高級(jí)編程 iOS與OS X多線程和內(nèi)存管理》轻绞,在 2.3 章節(jié)記錄 Blocks 的實(shí)現(xiàn)。


如有錯(cuò)誤佣耐,歡迎指正政勃。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市兼砖,隨后出現(xiàn)的幾起案子奸远,更是在濱河造成了極大的恐慌,老刑警劉巖讽挟,帶你破解...
    沈念sama閱讀 216,651評(píng)論 6 501
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件懒叛,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡耽梅,警方通過(guò)查閱死者的電腦和手機(jī)芍瑞,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,468評(píng)論 3 392
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)褐墅,“玉大人拆檬,你說(shuō)我怎么就攤上這事⊥椎剩” “怎么了竟贯?”我有些...
    開(kāi)封第一講書(shū)人閱讀 162,931評(píng)論 0 353
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)逝钥。 經(jīng)常有香客問(wèn)我屑那,道長(zhǎng),這世上最難降的妖魔是什么艘款? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 58,218評(píng)論 1 292
  • 正文 為了忘掉前任持际,我火速辦了婚禮,結(jié)果婚禮上哗咆,老公的妹妹穿的比我還像新娘蜘欲。我一直安慰自己,他們只是感情好晌柬,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,234評(píng)論 6 388
  • 文/花漫 我一把揭開(kāi)白布姥份。 她就那樣靜靜地躺著郭脂,像睡著了一般。 火紅的嫁衣襯著肌膚如雪澈歉。 梳的紋絲不亂的頭發(fā)上展鸡,一...
    開(kāi)封第一講書(shū)人閱讀 51,198評(píng)論 1 299
  • 那天,我揣著相機(jī)與錄音埃难,去河邊找鬼莹弊。 笑死,一個(gè)胖子當(dāng)著我的面吹牛涡尘,可吹牛的內(nèi)容都是我干的箱硕。 我是一名探鬼主播,決...
    沈念sama閱讀 40,084評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼悟衩,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼剧罩!你這毒婦竟也來(lái)了?” 一聲冷哼從身側(cè)響起座泳,我...
    開(kāi)封第一講書(shū)人閱讀 38,926評(píng)論 0 274
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤惠昔,失蹤者是張志新(化名)和其女友劉穎,沒(méi)想到半個(gè)月后挑势,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體镇防,經(jīng)...
    沈念sama閱讀 45,341評(píng)論 1 311
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,563評(píng)論 2 333
  • 正文 我和宋清朗相戀三年潮饱,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了来氧。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 39,731評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡香拉,死狀恐怖啦扬,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情凫碌,我是刑警寧澤扑毡,帶...
    沈念sama閱讀 35,430評(píng)論 5 343
  • 正文 年R本政府宣布,位于F島的核電站盛险,受9級(jí)特大地震影響瞄摊,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜苦掘,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,036評(píng)論 3 326
  • 文/蒙蒙 一换帜、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧鹤啡,春花似錦惯驼、人聲如沸。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 31,676評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)。三九已至泣矛,卻和暖如春疲眷,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背您朽。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 32,829評(píng)論 1 269
  • 我被黑心中介騙來(lái)泰國(guó)打工狂丝, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人哗总。 一個(gè)月前我還...
    沈念sama閱讀 47,743評(píng)論 2 368
  • 正文 我出身青樓几颜,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親讯屈。 傳聞我的和親對(duì)象是個(gè)殘疾皇子蛋哭,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,629評(píng)論 2 354

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

  • Objective-C語(yǔ)言是一門動(dòng)態(tài)語(yǔ)言,它將很多靜態(tài)語(yǔ)言在編譯和鏈接時(shí)期做的事放到了運(yùn)行時(shí)來(lái)處理涮母。這種動(dòng)態(tài)語(yǔ)言的...
    有一種再見(jiàn)叫青春閱讀 583評(píng)論 0 3
  • iOS底層原理總結(jié) - 探尋OC對(duì)象的本質(zhì) 原文鏈接 對(duì)小碼哥底層班視頻學(xué)習(xí)的總結(jié)與記錄谆趾。面試題部分,通過(guò)對(duì)面試題...
    二斤寂寞閱讀 649評(píng)論 0 4
  • 六點(diǎn)五十三分叛本,列車到達(dá)徐州沪蓬。 天終于白的發(fā)亮。 漫長(zhǎng)的黑夜終于看到了盡頭来候,開(kāi)往上海的列車上跷叉,大多數(shù)人都在酣睡,只有...
    燈下塵_b6b2閱讀 350評(píng)論 0 0
  • 美杜莎從萬(wàn)科城18樓走樓梯一步一步的走上21樓天臺(tái)营搅,前幾天的天氣不好云挟,微雨,天臺(tái)地面上哪里都是濕淋淋的转质,沒(méi)有人上來(lái)...
    NeedUArya閱讀 287評(píng)論 0 1
  • 班主任很讓人討厭植锉。90%的班主任都很讓人討厭。讓人討厭的不是班主任峭拘,是這個(gè)教育環(huán)境俊庇。讓人討厭的是某些人性。 孩子是...
    梁森的簡(jiǎn)書(shū)閱讀 201評(píng)論 0 1