OC底层面试

2021-08-31  本文已影响0人  浅墨入画

关联对象补充

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

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()  {  }
image.png
(lldb) p &manager
(objc::AssociationsManager *) $0 = 0x00007ffeefbff350
(lldb) p &manager2
(objc::AssociationsManager *) $1 = 0x00007ffeefbff338

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

image.png

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

关联对象释放

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

// Replaced by NSZombies
- (void)dealloc {
    _objc_rootDealloc(self);
}
void
_objc_rootDealloc(id obj)
{
    ASSERT(obj);

    obj->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);
    }
}
id 
object_dispose(id obj)
{
    if (!obj) return nil;
    objc_destructInstance(obj);    
    free(obj);
    return nil;
}
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(调用)

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方法
}
// 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); 
}

进入call_load_methods源码,主要有三部分操作

initialize

load

c++构造函数

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

面试题二:Runtime是什么?
面试题三:方法的本质,sel是什么?IMP是什么?两者之间的关系又是什么?

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

sel是方法编号 - 在read_images期间就编译进了内存
imp是函数实现指针 ,找imp就是找函数的过程
sel相当于 一本书的目录title
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)class {
    return object_getClass(self);
}

Class object_getClass(id obj)
{
    if (obj) return obj->getIsa();
    else return Nil;
}

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

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;
}
<!-- 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 */
};
<!-- 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

最终回答如下

面试题六:内存平移问题

调试一
创建一个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]

分析:

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

调试三
修改属性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字节获取的是什么?

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?

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

image.png
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);
image.png image.png

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

上一篇 下一篇

猜你喜欢

热点阅读