關聯(lián)對象補充
上節(jié)課我們在探索關聯(lián)對象設置流程
南片,在_object_set_associative_reference
方法源碼中看到析構函數(shù)AssociationsManager manager;
却紧,這里有疑問葫辐?它為什么不是一個單例闷串?下面進行證明它為什么不是單例...
- 進入
_object_set_associative_reference
方法,編寫如下代碼
AssociationsManager manager;
AssociationsHashMap &associations(manager.get());
// 這里多創(chuàng)建一個manager2
AssociationsManager manager2;
AssociationsHashMap &associations2(manager2.get());
直接運行objc4-818.2源碼
會崩潰招拙,原因是重復加鎖AssociationsManager() { AssociationsManagerLock.lock(); }
解決辦法 先把class AssociationsManager
里面的加鎖证鸥、解鎖屏蔽掉,如下所示
// 修改前
class AssociationsManager {
using Storage = ExplicitInitDenseMap<DisguisedPtr<objc_object>, ObjectAssociationMap>;
static Storage _mapStorage;
public:
AssociationsManager() { AssociationsManagerLock.lock(); }
~AssociationsManager() { AssociationsManagerLock.unlock(); }
AssociationsHashMap &get() {
return _mapStorage.get();
}
// 這里的static只是聲明init方法是個類方法娶眷,并不是單例
static void init() {
// 這里面全局存儲著3張表似嗤,關聯(lián)對象表 AutoreleasePool表 散列表
_mapStorage.init();
}
};
// 修改后
class AssociationsManager {
using Storage = ExplicitInitDenseMap<DisguisedPtr<objc_object>, ObjectAssociationMap>;
static Storage _mapStorage;
public:
AssociationsManager() { }
~AssociationsManager() { }
- 重新運行工程,進行
lldb調(diào)試
(lldb) p &manager
(objc::AssociationsManager *) $0 = 0x00007ffeefbff350
(lldb) p &manager2
(objc::AssociationsManager *) $1 = 0x00007ffeefbff338
由打印信息可知AssociationsManager manager;
不是單例
-
_mapStorage.init();
全局存儲著3張表關聯(lián)對象表
届宠、AutoreleasePool表
烁落、散列表
,下面進行斷點調(diào)試
void arr_init(void)
{
AutoreleasePoolPage::init(); //AutoreleasePool表
SideTablesMap.init(); //散列表(里面包含兩張表豌注,弱引用表 引用計數(shù)表)
_objc_associations_init(); //關聯(lián)對象表
}
關聯(lián)對象釋放
關聯(lián)對象
是在什么時候釋放的伤塌?
關聯(lián)對象
也是需要移除的,關聯(lián)對象的生命周期跟著object
一起的轧铁。下面反向推導什么時候調(diào)用objc_removeAssociatedObjects
?
- 全局搜索什么時候調(diào)用
objc_removeAssociatedObjects
沒有找到每聪,那就尋找對象釋放函數(shù)dealloc
// Replaced by NSZombies
- (void)dealloc {
_objc_rootDealloc(self);
}
- 進入
_objc_rootDealloc
方法
void
_objc_rootDealloc(id obj)
{
ASSERT(obj);
obj->rootDealloc();
}
- 繼續(xù)進入
rootDealloc
方法
inline void
objc_object::rootDealloc()
{
if (isTaggedPointer()) return; // fixme necessary?
// 判斷弱引用表,關聯(lián)對象表
if (fastpath(isa.nonpointer &&
!isa.weakly_referenced &&
!isa.has_assoc &&
#if ISA_HAS_CXX_DTOR_BIT
!isa.has_cxx_dtor &&
#else
!isa.getClass(false)->hasCxxDtor() &&
#endif
!isa.has_sidetable_rc))
{
assert(!sidetable_present());
free(this);
}
else {
object_dispose((id)this);
}
}
- 進入
object_dispose
方法
id
object_dispose(id obj)
{
if (!obj) return nil;
objc_destructInstance(obj);
free(obj);
return nil;
}
- 進入
objc_destructInstance
方法
void *objc_destructInstance(id obj)
{
if (obj) {
// Read all of the flags at once for performance.
bool cxx = obj->hasCxxDtor();
bool assoc = obj->hasAssociatedObjects();
// This order is important.
if (cxx) object_cxxDestruct(obj);
// 移除關聯(lián)對象
if (assoc) _object_remove_assocations(obj, /*deallocating*/true);
obj->clearDeallocating();
}
return obj;
}
下面查看關聯(lián)對象
的整個數(shù)據(jù)結構
?
(lldb) p refs_result
(std::pair<objc::DenseMapIterator<DisguisedPtr<objc_object>, objc::DenseMap<const void *, objc::ObjcAssociation, objc::DenseMapValueInfo<objc::ObjcAssociation>, objc::DenseMapInfo<const void *>, objc::detail::DenseMapPair<const void *, objc::ObjcAssociation> >, objc::DenseMapValueInfo<objc::DenseMap<const void *, objc::ObjcAssociation, objc::DenseMapValueInfo<objc::ObjcAssociation>, objc::DenseMapInfo<const void *>, objc::detail::DenseMapPair<const void *, objc::ObjcAssociation> > >,
// 查看DenseMapInfo
objc::DenseMapInfo<DisguisedPtr<objc_object> >, objc::detail::DenseMapPair<DisguisedPtr<objc_object>, objc::DenseMap<const void *, objc::ObjcAssociation, objc::DenseMapValueInfo<objc::ObjcAssociation>, objc::DenseMapInfo<const void *>,
// 查看DenseMapPair
objc::detail::DenseMapPair<const void *, objc::ObjcAssociation> > >, false>, bool>) $0 = {
// 第一指針
first = {
Ptr = 0x0000000100717900
End = 0x0000000100717980
}
// 第二指針
second = true
}
- 下面查看底層源碼是怎么
包裝數(shù)據(jù)結構
?
template <
typename KeyT, typename ValueT,
typename ValueInfoT = DenseMapValueInfo<ValueT>,
typename KeyInfoT = DenseMapInfo<KeyT>,
typename Bucket = detail::DenseMapPair<KeyT, ValueT>,
bool IsConst = false>
OC底層簡單面試題
面試題一:load方法在什么時候調(diào)用齿风?
load_images
方法的主要作用是加載鏡像文件药薯,其中最重要的有兩個方法:prepare_load_methods
(加載) 和 call_load_methods
(調(diào)用)
- 進入
load_images
源碼實現(xiàn)
void
load_images(const char *path __unused, const struct mach_header *mh)
{
if (!didInitialAttachCategories && didCallDyldNotifyRegister) {
didInitialAttachCategories = true;
loadAllCategories();//加載所有分類
}
// Return without taking locks if there are no +load methods here.
if (!hasLoadMethods((const headerType *)mh)) return;
recursive_mutex_locker_t lock(loadMethodLock);
// Discover load methods 發(fā)現(xiàn)load方法
{
mutex_locker_t lock2(runtimeLock);
prepare_load_methods((const headerType *)mh);
}
// Call +load methods (without runtimeLock - re-entrant)
call_load_methods(); //調(diào)用load方法
}
- 進入
prepare_load_methods
->schedule_class_load
源碼,這里主要是根據(jù)類的繼承鏈遞歸調(diào)用獲取load
聂宾,直到cls不存在才結束遞歸果善,目的是為了確保父類的load優(yōu)先加載
// Recursively schedule +load for cls and any un-+load-ed superclasses.
// cls must already be connected.
static void schedule_class_load(Class cls)
{
if (!cls) return;
ASSERT(cls->isRealized()); // _read_images should realize
if (cls->data()->flags & RW_LOADED) return;
// Ensure superclass-first ordering
schedule_class_load(cls->superclass);
// 添加到表里邊
add_class_to_loadable_list(cls);
cls->setInfo(RW_LOADED);
}
- 進入
add_class_to_loadable_list
,主要是將load方法
和cls類名
一起加到loadable_classes表中
- 進入
getLoadMethod
系谐,主要是獲取方法的sel為load的方法
進入call_load_methods源碼
,主要有三部分操作
- 反復調(diào)用
類的+load
讨跟,直到不再有 - 調(diào)用一次
分類的+load
- 如果有類或更多未嘗試的分類纪他,則
運行更多的+load
initialize
-
initialize
是在第一次消息發(fā)送的時候進行調(diào)用,load先于initialize
- 分類中實現(xiàn)
initialize
方法會被優(yōu)先調(diào)用晾匠,并且本類中的initialize不會被調(diào)用茶袒, -
initialize
原理是消息發(fā)送
,所有當子類沒有實現(xiàn)時凉馆,會調(diào)用父類薪寓。是會被調(diào)用兩次 - 如果子類亡资,父類同時實現(xiàn),
先調(diào)用父類向叉,再調(diào)用子類
load
-
load
方法在應用程序加載過程中
(dyld)完成調(diào)用锥腻,在main之前
- 在底層進行
load_images
處理時,維護了兩個load的加載表母谎,一個是本類的表
瘦黑,另一個是分類的表
,所以說有先對本類的load發(fā)起調(diào)用 - 在對
類 load方法
進行處理時奇唤,進行遞歸處理幸斥,以確保父類優(yōu)先
被處理 - 在load方法的調(diào)用順序是
父類、子類咬扇、分類
- 在分類中l(wèi)oad調(diào)用順序甲葬,是根據(jù)
編譯的順序
為準
c++構造函數(shù)
- 在分析dyld后,可以確定這樣個調(diào)用流程
load->c++->main
- 但是如果c++寫在objc工程中懈贺,在
objc_init()
調(diào)用時经窖,會通過static_init()
方法優(yōu)先調(diào)用c++函數(shù),而不需要等到_dyld_objc_notify_register
向dyld注冊load_images
之后再調(diào)用 - 同時如果
objc_init()
自啟的話也不需要dyld進行啟動隅居,也可能會發(fā)生c++函數(shù)在load方法之前調(diào)用
的情況
疑問钠至?如果有LGA LGB LGC
三個分類,哪個分類先加載呢胎源?
這個主要看編譯順序棉钧,如果同名方法是load方法 -- 先主類load,后分類load
(分類之間涕蚤,看編譯順序
)
面試題二:Runtime是什么宪卿?
- runtime是由
C和C++匯編實現(xiàn)的一套API
,為OC語言加入了面向對象
万栅、以及運行時
的功能 - 運行時是指將數(shù)據(jù)類型的確定由
編譯時
推遲到了運行時
舉例:extension 和 category 的區(qū)別 - 平時編寫的OC代碼佑钾,在程序運行的過程中,其實最終會轉換成
runtime
的C語言代碼烦粒, runtime是OC的幕后工作者
面試題三:方法的本質休溶,sel是什么?IMP是什么扰她?兩者之間的關系又是什么兽掰?
方法的本質:發(fā)送消息
,消息會有以下幾個流程
-
快速查找
(objc_msgSend) -cache_t
緩存消息中查找 -
慢速查找
- 遞歸自己|父類 -lookUpImpOrForward
- 查找不到消息:
動態(tài)方法解析
-resolveInstanceMethod
-
消息快速轉發(fā)
-forwardingTargetForSelector
-
消息慢速轉發(fā)
- methodSignatureForSelector & forwardInvocation
sel是方法編號 - 在read_images
期間就編譯進了內(nèi)存
imp
是函數(shù)實現(xiàn)指針 徒役,找imp就是找函數(shù)的過程
sel
相當于 一本書的目錄title
imp
相當于 書本的頁碼
查找具體的函數(shù)就是想看這本書具體篇章的內(nèi)容
- 首先知道想看什么孽尽,即目錄
title - sel
- 根據(jù)目錄找到對應的頁碼 -
imp
- 通過頁碼去翻到具體的
內(nèi)容
面試題四:能否向編譯后得到的類中增加實例變量?能否向運行時創(chuàng)建的類中添加實例變量忧勿?
不能向編譯后
的得到的類中增加實例變量
原因是:編譯好的實例變量存儲的位置是ro杉女,一旦編譯完成瞻讽,內(nèi)存結構就完全確定了
可以向運?時
創(chuàng)建的類中添加實例變量
,只要類沒有注冊到內(nèi)存熏挎,運行時還是可以添加的
可以通過objc_allocateClassPair
運行時創(chuàng)建類速勇,并添加屬性、實例變量婆瓜、方法
等
const char *className = "SHObject";
Class objc_class = objc_getClass(className);
if (!objc_class) {
Class superClass = [NSObject class];
objc_class = objc_allocateClassPair(superClass, className, 0);
}
class_addIvar(objc_class, "name", sizeof(NSString *), log2(_Alignof(NSString *)), @encode(NSString *));
class_addMethod(objc_class, @selector(addName:), (IMP)addName, "V@:");
面試題五:[self class]和[super class]的區(qū)別以及原理分析
LGTeacher
中的init方法
中打印這兩種class調(diào)用
// LGTeacher繼承自LGPerson
#import "LGTeacher.h"
@implementation LGTeacher
- (instancetype)init{
self = [super init];
if (self) {
NSLog(@"%@ - %@",[self class],[super class]);
}
return self;
}
<!-- main.m -->
int main(int argc, const char * argv[]) {
@autoreleasepool {
// insert code here...
LGTeacher *teacher = [[LGTeacher alloc] init];
NSLog(@"%@",teacher);
}
return 0;
}
// 運行工程打印如下
2021-08-30 17:41:17.662930+0800 KCObjc[79229:10118616] -LGTeacher - LGTeacher
首先來分析[self class]
打印的為什么是LGTeacher
快集?
- 首先這兩個類中都沒有實現(xiàn)
class方法
,那么根據(jù)繼承關系廉白,他們最終會調(diào)用到NSObject中的class方法
- (Class)class {
return object_getClass(self);
}
Class object_getClass(id obj)
{
if (obj) return obj->getIsa();
else return Nil;
}
- 由打印可知這兩個方法返回的都是
self對應的類
个初。關于這個self是誰,這里涉及到消息發(fā)送objc_msgSend
,有兩個隱形參數(shù)
分別是id self
和SEL sel
猴蹂,這里主要來說下id self
-
[self class]
輸出LGTeacher
院溺,這里消息的發(fā)送者是LGTeacher對象
,通過調(diào)用NSObject的class
磅轻,但是消息的接受者沒有發(fā)生變化珍逸,所以是LGTeacher
接下來分析[super class]打印的為什么是LGTeacher?
-
[super class]
中super
是語法關鍵字聋溜,通過命令clang -rewrite-objc LGTeacher.m -o LGTeacher.cpp
查看super的本質谆膳。下面是編譯時的底層源碼,其中第一個參數(shù)是消息接收者
撮躁,是__rw_objc_super
結構
static instancetype _I_LGTeacher_init(LGTeacher * self, SEL _cmd) {
self = ((LGTeacher *(*)(__rw_objc_super *, SEL))(void *)objc_msgSendSuper)((__rw_objc_super){(id)self, (id)class_getSuperclass(objc_getClass("LGTeacher"))}, sel_registerName("init"));
if (self) {
NSLog((NSString *)&__NSConstantStringImpl__var_folders_d7_5qn4fnqn0p197t4lkw1bw1p40000gn_T_LGTeacher_c79449_mi_0,((Class (*)(id, SEL))(void *)objc_msgSend)((id)self, sel_registerName("class")),((Class (*)(__rw_objc_super *, SEL))
// objc_msgSendSuper
(void *)objc_msgSendSuper)((__rw_objc_super){(id)self, (id)class_getSuperclass(objc_getClass("LGTeacher"))}, sel_registerName("class")));
}
return self;
}
- 由上面我們看到
[super class]
的低層實現(xiàn)是objc_msgSendSuper方法
漱病,同時存在id self
和SEL sel
兩個隱形參數(shù)
<!-- objc_msgSendSuper底層源碼查看隱藏參數(shù) -->
OBJC_EXPORT void
objc_msgSendSuper(void /* struct objc_super *super, SEL op, ... */ )
OBJC_AVAILABLE(10.0, 2.0, 9.0, 1.0, 2.0);
<!-- objc_super源碼 -->
/// Specifies the superclass of an instance.
struct objc_super {
/// Specifies an instance of a class.
__unsafe_unretained _Nonnull id receiver; //消息接收者
/// Specifies the particular superclass of the instance to message.
#if !defined(__cplusplus) && !__OBJC2__
/* For compatibility with old objc-runtime.h header */
__unsafe_unretained _Nonnull Class class;
#else
__unsafe_unretained _Nonnull Class super_class; //父類
#endif
/* super_class is the first class to search */
};
- 由上面可知
id receiver
和Class super_class
兩個參數(shù),其中super_class
表示第一個要去查找的類把曼,至此我們可以得出結論杨帽,在LGTeacher中調(diào)用[super class]
,其內(nèi)部會調(diào)用objc_msgSendSuper方法
嗤军,并且會傳入?yún)?shù)objc_super
注盈,其中receiver是LGTeacher對象
,super_class是LGTeacher的父類
叙赚,也就是要第一個查找的類老客。 - 我們再來看
[super class]
在運行時是否如上一步的底層編碼所示,是objc_msgSendSuper
震叮,打開匯編調(diào)試發(fā)現(xiàn)會調(diào)用objc_msgSendSuper2
沿量,查看objc_msgSendSuper2匯編源碼
發(fā)現(xiàn)是從superclass中的cache中
查找方法
<!-- objc_msgSendSuper2底層源碼查看隱藏參數(shù) -->
OBJC_EXPORT id _Nullable
objc_msgSendSuper2(struct objc_super * _Nonnull super, SEL _Nonnull op, ...)
OBJC_AVAILABLE(10.6, 2.0, 9.0, 1.0, 2.0);
<!-- _objc_msgSendSuper2匯編源碼 -->
ENTRY _objc_msgSendSuper2
UNWIND _objc_msgSendSuper2, NoFrame
ldp p0, p16, [x0] // p0 = real receiver, p16 = class 取出receiver 和 class
ldr p16, [x16, #SUPERCLASS] // p16 = class->superclass
CacheLookup NORMAL, _objc_msgSendSuper2//cache中查找--快速查找
END_ENTRY _objc_msgSendSuper2
最終回答如下
-
[self class]
方法調(diào)用的本質是發(fā)送消息
,調(diào)用class的消息流程冤荆,拿到元類的類型,在這里是因為類已經(jīng)加載到內(nèi)存权纤,所以在讀取時是一個字符串類型钓简,這個字符串類型是在map_images
的readClass
時已經(jīng)加入表中乌妒,所以打印為LGTeacher
-
[super class]
打印的是LGTeacher
,原因是當前的super是一個關鍵字外邓,在這里只調(diào)用objc_msgSendSuper2
撤蚊,其實他的消息接收者
和[self class]
是一模一樣的,所以返回的是LGTeacher
面試題六:內(nèi)存平移問題
調(diào)試一
創(chuàng)建一個LGPerson類
损话,并實現(xiàn)實例方法saySomething
侦啸,下面代碼能否正常調(diào)用?
<!-- LGPerson.m文件 -->
#import "LGPerson.h"
@implementation LGPerson
- (void)saySomething{
NSLog(@"%s",__func__);
}
@end
int main(int argc, const char * argv[]) {
@autoreleasepool {
// insert code here...
LGPerson *person = [LGPerson alloc];
[person saySomething];
Class cls = [LGPerson class];
void *kc = &cls;
[(__bridge id)kc saySomething];
}
return 0;
}
// 控制臺打印信息
2021-08-30 22:26:50.334631+0800 004-內(nèi)存平移問題[90979:11344738] -[LGPerson saySomething]
2021-08-30 22:26:50.335322+0800 004-內(nèi)存平移問題[90979:11344738] -[LGPerson saySomething]
分析:
-
person
的 isa指向類LGPerson
丧枪,即person的首地址
指向LGPerson的首地址
光涂,我們可以通過LGPerson的內(nèi)存平移
找到cache,在cache
中查找方法 -
[(__bridge id)kc saySomething]
中的kc是來自于LGPerson
這個類拧烦,然后有一個指針kc
將其指向LGPerson的首地址
- 所以
person是指向LGPerson類的結構
忘闻,kc也是指向LGPerson類的結構
,然后都是在LGPerson中的methodList
中查找方法
調(diào)試二
接著上面的案例恋博,我們新增一個屬性kc_name
進行打印齐佳,在return 0;
前面添加斷點查看控制臺打印
以及lldb調(diào)試打印
<!-- LGPerson.h文件 -->
@interface LGPerson : NSObject
@property (nonatomic, copy) NSString *kc_name;
- (void)saySomething;
@end
<!-- LGPerson.m文件 -->
#import "LGPerson.h"
@implementation LGPerson
- (void)saySomething{
NSLog(@"%s - %@",__func__,self.kc_name);
}
@end
int main(int argc, const char * argv[]) {
@autoreleasepool {
// insert code here...
LGPerson *person = [LGPerson alloc];
[person saySomething];
Class cls = [LGPerson class];
void *kc = &cls;
[(__bridge id)kc saySomething];
}
return 0;
}
// 控制臺打印信息
2021-08-30 23:09:55.448534+0800 004-內(nèi)存平移問題[91230:11372868] -[LGPerson saySomething] - (null)
2021-08-30 23:09:55.449692+0800 004-內(nèi)存平移問題[91230:11372868] -[LGPerson saySomething] - <LGPerson: 0x600002cef160>
// lldb調(diào)試發(fā)現(xiàn)[(__bridge id)kc saySomething]; 打印的self.kc_name的LGPerson內(nèi)存地址與下面person內(nèi)存地址相同
(lldb) p person
(LGPerson *) $0 = 0x0000600002cef160
- 首先我們了解到取出
屬性的值
其實是要先計算出偏移大小,再通過內(nèi)存平移
獲取值债沮。其實是Person類
內(nèi)部存儲著成員變量
炼吴,每次偏移8字節(jié)
進行存取。 - 至于kc打印的
self. kc_name
的值<LGPerson: 0x6000019a1f40>
疫衩,是因為cls只有Person類
的內(nèi)存首地址硅蹦,但是沒有person對象
的內(nèi)存結構,所以kc只能在棧里面進行內(nèi)存平移
隧土。
調(diào)試三
修改屬性kc_name
關鍵字為retain
提针,代碼如下
<!-- LGPerson.h文件 -->
@interface LGPerson : NSObject
@property (nonatomic, retain) NSString *kc_name;
- (void)saySomething;
@end
<!-- LGPerson.m文件 -->
#import "LGPerson.h"
@implementation LGPerson
- (void)saySomething{
NSLog(@"%s - %@",__func__,self.kc_name);
}
@end
- (void)viewDidLoad {
[super viewDidLoad];
LGPerson *person = [LGPerson alloc];
person.kc_name = @"cooci";
[person saySomething];
Class cls = [LGPerson class];
void *kc = &cls;
[(__bridge id)kc saySomething];
}
kc
表示8字節(jié)指針,self.kc_name
的獲取相當于 kc首地址的指針平移8字節(jié)找kc_name
曹傀,那么此時的kc的指針地址是多少添瓷?平移8字節(jié)獲取的是什么?
-
kc
是一個指針险掀,是存在棧中的腋妙,棧是一個先進后出
的結構,參數(shù)傳入
就是一個不斷壓棧
的過程幕庐。其中隱藏參數(shù)也會壓入棧久锥,且每個函數(shù)都會有兩個隱藏參數(shù)(id self,sel _cmd)
异剥,可以通過clang -rewrite-objc ViewController.m -o ViewController.cpp
查看底層編譯 - 隱藏參數(shù)壓棧其地址是
遞減的
瑟由,而棧是從高地址->低地址
分配的,即在棧中參數(shù)會從前往后
一直壓
super通過clang
查看底層的編譯是objc_msgSendSuper
冤寿,其第一個參數(shù)是一個結構體__rw_objc_super
(self歹苦,class_getSuperclass)青伤,那么結構體中的屬性是如何壓棧的?
struct kc_struct{
NSNumber *num1;
NSNumber *num2;
} kc_struct;
- (void)viewDidLoad {
[super viewDidLoad];
struct kc_struct kcs = {@(10), @(20)};
LGPerson *person = [LGPerson alloc];
}
// LGPerson *person = [LGPerson alloc]; 下一行添加斷點殴瘦,lldb調(diào)試
(lldb) p &person
(LGPerson **) $0 = 0x00007ffee303b008
(lldb) p *(NSNumber **)0x00007ffee303b010
(__NSCFNumber *) $1 = 0x9f631adbc08bee8a (int)10
(lldb) p *(NSNumber **)0x00007ffee303b018
(__NSCFNumber *) $2 = 0x9f631adbc08bef6a (int)20
lldb調(diào)試
得出20先加入狠角,10后加入,因此結構體內(nèi)部的壓棧情況是 低地址->高地址
遞增的蚪腋,即棧中結構體內(nèi)部的成員是反向壓入棧
丰歌,而對象屬性參數(shù)壓棧是高地址->低地址
正向壓入棧
調(diào)試三
通過下面這段代碼打印棧的存儲
- (void)viewDidLoad {
[super viewDidLoad];
Class cls = [LGPerson class];
void *kc = &cls; //
LGPerson *person = [LGPerson alloc];
NSLog(@"%p - %p",&person,kc);
// 隱藏參數(shù) 會壓入棧幀
void *sp = (void *)&self;
void *end = (void *)&person;
long count = (sp - end) / 0x8;
for (long i = 0; i<count; i++) {
void *address = sp - 0x8 * i;
if ( i == 1) {
NSLog(@"%p : %s",address, *(char **)address);
}else{
NSLog(@"%p : %@",address, *(void **)address);
}
}
}
// 控制臺打印
2021-08-31 22:25:31.814991+0800 004-內(nèi)存平移問題[94665:11885963] 0x7ffee1e4e008 - 0x7ffee1e4e018
2021-08-31 22:25:31.815196+0800 004-內(nèi)存平移問題[94665:11885963] 0x7ffee1e4e038 : <ViewController: 0x7ffdde4139b0>
2021-08-31 22:25:31.815326+0800 004-內(nèi)存平移問題[94665:11885963] 0x7ffee1e4e030 : viewDidLoad
// viewDidLoad方法里面 [super viewDidLoad]; 壓棧的為什么不是UIViewController
2021-08-31 22:25:31.815461+0800 004-內(nèi)存平移問題[94665:11885963] 0x7ffee1e4e028 : ViewController
2021-08-31 22:25:31.815608+0800 004-內(nèi)存平移問題[94665:11885963] 0x7ffee1e4e020 : <ViewController: 0x7ffdde4139b0>
2021-08-31 22:25:31.815744+0800 004-內(nèi)存平移問題[94665:11885963] 0x7ffee1e4e018 : LGPerson
2021-08-31 22:25:31.815897+0800 004-內(nèi)存平移問題[94665:11885963] 0x7ffee1e4e010 : <LGPerson: 0x7ffee1e4e018>
由此可知棧中從高地址到低地址
的順序的:self - _cmd - (id)class_getSuperclass(objc_getClass("ViewController")) - self - cls - kc - person
,self和_cmd
是viewDidLoad
方法的兩個隱藏參數(shù)屉凯,是高地址->低地址
正向壓棧的立帖。class_getSuperClass 和 self
為objc_msgSendSuper2
中的結構體成員,是從最后一個成員變量神得,即低地址->高地址
反向壓棧的
注意
- 函數(shù)隱藏參數(shù)會從前往后一直壓厘惦,即從
高地址->低地址
開始入棧 - 結構體內(nèi)部的成員是從
低地址->高地址
通過上面調(diào)試,這里有幾個疑問哩簿?結構體壓棧的原理宵蕉?什么東西才會壓棧?上面打印棧的存儲
viewDidLoad方法里面[super viewDidLoad];
壓棧的為什么不是UIViewController
?
-
臨時變量
(傳入函數(shù)棧幀里面的參數(shù))才會壓棧节榜,其中viewDidLoad
方法里面的隱藏參數(shù)self
和_cmd
壓入LGPerson
的class的函數(shù)棧幀中羡玛,跟viewDidLoad
的函數(shù)棧幀沒有任何關系 -
viewDidLoad
方法的結構體{objc class}
為什么會壓棧進來呢?[super viewDidLoad];
潛在含義是objc_msgSend發(fā)送消息
宗苍,等同與objc_msgSendSuper(&kc_objc_super, @selector(viewDidLoad))
壓棧的為什么不是UIViewController
? 真機運行工程稼稿,通過匯編代碼查看[super viewDidLoad];
的底層含義
- 結構體第一個參數(shù)
receiver
是self
,第二個參數(shù)是superclass
還是currentclass
? 如果當前傳入的是currentclass
意味著壓棧的是ViewController
讳窟,反之壓棧的是UIViewController
- 現(xiàn)在查看
objc_msgSendSuper
底層源碼實現(xiàn)
OBJC_EXPORT void
objc_msgSendSuper(void /* struct objc_super *super, SEL op, ... */ )
OBJC_AVAILABLE(10.0, 2.0, 9.0, 1.0, 2.0);
OBJC_EXPORT id _Nullable
objc_msgSendSuper2(struct objc_super * _Nonnull super, SEL _Nonnull op, ...)
OBJC_AVAILABLE(10.6, 2.0, 9.0, 1.0, 2.0);
- 進行
lldb調(diào)試
當前的結構體里面super_class
傳入的是ViewController
让歼,即當前類類名