OC底层面试

关联对象补充

上节课我们在探索关联对象设置流程,在_object_set_associative_reference方法源码中看到析构函数AssociationsManager manager;,这里有疑问?它为什么不是一个单例?下面进行证明它为什么不是单例...

  • 进入_object_set_associative_reference方法,编写如下代码
AssociationsManager manager;
AssociationsHashMap &associations(manager.get());
// 这里多创建一个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张表,关联对象表  AutoreleasePool表  散列表
        _mapStorage.init();
    }
};
// 修改后
class AssociationsManager {
    using Storage = ExplicitInitDenseMap<DisguisedPtr<objc_object>, ObjectAssociationMap>;
    static Storage _mapStorage;

public:
    AssociationsManager()   {  }
    ~AssociationsManager()  {  }
  • 重新运行工程,进行lldb调试
image.png
(lldb) p &manager
(objc::AssociationsManager *) $0 = 0x00007ffeefbff350
(lldb) p &manager2
(objc::AssociationsManager *) $1 = 0x00007ffeefbff338

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

  • _mapStorage.init();全局存储着3张表关联对象表AutoreleasePool表散列表,下面进行断点调试
image.png

void arr_init(void) 
{
    AutoreleasePoolPage::init(); //AutoreleasePool表
    SideTablesMap.init();  //散列表(里面包含两张表,弱引用表 引用计数表)
    _objc_associations_init(); //关联对象表
}

关联对象释放

关联对象是在什么时候释放的?
关联对象也是需要移除的,关联对象的生命周期跟着object一起的。下面反向推导什么时候调用objc_removeAssociatedObjects?

  • 全局搜索什么时候调用objc_removeAssociatedObjects没有找到,那就寻找对象释放函数dealloc
// Replaced by NSZombies
- (void)dealloc {
    _objc_rootDealloc(self);
}
  • 进入_objc_rootDealloc方法
void
_objc_rootDealloc(id obj)
{
    ASSERT(obj);

    obj->rootDealloc();
}
  • 继续进入rootDealloc方法
inline void
objc_object::rootDealloc()
{
    if (isTaggedPointer()) return;  // fixme necessary?
    // 判断弱引用表,关联对象表
    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);
        // 移除关联对象
        if (assoc) _object_remove_assocations(obj, /*deallocating*/true);
        obj->clearDeallocating();
    }
    return obj;
}

下面查看关联对象整个数据结构?

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
}
  • 下面查看底层源码是怎么包装数据结构?
template <
    typename KeyT, typename ValueT,
    typename ValueInfoT = DenseMapValueInfo<ValueT>,
    typename KeyInfoT = DenseMapInfo<KeyT>,
    typename Bucket = detail::DenseMapPair<KeyT, ValueT>,
    bool IsConst = false>

OC底层简单面试题

面试题一:load方法在什么时候调用?
load_images 分析

load_images方法的主要作用是加载镜像文件,其中最重要的有两个方法:prepare_load_methods(加载) 和 call_load_methods(调用)

  • 进入load_images源码实现
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 发现load方法
    {
        mutex_locker_t lock2(runtimeLock);
        prepare_load_methods((const headerType *)mh);
    }

    // Call +load methods (without runtimeLock - re-entrant)
    call_load_methods(); //调用load方法
}
  • 进入prepare_load_methods -> schedule_class_load源码,这里主要是根据类的继承链递归调用获取load,直到cls不存在才结束递归,目的是为了确保父类的load优先加载
// 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源码,主要有三部分操作

  • 反复调用类的+load,直到不再有
  • 调用一次分类的+load
  • 如果有类或更多未尝试的分类,则运行更多的+load

initialize

  • initialize是在第一次消息发送的时候进行调用,load先于initialize
  • 分类中实现initialize方法会被优先调用,并且本类中的initialize不会被调用,
  • initialize原理是消息发送,所有当子类没有实现时,会调用父类。是会被调用两次
  • 如果子类,父类同时实现,先调用父类,再调用子类

load

  • load方法在应用程序加载过程中(dyld)完成调用,在main之前
  • 在底层进行load_images处理时,维护了两个load的加载表,一个是本类的表,另一个是分类的表,所以说有先对本类的load发起调用
  • 在对类 load方法进行处理时,进行递归处理,以确保父类优先被处理
  • 在load方法的调用顺序是父类、子类、分类
  • 在分类中load调用顺序,是根据编译的顺序为准

c++构造函数

  • 在分析dyld后,可以确定这样个调用流程load->c++->main
  • 但是如果c++写在objc工程中,在objc_init()调用时,会通过static_init()方法优先调用c++函数,而不需要等到_dyld_objc_notify_register向dyld注册load_images之后再调用
  • 同时如果objc_init()自启的话也不需要dyld进行启动,也可能会发生c++函数在load方法之前调用的情况

疑问?如果有LGA LGB LGC三个分类,哪个分类先加载呢?
这个主要看编译顺序,如果同名方法是load方法 -- 先主类load,后分类load(分类之间,看编译顺序

面试题二:Runtime是什么?
  • runtime是由C和C++汇编实现的一套API,为OC语言加入了面向对象、以及运行时的功能
  • 运行时是指将数据类型的确定由编译时推迟到了运行时
    举例:extension 和 category 的区别
  • 平时编写的OC代码,在程序运行的过程中,其实最终会转换成runtime的C语言代码, runtime是OC的幕后工作者
面试题三:方法的本质,sel是什么?IMP是什么?两者之间的关系又是什么?

方法的本质:发送消息,消息会有以下几个流程

  • 快速查找(objc_msgSend) - cache_t缓存消息中查找
  • 慢速查找 - 递归自己|父类 - lookUpImpOrForward
  • 查找不到消息:动态方法解析 - resolveInstanceMethod
  • 消息快速转发 - forwardingTargetForSelector
  • 消息慢速转发 - methodSignatureForSelector & forwardInvocation

sel是方法编号 - 在read_images期间就编译进了内存
imp是函数实现指针 ,找imp就是找函数的过程
sel相当于 一本书的目录title
imp 相当于 书本的页码

查找具体的函数就是想看这本书具体篇章的内容

  • 首先知道想看什么,即目录title - sel
  • 根据目录找到对应的页码 - imp
  • 通过页码去翻到具体的内容
面试题四:能否向编译后得到的类中增加实例变量?能否向运行时创建的类中添加实例变量?

不能向编译后的得到的类中增加实例变量
原因是:编译好的实例变量存储的位置是ro,一旦编译完成,内存结构就完全确定了

可以向运⾏时创建的类中添加实例变量,只要类没有注册到内存,运行时还是可以添加的
可以通过objc_allocateClassPair运行时创建类,并添加属性、实例变量、方法

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]的区别以及原理分析

LGTeacher中的init方法中打印这两种class调用

// 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

  • 首先这两个类中都没有实现class方法,那么根据继承关系,他们最终会调用到NSObject中的class方法
- (Class)class {
    return object_getClass(self);
}

Class object_getClass(id obj)
{
    if (obj) return obj->getIsa();
    else return Nil;
}
  • 由打印可知这两个方法返回的都是self对应的类。关于这个self是谁,这里涉及到消息发送objc_msgSend,有两个隐形参数分别是id selfSEL sel,这里主要来说下id self
  • [self class]输出LGTeacher,这里消息的发送者是LGTeacher对象,通过调用NSObject的class,但是消息的接受者没有发生变化,所以是LGTeacher

接下来分析[super class]打印的为什么是LGTeacher?

  • [super class]super 是语法关键字,通过命令clang -rewrite-objc LGTeacher.m -o LGTeacher.cpp查看super的本质。下面是编译时的底层源码,其中第一个参数是消息接收者,是__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]的低层实现是objc_msgSendSuper方法,同时存在id selfSEL sel两个隐形参数
<!-- objc_msgSendSuper底层源码查看隐藏参数 -->
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两个参数,其中super_class表示第一个要去查找的类,至此我们可以得出结论,在LGTeacher中调用[super class],其内部会调用objc_msgSendSuper方法,并且会传入参数objc_super,其中receiver是LGTeacher对象super_class是LGTeacher的父类,也就是要第一个查找的类。
  • 我们再来看[super class]在运行时是否如上一步的底层编码所示,是objc_msgSendSuper,打开汇编调试发现会调用objc_msgSendSuper2,查看objc_msgSendSuper2汇编源码发现是从superclass中的cache中查找方法
<!-- objc_msgSendSuper2底层源码查看隐藏参数 -->
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]方法调用的本质是发送消息,调用class的消息流程,拿到元类的类型,在这里是因为类已经加载到内存,所以在读取时是一个字符串类型,这个字符串类型是在map_imagesreadClass时已经加入表中,所以打印为LGTeacher
  • [super class]打印的是LGTeacher,原因是当前的super是一个关键字,在这里只调用objc_msgSendSuper2,其实他的消息接收者[self class]是一模一样的,所以返回的是LGTeacher
面试题六:内存平移问题

调试一
创建一个LGPerson类,并实现实例方法saySomething,下面代码能否正常调用?

<!-- 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-内存平移问题[90979:11344738] -[LGPerson saySomething]
2021-08-30 22:26:50.335322+0800 004-内存平移问题[90979:11344738] -[LGPerson saySomething]

分析:

  • person的 isa指向类LGPerson,即person的首地址指向 LGPerson的首地址,我们可以通过LGPerson的内存平移找到cache,在cache中查找方法
  • [(__bridge id)kc saySomething]中的kc是来自于LGPerson这个类,然后有一个指针kc将其指向LGPerson的首地址
  • 所以person是指向LGPerson类的结构,kc也是指向LGPerson类的结构,然后都是在LGPerson中的methodList中查找方法
image.png

调试二
接着上面的案例,我们新增一个属性kc_name进行打印,在return 0;前面添加断点查看控制台打印以及lldb调试打印

<!-- 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-内存平移问题[91230:11372868] -[LGPerson saySomething] - (null)
2021-08-30 23:09:55.449692+0800 004-内存平移问题[91230:11372868] -[LGPerson saySomething] - <LGPerson: 0x600002cef160>

// lldb调试发现[(__bridge id)kc saySomething]; 打印的self.kc_name的LGPerson内存地址与下面person内存地址相同
(lldb) p person
(LGPerson *) $0 = 0x0000600002cef160
  • 首先我们了解到取出属性的值其实是要先计算出偏移大小,再通过内存平移获取值。其实是Person类内部存储着成员变量,每次偏移8字节进行存取。
  • 至于kc打印的self. kc_name的值<LGPerson: 0x6000019a1f40>,是因为cls只有Person类的内存首地址,但是没有person对象的内存结构,所以kc只能在栈里面进行内存平移

调试三
修改属性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字节指针,self.kc_name的获取相当于 kc首地址的指针平移8字节找kc_name,那么此时的kc的指针地址是多少?平移8字节获取的是什么?

  • kc是一个指针,是存在栈中的,栈是一个先进后出的结构,参数传入就是一个不断压栈的过程。其中隐藏参数也会压入栈,且每个函数都会有两个隐藏参数(id self,sel _cmd),可以通过clang -rewrite-objc ViewController.m -o ViewController.cpp查看底层编译
  • 隐藏参数压栈其地址是递减的,而栈是从高地址->低地址分配的,即在栈中参数会从前往后一直压

super通过clang查看底层的编译是objc_msgSendSuper,其第一个参数是一个结构体__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调试
(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调试得出20先加入,10后加入,因此结构体内部的压栈情况是 低地址->高地址递增的,即栈中结构体内部的成员是反向压入栈,而对象属性参数压栈是高地址->低地址正向压入栈

调试三
通过下面这段代码打印栈的存储

- (void)viewDidLoad {
    [super viewDidLoad];
    Class cls = [LGPerson class];
    void  *kc = &cls;  // 
    LGPerson *person = [LGPerson alloc];
    NSLog(@"%p - %p",&person,kc);
    // 隐藏参数 会压入栈帧
    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-内存平移问题[94665:11885963] 0x7ffee1e4e008 - 0x7ffee1e4e018
2021-08-31 22:25:31.815196+0800 004-内存平移问题[94665:11885963] 0x7ffee1e4e038 : <ViewController: 0x7ffdde4139b0>
2021-08-31 22:25:31.815326+0800 004-内存平移问题[94665:11885963] 0x7ffee1e4e030 : viewDidLoad
// viewDidLoad方法里面 [super viewDidLoad]; 压栈的为什么不是UIViewController
2021-08-31 22:25:31.815461+0800 004-内存平移问题[94665:11885963] 0x7ffee1e4e028 : ViewController
2021-08-31 22:25:31.815608+0800 004-内存平移问题[94665:11885963] 0x7ffee1e4e020 : <ViewController: 0x7ffdde4139b0>
2021-08-31 22:25:31.815744+0800 004-内存平移问题[94665:11885963] 0x7ffee1e4e018 : LGPerson
2021-08-31 22:25:31.815897+0800 004-内存平移问题[94665:11885963] 0x7ffee1e4e010 : <LGPerson: 0x7ffee1e4e018>
image.png

由此可知栈中从高地址到低地址的顺序的:self - _cmd - (id)class_getSuperclass(objc_getClass("ViewController")) - self - cls - kc - personself和_cmdviewDidLoad方法的两个隐藏参数,是高地址->低地址正向压栈的。class_getSuperClass 和 selfobjc_msgSendSuper2中的结构体成员,是从最后一个成员变量,即低地址->高地址反向压栈的

注意

  • 函数隐藏参数会从前往后一直压,即从高地址->低地址开始入栈
  • 结构体内部的成员是从低地址->高地址

通过上面调试,这里有几个疑问?结构体压栈的原理?什么东西才会压栈?上面打印栈的存储viewDidLoad方法里面[super viewDidLoad];压栈的为什么不是UIViewController?

  • 临时变量(传入函数栈帧里面的参数)才会压栈,其中viewDidLoad方法里面的隐藏参数self_cmd压入LGPerson的class的函数栈帧中,跟viewDidLoad的函数栈帧没有任何关系
  • viewDidLoad方法的结构体{objc class}为什么会压栈进来呢?[super viewDidLoad];潜在含义是objc_msgSend发送消息,等同与objc_msgSendSuper(&kc_objc_super, @selector(viewDidLoad))

压栈的为什么不是UIViewController? 真机运行工程,通过汇编代码查看[super viewDidLoad];的底层含义

image.png
  • 结构体第一个参数receiverself,第二个参数是superclass还是currentclass? 如果当前传入的是currentclass意味着压栈的是ViewController,反之压栈的是UIViewController
  • 现在查看objc_msgSendSuper底层源码实现
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调试
image.png
image.png

当前的结构体里面super_class传入的是ViewController,即当前类类名

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 212,332评论 6 493
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,508评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 157,812评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,607评论 1 284
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,728评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,919评论 1 290
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,071评论 3 410
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,802评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,256评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,576评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,712评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,389评论 4 332
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,032评论 3 316
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,798评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,026评论 1 266
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,473评论 2 360
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,606评论 2 350

推荐阅读更多精彩内容

  • iOS 底层原理 文章汇总[https://www.jianshu.com/p/412b20d9a0f6] 【面试...
    Style_月月阅读 3,552评论 6 12
  • 面试题-1 Runtime Asssociate方法关联的对象,需要在dealloc中释放? 当我们对象释放时,会...
    含笑州阅读 327评论 0 1
  • 【面试-1】方法的调用顺序 类的方法 和 分类方法 重名,如果调用,是什么情况? 如果同名方法是普通方法,包括in...
    KB_MORE阅读 305评论 0 0
  • 【面试-1】通过 Asssociate 方法关联的对象,需要在dealloc中释放 当对象释放时,系统会自动调用d...
    木扬音阅读 1,010评论 0 17
  • 我是黑夜里大雨纷飞的人啊 1 “又到一年六月,有人笑有人哭,有人欢乐有人忧愁,有人惊喜有人失落,有的觉得收获满满有...
    陌忘宇阅读 8,531评论 28 53