刨根問底之OC對象本質

刨根問底之OC對象本質

[toc]

我們平時編寫的Objective-C代碼晚伙,底層實現(xiàn)其實都是C\C++代碼

在計算機中編譯過程是
Objective-C \Longrightarrow C\C++ \Longrightarrow 匯編代碼 \Longrightarrow 機器語言

1.內(nèi)存布局

  • Objective-C的面向對象是基于C/C++的結構體實現(xiàn)的
  • 將Objective-C代碼轉換為C/C++代碼可以通過xcode的內(nèi)置clang編譯器實現(xiàn)
  • 如果需要鏈接其他框架歧蕉,使用-framework參數(shù)。比如-framework UIKit
xcrun  -sdk  iphoneos  clang  -arch  arm64  -rewrite-objc  OC源文件  -o  輸出的CPP文件
### 不輸入-O 默認生成一個名字一樣的文字

比如

#import <Foundation/Foundation.h>

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        
        NSObject *obj = [[NSObject alloc] init];
        
        NSLog(@"Hello, World!");
        
    }
    return 0;
}

編譯

 xcrun -sdk iphoneos clang -arch arm64 -rewrite-objc main.m
 ###沒有-o默認生成一個名字一樣的文字

生成一個main.cpp文件,文件內(nèi)容太多,這里不放入了鹏溯。
關鍵代碼可以看到

//NSobject implementation NSobject的實現(xiàn)
struct NSObject_IMPL {
    Class isa;
};

通過command+單擊查看NSobject的定義為

@interface NSObject <NSObject> {
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wobjc-interface-ivars"
    Class isa  OBJC_ISA_AVAILABILITY;
#pragma clang diagnostic pop
}
//簡化為

@interface NSObject {
    Class isa ;
}

驗證了Nsobject的本質為結構體

struct NSObject_IMPL {
    Class isa;
};

Class的內(nèi)容為

typedef struct objec_class *Class

class是一個指向結構體objec_class的指針,所以isa在64系統(tǒng)中站占8個字節(jié)

1.1 Object對象占用字節(jié)數(shù)

按照之前的理解淹仑,一個NSobject對象應該占8個字節(jié)
但是

#import <objc/runtime.h>
int main(int argc, const char * argv[]) {
    @autoreleasepool {
        NSObject *obj = [[NSObject alloc] init];
        // 獲得NSObject實例對象的成員變量所占用的大小 >> 8
        NSLog(@"%zd", class_getInstanceSize([NSObject class]));
        // 獲得obj指針所指向內(nèi)存的大小 :分配內(nèi)存的大小>>16
        NSLog(@"%zd", malloc_size((__bridge const void *)obj));        
    }
    return 0;
}

打印結果

2020-11-19 21:13:05.329575+0800 OC本質[91718:1318885] 8
2020-11-19 21:13:05.329956+0800 OC本質[91718:1318885] 16
2020-11-19 21:13:05.330002+0800 OC本質[91718:1318885] Hello, World!
Program ended with exit code: 0

一個對象實例化后分配了16個字節(jié)丙挽,但是實際上用了8個字節(jié)。

可以從蘋果源碼中找到答案
https://opensource.apple.com/tarballs/objc4/
下載最新的 781查看class_getInstanceSize 可知

size_t class_getInstanceSize(Class cls)
{
    if (!cls) return 0;
    return cls->alignedInstanceSize();
}

  // Class's ivar size rounded up to a pointer-size boundary.
  //返回類的成員變量所占的大小
    uint32_t alignedInstanceSize() const {
        return word_align(unalignedInstanceSize());
    }


查找過程如下


image.png

進一步驗證匀借,查看alloc源碼

+ (id)alloc {
    return _objc_rootAlloc(self);
}

id
_objc_rootAlloc(Class cls)
{
    return callAlloc(cls, false/*checkNil*/, true/*allocWithZone*/);
}

callAlloc(Class cls, bool checkNil, bool allocWithZone=false)
{
#if __OBJC2__
    if (slowpath(checkNil && !cls)) return nil;
    if (fastpath(!cls->ISA()->hasCustomAWZ())) {
        return _objc_rootAllocWithZone(cls, nil);
    }
#endif

    // No shortcuts available.
    if (allocWithZone) {
        return ((id(*)(id, SEL, struct _NSZone *))objc_msgSend)(cls, @selector(allocWithZone:), nil);
    }
    return ((id(*)(id, SEL))objc_msgSend)(cls, @selector(alloc));
}


_objc_rootAllocWithZone(Class cls, malloc_zone_t *zone __unused)
{
    // allocWithZone under __OBJC2__ ignores the zone parameter
    return _class_createInstanceFromZone(cls, 0, nil,
                                         OBJECT_CONSTRUCT_CALL_BADALLOC);
}



/***********************************************************************
* class_createInstance
* fixme
* Locking: none
*
* Note: this function has been carefully written so that the fastpath
* takes no branch.
**********************************************************************/
static ALWAYS_INLINE id
_class_createInstanceFromZone(Class cls, size_t extraBytes, void *zone,
                              int construct_flags = OBJECT_CONSTRUCT_NONE,
                              bool cxxConstruct = true,
                              size_t *outAllocatedSize = nil)
{
    ASSERT(cls->isRealized());

    // Read class's info bits all at once for performance
    bool hasCxxCtor = cxxConstruct && cls->hasCxxCtor();
    bool hasCxxDtor = cls->hasCxxDtor();
    bool fast = cls->canAllocNonpointer();
    size_t size;

    size = cls->instanceSize(extraBytes);
    if (outAllocatedSize) *outAllocatedSize = size;

    id obj;
    if (zone) {
        obj = (id)malloc_zone_calloc((malloc_zone_t *)zone, 1, size);
    } else {
        obj = (id)calloc(1, size);
    }
    if (slowpath(!obj)) {
        if (construct_flags & OBJECT_CONSTRUCT_CALL_BADALLOC) {
            return _objc_callBadAllocHandler(cls);
        }
        return nil;
    }

    if (!zone && fast) {
        obj->initInstanceIsa(cls, hasCxxDtor);
    } else {
        // Use raw pointer isa on the assumption that they might be
        // doing something weird with the zone or RR.
        obj->initIsa(cls);
    }

    if (fastpath(!hasCxxCtor)) {
        return obj;
    }

    construct_flags |= OBJECT_CONSTRUCT_FREE_ONFAILURE;
    return object_cxxConstructFromClass(obj, cls, construct_flags);
    
}

  size_t instanceSize(size_t extraBytes) const {
        if (fastpath(cache.hasFastInstanceSize(extraBytes))) {
            return cache.fastInstanceSize(extraBytes);
        }

        size_t size = alignedInstanceSize() + extraBytes;
        // CF requires all objects be at least 16 bytes.
        //每個oc對象最少16個字節(jié)
        if (size < 16) size = 16;
        return size;
    }

_class_createInstanceFromZone 可知size是從instanceSize函數(shù)來的颜阐,進行calloc分配內(nèi)存,而instanceSize中判斷了如果小于16就分配16個字節(jié)

其實一個對象實例化占的大小,跟他成員變量有關吓肋,修改編寫Person類

#import <Foundation/Foundation.h>

NS_ASSUME_NONNULL_BEGIN

@interface Person : NSObject
@property (nonatomic, copy) NSString *name;
@property (nonatomic, copy) NSString *name1;
@property (nonatomic, copy) NSString *name2;
@property (nonatomic, copy) NSString *name3;
@property (nonatomic, copy) NSString *name4;
@property (nonatomic, copy) NSString *name5;
@property (nonatomic, copy) NSString *name6;
@property (nonatomic, copy) NSString *name7;
@property (nonatomic, copy) NSString *name8;
@property (nonatomic, copy) NSString *name9;
@property (nonatomic, copy) NSString *name0;
@property (nonatomic, copy) NSString *name11;
@property (nonatomic, copy) NSString *name12;

@end

NS_ASSUME_NONNULL_END

查看結果

2020-11-19 22:04:06.540106+0800 OC本質[1735:19907] 112
2020-11-19 22:04:06.540600+0800 OC本質[1735:19907] 112
2020-11-19 22:04:06.540665+0800 OC本質[1735:19907] Hello, World!
Program ended with exit code: 0

實際上isa是8個字節(jié)瞬浓,然后每個String是8個字節(jié),所以總共占用的是
8+8*13=112個字節(jié)

1.1.1 總結

系統(tǒng)分配了16個字節(jié)給NSObject對象(通過malloc_size函數(shù)獲得)
但NSObject對象內(nèi)部只使用了8個字節(jié)的空間(64bit環(huán)境下蓬坡,可以通過class_getInstanceSize函數(shù)獲得)
但是成員變量也會占用空間猿棉,實際上是對象的大小有isa占用字節(jié)和成員變量大小之和決定的

1.2 常用LLDB指令獲取內(nèi)存

  • print、p:打印
  • po:打印對象
  • 讀取內(nèi)存
    • memory read/數(shù)量格式字節(jié)數(shù) 內(nèi)存地址 可以簡寫x
    • x/數(shù)量格式字節(jié)數(shù) 內(nèi)存地址
    • x/3xw 0x10010
      • 格式
        • x是16進制(小段模式)屑咳,f是浮點萨赁,d是10進制
      • 字節(jié)大小
        • b:byte 1字節(jié),h:half word 2字節(jié)
        • w:word 4字節(jié),g:giant word 8字節(jié)
  • 修改內(nèi)存中的值
    • memory write 內(nèi)存地址 數(shù)值
    • memory write 0x0000010 10
int main(int argc, const char * argv[]) {
    @autoreleasepool {
        
        NSObject *obj = [[NSObject alloc] init];
//        Person *obj = [[Person alloc] init];
        // 獲得NSObject實例對象的成員變量所占用的大小 >> 8
        NSLog(@"%zd", class_getInstanceSize([Person class]));
        // 獲得obj指針所指向內(nèi)存的大小 >> 16
        NSLog(@"%zd", malloc_size((__bridge const void *)obj));
       
        NSLog(@"Hello, World!");
        
    }
    return 0;
}

利用lldb,可以看到nsobjec只占用了8個字節(jié)

image.png

1.3clang分析有成員變量的結構

@interface Person : NSObject
{

@public
int _age1;
int _age2;
}

@end

@implementation Person

@end


int main(int argc, const char * argv[]) {
    @autoreleasepool {
        
        Person *obj = [[Person alloc] init];

        NSLog(@"%zd", class_getInstanceSize([Person class]));
        // 獲得obj指針所指向內(nèi)存的大小 >> 16
        NSLog(@"%zd", malloc_size((__bridge const void *)obj));
       
        NSLog(@"Hello, World!");
        
    }
    return 0;
}

打印結果

2020-11-19 22:51:41.753207+0800 OC本質[4754:73510] 16
2020-11-19 22:51:41.753646+0800 OC本質[4754:73510] 16
2020-11-19 22:51:41.753681+0800 OC本質[4754:73510] Hello, World!
Program ended with exit code: 0

clang結果為

struct Person_IMPL {
    struct NSObject_IMPL NSObject_IVARS;
    int _age1;
    int _age2;
};

//NSobject implementation NSobject的實現(xiàn)
struct NSObject_IMPL {
    Class isa;
};

此時的內(nèi)存結構圖類似如下诚欠,假如person的地址是0x10010


image.png

去掉一個age

#import <Foundation/Foundation.h>
#import <objc/runtime.h>
#import <malloc/malloc.h>
@interface Person : NSObject
{

@public
int _age1;
//int _age2;
}

@end

@implementation Person

@end


int main(int argc, const char * argv[]) {
    @autoreleasepool {
        
        Person *obj = [[Person alloc] init];

        NSLog(@"%zd", class_getInstanceSize([Person class]));
        // 獲得obj指針所指向內(nèi)存的大小 >> 16
        NSLog(@"%zd", malloc_size((__bridge const void *)obj));
       
        NSLog(@"Hello, World!");
        
    }
    return 0;
}

發(fā)現(xiàn)還是

2020-11-19 23:01:32.460860+0800 OC本質[5383:84288] 16
2020-11-19 23:01:32.461498+0800 OC本質[5383:84288] 16
2020-11-19 23:01:32.461551+0800 OC本質[5383:84288] Hello, World!
Program ended with exit code: 0

因為內(nèi)存對齊:結構體的大小必須是最大成員大小的倍數(shù)
所以必須最大成員isa大小8的倍數(shù)

1.3.1 類的屬性

@interface Person : NSObject
{

@public
int _age1;
int _age2;
}
@property (nonatomic, assign) int height;
@end

@implementation Person

@end




int main(int argc, const char * argv[]) {
    @autoreleasepool {
        
        Person *obj = [[Person alloc] init];

        NSLog(@"%zd", class_getInstanceSize([Person class]));
        // 獲得obj指針所指向內(nèi)存的大小 >> 16
        NSLog(@"%zd", malloc_size((__bridge const void *)obj));
       
        NSLog(@"Hello, World!");
        
    }
    return 0;
}

clang之后


extern "C" unsigned long OBJC_IVAR_$_Person$_height;
struct Person_IMPL {
    struct NSObject_IMPL NSObject_IVARS;
    int _age1;
    int _age2;
    int _height;
};

// @property (nonatomic, assign) int height;
/* @end */


// @implementation Person


static int _I_Person_height(Person * self, SEL _cmd) { return (*(int *)((char *)self + OBJC_IVAR_$_Person$_height)); }
static void _I_Person_setHeight_(Person * self, SEL _cmd, int height) { (*(int *)((char *)self + OBJC_IVAR_$_Person$_height)) = height; }
// @end


也進一步驗證了屬性的本質會生成一個成員下劃線開頭的變量,并生成get和set方法

查看打印結果為

2020-11-20 10:28:59.689581+0800 OC本質[7058:88814] 24
2020-11-20 10:28:59.690352+0800 OC本質[7058:88814] 32
2020-11-20 10:28:59.690415+0800 OC本質[7058:88814] Hello, World!
Program ended with exit code: 0

利用lldb看下慰安,由于是小段模式,所以4*8=32字節(jié)后聪铺,才不屬于person對象化焕,所以實際上內(nèi)存分配的是32個字節(jié)

(lldb) x/5xg 0x10052b7e0
0x10052b7e0: 0x001d8001000081d1 0x0000000000000000
0x10052b7f0: 0x0000000000000000 0x0000000000000000
0x10052b800: 0x00007fff76094b8b
  • 數(shù)字24:好理解,因為isa占8個字節(jié)铃剔,3個數(shù)字一共占3*4=12個撒桨,也就是8+12=20,但是由于內(nèi)存對齊的緣故查刻,必須是8的倍數(shù),也就是必須是24
  • 數(shù)字32:實例對象需要的是24個字節(jié)凤类,但是看上面源碼calloc,也就是調用calloc(1,24)穗泵,系統(tǒng)分配的時候,由于操作系統(tǒng)所以必須給32個字節(jié)谜疤,理由是#define NANO_MAX_SIZE 256 /* Buckets sized {16, 32, 48, ..., 256} */ 操作系統(tǒng)對齊佃延,會根據(jù)傳入的大小分配是如圖的數(shù)組,所以是32夷磕,觀察就是必須是16的倍數(shù)

#define NANO_MAX_SIZE 256 /* Buckets sized {16, 32, 48, ..., 256} */是在calloc源碼 https://opensource.apple.com/tarballs/libmalloc/ 這里最新的是libmalloc-283.100.5 里可以看到

2 OC 對象分類

Objective-C中的對象苇侵,簡稱OC對象,主要可以分為3種

  • instance對象(實例對象)
  • class對象(類對象)
  • meta-class對象(元類對象)

2.1 instance對象

instance對象就是通過類alloc出來的對象企锌,每次調用alloc都會產(chǎn)生新的instance對象

NSObject *obj1 = [[NSObject alloc] init];
 NSObject *obj2 = [[NSObject alloc] init];
  • object1、object2是NSObject的instance對象(實例對象)
  • 它們是不同的兩個對象于未,分別占據(jù)著兩塊不同的內(nèi)存
  • instance對象在內(nèi)存中存儲的信息包括
    • isa指針
    • 其他成員變量

2.2 class對象

描述實例對象信息的對象撕攒,獲取類對象通過class或者runtime的object_getClass獲取

#import <Foundation/Foundation.h>
#import <objc/runtime.h>
int main(int argc, const char * argv[]) {
    @autoreleasepool {
        // insert code here...
        NSLog(@"Hello, World!");
        
        NSObject *obj1 = [[NSObject alloc] init];
        NSObject *obj2 = [[NSObject alloc] init];
        
        //獲取類對象
        Class objectClass1 = [obj1 class];
        Class objectClass2 = [obj2 class];
        Class objectClass3 = [NSObject class];
        Class objectClass_runtime1 = object_getClass(obj1);
        Class objectClass_runtime2 = object_getClass(obj2);
      
        
        NSLog(@"實例對象地址 %p %p",
              obj1,
              obj1);
        
        NSLog(@"類對象地址 %p %p %p %p %p",
              objectClass1,
              objectClass2,
              objectClass3,
              objectClass_runtime1,
              objectClass_runtime2
             );
    }
    return 0;
}


打印結果

2020-11-20 15:26:36.506594+0800 oc對象類型[32655:410913] Hello, World!
2020-11-20 15:26:36.507049+0800 oc對象類型[32655:410913] 實例對象地址 0x102972be0 0x102972be0
2020-11-20 15:26:36.507206+0800 oc對象類型[32655:410913] 類對象地址 0x7fff922ca118 0x7fff922ca118 0x7fff922ca118 0x7fff922ca118 0x7fff922ca118
Program ended with exit code: 0
  • objectClass1, objectClass2,objectClass3,objectClass_runtime1, objectClass_runtime2 都是獲取的NSObject的class(類)對象
  • 它們是同一個對象。由類對象地址一樣可知每個類在內(nèi)存中有且只有一個class對象

實例對象里存儲的成員變量由于每個實例對象的成員變量值可能不一樣烘浦,所以要每個實例變量都要有一份成員變量

class對象在內(nèi)存中存儲的信息(只需要一份即可的信息)主要包括

  • isa指針
  • superclass指針
  • 類的屬性信息(@property)抖坪、類的對象方法信息(instance method)
  • 類的協(xié)議信息(protocol)、類的成員變量描述信息(ivar)(比如成員變量的類型)
  • ......

簡化表格如下


image.png

2.3 meta-class對象

meta-class對象是描述類對象的對象闷叉,通過runtime的object_getClass傳入類對象獲取得到,并且可以通過class_isMetaClass判斷是否是元類對象

 // meta-class對象擦俐,元類對象
// 將類對象當做參數(shù)傳入,獲得元類對象
Class objectMetaClass = object_getClass(objectClass5);
NSLog(@"元類對象 - %p %d", objectMetaClass, class_isMetaClass(objectMetaClass));

打印結果

2020-11-20 15:43:55.683923+0800 oc對象類型[34798:441311] 元類對象 - 0x7fff922ca0f0 1

注意不能通過[objectClass5 class]獲取握侧,因為他打印的還是類對象的地址蚯瞧,其實不僅僅是他,[[[objectClass5 class] class] class]不管調用多少次class方法都是類對象

  • 每個類在內(nèi)存中有且只有一個meta-class對象
  • meta-class對象和class對象的內(nèi)存結構是一樣的都是typedef struct objec_class *Class品擎,但是用途不一樣埋合,在內(nèi)存中存儲的信息主要包括
    • isa指針
    • superclass指針
    • 類的類方法信息(class method)
    • ......
      但是里面其他信息的字段都是空的


      image.png

2.4 objc_getClass、object_getClass區(qū)別

查看runtime源碼https://opensource.apple.com/tarballs/objc4/

object_getClass

/***********************************************************************
* object_getClass.
* Locking: None. If you add locking, tell gdb (rdar://7516456).
**********************************************************************/
Class object_getClass(id obj)
{   //傳入一個對象萄传,下面詳細介紹甚颂,這里簡單說一下
//通過對象的isa指針返回
//如果傳入的instance對象,則返回class對象
//如果傳入的是class對象秀菱,則返回的是元類對象
//如果傳入的是原來對象振诬,則返回的是NSOBject的元類對象
    if (obj) return obj->getIsa();
    else return Nil;
}

objc_getClass

Class objc_getClass(const char *aClassName)
{
    if (!aClassName) return Nil;
   //傳入字符串類名,將一個類對象返回去
    // NO unconnected, YES class handler
    return look_up_class(aClassName, NO, YES);
}

所以區(qū)別是

  • objc_getClass:傳入字符串類名衍菱,將一個類對象返回去
  • object_getClass:傳入的一個對象赶么,返回他的isa。具體是
    • //如果傳入的instance對象脊串,則返回class對象
    • //如果傳入的是class對象禽绪,則返回的是元類對象
    • //如果傳入的是原來對象蓖救,則返回的是NSOBject的元類對象

3 isa指針和superclass指針

3.1 isa指針

image.png
  • instance的isa指向class
    • 當調用對象方法時,通過instance的isa找到class印屁,最后找到對象方法的實現(xiàn)進行調用
  • class的isa指向meta-class
    • 當調用類方法時循捺,通過class的isa找到meta-class,最后找到類方法的實現(xiàn)進行調用

3.2 superclass指針

編寫兩個個類
@interface LYJStudent : LYJPerson
@interface LYJPerson : NSObject
則這兩個類和NSObject的superclas指針為
[圖片上傳失敗...(image-df27de-1606027544665)]

LYJStudentinstance對象要調用LYJPerson的對象方法時雄人,會先通過isa找到LYJStudent的class从橘,然后通過superclass找到LYJPerson的class,最后找到對象方法的實現(xiàn)進行調用

3.2.1 meta-class(元類對象)的superclass指針

image.png

LYJStudent的class要調用LYJPerson的類方法時础钠,會先通過isa找到LYJStudent的meta-class恰力,然后通過superclass找到LYJPerson的meta-class,最后找到類方法的實現(xiàn)進行調用

3.3 isa旗吁、superclass總結

image.png
  • instance的isa指向class
  • class的isa指向meta-class
  • meta-class的isa指向基類的meta-class
  • class的superclass指向父類的class
    • 如果沒有父類踩萎,superclass指針為nil
  • meta-class的superclass指向父類的meta-class
    • 基類的meta-class的superclass指向基類的class,后面驗證證明這個
  • instance調用對象方法的軌跡
    • isa找到class很钓,方法不存在香府,就通過superclass找父類
  • class調用類方法的軌跡
    • isa找meta-class,方法不存在码倦,就通過superclass找父類
image.png

舉例化:上面的LYJStudent就是圖中的Subclass,LYJPerson就是圖中的Superclass,NSObject就是圖中的Root Class,代入就比較好理解

  1. meta-class的isa指向基類的meta-class
    • LYJStudent企孩、LYJPerson和NSObject的元類對象的isa都是指向NSobject的元類對象
  2. class的superclass指向父類的class
    • LYJStudent的類對象superclass指向LYJPerson的類對象,LYJPerson的類對象superclass指向NSObject的類對象袁稽,NSObject沒有父類勿璃,則指向的是nil
  3. meta-class的superclass指向父類的meta-class
    • LYJStudent的元類對象superclass指向LYJPerson的元類對象,LYJPerson的元類對象superclass指向NSObject的元類對象推汽,NSObject的元類對象superclass指向NSObject的類對象(后面驗證證明這個)

3.3.1 isa和superclass細節(jié)問題

編寫如下代碼

  • 原始代碼
#import <Foundation/Foundation.h>
#import "NSObject+Test.h"

@interface LYJPerson : NSObject

+ (void)test;

@end

@implementation LYJPerson

+ (void)test
{
    NSLog(@"+[LYJPerson test] - %p", self);
}

@end
int main(int argc, const char * argv[]) {
    @autoreleasepool {
        NSLog(@"[LYJPerson class] - %p", [LYJPerson class]);
        NSLog(@"[NSObject class] - %p", [NSObject class]);
      
        [LYJPerson test];
        [NSObject test];
    }
    return 0;
}

#import <Foundation/Foundation.h>

NS_ASSUME_NONNULL_BEGIN

@interface NSObject (Test)
+ (void)test;

@end

NS_ASSUME_NONNULL_END


#import "NSObject+Test.h"

@implementation NSObject (Test)
+ (void)test
{
    NSLog(@"+[NSObject test] - %p", self);
}

- (void)test
{
    NSLog(@"-[NSObject test] - %p", self);
}

@end

打印為

2020-11-21 11:39:30.542278+0800 isa和superclass[10995:164119] [LYJPerson class] - 0x100008190
2020-11-21 11:39:30.542675+0800 isa和superclass[10995:164119] [NSObject class] - 0x7fff96159118
2020-11-21 11:39:30.542727+0800 isa和superclass[10995:164119] +[LYJPerson test] - 0x100008190
2020-11-21 11:39:30.542766+0800 isa和superclass[10995:164119] +[NSObject test] - 0x7fff96159118
Program ended with exit code: 0

可以看到[LYJPerson test]執(zhí)行時补疑,test方法的方法調用者是LYJPerson類對象,[NSObject test]執(zhí)行時歹撒,test方法的方法調用者是NSObject的類對象

  • 當注釋掉LYJPersontest方法

@interface LYJPerson : NSObject

+ (void)test;

@end

@implementation LYJPerson

//+ (void)test
//{
//    NSLog(@"+[LYJPerson test] - %p", self);
//}

@end
int main(int argc, const char * argv[]) {
    @autoreleasepool {
         NSLog(@"[LYJPerson class] - %p", [LYJPerson class]);
        NSLog(@"[NSObject class] - %p", [NSObject class]);
      
        [LYJPerson test];
        [NSObject test];
           }
    return 0;
}

打印為

2020-11-21 11:40:12.591616+0800 isa和superclass[11038:164790] [LYJPerson class] - 0x100008170
2020-11-21 11:40:12.592032+0800 isa和superclass[11038:164790] [NSObject class] - 0x7fff96159118
2020-11-21 11:40:12.592112+0800 isa和superclass[11038:164790] +[NSObject test] - 0x100008170
2020-11-21 11:40:12.592158+0800 isa和superclass[11038:164790] +[NSObject test] - 0x7fff96159118
Program ended with exit code: 0

可以看到[LYJPerson test]執(zhí)行時癣丧,test方法時先去LYJPerson對象查找,發(fā)現(xiàn)沒有就去NSObject查找栈妆,結果找到了test方法胁编,結果調用了NSObjecttest方法,但是方法調用者還是LYJPerson類對象鳞尔,[NSObject test]執(zhí)行時嬉橙,test方法的方法調用者是NSObject的類對象

  • 當把NSObject的類方法test也注釋掉
#import "NSObject+Test.h"

@implementation NSObject (Test)
//+ (void)test
//{
//    NSLog(@"+[NSObject test] - %p", self);
//}

- (void)test
{
    NSLog(@"-[NSObject test] - %p", self);
}

@end

打印為

2020-11-21 11:40:43.127454+0800 isa和superclass[11080:165724] [LYJPerson class] - 0x100008150
2020-11-21 11:40:43.127854+0800 isa和superclass[11080:165724] [NSObject class] - 0x7fff96159118
2020-11-21 11:40:43.127901+0800 isa和superclass[11080:165724] -[NSObject test] - 0x100008150
2020-11-21 11:40:43.127939+0800 isa和superclass[11080:165724] -[NSObject test] - 0x7fff96159118
Program ended with exit code: 0

可以看到[LYJPerson test]執(zhí)行時,test方法時先去LYJPerson對象查找寥假,發(fā)現(xiàn)沒有就去NSObject的元類對象中查找市框,結果沒找到找到test方法,按照上面說的就去糕韧,通過superclassNSObject的類對象里找枫振,找到了test方法喻圃,結果執(zhí)行了類對象的test方法,也就是實例方法
但是方法調用者還是LYJPerson類對象粪滤,[NSObject test]執(zhí)行時斧拍,跟[LYJPerson test]類似

驗證了上面說的第三點,NSObject的元類對象superclass指向NSObject的類對象

oc對象的方法調用都是消息發(fā)送機制杖小,轉換成objc_msgSend(方法調用者肆汹,@selector(test))

3.3.2 isa細節(jié)問題

編寫測試代碼



#import <Foundation/Foundation.h>
#import <objc/runtime.h>

// LYJPerson
@interface LYJPerson : NSObject <NSCopying>
{
@public
    int _age;
}
@property (nonatomic, assign) int no;
- (void)personInstanceMethod;
+ (void)personClassMethod;
@end

@implementation LYJPerson

- (void)test
{
    
}

- (void)personInstanceMethod
{
    
}
+ (void)personClassMethod
{
    
}
- (id)copyWithZone:(NSZone *)zone
{
    return nil;
}
@end

// LYJStudent
@interface LYJStudent : LYJPerson <NSCoding>
{
@public
    int _weight;
}
@property (nonatomic, assign) int height;
- (void)studentInstanceMethod;
+ (void)studentClassMethod;
@end

@implementation LYJStudent
- (void)test
{
    
}
- (void)studentInstanceMethod
{
    
}
+ (void)studentClassMethod
{
    
}
- (id)initWithCoder:(NSCoder *)aDecoder
{
    return nil;
}

- (void)encodeWithCoder:(NSCoder *)aCoder
{
    
}
@end
int main(int argc, const char * argv[]) {
    @autoreleasepool {
        //        LYJStudent *student = [[LYJStudent alloc] init];
        LYJPerson *person = [[LYJPerson alloc] init];
        
        Class personClass = [LYJPerson class];
        
        
        Class personMetaClass = object_getClass(personClass);
        
        NSLog(@"%p %p %p", person, personClass, personMetaClass);
        
        
    }
    return 0;
}

lldb

(lldb) p/x person->isa
(Class) $0 = 0x001d800100008461 LYJPerson
(lldb) p/x personClass
(Class) $1 = 0x0000000100008460 LYJPerson
(lldb) 

發(fā)現(xiàn)person->isa和person的類對象不一樣

這是因為
從64bit開始,isa需要進行一次位運算予权,才能計算出真實地址
isa指向的地址&ISA_MASK 得到對應的地址
ISA_MASK的值如下

image.png

查看源碼https://opensource.apple.com/tarballs/objc4/


# if __arm64__
#   define ISA_MASK        0x0000000ffffffff8ULL
#   define ISA_MAGIC_MASK  0x000003f000000001ULL
#   define ISA_MAGIC_VALUE 0x000001a000000001ULL
#   define ISA_BITFIELD                                                      \
      uintptr_t nonpointer        : 1;                                       \
      uintptr_t has_assoc         : 1;                                       \
      uintptr_t has_cxx_dtor      : 1;                                       \
      uintptr_t shiftcls          : 33; /*MACH_VM_MAX_ADDRESS 0x1000000000*/ \
      uintptr_t magic             : 6;                                       \
      uintptr_t weakly_referenced : 1;                                       \
      uintptr_t deallocating      : 1;                                       \
      uintptr_t has_sidetable_rc  : 1;                                       \
      uintptr_t extra_rc          : 19
#   define RC_ONE   (1ULL<<45)
#   define RC_HALF  (1ULL<<18)

# elif __x86_64__
#   define ISA_MASK        0x00007ffffffffff8ULL
#   define ISA_MAGIC_MASK  0x001f800000000001ULL
#   define ISA_MAGIC_VALUE 0x001d800000000001ULL
#   define ISA_BITFIELD                                                        \
      uintptr_t nonpointer        : 1;                                         \
      uintptr_t has_assoc         : 1;                                         \
      uintptr_t has_cxx_dtor      : 1;                                         \
      uintptr_t shiftcls          : 44; /*MACH_VM_MAX_ADDRESS 0x7fffffe00000*/ \
      uintptr_t magic             : 6;                                         \
      uintptr_t weakly_referenced : 1;                                         \
      uintptr_t deallocating      : 1;                                         \
      uintptr_t has_sidetable_rc  : 1;                                         \
      uintptr_t extra_rc          : 8
#   define RC_ONE   (1ULL<<56)
#   define RC_HALF  (1ULL<<7)

# else
#   error unknown architecture for packed isa
# endif

驗證一下
還是上面的測試代碼昂勉,由于我的電腦是intel的x86架構,不是M1的arm架構扫腺,所以執(zhí)行& 0x00007ffffffffff8ULL
執(zhí)行 lldb如下

(lldb) p/x person->isa
(Class) $0 = 0x001d800100008461 LYJPerson
(lldb) p/x personClass
(Class) $1 = 0x0000000100008460 LYJPerson
(lldb) p/x 0x001d800100008461 & 0x00007ffffffffff8ULL
(unsigned long long) $2 = 0x0000000100008460
(lldb) 

可看到得到的結果0x0000000100008460跟personClass一致 得到了驗證

進一步驗證類對象的isa

(lldb) p/x person->isa
(Class) $0 = 0x001d800100008461 LYJPerson
(lldb) p/x personClass
(Class) $1 = 0x0000000100008460 LYJPerson
(lldb) p/x 0x001d800100008461 & 0x00007ffffffffff8ULL
(unsigned long long) $2 = 0x0000000100008460
(lldb) p/x personClass->isa
error: <user expression 3>:1:12: member reference base type 'Class' is not a structure or union
personClass->isa
~~~~~~~~~~~^ ~~~
(lldb) 

發(fā)現(xiàn)獲取不到isa

因為class的結構是typedef struct objec_class *Class
而objec_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;
/* Use `Class` instead of `struct objc_class *` */

所以可以自定義objec_class岗照,讓類對象指向這個自定義的

struct lyj_objc_class {
    Class isa;
};
int main(int argc, const char * argv[]) {
    @autoreleasepool {
        //        LYJStudent *student = [[LYJStudent alloc] init];
        LYJPerson *person = [[LYJPerson alloc] init];
        
        Class personClass = [LYJPerson class];
        
        struct lyj_objc_class *personClass2 = (__bridge struct lyj_objc_class *)(personClass);
        
        Class personMetaClass = object_getClass(personClass);
        
        NSLog(@"%p %p %p", person, personClass, personMetaClass);
        
        
    }
    return 0;
}

然后進行l(wèi)ldb可得到

(lldb) p/x personClass
(Class) $0 = 0x0000000100008460 LYJPerson
(lldb) p/x personClass2
(lyj_objc_class *) $1 = 0x0000000100008460
(lldb) p/x personClass2->isa
(Class) $2 = 0x0000000100008438
(lldb) p/x personMetaClass
(Class) $3 = 0x0000000100008438
(lldb) p/x 0x0000000100008438 &0x00007ffffffffff8ULL
(unsigned long long) $4 = 0x0000000100008438
(lldb) 

發(fā)現(xiàn)類對象的isa也是&ISA_MASK 得到他的元類對象

3.3.3 superclass細節(jié)問題

還是上面的代碼,看下superclass的地址是什么


struct lyj_objc_class {
    Class isa;
    Class _Nullable super_class;
};
int main(int argc, const char * argv[]) {
    @autoreleasepool {
        //        LYJStudent *student = [[LYJStudent alloc] init];
        LYJPerson *person = [[LYJPerson alloc] init];
        
        Class personClass = [LYJPerson class];
        
        struct lyj_objc_class *personClass2 = (__bridge struct lyj_objc_class *)(personClass);
        
        Class studentClass = [LYJStudent class];
        
        struct lyj_objc_class *studentClass2 = (__bridge struct lyj_objc_class *)(studentClass);
      
        
        Class personMetaClass = object_getClass(personClass);
        
        NSLog(@"%p %p %p", person, personClass, personMetaClass);
        
        
    }
    return 0;
}

然后執(zhí)行可知

(lldb) p/x personClass
(Class) $3 = 0x0000000100008468 LYJPerson
(lldb) p/x studentClass2->super_class
(Class) $4 = 0x0000000100008468 LYJPerson
(lldb) 

可以驗證類對象的super_class就是指向父類的類對象

4.窺探struct objc_class的結構

下載蘋果源碼
https://opensource.apple.com/tarballs/objc4/

由于isa是class類型笆环,class類型結構又是typedef struct objec_class *Class
窺探struct objc_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;
/* Use `Class` instead of `struct objc_class *` */

可看到上部分代碼在 OBJC2已經(jīng)過時
現(xiàn)在 OBJC2是


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

..................
}


struct objc_object {
private:
    isa_t isa;

public:

    // ISA() assumes this is NOT a tagged pointer object
    Class ISA();
..................
}

可以簡化為

struct objc_class {
     Class ISA;
    Class superclass;
    cache_t cache;             // 方法緩存    class_data_bits_t bits;    // class_rw_t * plus custom rr/alloc flags 用于獲取具體的類信息

..................
}

通過

 class_rw_t *data() const {
        return bits.data();
    }
    
  class_rw_t* data() const {
        return (class_rw_t *)(bits & FAST_DATA_MASK);
    }
    
    
struct class_rw_t {
    // Be warned that Symbolication knows the layout of this structure.
    uint32_t flags;
    uint16_t witness;
#if SUPPORT_INDEXED_ISA
    uint16_t index;
#endif

  .........
  //方法列表
    const method_array_t methods() const {
        auto v = get_ro_or_rwe();
        if (v.is<class_rw_ext_t *>()) {
            return v.get<class_rw_ext_t *>()->methods;
        } else {
            return method_array_t{v.get<const class_ro_t *>()->baseMethods()};
        }
    }
//屬性列表
    const property_array_t properties() const {
        auto v = get_ro_or_rwe();
        if (v.is<class_rw_ext_t *>()) {
            return v.get<class_rw_ext_t *>()->properties;
        } else {
            return property_array_t{v.get<const class_ro_t *>()->baseProperties};
        }
    }
//協(xié)議列表
    const protocol_array_t protocols() const {
        auto v = get_ro_or_rwe();
        if (v.is<class_rw_ext_t *>()) {
            return v.get<class_rw_ext_t *>()->protocols;
        } else {
            return protocol_array_t{v.get<const class_ro_t *>()->baseProtocols};
        }
    }
    //只讀的信息
    const class_ro_t *ro() const {
        auto v = get_ro_or_rwe();
        if (slowpath(v.is<class_rw_ext_t *>())) {
            return v.get<class_rw_ext_t *>()->ro;
        }
        return v.get<const class_ro_t *>();
    }

    void set_ro(const class_ro_t *ro) {
        auto v = get_ro_or_rwe();
        if (v.is<class_rw_ext_t *>()) {
            v.get<class_rw_ext_t *>()->ro = ro;
        } else {
            set_ro_or_rwe(ro);
        }
    }

得到


struct class_ro_t {
    uint32_t flags;
    uint32_t instanceStart;
    uint32_t instanceSize;//實例對象占用空間
#ifdef __LP64__
    uint32_t reserved;
#endif

    const uint8_t * ivarLayout;
    
    const char * name;//類名
    method_list_t * baseMethodList;//方法列表
    protocol_list_t * baseProtocols;//協(xié)議信息
    const ivar_list_t * ivars;//成員變量的描述信息

    const uint8_t * weakIvarLayout;
    property_list_t *baseProperties;

   

具體的關系如下圖


image.png
?著作權歸作者所有,轉載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市咧织,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌籍救,老刑警劉巖习绢,帶你破解...
    沈念sama閱讀 218,682評論 6 507
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異蝙昙,居然都是意外死亡闪萄,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,277評論 3 395
  • 文/潘曉璐 我一進店門奇颠,熙熙樓的掌柜王于貴愁眉苦臉地迎上來败去,“玉大人,你說我怎么就攤上這事烈拒≡苍#” “怎么了?”我有些...
    開封第一講書人閱讀 165,083評論 0 355
  • 文/不壞的土叔 我叫張陵荆几,是天一觀的道長吓妆。 經(jīng)常有香客問我,道長吨铸,這世上最難降的妖魔是什么行拢? 我笑而不...
    開封第一講書人閱讀 58,763評論 1 295
  • 正文 為了忘掉前任,我火速辦了婚禮诞吱,結果婚禮上舟奠,老公的妹妹穿的比我還像新娘竭缝。我一直安慰自己,他們只是感情好沼瘫,可當我...
    茶點故事閱讀 67,785評論 6 392
  • 文/花漫 我一把揭開白布抬纸。 她就那樣靜靜地躺著,像睡著了一般松却。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上溅话,一...
    開封第一講書人閱讀 51,624評論 1 305
  • 那天晓锻,我揣著相機與錄音,去河邊找鬼飞几。 笑死砚哆,一個胖子當著我的面吹牛,可吹牛的內(nèi)容都是我干的屑墨。 我是一名探鬼主播躁锁,決...
    沈念sama閱讀 40,358評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼卵史!你這毒婦竟也來了战转?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 39,261評論 0 276
  • 序言:老撾萬榮一對情侶失蹤以躯,失蹤者是張志新(化名)和其女友劉穎槐秧,沒想到半個月后,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體忧设,經(jīng)...
    沈念sama閱讀 45,722評論 1 315
  • 正文 獨居荒郊野嶺守林人離奇死亡刁标,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,900評論 3 336
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了址晕。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片膀懈。...
    茶點故事閱讀 40,030評論 1 350
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖谨垃,靈堂內(nèi)的尸體忽然破棺而出启搂,到底是詐尸還是另有隱情,我是刑警寧澤刘陶,帶...
    沈念sama閱讀 35,737評論 5 346
  • 正文 年R本政府宣布狐血,位于F島的核電站,受9級特大地震影響易核,放射性物質發(fā)生泄漏匈织。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 41,360評論 3 330
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望缀匕。 院中可真熱鬧纳决,春花似錦、人聲如沸乡小。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,941評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽满钟。三九已至胜榔,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間湃番,已是汗流浹背夭织。 一陣腳步聲響...
    開封第一講書人閱讀 33,057評論 1 270
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留吠撮,地道東北人尊惰。 一個月前我還...
    沈念sama閱讀 48,237評論 3 371
  • 正文 我出身青樓,卻偏偏與公主長得像泥兰,于是被迫代替她去往敵國和親弄屡。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 44,976評論 2 355

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

  • 1鞋诗、Objective-C的面向對象都是基于什么數(shù)據(jù)結構實現(xiàn)的膀捷? 我們平時編寫的Objective-C代碼,底層實...
    Stago閱讀 212評論 0 0
  • 做過iOS開發(fā)的同學都應該知道我們平時編寫的OC代碼的底層實現(xiàn)都是通過C/C++實現(xiàn)的削彬,所以OC的對象都是基于C/...
    小帥798閱讀 458評論 0 0
  • 1全庸、Objective-C的面向對象都是基于什么數(shù)據(jù)結構實現(xiàn)的?由于我們平時編寫的Objective-C代碼吃警,底層...
    IIronMan閱讀 1,821評論 0 3
  • 1.oc對象的本質 我們平時編寫的Objective-C代碼糕篇,底層實現(xiàn)其實都是C\C++代碼啄育。編譯過程 所以Obj...
    Coder_Cat閱讀 724評論 0 1
  • 探尋OC對象的本質酌心,我們平時編寫的Objective-C代碼,底層實現(xiàn)其實都是C\C++代碼挑豌,如圖所示: OC的對...
    二豬哥閱讀 560評論 0 6