Objective-C基礎之一(深刻理解OC對象)

OC對象的本質

日常咱們使用Objective-C語法來編寫代碼,可是它的底層其實都是C或C++代碼。Objective-C實際上是在C語言的基礎上增長了面向對象的特性。咱們能夠經過如下命令將Objective-C代碼轉換成C++代碼:ios

xcrun -sdk iphoneos clang -arch arm64 -rewrite-objc OC文件 -o 輸出目標cpp文件
複製代碼

若是OC文件須要連接其它的框架,可使用-framework參數:面試

xcrun -sdk iphoneos clang -arch arm64 -rewrite-objc OC文件 -o 輸出目標cpp文件 -framework 框架名稱
複製代碼

與此同時,還須要下載runtime的源碼,經過objc源碼地址下載最新版本的objc源碼,以便於後續使用。數組

OC對象的底層實現

在開發過程當中,最經常使用到的就是OC的對象。幾乎全部的類對象都是NSObject的子類,可是拋開OC的限制,NSObject底層是如何實現的呢?上文說到,全部的OC代碼最後都會轉換成C代碼,因此咱們經過一個例子來認識NSObject的底層實現。緩存

  • 首先,建立一個XLPerson對象
@interface XLPerson : NSObject

@end

@implementation XLPerson


@end
複製代碼
  • 進入XLPerson.m文件所在目錄,使用以下指令,將XLPerson.m文件轉換成XLPerson_cpp.cpp文件
xcrun -sdk iphoneos clang -arch arm64 -rewrite-objc XLPerson.m -o XLPerson_cpp.cpp
複製代碼
  • 在生成的XLPerson_cpp.cpp文件中搜索XLPerson,就能找到如下結構體定義
struct XLPerson_IMPL {
	struct NSObject_IMPL NSObject_IVARS;
};
複製代碼

XLPerson_IMPL中包含一個結構體成員NSObject_IVARS,它是NSObject_IMPL類型,查看NSObject_IMPL的代碼以下:bash

struct NSObject_IMPL {
	Class isa;
};
複製代碼

由此能夠看出,OC中的對象其實就是經過結構體來實現的。在NSObject_IMPL包含了一個Class類型的成員isa。繼續查看Class的定義:app

/// An opaque type that represents an Objective-C class.
typedef struct objc_class *Class;
複製代碼

能夠發現其實Class就是一個objc_class類型的結構體指針。在最新的objc4的源碼中的objc-runtime-new.h文件中,能夠找到最新的objc_class的定義以下:框架

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

    class_rw_t *data() { 
        return bits.data();
    }
}
複製代碼

objc_class繼承自結構體objc_object,而結構體objc_object的具體定義以下,內部只有一個isa指針iphone

/// Represents an instance of a class.
struct objc_object {
    Class _Nonnull isa  OBJC_ISA_AVAILABILITY;
};
複製代碼

因爲繼承關係,結構體objc_class天然也就繼承了objc_object的isa指針,因此objc_class也能夠轉換成以下寫法:函數

struct objc_class {
    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

    class_rw_t *data() { 
        return bits.data();
    }
}
複製代碼
  • isa是繼承自objc_object的屬性(具體做用後文會說明)
  • superclass表示當前類的父類
  • cache則表明方法緩存。
  • bits是class_data_bits_t類型的屬性,用來存放類的具體信息。

查看class_data_bits_t的具體實現以下:學習

//此處只列出核心的代碼
struct class_data_bits_t {
    ......
    class_rw_t* data() {
        return (class_rw_t *)(bits & FAST_DATA_MASK);
    }
    ......
}

複製代碼

這時候發現了經過bits的內部函數data()能夠拿到class_rw_t類型的數據,查看class_rw_t的源碼以下:

struct class_rw_t {
    // Be warned that Symbolication knows the layout of this structure.
    uint32_t flags;
    uint32_t version;

    const class_ro_t *ro;        //只讀的屬性ro

    method_array_t methods;      //方法列表
    property_array_t properties; //屬性列表
    protocol_array_t protocols;  //協議列表

    Class firstSubclass;
    Class nextSiblingClass;
}

複製代碼

在結構體class_rw_t中存放着

  • 方法列表methods
  • 屬性列表properties
  • 協議列表protocols。
  • 一個class_ro_t類型的只讀變量ro

繼續查看class_ro_t的源碼以下:

struct class_ro_t {
    uint32_t flags;
    uint32_t instanceStart;
    uint32_t instanceSize;  //當前instance對象佔用內存的大小
    const uint8_t * ivarLayout;
    const char * name;              //類名
    method_list_t * baseMethodList; //基礎的方法列表
    protocol_list_t * baseProtocols;//基礎協議列表
    const ivar_list_t * ivars;      //成員變量列表
    
    const uint8_t * weakIvarLayout;
    property_list_t *baseProperties;//基本屬性列表
}

複製代碼

此處就不得不說class_rw_tclass_ro_t的區別了,class_ro_t中存放着類最原始的方法列表,屬性列表等等,這些在編譯期就已經生成了,並且它是隻讀的,在運行期沒法修改。而class_rw_t不只包含了編譯器生成的方法列表、屬性列表,還包含了運行時動態生成的方法和屬性。它是可讀可寫的。至於class_rw_tclass_ro_t更深層次的區別,我會放在介紹runtime的時候詳細說明。

OC對象的內存分配

allocWithZone

在iOS中通常使用以下[[NSObject alloc] init]建立對象,其中[NSObject alloc]就是爲NSObject分配內存空間,下面,咱們就從源碼入手,來理解OC對象是如何分配內存的。

  • [NSObject alloc]實際上是調用allocWithZone方法來分配內存空間,因此咱們查看objc源碼中的NSObject.mm文件。找到_objc_rootAllocWithZone函數:
id 
_objc_rootAllocWithZone(Class cls, malloc_zone_t *zone)
{
    id obj;

#if __OBJC2__
    // allocWithZone under __OBJC2__ ignores the zone parameter
    (void)zone;
    obj = class_createInstance(cls, 0);
#else
    if (!zone) {
        obj = class_createInstance(cls, 0);
    }
    else {
        obj = class_createInstanceFromZone(cls, 0, zone);
    }
#endif

    if (slowpath(!obj)) obj = callBadAllocHandler(cls);
    return obj;
}
複製代碼
  • 找到其中分配內存的方法class_createInstance(cls, 0);
id class_createInstance(Class cls, size_t extraBytes)
{
    return _class_createInstanceFromZone(cls, extraBytes, nil);
}
複製代碼
  • 找到_class_createInstanceFromZone(cls, extraBytes, nil)方法,因爲方法較長,此處只展現核心代碼:
id
_class_createInstanceFromZone(Class cls, size_t extraBytes, void *zone, 
                              bool cxxConstruct = true, 
                              size_t *outAllocatedSize = nil)
{
    ......
    
    size_t size = cls->instanceSize(extraBytes);
    if (outAllocatedSize) *outAllocatedSize = size;

    id obj;
    if (!zone  &&  fast) {
        obj = (id)calloc(1, size);
        if (!obj) return nil;
        obj->initInstanceIsa(cls, hasCxxDtor);
    } 
    
    ......
}
複製代碼

能夠看出,其中真正用來分配內存的是C函數calloc,calloc函數傳入了兩個參數,第一個參數表示對象的個數,第二個參數size表示對象佔據的內存字節數。所以size就表示當前對象所須要的內存大小。

  • 這裏的size變量表示當前對象所佔用的內存大小,能夠查看cls->instanceSize(extraBytes)屬性內部實現,以下:
// May be unaligned depending on class's ivars. uint32_t unalignedInstanceSize() { assert(isRealized()); return data()->ro->instanceSize; } // Class's ivar size rounded up to a pointer-size boundary.
    uint32_t alignedInstanceSize() {
        return word_align(unalignedInstanceSize());
    }

    size_t instanceSize(size_t extraBytes) {
        size_t size = alignedInstanceSize() + extraBytes;
        // CF requires all objects be at least 16 bytes.
        if (size < 16) size = 16;
        return size;
    }

複製代碼

其中cls->unalignedInstanceSize()表示未進行內存對齊的內存大小,cls->alignedInstanceSize()是對未對齊的內存進行內存對齊操做,獲得最終所需的內存大小。

這裏有個細節,就是執行對齊操做獲得的內存大小若是小於16個字節,那麼最後分配的內存大小爲16個字節,也就是說,咱們建立對象時,分配的內存最少是16個字節。

OC對象的內存分配

獲取內存大小的方法?

在iOS中,咱們能夠經過三種方式來獲取一個對象的內存大小。

sizeof

sizeof,它其實不是一個函數,而是一個運算符,它和宏定義相似,在編譯期就將傳入的類型轉換成具體的佔用內存的大小。例如int是4個字節,那麼sizeof(int)在編譯期就會直接被替換成4

注意:sizeof須要傳入一個類型過去,它返回的是一個類型所佔用的內存空間

class_getInstanceSize

class_getInstanceSize(Class _Nullable cls),傳入一個Class類型的對象就能獲得當前Class所佔用的內存大小。例如,class_getInstanceSize([NSObject class]),最後返回的是8,也就說明NSObject對象在內存中佔用8個字節,並且因爲NSObject最後會轉化成結構體NSObject_IMPL,並且內部只有一個isa指針,因此也就能夠理解爲isa指針佔用8個字節的存儲空間。

class_getInstanceSize函數內部其實就是調用alignedInstanceSize函數獲取到對象所須要的真實內存大小。

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

在調用calloc函數進行內存分配的時候,是將alignedInstanceSize的值看成參數賦值給calloc函數,所以calloc函數能夠有以下寫法:

id
_class_createInstanceFromZone(Class cls, size_t extraBytes, void *zone, 
                              bool cxxConstruct = true, 
                              size_t *outAllocatedSize = nil)
{
    ......
    size_t size = class_getInstanceSize(cls);
    if (outAllocatedSize) *outAllocatedSize = size;

    id obj;
    if (!zone  &&  fast) {
        obj = (id)calloc(1, size);
        if (!obj) return nil;
        obj->initInstanceIsa(cls, hasCxxDtor);
    } 
    ......
}
複製代碼

由此能夠看出,class_getInstanceSize(Class _Nullable cls)所返回的實際上是對象實際所須要的內存大小。

malloc_size

malloc_size(const void *ptr)函數,傳入const void *類型的參數,就能夠獲取到當前操做系統所分配的內存大小。例如:仍是利用NSObject來進行測試,malloc_size((__bridge const void *)([[NSObject alloc] init])),將NSObject類型的實例對象做爲參數,最後獲得的值爲16,和咱們以前使用class_getInstanceSize([NSObject class])獲得的8不相同。

這是由於在iOS中,在分配內存時,若是對象所須要的內存大小小於16個字節,那麼就分配給這個對象16個字節的內存空間。也就是每一個對象至少分配16個字節的內存空間

size_t instanceSize(size_t extraBytes) {
        size_t size = alignedInstanceSize() + extraBytes;
        // CF requires all objects be at least 16 bytes.
        if (size < 16) size = 16;
        return size;
    }
複製代碼

三種獲取內存方法對比

  • 首先,建立對象XLPerson,添加3個屬性,以下
@interface XLPerson : NSObject{
    int _height;
    int _age;
    long _num;
}
複製代碼
  • 在main函數中建立XLPerson實例對象,而且使用上面的三種方式分別獲取XLPerson類的內存大小:
#import <Foundation/Foundation.h>
#import <objc/runtime.h>
#import <malloc/malloc.h>

@interface XLPerson : NSObject{
    @public
    int _height;
    int _age;
    long _num;
}

@end

@implementation XLPerson

@end

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        
        XLPerson *p = [[XLPerson alloc] init];
        p->_height = 10;
        p->_age = 20;
        p->_num = 25;
        
        NSLog(@"sizeof --> %lu", sizeof(p));
        
        NSLog(@"class_getInstanceSize --> %lu", class_getInstanceSize([XLPerson class]));
        
        NSLog(@"malloc_size --> %lu", malloc_size((__bridge const void *)(p)));
    }
    return 0;
}
複製代碼
  • 運行程序,打印出結果以下:

能夠看出此時sizeof(p)返回8個字節,class_getInstanceSize返回24個字節,malloc_size則返回32個字節。3個方法返回的內存大小都不同,這是爲何呢?

sizeof(p)爲何只返回8個字節呢?

其實sizeof(p)返回8個字節,這個很好理解,由於sizeof傳入的是p,而p在此處表示的是一個指向XLPerson實例對象的一個指針,在iOS中,指針類型所佔用的內存大小爲8個字節。所以sizeof(p)所返回的並非XLPerson對象的內存大小。

要想使用sizeof獲取到XLPerson對象的內存大小,就須要知道XLPerson最終會轉換成什麼類型。經過上文的學習,咱們知道,XLPerson內部實際上是一個結構體,經過xcrun指令將文件轉換成.cpp文件

xcrun -sdk iphoneos clang -arch arm64 -rewrite-objc main.m -o main.cpp
複製代碼

分析main.cpp文件能夠得出,XLPerson最終會轉換成以下結構體類型

struct NSObject_IMPL {
    Class isa;
};

struct XLPerson_IMPL {
    struct NSObject_IMPL NSObject_IVARS;
    int _height;
    int _age;
    long _num;
};
複製代碼

此時調用sizeof(struct XLPerson_IMPL)就能夠得出struct XLPerson_IMPL類型所佔用的內存爲24字節,其實也就是XLPerson所佔用的內存是24個字節。

由此可看出運算符sizeof(struct XLPerson_IMPL)和函數class_getInstanceSize([XLPerson class]返回的是對象真正所須要的內存大小。

爲什麼malloc_size返回的內存大小和對象實際所需內存不一樣?

在瞭解malloc_size函數以前,咱們先來分析一下XLPerson內部結構體所須要的真實內存大小。

struct XLPerson_IMPL {
    struct NSObject_IMPL NSObject_IVARS;
    int _height;
    int _age;
    long _num;
};
複製代碼
  • 首先咱們知道NSObject_IMPL內部其實只有一個isa指針,所以它所佔用的內存是8個字節
  • int類型的變量_height佔用4個字節
  • int類型的變量_age佔用4個字節
  • long類型的變量_num佔用8個字節

因此,單純從結構體層面分析的話,咱們能夠看出XLPerson_IMPL結構體所須要的內存是24個字節,這和上文的sizeof(struct XLPerson_IMPL)以及函數class_getInstanceSize([XLPerson class]返回的內存大小一致。因而可知,XLPerson所須要的內存就是24個字節。

但是爲何malloc_size所返回的內存大小確是32個字節呢?這就要說到內存對齊操做

結構體的內存對齊操做

首先,咱們先將上文中提到的XLPerson屬性進行修改,去掉其中的_age屬性

@interface XLPerson : NSObject{
    @public
    int _height;
    long _num;
}
@end
複製代碼

而後從新運行項目,能夠看到XLPerson實際佔用的內存仍是24個字節,而經過分析咱們能夠發現XLPerson只須要20個字節的內存空間。

這就是結構體內存對齊操做所致使的,也就是上文中所說的alignedInstanceSize函數的做用。那麼什麼是結構體的內存對齊操做?

結構體不像數組,結構體中能夠存放不一樣類型的數據,它的大小也不是簡單的各個數據成員大小之和,限於讀取內存的要求,而是每一個成員在內存中的存儲都要按照必定偏移量來存儲,根據類型的不一樣,每一個成員都要按照必定的對齊數進行對齊存儲,最後整個結構體的大小也要按照必定的對齊數進行對齊。

結構體的內存對齊規則以下:

  • 第一個成員的首地址爲0
  • 每一個成員的首地址是自身大小的整數倍
  • 結構體的內存總大小是其成員中所含最大類型的整數倍

這就是爲什麼XLPerson的內存大小爲24個字節的緣由。

iOS系統的內存對齊操做

既然XLPerson的內存佔用爲24個字節,那麼爲何系統會給它分配32個字節呢?其實在iOS系統中也存在內存對齊操做。

咱們能夠經過打印內存信息來查看是否分配了32個字節,依舊是使用上面的例子

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        
        XLPerson *p = [[XLPerson alloc] init];
        p->_height = 1;
        p->_num = 3;
    }
    return 0;
}
複製代碼
  • 斷點狀態下,使用po p獲取到p的內存地址爲0x1005b4fd0
(lldb) po p
<XLPerson: 0x1005b4fd0>
複製代碼
  • 打開Xcode下Debug->Debug Workflow->View Memory,輸入剛剛獲取到的內存地址,能夠獲得對象p的內存分配狀況

其中前8個字節存儲着isa指針,藍色框中的四個字節存放着_height=1,而綠色框中的8個字節存放着_num=3,這裏由於結構體內存對齊原則,因此_num=3的內存地址從第17個字節開始,整個紅色框的32個字節,就是系統分配給XLPerson實例對象的內存空間,這也證實了malloc_size((__bridge const void *)(p))返回的確實是系統分配給p對象的內存空間。

OC對象的分類

OC對象主要分爲3種

  • instance實例對象
  • class類對象
  • mata-class元類對象

instance對象

instance對象就是經過alloc操做建立出來的對象,每次調用alloc操做都會建立出不一樣的instance對象,它們擁有各自獨立分配的內存空間。例如上文中使用的XLPerson的實例對象

XLPerson *p1 = [[XLPerson alloc] init];
XLPerson *p2 = [[XLPerson alloc] init];
複製代碼

其中p一、p2就是實例對象,在內存中能夠同時擁有多個同一類對象的實例對象。它們各自擁有一塊內存空間,用來存儲獨有的信息。實例對象內部存放的內容以下(以XLPerson的實例對象爲例):

XLPerson *p1 = [[XLPerson alloc] init];
p->_height = 10;
p->_num = 25;
複製代碼
  • isa指針,指向它的類對象
  • _height = 10
  • _num = 25

由於經過[XLPerson alloc]就能建立一個實例對象,因此每一個實例對象內部會存放着一個isa指針,指向它的類對象,還存放着定義好的其它的成員變量的具體值。

class類對象

類對象是將具備類似屬性和方法的對象抽象出來,從而造成類對象。它能夠定義一些類似的方法和屬性,不一樣的實例對象去引用類對象的屬性或者方法,能減小代碼的重複率。

運行以下代碼:

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        XLPerson *p1 = [[XLPerson alloc] init];
        XLPerson *p2 = [[XLPerson alloc] init];
        
        Class c1 = [XLPerson class];
        Class c2 = [p1 class];
        Class c3 = [p2 class];
        Class c4 = object_getClass(p1);
        Class c5 = object_getClass(p2);
        
        NSLog(@"\n c1 -> %p,\n c2 -> %p,\n c3 -> %p,\n c4 -> %p,\n c5 -> %p", c1, c2, c3, c4, c5);
    }
    return 0;
}
複製代碼

能夠獲得結果爲:

經過結果能夠發現,全部的class對象的內存地址都是相同的,這也就說明在內存中只有一個class對象,不論是使用上面的哪一種方法獲取到的class對象都是同一個。

class對象內部其實就是一個object_class的結構體,具體的結構定義在上文已經介紹過,這裏只列舉出class對象存儲的主要信息:

  • isa指針
  • superClass
  • 屬性信息(properties),存放着屬性的名稱,屬性的類型等等,這些信息在內存中只須要存放一份
  • 對象方法信息(methods)
  • 協議信息(protocols)
  • 成員變量信息等等(ivars)

mata-class元類對象

元類其實也是一個class類型的對象,它內部的結構和類對象一致,可是元類對象中只存放了以下信息:

  • isa指針
  • superClass
  • 類方法信息(class method)

元類和類同樣,在內存中只會存在一個元類對象。能夠經過runtime的方法獲取元類對象

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        XLPerson *p1 = [[XLPerson alloc] init];
        XLPerson *p2 = [[XLPerson alloc] init];
        
        Class c1 = object_getClass(p1);
        Class c2 = object_getClass(p2);
        
        Class mataC1 = object_getClass(c1);
        Class mataC2 = object_getClass(c2);
        
        BOOL c1_isMataClass = class_isMetaClass(c1);
        BOOL c2_isMataClass = class_isMetaClass(c2);
        BOOL mataC1_isMataClass = class_isMetaClass(mataC1);
        BOOL mataC2_isMataClass = class_isMetaClass(mataC2);
        NSLog(@"\n c1_isMataClass:%d,\n c2_isMataClass:%d,\n mataC1_isMataClass:%d,\n mataC2_isMataClass:%d"
              ,c1_isMataClass, c2_isMataClass, mataC1_isMataClass, mataC2_isMataClass);
        NSLog(@"\n c1 -> %p,\n c2 -> %p,\n mataC1 -> %p,\n mataC2 -> %p",
              c1, c2, mataC1, mataC2);
    }
    return 0;
}
複製代碼

調用結果以下:

在上圖中,c1和c2都是類對象,因此返回0,mataC1和mataC2都是元類對象,因此返回1。同時mataC1和mataC2的內存地址徹底相同,這也說明了元類對象在內存中確實只存在一份。

instance對象、class對象和mata-class對象的關係

上文屢次提到,在Class對象內部都會有一個isa指針,那麼這個isa指針的做用是什麼呢?其實isa指針是instance對象、class對象和mata-class對象之間的橋樑。

isa指針的做用

  • instance對象的isa指針指向class對象,並且上文也說到,在instance對象中只存儲了isa指針和具體的屬性的值,當咱們調用instance對象的實例方法時,實際上是經過isa指針找到它的類對象,在類對象的對象方法列表(methods)中查找到方法的實現,並調用。
  • class對象的isa指針指向mata-class對象,當調用類方法時,會經過類對象的isa指針找到它的元類對象mata-class,而後在mata-class的方法列表中找到對應類方法的實現並執行。

superClass指針的做用

superClass其實就是指向class對象或者mata-class對象的父類,下面咱們以一個簡單的例子來具體說明:

@interface XLPerson : NSObject

- (void)run;
+ (void)sleep;

@end

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        XLPerson *p1 = [[XLPerson alloc] init];
        [p1 run];
        [XLPerson sleep];
    }
}
複製代碼

class對象中superClass指針的做用

XLPerson繼承自NSObject,而且聲明瞭一個類方法sleep()和一個對象方法run(),當p1調用對象方法run()時

  • 首先會經過實例對象p1內部的的isa指針找到它的類對象。在類對象的方法列表中查找run()方法。
  • 若是類對象的方法列表中沒有此方法的實現,那麼會經過類對象的superClass指針找到它的父類對象,在此處就是NSObject對象,而且在NSObject對象的方法列表中尋找run()方法的實現,而後調用。

mata-class對象中superClass指針的做用

仍是以上面的例子來講明,當XLPerson調用類方法sleep()時

  • 首先經過XLPerson的isa指針找到它的元類對象,在元類對象的方法列表中尋找sleep()方法。
  • 若是在XLPerson的元類對象中沒有找到sleep()方法,那麼會經過XLPerson的元類對象的superClass指針找到XLPerson的父類對象的元類對象,此處就是NSObject的元類對象,在NSObject元類對象的方法列表中找到sleep()方法並執行。

isa、superClass總結

首先先看一張很是經典的描述instance對象、類對象以及元類對象之間關係的圖片。途中虛線表明isa指針,實線表明superClass指針。

  • instance對象的isa指針指向它的class對象
  • class對象的isa指針指向它的mata-class對象
  • mata-class對象的isa指向基類對象mata-class
  • class對象的superClass指向它的父類的class對象,基類的class對象的superClass指向nil
  • mata-class的superClass指向父類對象的mata-class,基類對象的mata-class的superClass指向基類對象自身(此處是比較特殊的地方)
  • 實例方法查找路線
    • 首先會經過isa指針到class對象中找
    • 若是找不到,經過superClass到父類的class對象中找
    • 若是還找不到,再到基類的class對象中查找
  • 類方法的查找路線
    • 首先會經過類對象的isa指針到mata-class中查找
    • 若是mata-class中找不到,經過superClass到父類的元類對象中查找
    • 若是在父類的元類對象中找不到,就到基類對象的元類對象中查找
    • 若是基類的元類對象中找不到,那麼會到基類對象中查找。

OC對象面試題

一個NSObject對象佔用了多少內存?

系統給一個NSObject對象分配了16個字節的內存空間(經過malloc_size函數申請內存),可是NSObject對象內部只有一個isa指針,因此它實際使用到了8個字節的內存,而因爲ios的內存對齊原則,系統最少分配16個字節的內存空間。

能夠經過class_getInstanceSize函數來獲取NSObject佔用內存大小

對象的isa指向哪裏?有什麼做用?

  • 實例對象的isa指針指向class對象
  • class對象的isa指針指向mata-class對象
  • meta-class對象的isa指針指向基類的meta-class對象

OC的類的信息(方法、屬性、成員變量等等)分別存放在哪?

  • OC中實例對象的方法、屬性、成員變量、協議信息等等存放在class對象中
  • 類方法存放在mata-class中
  • 成員變量的具體值存放在實例對象中,由於成員變量的描述信息好比它的類型是int等等,在內存中只需存儲一份,因此將屬性描述信息存放在類對象中,可是成員變量的值每一個實例變量都不相同,因此每一個實例對象存放一份

結束語

以上內容純屬我的理解,若是有什麼不對的地方歡迎留言指正。

一塊兒學習,一塊兒進步~~~

相關文章
相關標籤/搜索