OC底層面試

關聯(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(); }

image.png

解決辦法 先把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)試
image.png
(lldb) p &manager
(objc::AssociationsManager *) $0 = 0x00007ffeefbff350
(lldb) p &manager2
(objc::AssociationsManager *) $1 = 0x00007ffeefbff338

由打印信息可知AssociationsManager manager;不是單例

  • _mapStorage.init();全局存儲著3張表關聯(lián)對象表届宠、AutoreleasePool表烁落、散列表,下面進行斷點調(diào)試
image.png

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ù)結構?

image.png
(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 分析

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 selfSEL 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 selfSEL 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 receiverClass 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_imagesreadClass時已經(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中查找方法
image.png

調(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>
image.png

由此可知棧中從高地址到低地址的順序的:self - _cmd - (id)class_getSuperclass(objc_getClass("ViewController")) - self - cls - kc - personself和_cmdviewDidLoad方法的兩個隱藏參數(shù)屉凯,是高地址->低地址正向壓棧的立帖。class_getSuperClass 和 selfobjc_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];的底層含義

image.png
  • 結構體第一個參數(shù)receiverself,第二個參數(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)試
image.png
image.png

當前的結構體里面super_class傳入的是ViewController让歼,即當前類類名

最后編輯于
?著作權歸作者所有,轉載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市丽啡,隨后出現(xiàn)的幾起案子谋右,更是在濱河造成了極大的恐慌,老刑警劉巖补箍,帶你破解...
    沈念sama閱讀 219,589評論 6 508
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件改执,死亡現(xiàn)場離奇詭異,居然都是意外死亡坑雅,警方通過查閱死者的電腦和手機辈挂,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,615評論 3 396
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來裹粤,“玉大人终蒂,你說我怎么就攤上這事。” “怎么了后豫?”我有些...
    開封第一講書人閱讀 165,933評論 0 356
  • 文/不壞的土叔 我叫張陵悉尾,是天一觀的道長。 經(jīng)常有香客問我挫酿,道長,這世上最難降的妖魔是什么愕难? 我笑而不...
    開封第一講書人閱讀 58,976評論 1 295
  • 正文 為了忘掉前任早龟,我火速辦了婚禮,結果婚禮上猫缭,老公的妹妹穿的比我還像新娘葱弟。我一直安慰自己,他們只是感情好猜丹,可當我...
    茶點故事閱讀 67,999評論 6 393
  • 文/花漫 我一把揭開白布芝加。 她就那樣靜靜地躺著,像睡著了一般射窒。 火紅的嫁衣襯著肌膚如雪藏杖。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,775評論 1 307
  • 那天脉顿,我揣著相機與錄音蝌麸,去河邊找鬼。 笑死艾疟,一個胖子當著我的面吹牛来吩,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播蔽莱,決...
    沈念sama閱讀 40,474評論 3 420
  • 文/蒼蘭香墨 我猛地睜開眼弟疆,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了盗冷?” 一聲冷哼從身側響起怠苔,我...
    開封第一講書人閱讀 39,359評論 0 276
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎正塌,沒想到半個月后嘀略,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,854評論 1 317
  • 正文 獨居荒郊野嶺守林人離奇死亡乓诽,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 38,007評論 3 338
  • 正文 我和宋清朗相戀三年帜羊,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片鸠天。...
    茶點故事閱讀 40,146評論 1 351
  • 序言:一個原本活蹦亂跳的男人離奇死亡讼育,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情奶段,我是刑警寧澤饥瓷,帶...
    沈念sama閱讀 35,826評論 5 346
  • 正文 年R本政府宣布,位于F島的核電站痹籍,受9級特大地震影響呢铆,放射性物質發(fā)生泄漏。R本人自食惡果不足惜蹲缠,卻給世界環(huán)境...
    茶點故事閱讀 41,484評論 3 331
  • 文/蒙蒙 一棺克、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧线定,春花似錦娜谊、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,029評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至芭商,卻和暖如春派草,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背蓉坎。 一陣腳步聲響...
    開封第一講書人閱讀 33,153評論 1 272
  • 我被黑心中介騙來泰國打工澳眷, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人蛉艾。 一個月前我還...
    沈念sama閱讀 48,420評論 3 373
  • 正文 我出身青樓钳踊,卻偏偏與公主長得像,于是被迫代替她去往敵國和親勿侯。 傳聞我的和親對象是個殘疾皇子拓瞪,可洞房花燭夜當晚...
    茶點故事閱讀 45,107評論 2 356

推薦閱讀更多精彩內(nèi)容

  • iOS 底層原理 文章匯總[http://www.reibang.com/p/412b20d9a0f6] 【面試...
    Style_月月閱讀 3,568評論 6 12
  • 面試題-1 Runtime Asssociate方法關聯(lián)的對象,需要在dealloc中釋放? 當我們對象釋放時助琐,會...
    含笑州閱讀 329評論 0 1
  • 【面試-1】方法的調(diào)用順序 類的方法 和 分類方法 重名兵钮,如果調(diào)用蛆橡,是什么情況? 如果同名方法是普通方法掘譬,包括in...
    KB_MORE閱讀 309評論 0 0
  • 【面試-1】通過 Asssociate 方法關聯(lián)的對象泰演,需要在dealloc中釋放 當對象釋放時,系統(tǒng)會自動調(diào)用d...
    木揚音閱讀 1,015評論 0 17
  • 我是黑夜里大雨紛飛的人啊 1 “又到一年六月葱轩,有人笑有人哭睦焕,有人歡樂有人憂愁藐握,有人驚喜有人失落,有的覺得收獲滿滿有...
    陌忘宇閱讀 8,536評論 28 53