***********************??MJPerson.h ??**************************
#import <Foundation/Foundation.h>
@interface MJPerson : NSObject
@property (copy, nonatomic) NSString *name;
- (void)print;
@end
***********************??MJPerson.m ??**************************
#import "MJPerson.h"
@implementation MJPerson
- (void)print
{
NSLog(@"my name is %@", self->_name);
}
@end
@implementation ViewController
/*
1.print為什么能夠調(diào)用成功挟鸠?
2.為什么self.name變成了ViewController等其他內(nèi)容
*/
- (void)viewDidLoad {
[super viewDidLoad];
id cls = [MJPerson class];
void *obj = &cls;
[(__bridge id)obj print];
}
@end
RUN>
*********************** ??運行結(jié)果?? ************************** 2021-05-08 16:03:34.586222+0800 Interview02-super[4242:166676] my name is <ViewController: 0x7fb47340ecf0>
1.print為什么能夠調(diào)用成功没咙?
2.為什么self.name變成了ViewController等其他內(nèi)容
- (void)viewDidLoad {
[super viewDidLoad];
NSString *test = @"123";
id cls = [MJPerson class];
void *obj = &cls;
[(__bridge id)obj print];
}
RUN>
*********************** ??運行結(jié)果?? ************************** 2021-05-08 16:05:32.180416+0800 Interview02-super[4288:170283] my name is 123
很奇怪,怎么會這樣呢? 首先第一點是為什么會調(diào)用到MJPerson
的- (void)print
方法,第二,局部變量 NSString *test = @"123";
怎么就變成了person的實例成員芭逝。
- (void)viewDidLoad {
[super viewDidLoad];
//調(diào)用print
MJPerson *person = [[MJPerson alloc]init];
[person print];
}
RUN>
*********************** ??運行結(jié)果?? ************************** 2021-05-09 19:55:04.568600+0800 Interview02-super[2315:98649] my name is (null)
id cls = [MJPerson class];
void *obj = &cls;
[(__bridge id)obj print];
這段代碼在內(nèi)存結(jié)構(gòu)上發(fā)生了什么情況
[(__bridge id)obj print];
調(diào)用了能夠調(diào)用到MJPerson
的- (void)print
方法荡灾。那我們把obj看出person的instance會是什么情況
他們的內(nèi)存結(jié)構(gòu)何其相似.cls在這里不就等價于
isa么?他們都指向
MJPerson
類對象.
[person print];通過實例對象 person
的isa
指針找到MJPerson 類對象
,然后從類對象的方法列表中查找方法.所以毡惜,方法的調(diào)用本質(zhì)就是只要能找到類對象.而[(__bridge id)obj print];,
指針變量obj
中存儲的cls
恰巧就指向類對象,當(dāng)消息系統(tǒng)objc_msgSend
把obj當(dāng)成類了拓轻,在他所指向的地址,獲取前8位地址作為類的isa指針经伙,指向了MJPerson 類對象
扶叉,所以能夠當(dāng)成MJPerson類,所以最后能調(diào)用成功.
接著分析 my name is 123
這個局部變量 NSString *test = @"123";
怎么就變成了person的實例成員帕膜。
// 局部變量分配在椩嫜酰空間
// 棧空間分配垮刹,從高地址到低地址
void test()
{
long long a = 4; // 0x7ffee638bff8
long long b = 5; // 0x7ffee638bff0
long long c = 6; // 0x7ffee638bfe8
long long d = 7; // 0x7ffee638bfe0
NSLog(@"%p %p %p %p", &a, &b, &c, &d);
}
int main(int argc, char * argv[]) {
@autoreleasepool {
test();
return UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class]));
}
}
RUN>
*********************** ??運行結(jié)果?? ************************** 2021-05-09 20:30:17.556720+0800 Interview02-super[2557:120622] 0x7ffeed6a7c28 0x7ffeed6a7c20 0x7ffeed6a7c18 0x7ffeed6a7c10
從打印結(jié)果中可以看到,棧內(nèi)存分配空間是從高地址往低地址分配的,<font color='red'>先創(chuàng)建的局部變量分配在高地址,后創(chuàng)建的分配在低地址</font>.
NSString *test = @"123";
id cls = [MJPerson class];
void *obj = &cls;
[(__bridge id)obj print];
繼續(xù)查看這段代碼的內(nèi)存結(jié)構(gòu)
上面我們已經(jīng)分析過达吞,把cla看出isa指針,isa指針后面8個地址就是_name 屬性指向地址危纫。在獲取
self.name的時候,如果轉(zhuǎn)換成匯編代碼會發(fā)現(xiàn)本質(zhì)上就是找到
isa指針,然后越過8個字節(jié),從而找到
_name.這就是內(nèi)存訪問的本質(zhì):找到某塊內(nèi)存的地址,讀取地址中的值.
[(__bridge id)obj print];
也會同樣的越過cls
這8個字節(jié)找到test
.所以最后就打印的是my name is 123
;
super 關(guān)鍵字的一點補充
在前面講super
關(guān)鍵字的時候,我們看到super
轉(zhuǎn)換為c++
代碼的時候,被轉(zhuǎn)換成了objc_msgSendSuper(arg1,arg2)
函數(shù).其實實際上底層執(zhí)行并不是objc_msgSendSuper(arg1,arg2)
函數(shù),而是objc_msgSendSuper2(arg1,arg2)
函數(shù).我們在[super viewDidLoad];
處打個斷點,然后顯示匯編語言看一下:
并且上面講的objc_msgSendSuper(arg1,arg2)
中的第一個參數(shù)arg1
是__rw_objc_super
結(jié)構(gòu)體,這個結(jié)構(gòu)體如下:
struct __rw_objc_super {
struct objc_object *object;
struct objc_object *superClass;
}
而objc_super2
的結(jié)構(gòu)體如下:
struct objc_super2 {
id receiver;
Class current_class;
};
可以看到objc_super2
這個結(jié)構(gòu)體中傳入的是Class current_class;
也就是當(dāng)前類.而在_objc_msgSendSuper2
內(nèi)部獲取當(dāng)前類的superClass
:
ENTRY _objc_msgSendSuper2
UNWIND _objc_msgSendSuper2, NoFrame
MESSENGER_START
ldp x0, x16, [x0] // x0 = real receiver, x16 = class
ldr x16, [x16, #SUPERCLASS] // x16 = class->superclass
CacheLookup NORMAL
END_ENTRY _objc_msgSendSuper2
ENTRY _objc_msgLookupSuper2
UNWIND _objc_msgLookupSuper2, NoFrame
ldp x0, x16, [x0] // x0 = real receiver, x16 = class
ldr x16, [x16, #SUPERCLASS] // x16 = class->superclass
CacheLookup LOOKUP
END_ENTRY _objc_msgLookupSuper2
所以,super
底層其實是調(diào)用objc_msgSendSuper2()
函數(shù),然后傳入的是當(dāng)前類對象,只不過在內(nèi)部又回取出當(dāng)前類對象的superclass
.
這只是一個小細(xì)節(jié),和我們最開頭說的也不矛盾,知悉就好.
特別備注
本系列文章總結(jié)自MJ老師在騰訊課堂iOS底層原理班(下)/OC對象/關(guān)聯(lián)對象/多線程/內(nèi)存管理/性能優(yōu)化宗挥,相關(guān)圖片素材均取自課程中的課件。如有侵權(quán)种蝶,請聯(lián)系我刪除,謝謝瞒大!