刨根問底之OC對象本質
[toc]
我們平時編寫的Objective-C代碼晚伙,底層實現(xiàn)其實都是C\C++代碼
在計算機中編譯過程是
Objective-C C\C++
匯編代碼
機器語言
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());
}
查找過程如下
進一步驗證匀借,查看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é),所以總共占用的是
個字節(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é)
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
去掉一個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ù)字一共占
個撒桨,也就是
,但是由于內(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)(比如成員變量的類型)
- ......
簡化表格如下
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指針
- 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)]
當LYJStudent
的instance
對象要調用LYJPerson的對象方法時雄人,會先通過isa找到LYJStudent的class从橘,然后通過superclass找到LYJPerson的class,最后找到對象方法的實現(xiàn)進行調用
3.2.1 meta-class(元類對象)的superclass指針
當LYJStudent
的class要調用LYJPerson
的類方法時础钠,會先通過isa找到LYJStudent
的meta-class恰力,然后通過superclass找到LYJPerson
的meta-class,最后找到類方法的實現(xiàn)進行調用
3.3 isa旗吁、superclass總結
- 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找父類
舉例化:上面的LYJStudent
就是圖中的Subclass,LYJPerson
就是圖中的Superclass,NSObject
就是圖中的Root Class,代入就比較好理解
- meta-class的isa指向基類的meta-class
- LYJStudent企孩、LYJPerson和NSObject的元類對象的isa都是指向NSobject的元類對象
- class的superclass指向父類的class
- LYJStudent的類對象superclass指向LYJPerson的類對象,LYJPerson的類對象superclass指向NSObject的類對象袁稽,NSObject沒有父類勿璃,則指向的是nil
- 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
的類對象
- 當注釋掉
LYJPerson
的test
方法
@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方法胁编,結果調用了NSObject
test方法,但是方法調用者還是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方法,按照上面說的就去糕韧,通過superclass
去NSObject
的類對象里找枫振,找到了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
的值如下
查看源碼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;
具體的關系如下圖