Objective - C 對象的本質(zhì)(一) OC語言的本質(zhì)及NSObject的內(nèi)存

(一)OC語言的本質(zhì)

  • 其實我們編寫的OC代碼,底層實現(xiàn)都是C/C++代碼
  • Objective-C的面向?qū)ο蠖际腔贑\C++的數(shù)據(jù)結(jié)構(gòu)實現(xiàn)的


    語言轉(zhuǎn)化流程

那么,是基于什么數(shù)據(jù)結(jié)構(gòu)實現(xiàn)的呢?結(jié)構(gòu)體

(1)將OC代碼轉(zhuǎn)換為C++代碼

xcrun -sdk iphoneos clang -arch arm64 -rewrite-objc OC源文件 -o 輸出的CPP文件

  • xcrun Xcode run簡寫
  • -sdk iphoneos 指定SDK 運行的OS平臺
  • clang 編譯器的一種
  • -arch arm64 聲明架構(gòu)代碼(i386 、armv7 、arm64)
  • -rewrite-objc 重寫objc代碼
  • -o 輸出
  • 如果需要鏈接其他框架,使用-framework參數(shù)。如-framework UIKit

示例:

xcrun -sdk iphoneos clang -arch arm64 -rewrite-objc main.m -o main.cpp
(2)NSObject的本質(zhì)

我們在.cpp文件中,找到這樣一段代碼,發(fā)現(xiàn)NSObject底層實現(xiàn)

struct NSObject_IMPL {//NSObject implementation
    Class isa;//很重要,后面會講到
};

另外我們在OC文件中,找到NSObject頭文件的定義

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

通過這個我們可以側(cè)面證明,NSObject的底層實現(xiàn)是通過C/C++的結(jié)構(gòu)體的數(shù)據(jù)結(jié)構(gòu)實現(xiàn)的

isa指針如果不好理解的話,isa指針可以類比數(shù)組的首元素,數(shù)組的首元素的地址也就是數(shù)組的首地址

(2)NSObject的占用的內(nèi)存大小

我們繼續(xù)查看isa,發(fā)現(xiàn)是這么定義的

typedef struct objc_class *Class;

這是一個指向結(jié)構(gòu)體的指針

如下面的代碼,我們通過runtime和malloc的方法得到NSObject所占用內(nèi)存大小的信息,為什么會有8和16的區(qū)別呢?

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

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        // insert code here...
        
        NSObject *obj = [[NSObject alloc] init];
          
        //獲得NSObject類的實例對象中成員變量所占內(nèi)存的大小(內(nèi)存對齊后)
        NSLog(@"%zd",class_getInstanceSize([obj class]));//8
        //獲得obj指針所指向內(nèi)存的大小
        NSLog(@"%zu",malloc_size((__bridge const void *)obj));//16
    }
    return 0;
}

查看runtime源碼找到class_getInstanceSize方法

  size_t class_getInstanceSize(Class cls)
  {
    if (!cls) return 0;
    return cls->alignedInstanceSize();
  }
  //再深入查看,發(fā)現(xiàn)這一條解釋 返回的是成員變量占用的內(nèi)存大小
  // Class's ivar size rounded up to a pointer-size boundary.
  uint32_t alignedInstanceSize() const {
     return word_align(unalignedInstanceSize());
  }

而在NSObject調(diào)用alloc方法實際上allocWithZone方法,在runtime中最后調(diào)用的是_objc_rootAllocWithZone方法,我們深入查看,最后找到instanceSize方法,是指定對象內(nèi)存大小的方法

NEVER_INLINE
id
_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);
}

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.
        //很重要?。。?        if (size < 16) size = 16;//如果不足16個字節(jié),強制分配16個字節(jié)
        return size;
    }

所以也就能解釋,系統(tǒng)給NSobject對象分配16個字節(jié),實際使用到的只有8個字節(jié),分別通過class_getInstanceSize函數(shù)和malloc_size函數(shù)可以知道

(3)容易混淆的兩個函數(shù)
  • class_getInstanceSize
  • malloc_size
    上面用到了兩個函數(shù),很容易混淆他們之間的區(qū)別
  1. 創(chuàng)建一個實例對象,(內(nèi)存對齊后)至少需要多少內(nèi)存?
    #import <objc/runtime.h>
    class_getInstanceSize([NSObject class]);

  2. 創(chuàng)建一個實例對象,實際上分配了多少內(nèi)存?
    #import <malloc/malloc.h>
    malloc_size((__bridge const void *)obj);

(3)窺探NSObject的內(nèi)存
也可以使用LLDB指令
(4)常用的LLDB指令
常用LLDB指令
讀取內(nèi)存方法不同 順序會有區(qū)別(高高低低原則)

具體窺探內(nèi)存方法以及LLDB指令的使用在 Swift-七、枚舉類型 可選項也有講述

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務。

友情鏈接更多精彩內(nèi)容