本篇主要是對(duì)小碼哥底層視頻學(xué)習(xí)的總結(jié)。方便日后復(fù)習(xí)。
本篇學(xué)習(xí)總結(jié):
- NSObject對(duì)象/自定義類(lèi)的對(duì)象/繼承關(guān)系的類(lèi)的類(lèi)的對(duì)象內(nèi)存分配情況以及類(lèi)信息情況
- OC對(duì)象類(lèi)別有哪些呢?
- OC對(duì)象的類(lèi)信息存儲(chǔ)位置在哪里呢砾跃?
- OC對(duì)象中常說(shuō)的isa指針是怎么回事呢?
- OC對(duì)象中常說(shuō)的superclass指針怎么回事呢?
好了沉迹,帶著問(wèn)題,我們一一開(kāi)始閱讀吧 ??
一.NSObject 對(duì)象內(nèi)存分配情況
1.面試題:在64bit環(huán)境下害驹,一個(gè)NSObject對(duì)象占用多少內(nèi)存鞭呕?
探尋OC對(duì)象的本質(zhì),我們平時(shí)編寫(xiě)的Objective-C代碼宛官,轉(zhuǎn)化成底層都是C\C++代碼葫松。
OC代碼如下:
int main(int argc, const char * argv[]) {
@autoreleasepool {
// insert code here...
NSLog(@"Hello, World!");
}
return 0;
}
我們要想看到OC代碼轉(zhuǎn)化為C++文件,需要通過(guò)命令行進(jìn)行操作底洗,現(xiàn)在將OC的main.m 文件轉(zhuǎn)化為C++文件腋么,
第一個(gè)方法是安裝插件轉(zhuǎn)換:
第二個(gè)方法是直接 cd main.m文件所在文件夾
然后再執(zhí)行下面的命令行工具(用于mac命令行項(xiàng)目)
clang -rewrite-objc main.m -o main.cpp // 這種方式?jīng)]有指定架構(gòu)例如arm64架構(gòu) 其中cpp代表(c plus plus)
生成 main.cpp
我們還可以指定架構(gòu)模式的命令行,使用Xcode工具xrun(用于iOS應(yīng)用)
xcrun -sdk iphoneos clang -arch arm64 -rewrite-objc main.m -o main-arm64.cpp
生成 main-arm64.cpp
下面提示代碼c++轉(zhuǎn)化成功:
我們將生成的文件添加到項(xiàng)目中亥揖,不需要編譯
我們打開(kāi)main-arm64.cpp文件珊擂,搜索NSObject,可以找到NSObjcet_IMPL (IMPL代表 implementation 實(shí)現(xiàn))费变,代碼如下:
struct NSObject_IMPL {
Class isa;
};
發(fā)現(xiàn)里面只有一個(gè)Class類(lèi)型的isa成員變量摧扇,順勢(shì)點(diǎn)進(jìn)入Class查看一下它的結(jié)構(gòu),代碼如下:
typedef struct objc_class *Class;
查看 objc_class挚歧,結(jié)構(gòu)如下:
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;
原來(lái)typedef struct objc_class Class就是一種結(jié)構(gòu)體的指針扛稽。
這時(shí)候我們回到第一個(gè)面試題,一個(gè)NSObject對(duì)象在內(nèi)存中占用多少內(nèi)存昼激,其實(shí)就是isa結(jié)構(gòu)體類(lèi)型的指針在內(nèi)存中占用的空間庇绽,如果64bit占用8個(gè)字節(jié),如果32bit占用4個(gè)字節(jié)橙困。咱們這里探討的是64bit瞧掺,也就是說(shuō)一個(gè)NSObjec對(duì)象所占用的內(nèi)存是8個(gè)字節(jié)。到這里我們已經(jīng)可以基本解答第一個(gè)問(wèn)題凡傅。但是我們發(fā)現(xiàn)NSObject對(duì)象中還有很多方法辟狈,那這些方法不占用內(nèi)存空間嗎?其實(shí)類(lèi)的方法等也占用內(nèi)存空間,但是這些方法所占用的存儲(chǔ)空間并不在NSObject對(duì)象中,如果是自定義的類(lèi)哼转,又是如何計(jì)算內(nèi)存呢明未,我們繼續(xù)探討OC對(duì)象本質(zhì)問(wèn)題。
二.自定義類(lèi)的實(shí)例對(duì)象內(nèi)存分配情況
2.面試題:在64bit環(huán)境下壹蔓,自定類(lèi)的實(shí)例對(duì)象占用多少內(nèi)存呢趟妥?
首先創(chuàng)建一個(gè)Student類(lèi)
@interface Student : NSObject
{
@public
int _no;
int _age;
}
@end
@implementation Student
@end
int main(int argc, const char * argv[]) {
@autoreleasepool {
Student *stu = [[Student alloc] init];
stu->_no = 4;
stu->_age = 5;
NSLog(@"%zd", class_getInstanceSize([Student class]));
NSLog(@"%zd", malloc_size((__bridge const void *)stu));
struct Student_IMPL *stuImpl = (__bridge struct Student_IMPL *)stu;
NSLog(@"no is %d, age is %d", stuImpl->_no, stuImpl->_age);
}
return 0;
}
我們按照上面的OC代碼轉(zhuǎn)C++文件的方式進(jìn)行轉(zhuǎn)換。我們從 main-arm64.cpp 文件中搜索 Student,搜索結(jié)果代碼如下:
struct Student_IMPL {
struct NSObject_IMPL NSObject_IVARS;
int _no;
int _age;
};
我們發(fā)現(xiàn)Student類(lèi)轉(zhuǎn)化為C++的結(jié)構(gòu)體后第一項(xiàng)是struct NSObject_IMPL 佣蓉,前面探討NSObject對(duì)象的時(shí)候?qū)戇^(guò)
struct NSObject_IMPL {
Class isa;
};
我們將這部分代碼進(jìn)行替換披摄,替換結(jié)果如下:
struct Student_IMPL {
Class *isa;
int _no;
int _age;
};
遵循上面計(jì)算NSObject對(duì)象內(nèi)存的方式,結(jié)構(gòu)體內(nèi)的各個(gè)成員變量占用內(nèi)存總和就是結(jié)構(gòu)體占用總的內(nèi)存大小勇凭,咱們給Student對(duì)象計(jì)算一下內(nèi)存大小吧:
isa指針8個(gè)字節(jié)空間+int類(lèi)型_no4個(gè)字節(jié)空間+int類(lèi)型_age4個(gè)字節(jié)空間共16個(gè)字節(jié)空間
上面的方法是根據(jù)類(lèi)型推算出來(lái)的內(nèi)存大小疚膊,我們還可以根據(jù)代碼計(jì)算出來(lái)
NSLog(@"NSObject = %zd",class_getInstanceSize([NSObject class]));
//類(lèi)對(duì)象實(shí)際需要內(nèi)存大小
NSLog(@"Student = %zd", class_getInstanceSize([Student class]));
//系統(tǒng)分配
NSLog(@"Student = %zd", malloc_size((__bridge const void *)stu));
窺探內(nèi)存結(jié)構(gòu)
我們還需要進(jìn)一步直觀的看到內(nèi)存數(shù)據(jù),那怎么做呢虾标?
方式一:通過(guò)打斷點(diǎn)
Debug Workflow -> viewMemory address中輸入一個(gè)NSObject對(duì)象的地址寓盗,stu對(duì)象的內(nèi)存地址查看方式也是同樣的操作。
從上圖中璧函,我們可以發(fā)現(xiàn)讀取數(shù)據(jù)從高位數(shù)據(jù)開(kāi)始讀傀蚌,查看前16位字節(jié),每四個(gè)字節(jié)讀出的數(shù)據(jù)為
16進(jìn)制 0x0000004(4字節(jié)) 0x0000005(4字節(jié)) isa的地址為 00D1081000001119(8字節(jié))
方式二:通過(guò)lldb指令xcode自帶的調(diào)試器
先看幾個(gè)常用的命令行
memory read 0x10074c450
// 簡(jiǎn)寫(xiě) x 0x10074c450
// 增加讀取條件
// memory read/數(shù)量格式字節(jié)數(shù) 內(nèi)存地址
// 簡(jiǎn)寫(xiě) x/數(shù)量格式字節(jié)數(shù) 內(nèi)存地址
// 格式 x是16進(jìn)制蘸吓,f是浮點(diǎn)喳张,d是10進(jìn)制
// 字節(jié)大小 b:byte 1字節(jié),h:half word 2字節(jié)美澳,w:word 4字節(jié),g:giant word 8字節(jié)
示例:x/4xw // /后面表示如何讀取數(shù)據(jù) w表示4個(gè)字節(jié)4個(gè)字節(jié)讀取摸航,x表示以16進(jìn)制的方式讀取數(shù)據(jù)制跟,4則表示讀取4次
同時(shí)也可以通過(guò)lldb修改內(nèi)存中的值
memory write 0x100400c68 6
將_no的值改為了6
三.繼承關(guān)系的類(lèi)的類(lèi)的對(duì)象內(nèi)存分配情況
3.面試題:在64bit環(huán)境下,繼承關(guān)系的子父類(lèi)占用內(nèi)存情況如何呢酱虎?
// Person
@interface Person : NSObject
{
@public
int _age;
}
@property (nonatomic, assign) int height;
@end
@implementation Person
@end
//Student
@interface Student : Person
{
int _no;
}
@end
@implementation Student
@end
int main(int argc, const char * argv[]) {
@autoreleasepool {
NSLog(@"stu - %zd", class_getInstanceSize([Student class]));
NSLog(@"person - %zd", class_getInstanceSize([Person class]));
}
return 0;
}
//打印結(jié)果如下:
Interview01-OC對(duì)象的本質(zhì)[2872:67593] stu - 24
Interview01-OC對(duì)象的本質(zhì)[2872:67593] person - 16
其實(shí)這道題主要考察的面試題是什么呢雨膨?繼承類(lèi)的內(nèi)存大小如何計(jì)算呢?
我們依次將上面的Student子類(lèi)跟Person父類(lèi)轉(zhuǎn)化成C++結(jié)構(gòu)體寫(xiě)出來(lái)
struct NSObject_IMPL {
Class isa;//8
};
struct Person_IMPL {
struct NSObject_IMPL NSObject_IVARS; // 8
int _age; // 4
}; // 16 內(nèi)存對(duì)齊:結(jié)構(gòu)體的大小必須是最大成員大小的倍數(shù)
struct Student_IMPL {
struct Person_IMPL Person_IVARS; // 16
int _no; // 4
}; // 16
這時(shí)候你會(huì)疑問(wèn)了Person_IMPL 不是占用12個(gè)字節(jié)嗎读串,怎么顯示16呢聊记?那是因?yàn)橄到y(tǒng)給對(duì)象分配內(nèi)存時(shí)會(huì)遵循內(nèi)存對(duì)齊:結(jié)構(gòu)體的大小必須是最大成員大小的倍原則,也就說(shuō)Person_IMPL結(jié)構(gòu)體中的成員變量(isa跟_age)實(shí)際需要12字節(jié)空間恢暖,但是系統(tǒng)根據(jù)原則確分配了16字節(jié)排监,所以結(jié)果是16字節(jié)。
而** Student_IMPL怎么又成了16字節(jié)呢杰捂,上面說(shuō)了系統(tǒng)給Person_IMPL分配了16字節(jié)舆床,實(shí)際占用12字節(jié),還留有4字節(jié)空余,恰好放_no**4字節(jié)的變量挨队,這樣出來(lái)的結(jié)果就是系統(tǒng)分配16字節(jié)恰好夠Student_IMPL對(duì)象使用谷暮。
敲黑板了!J⒖选湿弦!
所以,綜上腾夯,我們總結(jié)一下系統(tǒng)給對(duì)象分配存儲(chǔ)空間的原則:編譯器在給結(jié)構(gòu)體開(kāi)辟空間時(shí)颊埃,首先找到結(jié)構(gòu)體中最寬的基本數(shù)據(jù)類(lèi)型,然后尋找內(nèi)存地址能是該基本數(shù)據(jù)類(lèi)型的整倍的位置俯在,作為結(jié)構(gòu)體的首地址竟秫。將這個(gè)最寬的基本數(shù)據(jù)類(lèi)型的大小作為對(duì)齊模數(shù)。
為結(jié)構(gòu)體的一個(gè)成員開(kāi)辟空間之前跷乐,編譯器首先檢查預(yù)開(kāi)辟空間的首地址相對(duì)于結(jié)構(gòu)體首地址的偏移是否是本成員的整數(shù)倍肥败,若是,則存放本成員愕提,反之馒稍,則在本成員和上一個(gè)成員之間填充一定的字節(jié),以達(dá)到整數(shù)倍的要求浅侨,也就是將預(yù)開(kāi)辟空間的首地址后移幾個(gè)字節(jié)纽谒。
我們可以總結(jié)內(nèi)存對(duì)齊為兩個(gè)原則:
原則 1. 前面的地址必須是后面的地址正數(shù)倍,不是就補(bǔ)齊。
原則 2. 整個(gè)Struct的地址必須是最大字節(jié)的整數(shù)倍如输。
如果有興趣可以進(jìn)一步研究底層實(shí)現(xiàn)鼓黔,這里我就做個(gè)學(xué)習(xí)總結(jié).
四.OC對(duì)象的類(lèi)別以及存儲(chǔ)信息
4.面試題:OC對(duì)象都有哪些呢?
5.面試題:OC的類(lèi)信息存儲(chǔ)在哪里呢不见?
先來(lái)一段代碼
#import <Foundation/Foundation.h>
#import <objc/runtime.h>
/* Person */
@interface Person : NSObject <NSCopying>
{
@public
int _age;
}
@property (nonatomic, assign) int height;
- (void)personMethod;
+ (void)personClassMethod;
@end
@implementation Person
- (void)personMethod {}
+ (void)personClassMethod {}
@end
/* Student */
@interface Student : Person <NSCoding>
{
@public
int _no;
}
@property (nonatomic, assign) int score;
- (void)studentMethod;
+ (void)studentClassMethod;
@end
@implementation Student
- (void)studentMethod {}
+ (void)studentClassMethod {}
@end
int main(int argc, const char * argv[]) {
@autoreleasepool {
NSObject *object1 = [[NSObject alloc] init];
NSObject *object2 = [[NSObject alloc] init];
Student *stu = [[Student alloc] init];
[Student load];
Person *p1 = [[Person alloc] init];
p1->_age = 10;
[p1 personMethod];
[Person personClassMethod];
Person *p2 = [[Person alloc] init];
p2->_age = 20;
}
return 0;
}
OC對(duì)象類(lèi)分為幾類(lèi)呢澳化?
- instance對(duì)象(實(shí)例對(duì)象)
- class對(duì)象(類(lèi)對(duì)象)
- meta-class對(duì)象(元類(lèi)對(duì)象)
instance對(duì)象
通過(guò)類(lèi)alloc 出來(lái)的對(duì)象,每次調(diào)用alloc 都會(huì)產(chǎn)生新的instance對(duì)象稳吮。
NSObjcet *object1 = [[NSObjcet alloc] init];
NSObjcet *object2 = [[NSObjcet alloc] init];
//內(nèi)存打印地址如下:
object1 = 0x100723a60 object2 = 0x100723720
object1和object2都是NSObject 的instance對(duì)象(實(shí)例對(duì)象)缎谷,但是是兩個(gè)不同的對(duì)象,從打印結(jié)果就能看出來(lái)灶似,它們分別占據(jù)不同的內(nèi)存地址列林。
instance對(duì)象在內(nèi)存中存儲(chǔ)的信息包括:
1.isa指針
2.成員變量具體的數(shù)據(jù)
class對(duì)象
我們通過(guò)class方法或者runtime方法得到一個(gè)class對(duì)象,class對(duì)象也就是類(lèi)對(duì)象
Class objectClass1 = [object1 class];
Class objectClass2 = [object2 class];
Class objectClass3 = [NSObject class];
// runtime
Class objectClass4 = object_getClass(object1);
Class objectClass5 = object_getClass(object2);
NSLog(@"%p %p %p %p %p", objectClass1, objectClass2, objectClass3, objectClass4, objectClass5);
//內(nèi)存打印地址如下:
objectClass1 = 0x7fff97528118 objectClass2 = 0x7fff97528118 objectClass3 = 0x7fff97528118 objectClass4 = 0x7fff97528118 objectClass5 = 0x7fff97528118
// 而調(diào)用類(lèi)對(duì)象的class方法時(shí)得到還是類(lèi)對(duì)象酪惭,無(wú)論調(diào)用多少次都是類(lèi)對(duì)象
Class cls = [[NSObject class] class];
Class objectClass6 = [NSObject class];
NSLog(@"objectClass = %p cls = %p", objectClass6, cls); // 后面兩個(gè)地址相同希痴,說(shuō)明多次調(diào)用class得到的還是類(lèi)對(duì)象
//打印結(jié)果如下:
objectClass = 0x7fff97528118 cls = 0x7fff97528118
每個(gè)類(lèi)在內(nèi)存中有且只有一個(gè)class對(duì)象(類(lèi)對(duì)象),通過(guò)打印內(nèi)存地址就可以看出來(lái)撞蚕。
class對(duì)象在內(nèi)存中存儲(chǔ)的信息包括:
1.isa指針
2.superclass指針
3.類(lèi)的屬性信息(@property)润梯,類(lèi)的成員變量信息(ivar)
4.類(lèi)的方法信息(method),類(lèi)的協(xié)議信息(protocol)
寫(xiě)到這里有人就有疑問(wèn)了,剛才不是說(shuō)在instance對(duì)象中存儲(chǔ)成員變量信息嗎纺铭,怎么class對(duì)象中也存儲(chǔ)成員變量和屬性變量呢寇钉,這里要特意說(shuō)明一點(diǎn):
成員變量的值時(shí)存儲(chǔ)在實(shí)例對(duì)象中的,因?yàn)橹挥挟?dāng)我們創(chuàng)建實(shí)例對(duì)象的時(shí)候才為成員變賦值舶赔。但是成員變量叫什么名字扫倡,是什么類(lèi)型,只需要有一份就可以了竟纳。所以存儲(chǔ)在class對(duì)象中撵溃。
meta-class對(duì)象
只能是通過(guò)class對(duì)象獲取到meta-class對(duì)象,通過(guò)下面的方法獲取到锥累。
//runtime中傳入類(lèi)對(duì)象此時(shí)得到的就是元類(lèi)對(duì)象
Class objectMetaClass = object_getClass([NSObject class]);
NSLog(@"objectMetaClass = %p",objectMetaClass);
//內(nèi)存打印地址如下:
objectMetaClass = 0x7fff975280f0
//檢查是否為元類(lèi)對(duì)象
BOOL ismetaclass = class_isMetaClass(objectMetaClass);// 判斷該對(duì)象是否為元類(lèi)對(duì)象
NSLog(@"objectMetaClass 是否是元類(lèi)對(duì)象 - %ld",ismetaclass);
//打印結(jié)果如下:
objectMetaClass 是否是元類(lèi)對(duì)象 - 1
每一個(gè)類(lèi)的meta-class對(duì)象在內(nèi)存中有且只有一個(gè)缘挑,class對(duì)象跟meta-class對(duì)象結(jié)構(gòu)一樣,都是*struct objc_class Class桶略,但是用途不一樣语淘。
meta-class對(duì)象在內(nèi)存中存儲(chǔ)的信息包括:
1.isa指針
2.superclass指針
3.類(lèi)的類(lèi)方法信息(class-method)
既然class對(duì)象跟meta-class對(duì)象結(jié)構(gòu)一樣,那么class對(duì)象中是不是也有類(lèi)方法信息呢际歼?meta-class對(duì)象中是不是也有class對(duì)象中存儲(chǔ)的屬性信息惶翻,成員變量信息,方法信息鹅心,協(xié)議信息呢吕粗?答案是有的,只不過(guò)對(duì)應(yīng)的值可能是空的旭愧,所以忽略不計(jì)颅筋。
五.OC對(duì)象的isa指針指向問(wèn)題
前面已經(jīng)說(shuō)到了一個(gè)NSObject對(duì)象轉(zhuǎn)化為C++文件后,
struct NSObject_IMPL {
Class isa;
};
所以說(shuō)任何一個(gè)繼承NSObject的對(duì)象都包含一個(gè)isa指針输枯,那么各個(gè)對(duì)象的isa指針又分別指向哪里呢垃沦?
我們先看兩個(gè)常用的調(diào)用方法
MJStudent *student = [[MJStudent alloc]init];
//方法1:調(diào)用實(shí)例方法
[student studentInstanceMethod];
方法1:student實(shí)例對(duì)象調(diào)用了實(shí)例方法,我們?cè)谇懊嬷v到過(guò)用押,實(shí)例方法信息存儲(chǔ)在class對(duì)象中,這時(shí)候instace對(duì)象中存儲(chǔ)的isa指針起到作用了靶剑,instace對(duì)象中的isa指針指向class對(duì)象蜻拨,我們通過(guò)isa指針找到class對(duì)象,進(jìn)而找到實(shí)例方法列表桩引,調(diào)用對(duì)應(yīng)方法缎讼。
//方法2:調(diào)用類(lèi)方法
[MJStudent studentClassMethod];
方法2:MJStudent類(lèi)對(duì)象調(diào)用了類(lèi)方法,我們?cè)谇懊嬷v到過(guò)坑匠,類(lèi)方法信息存儲(chǔ)在meta-class對(duì)象中血崭,這時(shí)候class對(duì)象中存儲(chǔ)的isa指針起到作用了,class對(duì)象中的isa指針指向meta-class對(duì)象,我們通過(guò)isa指針找到meta-class對(duì)象夹纫,進(jìn)而找到類(lèi)方法列表咽瓷,調(diào)用對(duì)應(yīng)方法。仿照上圖“對(duì)象方法調(diào)用軌跡.png”
總結(jié):instance對(duì)象——<isa指針>——class對(duì)象——<isa指針>——meta-class對(duì)象——<isa指針>——基類(lèi)NSObject元類(lèi)對(duì)象
詳細(xì)看下面isa指針圖例:
總結(jié)兩點(diǎn)比較坑的地方:
a.基類(lèi)的元類(lèi)對(duì)象的superclass指針指向基類(lèi)的類(lèi)對(duì)象
b.類(lèi)的元類(lèi)對(duì)象的isa指針指向基類(lèi)的元類(lèi)對(duì)象
六.OC對(duì)象的superclass指針指向位置
我們還是以Student類(lèi)和Person類(lèi)進(jìn)行說(shuō)明舰讹,不清楚類(lèi)信息的小伙伴請(qǐng)往前瀏覽一下茅姜。Student類(lèi)是子類(lèi),Person類(lèi)是父類(lèi)月匣。
//方法1:調(diào)用父類(lèi)的實(shí)例方法
[student personInstanceMethod];
//方法2:調(diào)用父類(lèi)的類(lèi)方法
[MJStudent personClassMethod];
方法1:當(dāng)給student實(shí)例對(duì)象發(fā)送personInstanceMethod消息時(shí)钻洒,student實(shí)例對(duì)象會(huì)通過(guò)isa指針找到對(duì)應(yīng)MJStudent類(lèi)對(duì)象,因?yàn)轭?lèi)對(duì)象中存儲(chǔ)中對(duì)象方法信息锄开,先從MJStudent類(lèi)對(duì)象的實(shí)例方法信息中查找對(duì)應(yīng)的方法素标,如果找到進(jìn)行相應(yīng),沒(méi)找到則繼續(xù)向父類(lèi)查找萍悴,那么子類(lèi)怎么才能找到父類(lèi)呢头遭,這時(shí)候需要用到superclass指針了,通過(guò)superclass指針找到MJPerson的類(lèi)對(duì)象退腥,繼續(xù)從類(lèi)對(duì)象那個(gè)的實(shí)例方法中查找任岸,如果找到進(jìn)行相應(yīng),沒(méi)找到則繼續(xù)通過(guò)superclass查找基類(lèi)NSObject類(lèi)對(duì)象方法列表狡刘,如果還沒(méi)找到享潜,返回nil,就是咱們常見(jiàn)的報(bào)錯(cuò)信息嗅蔬,找不到此方法剑按。
方法2:跟方法1類(lèi)似,簡(jiǎn)單說(shuō)一下吧
當(dāng)給MJStudent類(lèi)對(duì)象發(fā)送personClassMethod消息時(shí)澜术,MJStudent類(lèi)對(duì)象會(huì)通過(guò)isa指針找到對(duì)應(yīng)MJStudent元類(lèi)對(duì)象艺蝴,因?yàn)樵?lèi)對(duì)象中存儲(chǔ)中類(lèi)方法信息,先從MJStudent元類(lèi)對(duì)象的類(lèi)信息中查找對(duì)應(yīng)的方法鸟废,如果找到進(jìn)行相應(yīng)猜敢,沒(méi)找到則繼續(xù)向父類(lèi)查找,那么子類(lèi)怎么才能找到父類(lèi)呢盒延,這時(shí)候需要用到superclass指針了缩擂,通過(guò)superclass指針找到MJPerson的元類(lèi)對(duì)象,繼續(xù)從元類(lèi)對(duì)象那個(gè)的類(lèi)方法中查找添寺,如果找到進(jìn)行相應(yīng)胯盯,沒(méi)找到則繼續(xù)通過(guò)superclass查找基類(lèi)NSObject元類(lèi)對(duì)象方法列表,如果還沒(méi)找到计露,這個(gè)時(shí)候跟方法1的查找不太一樣了博脑,如果NSObject的元類(lèi)對(duì)象的類(lèi)方法中找到憎乙,就從NSObject的類(lèi)方法的實(shí)例方法中去查找,還沒(méi)有找到叉趣,則返回nil泞边,就是咱們常見(jiàn)的報(bào)錯(cuò)信息,找不到此方法。
總結(jié):
1.子類(lèi)類(lèi)對(duì)象——<superclass指針>——父類(lèi)類(lèi)對(duì)象——<superclass指針>——基類(lèi)類(lèi)對(duì)象
2.子類(lèi)元類(lèi)對(duì)象——<superclass指針>——父類(lèi)元類(lèi)對(duì)象——<superclass指針>——基類(lèi)元類(lèi)對(duì)象——<superclass指針>基類(lèi)的類(lèi)對(duì)象
看完以上的解析,再來(lái)看這經(jīng)典的圖也不是那么的晦澀了
對(duì)isa值漫、superclass總結(jié)
instance的isa指向class
class的isa指向meta-class
meta-class的isa指向基類(lèi)的meta-class,基類(lèi)的isa指向自己
class的superclass指向父類(lèi)的class椭蹄,如果沒(méi)有父類(lèi),superclass指針為nil
meta-class的superclass指向父類(lèi)的meta-class净赴,基類(lèi)的meta-class的superclass指向基類(lèi)的class
instance調(diào)用對(duì)象方法的軌跡绳矩,isa找到class,方法不存在玖翅,就通過(guò)superclass找父類(lèi)
class調(diào)用類(lèi)方法的軌跡翼馆,isa找meta-class,方法不存在金度,就通過(guò)superclass找父類(lèi)
七.代碼求證isa指針指向是否正確
我們先寫(xiě)一段代碼:
NSObject *object = [[NSObject alloc] init];//instance對(duì)象
Class objectClass = [NSObject class];//類(lèi)對(duì)象
Class objectMetaClass = object_getClass([NSObject class]);//元類(lèi)對(duì)象
NSLog(@"object - %p objectClass - %p objectMetaClass - %p", object, objectClass, objectMetaClass);
//打印結(jié)果如下:
object - 0x10051e0b0 //instance對(duì)象內(nèi)存地址
objectClass - 0x7fff9abb6118 //類(lèi)對(duì)象內(nèi)存地址
objectMetaClass - 0x7fff9abb60f0 //元類(lèi)對(duì)象內(nèi)存地址
我們通過(guò)命令行打印如下:
程序打佑γ摹:類(lèi)對(duì)象內(nèi)存地址 - 0x7fff9abb6118
命令行打印:實(shí)例對(duì)象->isa指針獲取到的類(lèi)對(duì)象內(nèi)存地址 - 0x001dffff9abb6119
怎么不一樣了呢猜极?
原因是:這是因?yàn)閺?4bit開(kāi)始中姜,isa需要進(jìn)行一次位運(yùn)算,才能計(jì)算出真實(shí)地址跟伏。而位運(yùn)算的值我們可以通過(guò)下載objc源代碼找到丢胚。
arm64:表示真機(jī)應(yīng)用
x86_64:表示mac應(yīng)用
小碼哥視頻中創(chuàng)建的demo是mac應(yīng)用,所以用 0x00007ffffffffff8計(jì)算
我們按照這個(gè)原則再來(lái)操作一遍:
果然跟程序打印出來(lái)的結(jié)果一樣受扳,這足以證明上面總結(jié)的isa指針指向的正確性携龟。
但我們?cè)俅螄L試驗(yàn)證類(lèi)方法的isa指針指向的元類(lèi)對(duì)象的內(nèi)存地址跟程序自然打印的是否一樣的時(shí)候,發(fā)現(xiàn)了如下問(wèn)題
小碼哥教給的做法是:為了拿到isa指針的地址勘高,我們自己創(chuàng)建一個(gè)同樣的結(jié)構(gòu)體并通過(guò)強(qiáng)制轉(zhuǎn)化拿到isa指針峡蟋。
struct mj_objc_class {
Class isa;
Class superclass;
};
struct mj_objc_class *nsobjectclass = (__bridge struct mj_objc_class *)([NSObject class]);
將OC中的類(lèi)對(duì)象轉(zhuǎn)化成C語(yǔ)言的結(jié)構(gòu)體指針的時(shí)候需要進(jìn)行橋接,直接點(diǎn)fix就行
程序打踊:元類(lèi)對(duì)象內(nèi)存地址 - 0x7fff9abb60f0
命令行打硬阋凇:類(lèi)對(duì)象->isa指針獲取到的元類(lèi)類(lèi)對(duì)象內(nèi)存地址 - 0x00007fff9abb60f0
總結(jié)一下本文面試題:
- 1.面試題:一個(gè)NSObject對(duì)象占用多少內(nèi)存?
答:一個(gè)NSObject對(duì)象在內(nèi)存中占用多少內(nèi)存立美,其實(shí)就是isa結(jié)構(gòu)體類(lèi)型的指針在內(nèi)存中占用的空間(64bit占8個(gè)字節(jié),32bit占4個(gè)字節(jié))
- 2.面試題:自定義類(lèi)的實(shí)例對(duì)象占用多少內(nèi)存方灾?
答:根據(jù)情況而定建蹄,具體分析方法看上述說(shuō)明
- 3.面試題:繼承關(guān)系的子父類(lèi)的實(shí)例對(duì)象占用多少內(nèi)存碌更?
答:根據(jù)情況而定,具體分析方法看上述說(shuō)明
- 4.面試題:OC對(duì)象的類(lèi)別有哪些呢洞慎?
instance對(duì)象(實(shí)例對(duì)象)
class對(duì)象(類(lèi)對(duì)象)
meta-class對(duì)象(元類(lèi)對(duì)象)
- 5.面試題:OC對(duì)象的isa指針指向哪里呢痛单?
答:instance對(duì)象的isa指針指向class對(duì)象,class對(duì)象的isa指針指向meta-class對(duì)象劲腿,meta-class對(duì)象的isa指針指向基類(lèi)的meta-class對(duì)象旭绒,基類(lèi)自己的isa指針也指向自己。
- 6.面試題:OC的類(lèi)信息存放在哪里焦人?
instance對(duì)象(實(shí)例方法):存放isa指針挥吵,成員變量的具體數(shù)據(jù)
class對(duì)象(類(lèi)對(duì)象):存放isa指針,superclass指針花椭,類(lèi)的成員變量(ivar)忽匈,類(lèi)的屬性信息(property),類(lèi)的協(xié)議信息(protocol)矿辽,類(lèi)的方法列表(instance method list)
meta-class對(duì)象(元類(lèi)對(duì)象:存放isa指針丹允,superclass指針,類(lèi)的方法列表(class method list)
本篇學(xué)習(xí)先記錄到此袋倔,感謝閱讀雕蔽,如有錯(cuò)誤,不吝賜教宾娜,
非常感謝大佬帶飛:http://www.reibang.com/u/3171707d8892