IOS 底層原理 類的本質--(2)

底層原理 類的本質

複習一下IOS 底層原理 對象的本質--(1),能夠看出來實例對象實際是上結構體,那麼這個結構體是有類指針和成員變量組成的。c++

//Person
@interface Person : NSObject
{
	@public
	int _age;//4bytes
	int _level;//4bytes
	int _code;//4bytes
}
@end
@implementation Person
@end
複製代碼

通過xcrun -sdk iphoneos clang -arch arm64 -rewrite-objc main.m -o main64.cpp編譯以後其實Person對象是:git

struct Person_IMPL {
	struct NSObject_IMPL NSObject_IVARS;
	int _age;
	int _level;
	int _code;
};
複製代碼

NSObject_IMPL結構體:github

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

那麼NSObject在內存中包括緩存

  • isa指針
  • 其餘成員變量

isa地址就是 instance的地址,其餘成員變量排在後邊,也就是 instance的地址就是 isa的地址。

那麼這個isa指向的究竟是什麼呢? 請往下繼續看: 先看下這段代碼:bash

NSObject *ob1=[[NSObject alloc]init];
NSObject *ob2=[[NSObject alloc]init];

Class cl1 = object_getClass([ob1 class]);
Class cl2 = object_getClass([ob2 class]);

Class cl3 = ob1.class;
Class cl4 = ob2.class;

Class cl5 = NSObject.class;

NSLog(@" %p %p %p %p %p",cl1,cl2,cl3,cl4,cl5);
//0x7fff8e3ba0f0 0x7fff8e3ba0f0 
//0x7fff8e3ba140 0x7fff8e3ba140 0x7fff8e3ba140
複製代碼

這代碼是輸出了幾個NSObject的對象的類和NSObject的類對象的地址,能夠看到cl1==cl2cl3==cl4==cl5iphone

Class的本質

咱們知道無論是類對象仍是元類對象,類型都是Class,class和mete-class的底層都是objc_class結構體的指針,內存中就是結構體。函數

Class objectClass = [NSObject class];        
Class objectMetaClass = object_getClass([NSObject class]);

複製代碼

點擊class來到內部,能夠發現post

typedef struct objc_class *Class;
複製代碼

class對象實際上是指向objc_class的結構體,所以咱們能夠說類對象或元類對象在內存中其實就是objc_class結構體。學習

來到objc_class內部,在源碼中常常看到這段源碼ui

struct objc_class {
    Class _Nonnull isa  OBJC_ISA_AVAILABILITY;//isa

#if !__OBJC2__
    Class _Nullable super_class                    //父類          OBJC2_UNAVAILABLE;
    const char * _Nonnull name                              //obj名字 OBJC2_UNAVAILABLE;
    long version                                            //版本 OBJC2_UNAVAILABLE;
    long info                                               //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_UNAVAILABLE,說明代碼已經不在使用了。那麼objc_class結構體內部結構究竟是什麼呢?經過objc搜尋runtime的內容能夠看到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();
    }
    void setData(class_rw_t *newData) {
        bits.setData(newData);
    }

    void setInfo(uint32_t set) {
        assert(isFuture()  ||  isRealized());
        data()->setFlags(set);
    }

    void clearInfo(uint32_t clear) {
        assert(isFuture()  ||  isRealized());
        data()->clearFlags(clear);
    }
    //**後邊省略
複製代碼

咱們發現這個結構體繼承 objc_object 而且結構體內有一些函數,由於這是c++結構體,在c上作了擴展,所以結構體中能夠包含函數。咱們來到objc_object內,截取部分代碼

struct objc_object {
private:
    isa_t isa;

public:

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

    // getIsa() allows this to be a tagged pointer object
    Class getIsa();

    // initIsa() should be used to init the isa of new objects only.
    // If this object already has an isa, use changeIsa() for correctness.
    // initInstanceIsa(): objects with no custom RR/AWZ
    // initClassIsa(): class objects
    // initProtocolIsa(): protocol objects
    // initIsa(): other objects
    void initIsa(Class cls /*nonpointer=false*/);
    void initClassIsa(Class cls /*nonpointer=maybe*/);
    void initProtocolIsa(Class cls /*nonpointer=maybe*/);
    void initInstanceIsa(Class cls, bool hasCxxDtor);
複製代碼

那麼咱們以前瞭解到的,類中存儲的類的成員變量信息,方法列表,協議列表,截取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;

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

    Class firstSubclass;
    Class nextSiblingClass;
**
//後邊省略
}
複製代碼

class_rw_t是經過bits.data()獲取的,截取bits.data()查看內部實現,而僅僅是bits&FAST_DATA_MASK

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

而成員變量則是存儲在class_ro_t內部中的,咱們來到class_ro_t內部查看:

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;//協議列表
    const ivar_list_t * ivars;//成員變量列表

    const uint8_t * weakIvarLayout;
    property_list_t *baseProperties;//屬性列表

    method_list_t *baseMethods() const {
        return baseMethodList;
    }
};
複製代碼

最後經過一張圖總結一下:

那麼咱們來證實一下: 咱們能夠自定義一下一個和系統同樣的結構體,那麼咱們當咱們強制轉化的時候,他們賦值會一一對應,此時咱們就能夠拿到結構體的內部的值。 下邊代碼是咱們自定義的值:

//
//  Header.h
//  day02-類的本質1
//
//  Created by Charlie on 2019/7/2.
//  Copyright © 2019 www.fgyong.cn. All rights reserved.
//

#ifndef Header_h
#define Header_h

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

# if __arm64__
# define ISA_MASK 0x0000000ffffffff8ULL
# elif __x86_64__
# define ISA_MASK 0x00007ffffffffff8ULL
# endif

#if __LP64__
typedef uint32_t mask_t;
#else
typedef uint16_t mask_t;
#endif
typedef uintptr_t cache_key_t;

struct bucket_t {
	cache_key_t _key;
	IMP _imp;
};

struct cache_t {
	bucket_t *_buckets;
	mask_t _mask;
	mask_t _occupied;
};

struct entsize_list_tt {
	uint32_t entsizeAndFlags;
	uint32_t count;
};

struct method_t {
	SEL name;
	const char *types;
	IMP imp;
};

struct method_list_t : entsize_list_tt {
	method_t first;
};

struct ivar_t {
	int32_t *offset;
	const char *name;
	const char *type;
	uint32_t alignment_raw;
	uint32_t size;
};

struct ivar_list_t : entsize_list_tt {
	ivar_t first;
};

struct property_t {
	const char *name;
	const char *attributes;
};

struct property_list_t : entsize_list_tt {
	property_t first;
};

struct chained_property_list {
	chained_property_list *next;
	uint32_t count;
	property_t list[0];
};

typedef uintptr_t protocol_ref_t;
struct protocol_list_t {
	uintptr_t count;
	protocol_ref_t list[0];
};

struct class_ro_t {
	uint32_t flags;
	uint32_t instanceStart;
	uint32_t instanceSize;  // instance對象佔用的內存空間
#ifdef __LP64__
	uint32_t reserved;
#endif
	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;
};

struct class_rw_t {
	uint32_t flags;
	uint32_t version;
	const class_ro_t *ro;
	method_list_t * methods;    // 方法列表
	property_list_t *properties;    // 屬性列表
	const protocol_list_t * protocols;  // 協議列表
	Class firstSubclass;
	Class nextSiblingClass;
	char *demangledName;
};

#define FAST_DATA_MASK 0x00007ffffffffff8UL
struct class_data_bits_t {
	uintptr_t bits;
public:
	class_rw_t* data() { // 提供data()方法進行 & FAST_DATA_MASK 操做
		return (class_rw_t *)(bits & FAST_DATA_MASK);
	}
};

/* OC對象 */
struct xx_objc_object {
	void *isa;
};

/* 類對象 */
struct fy_objc_class : xx_objc_object {
	Class superclass;
	cache_t cache;
	class_data_bits_t bits;
public:
	class_rw_t* data() {
		return bits.data();
	}
	
	fy_objc_class* metaClass() { // 提供metaClass函數,獲取元類對象
		// 上一篇咱們講解過,isa指針須要通過一次 & ISA_MASK操做以後才獲得真正的地址
		return (fy_objc_class *)((long long)isa & ISA_MASK);
	}
};
#endif /* Header_h */

複製代碼

這段代碼親測可用,直接複製本身新建.h文件導入'main.m'便可,將main.m改爲main.mm或者將其餘某一個.m改爲.mm運行就能夠運行了。

那麼咱們再拿出來經典的那張圖挨着分析isasuperclass的指向

instance 對象驗證

使用 p/x輸出obj16進制的地址,而後isa指針須要通過一次 & ISA_MASK操做以後才獲得真正的地址。實施以後:

//object

Printing description of student:
<Student: 0x1021729c0>
(lldb) p/x object->isa //查看isa指針地址
(Class) $0 = 0x001dffff8e3ba141 NSObject 
(lldb) p/x objectClass//輸出 objectClass的地址
(fy_objc_class *) $1 = 0x00007fff8e3ba140
(lldb) p/x 0x001dffff8e3ba141&0x00007ffffffffff8//計算得出object->isa真正的地址
(long) $2 = 0x00007fff8e3ba140 //0x00007fff8e3ba140是 objectClass地址和object->isa地址同樣


//person

Printing description of person: <Person: 0x102175300>
(lldb) p/x person->isa
(Class) $3 = 0x001d800100002469 Person
(lldb) p/x 0x001d800100002469&0x00007ffffffffff8
(long) $4 = 0x0000000100002468
(lldb) p/x personClass
(fy_objc_class *) $5 = 0x0000000100002468//isa 和personclass地址都是0x0000000100002468

//student

(lldb) p/x student->isa
(Class) $6 = 0x001d8001000024b9 Student
(lldb) p/x 0x001d8001000024b9&0x00007ffffffffff8
(long) $7 = 0x00000001000024b8
(lldb) p/x studentClass
(fy_objc_class *) $8 = 0x00000001000024b8//studentclass 和isa地址都是0x00000001000024b8
(lldb) 
複製代碼

從面的輸出結果中咱們能夠發現instance對象中確實存儲了isa指針和其成員變量,同時將instance對象的isa指針通過&運算以後計算出的地址確實是其相應類對象的內存地址。由此咱們證實isa,superclass指向圖中的1,2,3號線。

class 對象驗證

接着咱們來看class對象,一樣經過上一篇文章,咱們明確class對象中存儲着isa指針,superclass指針,以及類的屬性信息,類的成員變量信息,類的對象方法,和類的協議信息,而經過上面對object源碼的分析,咱們知道這些信息存儲在class對象的class_rw_t中,咱們經過強制轉化來窺探其中的內容

//objectClass and objectMetaClass

(lldb) p/x objectClass->isa
(__NSAtom *) $6 = 0x001dffff8e3ba0f1
(lldb) p/x 0x001dffff8e3ba0f1&0x00007ffffffffff8
(long) $7 = 0x00007fff8e3ba0f0
(lldb) p/x objectMetaClass
(fy_objc_class *) $8 = 0x00007fff8e3ba0f0

//personClass and personMetaClass

(lldb) p/x personClass->isa
(__NSAtom *) $9 = 0x001d800100002441
(lldb) p/x personMetaClass
(fy_objc_class *) $10 = 0x0000000100002440
(lldb) p/x 0x001d800100002441&0x00007ffffffffff8
(long) $11 = 0x0000000100002440

//sutdentClass and studentMetaClass

(lldb) p/x studentClass->isa
(__NSAtom *) $12 = 0x001d800100002491
(lldb) p/x 0x001d800100002491&0x00007ffffffffff8
(long) $13 = 0x0000000100002490
(lldb) p/x studentMetaClass
(fy_objc_class *) $14 = 0x0000000100002490

複製代碼

有此結果得知objectMetaClass==objectClass->isa==0x00007fff8e3ba0f0,personClass->isa==personMetaClass==0x0000000100002440,studentClass->isa==studentMetaClass==0x0000000100002490。 由此咱們證實isa,superclass指向圖中,isa指針的4,5,6號線,以及superclass指針的7,8,9號線。

meta-class對象驗證

最後咱們來看meta-class元類對象,上文提到meta-class中存儲着isa指針,superclass指針,以及類的類方法信息。同時咱們知道meta-class元類對象與class類對象,具備相同的結構,只不過存儲的信息不一樣,而且元類對象的isa指針指向基類的元類對象,基類的元類對象的isa指針指向本身。元類對象的superclass指針指向其父類的元類對象,基類的元類對象的superclass指針指向其類對象。 與class對象相同,咱們一樣經過模擬對person元類對象調用.data函數,即對bits進行&FAST_DATA_MASK(0x00007ffffffffff8UL)運算,並轉化爲class_rw_t

// objectMetaClass->superclass = 0x00007fff8e3ba140  NSObject
//objectMetaClass->isa =   0x00007fff8e3ba0f0
//objectMetaClass = 0x00007fff8e3ba0f0

(lldb) p/x objectMetaClass->superclass
(Class) $20 = 0x00007fff8e3ba140 NSObject
(lldb) p/x objectMetaClass->isa
(__NSAtom *) $21 = 0x001dffff8e3ba0f1
(lldb) p/x 0x001dffff8e3ba0f1&0x00007ffffffffff8
(long) $22 = 0x00007fff8e3ba0f0
(lldb) p/x objectMetaClass
(fy_objc_class *) $23 = 0x00007fff8e3ba0f0

// personMetaClass->superclas=0x00007fff8e3ba0f0
//personMetaClass->isa=0x00007fff8e3ba0f0
//personMetaClass = 0x0000000100002440

(lldb) p/x personMetaClass->superclass
(Class) $25 = 0x00007fff8e3ba0f0
(lldb) p/x personMetaClass->isa
(__NSAtom *) $26 = 0x001dffff8e3ba0f1
(lldb) p/x personMetaClass
(fy_objc_class *) $30 = 0x0000000100002440

// studentMetaClass->superclas=0x0000000100002440
//studentMetaClass->isa=0x00007fff8e3ba0f0


(lldb) p/x studentMetaClass->superclass
(Class) $27 = 0x0000000100002440
(lldb) p/x studentMetaClass->isa
(__NSAtom *) $28 = 0x001dffff8e3ba0f1
(lldb) p/x 0x001dffff8e3ba0f1 & 0x00007ffffffffff8
(long) $29 = 0x00007fff8e3ba0f0
複製代碼

有上面能夠看出,studentMetaClass->isa,personMetaClass->isa,objectMetaClass->isa結果mask以後都是0x00007fff8e3ba0f0,與p/x objectMetaClass結果一致,則驗證了13,14,15號線,studentMetaClass->superclass =0x0000000100002440,personMetaClass = 0x0000000100002440驗證12號線,personMetaClass->isa=0x00007fff8e3ba0f0objectMetaClass = 0x00007fff8e3ba0f0驗證了11號線,objectMetaClass->superclass = 0x00007fff8e3ba140 NSObject驗證10號線。

總結:

對象的isa指向哪裏?

  • instance對象的isa指向class對象
  • class對象的isa指向meta-class對象
  • meta-class對象的isa指向基類的meta-class對象
  • class和meta-class的內存結構同樣的,只是值不同

OC的類信息存放在哪裏?

  • 對象方法、屬性、成員變量、協議信息存放在class對象中
  • 類方法存放在meta-class對象中
  • 成員變量具體值存放在instance對象中

資料下載


最怕一輩子碌碌無爲,還安慰本身平凡難得。

本文章之因此圖片比較少,我以爲仍是跟着代碼敲一遍,印象比較深入。

相關文章
相關標籤/搜索