IOS底層原理 -5.運行時(2)

1. 面試題:isKindClass和isMemberOfClass

        NSLog(@"%d",[[NSObject class] isKindOfClass:[NSObject class]]);
        //這裏最終比較的是NSObject類對象的metaClass是不是與其類對象是不是相同,結合類圖所以結果不同
        NSLog(@"%d",[[NSObject class] isMemberOfClass:[NSObject class]]);
        NSLog(@"---------");
        //首先左邊LYMPerson類對象的metaClass與右邊LYMPerson類對象比較,如果不同繼續比較左邊LYMPerson類對象的metaClass的父類;結合類圖結果不相同
        NSLog(@"%d",[[LYMPerson class] isKindOfClass:[LYMPerson class]]);
        // LYMPerson類對象的metaClass與其類對象比較,結合類圖結果不相同
        NSLog(@"%d",[[LYMPerson class] isMemberOfClass:[LYMPerson class]]);
        NSLog(@"---------");
         // LYMPerson類對象的metaClass與NSObject比較,不同則繼續比較 LYMPerson`類對象的metaClass的父類`,因爲LYMPerson的最終父類就是NSObject, 結合類圖因此結果相同
        NSLog(@"%d",[[LYMPerson class] isKindOfClass:[NSObject class]]);
        // LYMPerson類對象的metaClass與NSObject比較,結合類圖結果不相同
        NSLog(@"%d",[[LYMPerson class] isMemberOfClass:[NSObject class]]);

在這裏插入圖片描述
想要研究上述問題得先看下下圖:
在這裏插入圖片描述
先來開一下在objc的NSObject部分開源代碼裏這部分的實現:

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

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

+ (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;
}

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

從源碼裏可知:

  • 在實例方法中:isMemberOfClass只是比較實例方法的類對象是不是和傳入的類是不是同一個;isKindOfClass是判斷實例方法的類對象與傳入的類進行比較,如果不相同 ,再和實例方法的類對象的父類進行比較,一層一層比較;
  • 在類方法中:isMemberOfClass只是拿到類對象的metaClass去判斷是不是等於傳入的類對象;isKindOfClass是拿到類對象的metaClass與傳入的類進行比較,如果不相同 ,再和類對象的原類對象的父類進行比較,一層一層比較;

綜上所述。如果需要全部輸出正確,上述代碼修改爲:

#import <objc/runtime.h>
int main(int argc, const char * argv[]) {
    @autoreleasepool {
        NSLog(@"%d",[NSObject isKindOfClass:[NSObject class]]);
        NSLog(@"%d",[NSObject isMemberOfClass:object_getClass([NSObject class])]);
        NSLog(@"---------");
        NSLog(@"%d",[LYMPerson isKindOfClass:object_getClass([LYMPerson class])]);
        NSLog(@"%d",[LYMPerson isMemberOfClass:object_getClass([LYMPerson class])]);
        NSLog(@"---------");
        NSLog(@"%d",[LYMPerson isKindOfClass:[NSObject class]]);
        NSLog(@"%d",[[LYMPerson superclass] isMemberOfClass:object_getClass([NSObject class])]);
        
    }
    return 0;
}

修改後的輸出如下圖:
在這裏插入圖片描述
上述面試題的每個輸出結果的分析,在每行代碼的上面註釋部分說明

面試題:類的底層原理和方法調用原理

推薦一個很好的博客:神經病院 Objective-C Runtime 入院第一天—— isa 和 Class

#import "LYMPerson.h"

@implementation LYMPerson
-(void)showName{
    NSLog(@"This name is %@",self.userName);
}
@end
#import "ViewController.h"
#import "LYMPerson.h"
@interface ViewController ()

@end

@implementation ViewController

- (void)viewDidLoad {
    [super viewDidLoad];
    id person = [LYMPerson class];
    void *cls = &(person);
    [(__bridge id)cls showName];
}


@end

最終輸出:
在這裏插入圖片描述
下面從兩個方面分析

1. 爲什麼可以輸出?

  • 任何一個OC的對象在編譯成C++後的結構體中都會有一個isa指針,父類的屬性,自己的屬性;方法的實現存儲在其類對象中,實例對象通過ISA可以找到類對象;

2. 輸出爲什麼是viewController中的?

先看一下內存分佈:
在這裏插入圖片描述
內存中最高位的是ViewController,LYMPerson在相對最低位;
增加NSString 後的內存分佈:

在這裏插入圖片描述
內存中大致圖:
在這裏插入圖片描述
首先使用iphoneos clang -arch arm64 -rewrite-objc -fobjc-arc -fobjc-runtime=ios-12.0.0 LYMPerson.m重寫成c++代碼:

struct NSObject_IMPL {
	__unsafe_unretained Class isa;
};
struct LYMPerson_IMPL {
	struct NSObject_IMPL NSObject_IVARS;
	NSString * _Nonnull __strong _userName;
};
  • 正常情況下如果需要使用_userName,那麼類的isa指針需要移動除過自己的8位開始查找,直到找到第一個停止;上述面試題中cls相當於isa指針,那個需要找到_userName輸出,就需要移動除過自己的8位開始查找,在上面圖片中的內存結構中剛好找到name變量,就直接輸出了name的值;同樣對於第一個示例代碼中的內存分佈中下8位剛好是viewController類;

總結:這道面試題知識點:objc類的底層實現、局部變量在棧中的分佈、方法調用的底層原理、

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章