OC底層原理05-成員魂挂、實(shí)例變量、元類

iOS--OC底層原理文章匯總

屬性 & 成員變量 & 實(shí)例變量

屬性(property)有setter馁筐、getter方法涂召;
成員變量(ivar)沒(méi)有setter、getter方法敏沉;屬性 = 下劃線成員變量 + setter + getter方法
實(shí)例變量:特殊的成員變量--是類的實(shí)例化

// 成員變量 vs 屬性
@interface Book : NSObject
{
    NSString * name; // 成員變量果正,諸如:NSArray,NSDictionary...
    NSObject *objc; // 實(shí)例變量-特殊的成員變量 類的實(shí)例化
    UIButton * btn; // 實(shí)例變量盟迟,諸如:UIView秋泳、UILabel、UITextView...
}

元類 & 元類方法

上一章OC底層原理04中队萤,在類中轮锥,屬性、實(shí)例方法都在bits中找到要尔。對(duì)象方法不在對(duì)象中舍杜,而在類中;類方法卻存在其元類中赵辕。
為了驗(yàn)證既绩,繼續(xù)在781源碼中通過(guò)LLDB調(diào)試

獲取元類method.list

打印之后,結(jié)果如下:
類方法在其元類中

元類中為什么會(huì)有類方法还惠?

首先定義兩個(gè)類Person饲握,Teacher

#import <Foundation/Foundation.h>
@interface LGPerson : NSObject
- (void)sayHello;
+ (void)sayHappy;

@end
#import "LGPerson.h"
@implementation LGPerson

- (void)sayHello{
    NSLog(@"LGPerson say : Hello!!!");
}

+ (void)sayHappy{
    NSLog(@"LGPerson say : Happy!!!");
}

@end

在main中首先定義可以獲取類中方法名的方法

void lgObjc_copyMethodList(Class pClass){
    unsigned int count = 0;
    Method *methods = class_copyMethodList(pClass, &count);
    for (unsigned int i=0; i < count; i++) {
        Method const method = methods[I];
        //獲取方法名
        NSString *key = NSStringFromSelector(method_getName(method));
        
        LGLog(@"Method, name: %@", key);
    }
    free(methods);
}

然后調(diào)用并打印

LGPerson *person = [LGPerson alloc];
Class pClass     = object_getClass(person);
lgObjc_copyMethodList(pClass);

其結(jié)果打印為:

Method, name: sayHello  // 只有一個(gè)對(duì)象方法,能打印出sayHello

元類中未什么會(huì)出現(xiàn)類方法這是有會(huì)可能出現(xiàn)在面試題中的,當(dāng)然方法不會(huì)這么直接的問(wèn)救欧。

  • 定義一個(gè)方法lgInstanceMethod_classToMetaclass衰粹,獲取類的實(shí)例方法
void lgInstanceMethod_classToMetaclass(Class pClass){
    
    const char *className = class_getName(pClass);
    Class metaClass = objc_getMetaClass(className);
    
    Method method1 = class_getInstanceMethod(pClass, @selector(sayHello));
    Method method2 = class_getInstanceMethod(metaClass, @selector(sayHello));

    Method method3 = class_getInstanceMethod(pClass, @selector(sayHappy));
    Method method4 = class_getInstanceMethod(metaClass, @selector(sayHappy));
    
    LGLog(@"%s - %p-%p-%p-%p",__func__,method1,method2,method3,method4);
}

打印結(jié)果

lgInstanceMethod_classToMetaclass - 0x1000031b0-0x0-0x0-0x100003148

class_getInstanceMethod 返回的是類的實(shí)例方法,如果在傳入的類或者類的父類中沒(méi)有找到指定的實(shí)例方法笆怠,則返回空


class_getInstanceMethod

method1铝耻、method4都打印出地址,method2蹬刷、method3為空瓢捉,進(jìn)一步印證:對(duì)象方法存在類中,而類方法存在元類中

  • 定義一個(gè)lgClassMethod_classToMetaclass 方法,用于獲取類的類方法
void lgClassMethod_classToMetaclass(Class pClass){
    
    const char *className = class_getName(pClass);
    Class metaClass = objc_getMetaClass(className);
    
    Method method1 = class_getClassMethod(pClass, @selector(sayHello));
    Method method2 = class_getClassMethod(metaClass, @selector(sayHello));

    Method method3 = class_getClassMethod(pClass, @selector(sayHappy));
    // 元類 為什么有 sayHappy 類方法 
    Method method4 = class_getClassMethod(metaClass, @selector(sayHappy));
    
    LGLog(@"%s-%p-%p-%p-%p",__func__,method1,method2,method3,method4);
}

打印結(jié)果

lgClassMethod_classToMetaclass-0x0-0x0-0x100003148-0x100003148

method1办成、method2中未找到泡态,method3、method4找到了類方法迂卢。首先我們?cè)賮?lái)看看class_getClassMethod某弦,它是用來(lái)獲取類

/***********************************************************************
* class_getClassMethod.  Return the class method for the specified
* class and selector.
**********************************************************************/
Method class_getClassMethod(Class cls, SEL sel)
{
    if (!cls  ||  !sel) return nil;

    return class_getInstanceMethod(cls->getMeta(), sel);
}

獲取類的類方法就是等同于獲取元類的實(shí)例方法,最后走的依然是class_getInstanceMethod而克。不同的是cls->getMeta():獲得元類刀崖,而在此例中,從元類中獲取元類拍摇,也就是根元類,但是它直接返回了自身馆截。

在原理04中分析到isa走位圖充活,類的元類是與類同名的元類,元類的元類是superMeta(如果有)蜡娶,superMeta的元類是根元類混卵,根元類的元類是根元類自身,如果再繼續(xù)查找就會(huì)陷入遞歸循環(huán)窖张,一直查找的是根元類幕随。

所以蘋果在判斷獲取元類的類方法時(shí),判斷如果cls是元類宿接,就返回元類自身this赘淮;否則就返回類cls的isa,即cls的元類睦霎,這也就是method3能找到sayHappy類方法的解釋梢卸。
所以類中存在類方法,去元類尋找類方法時(shí)副女,就返回元類自身蛤高,自然也就能在元類中找到類方法了。

 // NOT identical to this->ISA when this is a metaclass
    Class getMeta() {
        if (isMetaClass()) return (Class)this;
        else return this->ISA();
    }
  • 定義一個(gè)lgIMP_classToMetaclass方法,用于找尋是否存在方法實(shí)現(xiàn)
void lgIMP_classToMetaclass(Class pClass){
    
    const char *className = class_getName(pClass);
    Class metaClass = objc_getMetaClass(className);

    // - (void)sayHello;
    // + (void)sayHappy;
    IMP imp1 = class_getMethodImplementation(pClass, @selector(sayHello));
    IMP imp2 = class_getMethodImplementation(metaClass, @selector(sayHello));

    IMP imp3 = class_getMethodImplementation(pClass, @selector(sayHappy));
    IMP imp4 = class_getMethodImplementation(metaClass, @selector(sayHappy));

    NSLog(@"%p-%p-%p-%p",imp1,imp2,imp3,imp4);
    NSLog(@"%s",__func__);
}

SEL: 類成員方法的指針戴陡,它與C語(yǔ)言中的函數(shù)指針有所不同塞绿,函數(shù)指針是保存了方法的地址,但SEL只是獲取方法編號(hào)恤批。@selector()就是取類方法的編號(hào)异吻。
IMP: 函數(shù)指針,保存著方法的地址。

class_getMethodImplementation:返回具體方法的實(shí)現(xiàn)开皿。Developer Documentation 瞄一下:

class_getMethodImplementation文檔

大概說(shuō):向這個(gè)函數(shù)中傳遞一個(gè)類的實(shí)例消息時(shí)涧黄,它將返回傳入方法實(shí)現(xiàn)函數(shù)的指針。順帶說(shuō)了下赋荆,class_getMethodImplementationmethod_getImplementation(class_getInstanceMethod(cls, name))更快笋妥。
而返回的函數(shù)指針可能是runtime內(nèi)部的函數(shù),而不一定是實(shí)際的方法實(shí)現(xiàn)窄潭。 如果類的實(shí)例不響應(yīng)傳入方法春宣,則返回的函數(shù)指針將成為runtime消息轉(zhuǎn)發(fā)機(jī)制的一部分。
打印結(jié)果

 0x100001d00-0x7fff6b58c580-0x7fff6b58c580-0x100001d30

imp1:實(shí)例方法sayHellopClass中有具體實(shí)現(xiàn)嫉你,所以返回該方法imp函數(shù)指針地址月帝。
imp2:實(shí)例方法sayHello存在類中,并未在metaClass中幽污,所以進(jìn)行消息轉(zhuǎn)發(fā)嚷辅。
imp3:類方法sayHappy存在元類中,并未在pClass中自然不會(huì)有具體實(shí)現(xiàn)距误,所以進(jìn)行消息轉(zhuǎn)發(fā)簸搞。
imp4:類方法sayHappy存在元類中,且有具體實(shí)現(xiàn)准潭,所以返回的imp函數(shù)指針地址趁俊。

總結(jié)

  • 對(duì)象方法不在對(duì)象中,而在類中刑然;類方法卻存在其元類中寺擂。
  • 獲取類的類方法就是等同于獲取元類的實(shí)例方法,元類中會(huì)有類方法
  • 獲取元類的類方法時(shí)泼掠,如果cls是元類怔软,就返回元類自身this;否則就返回類cls的isa择镇,即cls的元類爽雄。
  • class_getInstanceMethod:返回的是類的實(shí)例方法,如果在傳入的類或者類的父類中沒(méi)有找到指定的實(shí)例方法沐鼠,則返回空挚瘟。
  • class_getClassMethod:獲取類方法叹谁,如果傳入的類或者類的父類中不包含傳入的類方法,則為空乘盖。
  • class_getMethodImplementation:返回具體方法的實(shí)現(xiàn),如果未找到焰檩,則進(jìn)行消息轉(zhuǎn)發(fā)。

拓展面試題:iskindOfClass & isMemberOfClass 差別

  • 類方法調(diào)用
        BOOL re1 = [(id)[NSObject class] isKindOfClass:[NSObject class]];     
        BOOL re2 = [(id)[NSObject class] isMemberOfClass:[NSObject class]];     
        BOOL re3 = [(id)[LGPerson class] isKindOfClass:[LGPerson class]];       
        BOOL re4 = [(id)[LGPerson class] isMemberOfClass:[LGPerson class]];     
        NSLog(@"\n re1 :%hhd\n re2 :%hhd\n re3 :%hhd\n re4 :%hhd\n",re1,re2,re3,re4);

打印結(jié)果:

 re1 :1
 re2 :0
 re3 :0
 re4 :0

先來(lái)瞅一瞅源碼

+ (BOOL)isKindOfClass:(Class)cls {
    // 獲取類的元類 vs 傳入類
    // 根元類 vs 傳入類
    // 根類 vs 傳入類
    // 如:re3 元類->根元類->NSObject -> nil vs LGPerson  ,return NO
    for (Class tcls = self->ISA(); tcls; tcls = tcls->superclass) {
        if (tcls == cls) return YES;
    }
    return NO;
}


//獲取類的元類订框,與 cls對(duì)比
+ (BOOL)isMemberOfClass:(Class)cls {
    return self->ISA() == cls;
}
// objc_object.h,之前分析部分
inline Class 
objc_object::ISA() 
{
    ASSERT(!isTaggedPointer()); 
#if SUPPORT_INDEXED_ISA
    if (isa.nonpointer) {
        uintptr_t slot = isa.indexcls;
        return classForIndex((unsigned)slot);
    }
    return (Class)isa.bits;
#else
    return (Class)(isa.bits & ISA_MASK);
#endif
}

+ isKindOfClass第一次是將獲取cls的metalClass 與 cls對(duì)比析苫,再對(duì)比的是獲取上次結(jié)果的父類superClass與 cls 進(jìn)行對(duì)比 ,到根元類的父類與cls 對(duì)比穿扳,如果一直沒(méi)有衩侥,一直追溯到根類的父類nil 與 cls對(duì)比
此例中對(duì)比過(guò)程:元類(isa) -> 根元類(父類) -> 根類NSObject(父類) -> nil(父類) 與 cls的對(duì)比
+isMemberOfClass獲取類的元類與 cls對(duì)比矛物,有則返回YES茫死。

 re1 :1 // NSObject vs NSObject(傳入類) -> YES
 re1 :0 // NSObjec -> 根元類 vs NSObject(傳入類) ->NO
 re1 :0 // LGPerson -> 元類(首次是找元類) -> 根元類(開(kāi)始找父類) -> 根類(NSObject)-> nil vs cls(LGPseron),從LGPerson依次查找元類履羞,到后續(xù)查找父類查找對(duì)比都不相等峦萎,輸出 NO
 re1 :0 // LGPerson的元類 vs cls(LGPerson),-> NO
  • 實(shí)例方法調(diào)用
        BOOL re5 = [(id)[NSObject alloc] isKindOfClass:[NSObject class]];     
        BOOL re6 = [(id)[NSObject alloc] isMemberOfClass:[NSObject class]];     
        BOOL re7 = [(id)[LGPerson alloc] isKindOfClass:[LGPerson class]];      
        BOOL re8 = [(id)[LGPerson alloc] isMemberOfClass:[LGPerson class]];     
        NSLog(@" \nre5 :%hhd\n re6 :%hhd\n re7 :%hhd\n re8 :%hhd\n",re5,re6,re7,re8);

打印結(jié)果:

 re5 :1 
 re6 :1 
 re7 :1 
 re8 :1

再看看源碼:

// 將獲取的對(duì)象類 與 傳入類cls對(duì)比忆首,相等返回YES爱榔,停止循環(huán)。如果不相等糙及,將上次獲取的 類的父類 與傳入類cls 對(duì)比详幽,依然循環(huán)對(duì)比
- (BOOL)isKindOfClass:(Class)cls {
     // 獲取對(duì)象的類 vs 傳入的類 
     // 父類 vs 傳入的類
     // 根類 vs 傳入的類
     // nil vs 傳入的類
     // 如:LGPerson -> NSObject -> nil vs LGPerson 
    */
    for (Class tcls = [self class]; tcls; tcls = tcls->superclass) {
        if (tcls == cls) return YES;
    }
    return NO;
}

// 獲取對(duì)象的類,與 傳入類對(duì)比
- (BOOL)isMemberOfClass:(Class)cls {
    return [self class] == cls;
}

- isKindOfClass將獲取的對(duì)象類 與 傳入類cls對(duì)比浸锨,相等返回YES妒潭,停止循環(huán)。如果不相等揣钦,將上次獲取的 類的父類 與傳入類cls 對(duì)比,依然循環(huán)對(duì)比
此例中對(duì)比過(guò)程:類對(duì)象 -> 父類(如果有) -> 根類NSObject -> nil 與 cls的對(duì)比漠酿。
-isMemberOfClass獲取對(duì)象的類冯凹,與 傳入類cls對(duì)比,有則返回YES,否則返回NO炒嘲。

 re5 :1 // 對(duì)象的isa=NSObject(根類) vs NSObject(傳入類宇姚,即根類) -> YES
 re6 :1 // 對(duì)象的類=NSObject(根類) vs NSObject(傳入類,即根類) -> YES
 re7 :1 // 對(duì)象的類=LGPseron  vs cls(LGPseron)夫凸,為YES浑劳,無(wú)需查找父類
 re8 :1 // 對(duì)象的類=LGPerson vs cls(LGPerson),為YES
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末夭拌,一起剝皮案震驚了整個(gè)濱河市魔熏,隨后出現(xiàn)的幾起案子衷咽,更是在濱河造成了極大的恐慌,老刑警劉巖蒜绽,帶你破解...
    沈念sama閱讀 219,188評(píng)論 6 508
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件镶骗,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡躲雅,警方通過(guò)查閱死者的電腦和手機(jī)鼎姊,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,464評(píng)論 3 395
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)相赁,“玉大人相寇,你說(shuō)我怎么就攤上這事∨タ疲” “怎么了唤衫?”我有些...
    開(kāi)封第一講書(shū)人閱讀 165,562評(píng)論 0 356
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)跺嗽。 經(jīng)常有香客問(wèn)我战授,道長(zhǎng),這世上最難降的妖魔是什么桨嫁? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 58,893評(píng)論 1 295
  • 正文 為了忘掉前任植兰,我火速辦了婚禮,結(jié)果婚禮上璃吧,老公的妹妹穿的比我還像新娘楣导。我一直安慰自己,他們只是感情好畜挨,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,917評(píng)論 6 392
  • 文/花漫 我一把揭開(kāi)白布筒繁。 她就那樣靜靜地躺著,像睡著了一般巴元。 火紅的嫁衣襯著肌膚如雪毡咏。 梳的紋絲不亂的頭發(fā)上,一...
    開(kāi)封第一講書(shū)人閱讀 51,708評(píng)論 1 305
  • 那天逮刨,我揣著相機(jī)與錄音呕缭,去河邊找鬼。 笑死修己,一個(gè)胖子當(dāng)著我的面吹牛恢总,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播睬愤,決...
    沈念sama閱讀 40,430評(píng)論 3 420
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼片仿,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來(lái)了尤辱?” 一聲冷哼從身側(cè)響起砂豌,我...
    開(kāi)封第一講書(shū)人閱讀 39,342評(píng)論 0 276
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤厢岂,失蹤者是張志新(化名)和其女友劉穎,沒(méi)想到半個(gè)月后奸鸯,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體咪笑,經(jīng)...
    沈念sama閱讀 45,801評(píng)論 1 317
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,976評(píng)論 3 337
  • 正文 我和宋清朗相戀三年娄涩,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了窗怒。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 40,115評(píng)論 1 351
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡蓄拣,死狀恐怖扬虚,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情球恤,我是刑警寧澤辜昵,帶...
    沈念sama閱讀 35,804評(píng)論 5 346
  • 正文 年R本政府宣布,位于F島的核電站咽斧,受9級(jí)特大地震影響堪置,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜张惹,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,458評(píng)論 3 331
  • 文/蒙蒙 一舀锨、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧宛逗,春花似錦坎匿、人聲如沸。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 32,008評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)。三九已至屎暇,卻和暖如春承桥,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背根悼。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 33,135評(píng)論 1 272
  • 我被黑心中介騙來(lái)泰國(guó)打工凶异, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人番挺。 一個(gè)月前我還...
    沈念sama閱讀 48,365評(píng)論 3 373
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像屯掖,于是被迫代替她去往敵國(guó)和親玄柏。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,055評(píng)論 2 355