「OC」源碼學習——對象的底層探索
前言
上次我們說到了源碼里面的調用順序,現在我們繼續了解我們上一篇文章沒有講完的關于對象的內容函數,完整了解對象的產生對于isa賦值以及內存申請的內容
函數內容
先把_objc_rootAllocWithZone
函數的內容先貼上來
static ALWAYS_INLINE id
_class_createInstance(Class cls, size_t extraBytes,int construct_flags = OBJECT_CONSTRUCT_NONE,bool cxxConstruct = true,size_t *outAllocatedSize = nil)
{ASSERT(cls->isRealized());//斷言確保類已加載并完成內存布局(即 "realized" 狀態)bool hasCxxCtor = cxxConstruct && cls->hasCxxCtor(); //查看父類是否存在C++的析構函數bool hasCxxDtor = cls->hasCxxDtor();//查看是否存在C++的析構函數bool fast = cls->canAllocNonpointer();//是否支持指針優化size_t size;size = cls->instanceSize(extraBytes);//計算所需要的字節數if (outAllocatedSize) *outAllocatedSize = size;//返回計算的字節數id obj = objc::malloc_instance(size, cls);//根據類對象申請對應的內存if (slowpath(!obj)) {if (construct_flags & OBJECT_CONSTRUCT_CALL_BADALLOC) {return _objc_callBadAllocHandler(cls);}return nil;}if (fast) {obj->initInstanceIsa(cls, hasCxxDtor);//如果是優化的isa指針先進入進行判斷再進入initIsa} 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;//添加一個flagreturn object_cxxConstructFromClass(obj, cls, construct_flags);
}
我們來看看instanceSize
這個函數究竟實現了什么,按照instanceSize
->fastInstanceSize
->align16
,依次步入,我們可以看到一下內容
static inline size_t align16(size_t x) {return (x + size_t(15)) & ~size_t(15);
}
這個程序就是一個16字節對齊的算法,先拋開這個算法,若是由我們來設計這個算法我們的思路是什么呢?最簡單的就是
(x + 15) / 16 * 16
當然很快我們就意識到了,使用乘法和除法運算的效率和位運算相比實在太低了,正好16是2的4次方,位運算一樣的解決啊
(x + 15) >> 4 << 4
OK現在設計完了我們在來看看蘋果設計的代碼
(x + 15) & ~15
~
有按位取反的意思通過按位與操作,將低 4 位(二進制 1111
)清零,得到最近的 16 的倍數。用size(15)
是為了兼容32位和64機型,因為size_t
是無符號整數類型,其位數由平臺決定(32 位系統為 4 字節,64 位系統為 8 字節)。當 x
是 size_t
類型時,size_t(15)
確保運算中的右操作數(15)與左操作數(x
)類型一致,避免隱式轉換帶來的風險
內存內容探究
GGObject *obj = [GGObject alloc];
NSLog(@"%lu", sizeof(obj));
NSLog(@"%lu", class_getInstanceSize(obj.class));
NSLog(@"%lu", malloc_size((__bridge const void*)(obj)));
根據這個內容我們可以看到自定義類對應的大小
當我們的類被編譯了之后,底層會類編譯成 isa + 成員變量
,所以在給類的實例分配內存的話這個內存塊存儲的就是 isa + 成員變量的值
。
內存對齊
學過C語言我們都知道,結構體是有內存對齊這個概念的,當然不同的排列所實現的內存對齊是不同的,例如
struct Mystruct1{char a; //1字節double b; //8字節int c; //4字節short d; //2字節
}Mystruct1;struct Mystruct2{double b; //8字節int c; //4字節short d; //2字節char a; //1字節
}Mystruct2;//計算 結構體占用的內存大小
NSLog(@"%lu-%lu",sizeof(Mystruct1),sizeof(Mystruct2));// 打印:24 - 16
結構體安排的順序會關系到結構體內存的大小,我們知道OC之中的對象其實就是由結構體構成的,那我們來探究一下不同的屬性對對象的內存是否有影響呢
內存重排
對象的本質 = isa + 成員變量的值。
#import <Foundation/Foundation.h>
@interface MyPerson : NSObject
// isa 8字節
@property (nonatomic, copy) NSString *name; // 8字節
@property (nonatomic, copy) NSString *hobby; // 8字節
@property (nonatomic, assign) int age; // 4字節
@property (nonatomic, assign) double height; // 8字節
@property (nonatomic, assign) short number; // 2字節
@end
一共38字節,16字節內存對齊得到總的字節數為48字節,在探究內存問題之前,先學幾個lldb命令
lldb指令:p 輸出10進制p/x 輸出16進制p/0 輸出8進制p/t 輸出2進制p/f 輸出浮點數x 輸出地址x/4gx 輸出4個字節地址x/6gx 輸出6個字節地址
現在我們用lldb進行調試
MyPerson *p = [GGObject alloc];p.name = @"bb";p.hobby = @"吃吃睡睡喝喝";p.height = 1.80;p.age = 26;p.number = 123;
其實嘗試著將類之中的屬性內容,打印出來可以看到,無論屬性怎么排列,這個MyPerson
類age和number這兩個屬性永遠會排在一起,這個就是編譯器的內存優化,對屬性進行重排
我們看到在類的內部,我們的屬性會通過重新排列獲得最小內存,接下來我們看看能能不能重排父類的屬性和子類屬性吧
還是可以看到子類屬性并不會跟父類屬性合并
@interface JCTestObject : NSObject
{@publicint count;// 若count在此處,為JCTestSubObject實例實際需要40字節,系統為實例分配48字節NSObject *obj1;NSObject *obj2;
// int count; // 若count在此處,為JCTestSubObject實例實際需要32字節,系統為實例分配32字節}
@end
@interface JCTestSubObject : JCTestObject
{@publicint count2;
}
isa走向
之前學習過關于isa指針的部分內容,
結論:
類的實例isa --> 類對象;
類對象isa --> 元類對象;
元類對象isa --> 根元類對象;
根元類對象isa --> 根元類對象自己。
objc_class
與 `objc_object
objc_object
是所有 Objective-C 對象的底層結構體,其定義如下
struct objc_object {isa_t isa; // 指向類對象或元類的指針
};
objc_class
是類的底層結構體,類對象(如 [LGPerson class]
)和元類(metaclass)均為此類型。其繼承自 objc_object
,定義如下:
struct objc_class : objc_object {Class superclass; // 父類指針cache_t cache; // 方法緩存(哈希表)class_data_bits_t bits; // 指向類信息(如方法列表、屬性等)
};
1
class_getClassMethod
和class_getInstanceMethod
先定義模版類
@interface JCObject : NSObject
{int _sum;
}
@property (nonatomic, copy) NSString *name;
@property (nonatomic, copy) NSString *hobby;
@property (nonatomic, assign) int age;
@property (nonatomic, assign) double height;
@property (nonatomic, assign) short number;
- (void)speak;
- (void)sayHello;
+ (void)walk;
@end@implementation JCObject- (void)speak {NSLog(@"%s", __func__);
}
+ (void)walk {NSLog(@"%s", __func__);
}-(void) sayHello {NSLog(@"%s", __func__);
}
在完成兩個函數
void jcInstanceMethod_classToMetaclass(Class pClass){const char *className = class_getName(pClass);Class metaClass = objc_getMetaClass(className);Method method1 = class_getInstanceMethod(pClass, @selector(speak));Method method2 = class_getInstanceMethod(metaClass, @selector(speak));Method method3 = class_getInstanceMethod(pClass, @selector(walk));Method method4 = class_getInstanceMethod(metaClass, @selector(walk));NSLog(@"%s - %p-%p-%p-%p",__func__,method1,method2,method3,method4);
}void jcClassMethod_classToMetaclass(Class pClass){const char *className = class_getName(pClass);Class metaClass = objc_getMetaClass(className);Method method1 = class_getClassMethod(pClass, @selector(speak));Method method2 = class_getClassMethod(metaClass, @selector(speak));Method method3 = class_getClassMethod(pClass, @selector(walk));Method method4 = class_getClassMethod(metaClass, @selector(walk));NSLog(@"%s-%p-%p-%p-%p",__func__,method1,method2,method3,method4);
}//main函數
JCObject *person = [JCObject alloc];
Class pClass = object_getClass(person);
jcObjc_copyMethodList(pClass);
jcInstanceMethod_classToMetaclass(pClass);
jcClassMethod_classToMetaclass(pClass);
得到的結果
jcInstanceMethod_classToMetaclass
要了解jcInstanceMethod_classToMetaclass
之中的內容就要從理解class_getInstanceMethod
開始,顧名思義class_getInstanceMethod
這個方法,主要是用于獲取實例方法,如果在傳入的類或者類的父類中沒有找到指定的實例方法,則返回NULL。從實例方法存儲在類中,類方法存儲在元類中
可以得知,JCObject的方法列表打印結果只有sayHello
方法
jcClassMethod_classToMetaclass
需要先了解class_getClassMethod
這個方法,看該方法的源碼實現,可以得出class_getClassMethod
的實現是獲取類的類方法
,其本質
就是獲取元類的實例方法
,最終還是會走到class_getInstanceMethod
//獲取類方法
Method class_getClassMethod(Class cls, SEL sel)
{if (!cls || !sel) return nil;return class_getInstanceMethod(cls->getMeta(), sel);
}??
//獲取元類
//在getMeta源碼中,如果判斷出cls是元類,那么就不會再繼續往下遞歸查找,會直接返回this,其目的是為了防止元類的無限遞歸查找
Class getMeta() {if (isMetaClass()) return (Class)this;else return this->ISA();
}
所以我們再來看看結果
對于speak方法存在于類對象之中,而walk方法存在于元類對象之中,所以使用class_getInstanceMethod
可以在類對象之中找到speak方法,而在元類對象之中找到walk的類方法
而對于class_getClassMethod
來說如果不是元類會先找到元類,然后再在元類之中查找類方法
iskindOfClass & isMemberOfClass
- iskindOfClass & isMemberOfClass 類方法調用
//-----使用 iskindOfClass & isMemberOfClass 類方法
BOOL re1 = [(id)[NSObject class] isKindOfClass:[NSObject class]]; //
BOOL re2 = [(id)[NSObject class] isMemberOfClass:[NSObject class]]; //
BOOL re3 = [(id)[LGPerson class] isKindOfClass:[LGPerson class]]; //
BOOL re4 = [(id)[LGPerson class] isMemberOfClass:[LGPerson class]]; //
NSLog(@" re1 :%hhd\n re2 :%hhd\n re3 :%hhd\n re4 :%hhd\n",re1,re2,re3,re4);
- iskindOfClass & isMemberOfClass 實例方法調用
//------iskindOfClass & isMemberOfClass 實例方法
BOOL re5 = [(id)[NSObject alloc] isKindOfClass:[NSObject class]]; //
BOOL re6 = [(id)[NSObject alloc] isMemberOfClass:[NSObject class]]; //
BOOL re7 = [(id)[LGPerson alloc] isKindOfClass:[LGPerson class]]; //
BOOL re8 = [(id)[LGPerson alloc] isMemberOfClass:[LGPerson class]]; //
NSLog(@" re5 :%hhd\n re6 :%hhd\n re7 :%hhd\n re8 :%hhd\n",re5,re6,re7,re8);
isKindOfClass
檢查對象是否屬于指定類或其繼承鏈中的任意父類。例如,若對象是Person
類的子類實例(如Student
),則[student isKindOfClass:[Person class]]
返回YES
//--isKindOfClass---類方法、對象方法
//+ isKindOfClass:第一次比較是 獲取類的元類 與 傳入類對比,再次之后的對比是獲取上次結果的父類 與 傳入 類進行對比
+ (BOOL)isKindOfClass:(Class)cls {// 獲取類的元類 vs 傳入類// 根元類 vs 傳入類// 根類 vs 傳入類// 舉例:LGPerson vs 元類 (根元類) (NSObject)for (Class tcls = self->ISA(); tcls; tcls = tcls->superclass) {if (tcls == cls) return YES;}return NO;
}//- isKindOfClass:第一次是獲取對象類 與 傳入類對比,如果不相等,后續對比是繼續獲取上次 類的父類 與傳入類進行對比
- (BOOL)isKindOfClass:(Class)cls {
/*
獲取對象的類 vs 傳入的類
父類 vs 傳入的類
根類 vs 傳入的類
nil vs 傳入的類
*/for (Class tcls = [self class]; tcls; tcls = tcls->superclass) {if (tcls == cls) return YES;}return NO;
}
isMemberOfClass
嚴格檢查對象是否直接屬于指定類,不包含繼承鏈。例如,Student
實例調用isMemberOfClass:[Person class]
會返回NO
,因為其直接類是Student
而非Person
//-----類方法
//+ isMemberOfClass : 獲取類的元類,與 傳入類對比
+ (BOOL)isMemberOfClass:(Class)cls {return self->ISA() == cls;
}
//-----實例方法
//- isMemberOfClass : 獲取對象的類,與 傳入類對比
- (BOOL)isMemberOfClass:(Class)cls {return [self class] == cls;
}
參考文章
iOS-底層原理 09:類 & isa 經典面試題分析