iOS 類的結構分析(二)

1. 類對象獲取

LSPerson *person = [[LSPerson alloc] init];
NSLog(@"[person class] = %@, isMetaClass=%d", [person class], class_isMetaClass([person class]));
NSLog(@"[LSPerson class] = %@, isMetaClass=%d", [LSPerson class], class_isMetaClass([LSPerson class]));

output:
//[person class] = LSPerson, isMetaClass=0
//[LSPerson class] = LSPerson, isMetaClass=0

根據(jù)以上代碼可以看到不管是對象調用class方法還是類調用class方法,得到的都是類對象睛榄,且不是元類對象荣茫,那么class底層是如何實現(xiàn)的呢?

class源碼

+ (Class)class {
    return self;
}

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

可以看到class的類方法直接返回調用類的類對象场靴;實例方法調用object_getClass且參數(shù)為實例對象啡莉。

object_getClass源碼

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

獲取當前對象的isa并返回港准,之前已經分析過對象的isa指向當前的類對象。

總結:不管是對象調用class方法還是類調用class方法咧欣,返回的都是類對象浅缸。

2. 獲取元類對象

那么如何獲取元類對象呢?在runtime中有objc_getMetaClass(const char * _Nonnull name)魄咕,參數(shù)是類名稱的c語言字符串

const char *className = [NSStringFromClass([LSPerson class]) UTF8String];
Class metaClass = objc_getMetaClass(className);
        
NSLog(@"LSPerson metaClass = %@, isMetaClass=%d", metaClass, class_isMetaClass(metaClass));

output:
//LSPerson metaClass = LSPerson, isMetaClass=1

objc_getMetaClass源碼

Class objc_getMetaClass(const char *aClassName)
{
    Class cls;

    if (!aClassName) return Nil;

    //根據(jù)類對象名稱字符串獲取類對象
    cls = objc_getClass (aClassName);
    if (!cls)
    {
        _objc_inform ("class `%s' not linked into application", aClassName);
        return Nil;
    }

    //返回類對象的isa指針指向 即元類對象
    return cls->ISA();
}

objc_getClass源碼 根據(jù)字符串獲取類

Class objc_getClass(const char *aClassName)
{
    if (!aClassName) return Nil;

    // NO unconnected, YES class handler
    return look_up_class(aClassName, NO, YES);
}

3. isKindOfClass和isMemberOfClass

isKindOfClass的對象方法 和 類方法

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

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

分析:
- (BOOL)isKindOfClass:(Class)cls對象方法,首先獲取對象的類對象tcls和要比較的對象cls進行比較疗杉,如果不想等則遞歸比較tcls的父類。
+ (BOOL)isKindOfClass:(Class)cls類方法蚕礼,首先獲取類的isa指針的指向烟具,即元類tcls與要比較的cls進行比較,如果不想等奠蹬,則遞歸查找tcls的父類進行比較

isMemberOfClass的對象方法 和 類方法

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

+ (BOOL)isMemberOfClass:(Class)cls {
    return self->ISA() == cls;
}

分析:
- (BOOL)isMemberOfClass:(Class)cls對象方法朝聋,比較對象的類對象和cls是否想等。
+ (BOOL)isMemberOfClass:(Class)cls類方法囤躁,獲取類對象的元類和cls比較是否想等冀痕。

題目解析:

//---類方法調用
BOOL re1 = [(id)[NSObject class] isKindOfClass:[NSObject class]];
//獲取[NSObject class]的元類和[NSObject class]進行比較,不相等狸演,則 
//查找[NSObject class]的元類的父類言蛇,根元類的父類指向根類,都為 
//[NSObject class]所以相等宵距,結果為1

BOOL re2 = [(id)[NSObject class] isMemberOfClass:[NSObject class]];     
//獲取[NSObject class]的元類和[NSObject class]比較腊尚,不相等,結果為0

BOOL re3 = [(id)[LSPerson class] isKindOfClass:[LSPerson class]];  
//獲取[LSPerson class]的元類和[LSPerson class]進行比較满哪,不相等
//則查找[LSPerson class]的元類的父類為根元類婿斥,和[LSPerson class]進行比較,不相等
//根元類的父類指向根類哨鸭,和[LSPerson class]進行比較民宿,不相等
//根類的父類指向nil,結果為0

BOOL re4 = [(id)[LSPerson class] isMemberOfClass:[LSPerson class]];       
//獲取[LSPerson class]的元類和[LSPerson class]比較像鸡,不相等活鹰,結果為0

//---實例方法調用
BOOL re5 = [(id)[NSObject alloc] isKindOfClass:[NSObject class]];
//獲取[NSObject alloc]的類對象[[NSObject alloc] class] 和 [NSObject class]比較相等,結果為1

BOOL re6 = [(id)[NSObject alloc] isMemberOfClass:[NSObject class]];     
//比較[NSObject alloc]的類對象[[NSObject alloc] class] 和 [NSObject class]相等只估,結果為1

BOOL re7 = [(id)[LSPerson alloc] isKindOfClass:[LSPerson class]];
//比較[LSPerson alloc]的類對象[[LSPerson alloc] class] 和 [LSPerson class]相等志群,結果為1

BOOL re8 = [(id)[LSPerson alloc] isMemberOfClass:[LSPerson class]];       
//比較[LSPerson alloc]的類對象[[LSPerson alloc] class] 和 [LSPerson class]相等,結果為1

注意:
不管是根據(jù)斷點源碼還是根據(jù)查看匯編仅乓,在調用isKindOfClass時并沒有走到NSObject.mm中的isKindOfClass對象方法和類方法赖舟,反而走的是objc_opt_isKindOfClass,這是因為llvm對一些不經常重寫的方法進行了優(yōu)化夸楣,如果重寫了宾抓,則進行msgSend消息發(fā)送流程子漩。

llvm中對部分方法進行轉發(fā)

// This is the table of ObjC "accelerated dispatch" functions.  They are a set
// of objc methods that are "seldom overridden" and so the compiler replaces the
// objc_msgSend with a call to one of the dispatch functions.  That will check
// whether the method has been overridden, and directly call the Foundation 
// implementation if not.  
// This table is supposed to be complete.  If ones get added in the future, we
// will have to add them to the table.
const char *AppleObjCTrampolineHandler::g_opt_dispatch_names[] = {
    "objc_alloc",
    "objc_autorelease",
    "objc_release",
    "objc_retain",
    "objc_alloc_init",
    "objc_allocWithZone",
    "objc_opt_class",
    "objc_opt_isKindOfClass",
    "objc_opt_new",
    "objc_opt_respondsToSelector",
    "objc_opt_self",
};

objc_opt_isKindOfClass源碼

// Calls [obj isKindOfClass]
BOOL
objc_opt_isKindOfClass(id obj, Class otherClass)
{
#if __OBJC2__
    printf("objc_opt_isKindOfClass(id obj, Class otherClass)");
    if (slowpath(!obj)) return NO;
    Class cls = obj->getIsa();
    if (fastpath(!cls->hasCustomCore())) {
        for (Class tcls = cls; tcls; tcls = tcls->superclass) {
            if (tcls == otherClass) return YES;
        }
        return NO;
    }
#endif
    return ((BOOL(*)(id, SEL, Class))objc_msgSend)(obj, @selector(isKindOfClass:), otherClass);
}

分析:如果是objc2,則根據(jù)傳入對象的isa獲取到Class cls石洗,此處如果傳入實例對象則獲取類對象幢泼,如果傳入類對象,則獲取元類讲衫,然后遞歸cls及其父類缕棵,和otherClass進行比較。

4. class_getClassMethod底層實現(xiàn)

Method class_getClassMethod(Class cls, SEL sel)
{
    if (!cls  ||  !sel) return nil;

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

Class getMeta() {
        if (isMetaClass()) return (Class)this;
        else return this->ISA();
    }

可以看到涉兽,獲取類方法其實是根據(jù)類找到元類招驴,然后去元類中找實例方法,從而側面證明了枷畏,在OC中區(qū)分的對象方法和類方法别厘,其實在C和C++層面并沒有區(qū)分,都是方法拥诡,只是存儲的位置不一樣触趴。

class_getInstanceMethod源碼

Method class_getInstanceMethod(Class cls, SEL sel)
{
    if (!cls  ||  !sel) return nil;

    // This deliberately avoids +initialize because it historically did so.

    // This implementation is a bit weird because it's the only place that 
    // wants a Method instead of an IMP.

#warning fixme build and search caches
        
    // Search method lists, try method resolver, etc.
    lookUpImpOrForward(nil, sel, cls, LOOKUP_RESOLVER);

#warning fixme build and search caches

    return _class_getMethod(cls, sel);
}

static Method _class_getMethod(Class cls, SEL sel)
{
    mutex_locker_t lock(runtimeLock);
    return getMethod_nolock(cls, sel);
}

static method_t *
getMethod_nolock(Class cls, SEL sel)
{
    method_t *m = nil;

    runtimeLock.assertLocked();

    // fixme nil cls?
    // fixme nil sel?

    ASSERT(cls->isRealized());
    
    while (cls  &&  ((m = getMethodNoSuper_nolock(cls, sel))) == nil) {
        cls = cls->superclass;
    }

    return m;
}

注意:while循環(huán)是從cls開始,遞歸其父類進行查找渴肉,所以如果在元類中查找類方法冗懦,直到根元類仍沒有找到,則因為根元類的父類為根類即NSObject仇祭,如果在NSObject中實現(xiàn)了同名的對象方法披蕉,仍然不會報錯。

image.png

如上圖所示前塔,如果調用[LSStudent sleep];,并不會報unrecognized selector sent to class的錯誤嚣艇,反而會調用根類中的- (void)sleep實例方法承冰。

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯(lián)系作者
  • 序言:七十年代末华弓,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子困乒,更是在濱河造成了極大的恐慌寂屏,老刑警劉巖,帶你破解...
    沈念sama閱讀 207,248評論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件娜搂,死亡現(xiàn)場離奇詭異迁霎,居然都是意外死亡,警方通過查閱死者的電腦和手機百宇,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,681評論 2 381
  • 文/潘曉璐 我一進店門考廉,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人携御,你說我怎么就攤上這事昌粤〖热疲” “怎么了?”我有些...
    開封第一講書人閱讀 153,443評論 0 344
  • 文/不壞的土叔 我叫張陵涮坐,是天一觀的道長凄贩。 經常有香客問我,道長袱讹,這世上最難降的妖魔是什么疲扎? 我笑而不...
    開封第一講書人閱讀 55,475評論 1 279
  • 正文 為了忘掉前任,我火速辦了婚禮捷雕,結果婚禮上椒丧,老公的妹妹穿的比我還像新娘。我一直安慰自己救巷,他們只是感情好瓜挽,可當我...
    茶點故事閱讀 64,458評論 5 374
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著征绸,像睡著了一般久橙。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上管怠,一...
    開封第一講書人閱讀 49,185評論 1 284
  • 那天淆衷,我揣著相機與錄音,去河邊找鬼渤弛。 笑死祝拯,一個胖子當著我的面吹牛,可吹牛的內容都是我干的她肯。 我是一名探鬼主播佳头,決...
    沈念sama閱讀 38,451評論 3 401
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼晴氨!你這毒婦竟也來了康嘉?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 37,112評論 0 261
  • 序言:老撾萬榮一對情侶失蹤籽前,失蹤者是張志新(化名)和其女友劉穎亭珍,沒想到半個月后,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體枝哄,經...
    沈念sama閱讀 43,609評論 1 300
  • 正文 獨居荒郊野嶺守林人離奇死亡肄梨,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 36,083評論 2 325
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了挠锥。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片众羡。...
    茶點故事閱讀 38,163評論 1 334
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖蓖租,靈堂內的尸體忽然破棺而出粱侣,到底是詐尸還是另有隱情辆毡,我是刑警寧澤,帶...
    沈念sama閱讀 33,803評論 4 323
  • 正文 年R本政府宣布甜害,位于F島的核電站舶掖,受9級特大地震影響,放射性物質發(fā)生泄漏尔店。R本人自食惡果不足惜眨攘,卻給世界環(huán)境...
    茶點故事閱讀 39,357評論 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望嚣州。 院中可真熱鬧鲫售,春花似錦、人聲如沸该肴。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,357評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽匀哄。三九已至秦效,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間涎嚼,已是汗流浹背阱州。 一陣腳步聲響...
    開封第一講書人閱讀 31,590評論 1 261
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留法梯,地道東北人苔货。 一個月前我還...
    沈念sama閱讀 45,636評論 2 355
  • 正文 我出身青樓,卻偏偏與公主長得像立哑,于是被迫代替她去往敵國和親夜惭。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 42,925評論 2 344