iOS底层原理(18)--面试题

443 阅读4分钟

上一节有提到关联对象,现在对关联对象的释放做一下补充:

一、关联对象释放的流程

1、dealloc

- (void)dealloc {
    _objc_rootDealloc(self);

}

2、_objc_rootDealloc

void _objc_rootDealloc(id obj)
{
    ASSERT(obj);
    obj->rootDealloc();
}

3、rootDealloc

inline void objc_object::rootDealloc()
{
    if (isTaggedPointer()) return// fixme necessary?
    if (fastpath(isa.nonpointer  &&  
                 !isa.weakly_referenced  &&  
                 !isa.has_assoc  &&  
                 !isa.has_cxx_dtor  &&  
                 !isa.has_sidetable_rc))
    {
        assert(!sidetable_present());
        free(this);
    } 
    else {
        object_dispose((id)this);
    }
}

4、object_dispose

id  object_dispose(id obj)
{
    if (!obj) return nil;
    objc_destructInstance(obj);    
    free(obj);
    return nil;
}

5、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);
        obj->clearDeallocating();

    }
    return obj;

}

6、_object_remove_assocations

void _object_remove_assocations(id object)

{

    ObjectAssociationMap refs{};
    {

        AssociationsManager manager;
        AssociationsHashMap &associations(manager.get());
        AssociationsHashMap::iterator i = associations.find((objc_object *)object);
        if (i != associations.end()) {
            refs.swap(i->second);
            associations.erase(i);
        }
    }
    // release everything (outside of the lock).
    for (auto &i: refs) {
        i.second.releaseHeldValue();
    }
}

二、load_images 的调用流程

image.png

dyld 中调用 mapImages 、loadImages

1、 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
    {
        mutex_locker_t lock2(runtimeLock);
        prepare_load_methods((const headerType *)mh);
    }
    // Call +load methods (without runtimeLock - re-entrant)
    call_load_methods();
}

2、prepare_load_methods

void prepare_load_methods(const headerType *mhdr)
{
    size_t count, i;
    runtimeLock.assertLocked();
    classref_t const *classlist = 
        _getObjc2NonlazyClassList(mhdr, &count); // why 

    for (i = 0; i < count; i++) {
        schedule_class_load(remapClass(classlist[i]));
    }
    category_t * const *categorylist = _getObjc2NonlazyCategoryList(mhdr, &count);

    for (i = 0; i < count; i++) {

        category_t *cat = categorylist[i];
        Class cls = remapClass(cat->cls);
        if (!cls) continue// category for ignored weak-linked class
        if (cls->isSwiftStable()) {

            _objc_fatal("Swift class extensions and categories on Swift "
                        "classes are not allowed to have +load methods");

        }
        realizeClassWithoutSwift(cls, nil);
        ASSERT(cls->ISA()->isRealized());
        add_category_to_loadable_list(cat);

    }
}

2.1 搜集类方法

2.1.1、 schedule_class_load
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); 

}
2.1.2、add_class_to_loadable_list
void add_class_to_loadable_list(Class cls)
{

    IMP method;
    loadMethodLock.assertLocked();
    method = cls->getLoadMethod();
    if (!method) return// Don't bother if cls has no +load method
    if (PrintLoading) {
        _objc_inform("LOAD: class '%s' scheduled for +load", 

                     cls->nameForLogging());
    }
    if (loadable_classes_used == loadable_classes_allocated) { // 扩容
        loadable_classes_allocated = loadable_classes_allocated*2 + 16;
        loadable_classes = (struct loadable_class *)
            realloc(loadable_classes,

                              loadable_classes_allocated *

                              sizeof(struct loadable_class));

    }

    loadable_classes[loadable_classes_used].cls = cls;
    loadable_classes[loadable_classes_used].method = method;
    loadable_classes_used++;

}
2.1.3、getLoadMethod
IMP 
objc_class::getLoadMethod()
{
    runtimeLock.assertLocked();

    const method_list_t *mlist;

    ASSERT(isRealized());
    ASSERT(ISA()->isRealized());
    ASSERT(!isMetaClass());
    ASSERT(ISA()->isMetaClass());

    mlist = ISA()->data()->ro()->baseMethods();
    if (mlist) {
        for (const auto& meth : *mlist) {
            const char *name = sel_cname(meth.name);
            if (0 == strcmp(name, "load")) {
                return meth.imp;
            }
        }
    }

    return nil;
}

2.2、搜集分类

2.2.1 、add_category_to_loadable_list
void add_category_to_loadable_list(Category cat)
{
    IMP method;

    loadMethodLock.assertLocked();

    method = _category_getLoadMethod(cat);

    // Don't bother if cat has no +load method
    if (!method) return;

    if (PrintLoading) {
        _objc_inform("LOAD: category '%s(%s)' scheduled for +load", 
                     _category_getClassName(cat), _category_getName(cat));
    }
    
    if (loadable_categories_used == loadable_categories_allocated) {
        loadable_categories_allocated = loadable_categories_allocated*2 + 16;
        loadable_categories = (struct loadable_category *)
            realloc(loadable_categories,
                              loadable_categories_allocated *
                              sizeof(struct loadable_category));
    }

    loadable_categories[loadable_categories_used].cat = cat;
    loadable_categories[loadable_categories_used].method = method;
    loadable_categories_used++;
}

2.2.2、 _category_getLoadMethod
IMP 
_category_getLoadMethod(Category cat)
{
    runtimeLock.assertLocked();

    const method_list_t *mlist;

    mlist = cat->classMethods;
    if (mlist) {
        for (const auto& meth : *mlist) {
            const char *name = sel_cname(meth.name);
            if (0 == strcmp(name, "load")) {
                return meth.imp;
            }
        }
    }

    return nil;
}

3、call_load_methods

void call_load_methods(void)
{
    static bool loading = NO;
    bool more_categories;

    loadMethodLock.assertLocked();

    // Re-entrant calls do nothing; the outermost call will finish the job.
    if (loading) return;
    loading = YES;

    void *pool = objc_autoreleasePoolPush();

    do {
        // 1. Repeatedly call class +loads until there aren't any more
        while (loadable_classes_used > 0) {
            call_class_loads();
        }

        // 2. Call category +loads ONCE
        more_categories = call_category_loads();

        // 3. Run more +loads if there are classes OR more untried categories
    } while (loadable_classes_used > 0  ||  more_categories);

    objc_autoreleasePoolPop(pool);

    loading = NO;
}

3.1 call_class_loads

static void call_class_loads(void)
{
    int i;
    
    // Detach current loadable list.
    struct loadable_class *classes = loadable_classes;
    int used = loadable_classes_used;
    loadable_classes = nil;
    loadable_classes_allocated = 0;
    loadable_classes_used = 0;
    
    // Call all +loads for the detached list.
    for (i = 0; i < used; i++) {
        Class cls = classes[i].cls;
        load_method_t load_method = (load_method_t)classes[i].method;
        if (!cls) continue; 

        if (PrintLoading) {
            _objc_inform("LOAD: +[%s load]\n", cls->nameForLogging());
        }
        (*load_method)(cls, @selector(load));
    }
    
    // Destroy the detached list.
    if (classes) free(classes);
}

调用顺序

load 、c++、main

void _objc_init(void)
{
    static bool initialized = false;
    if (initialized) return;
    initialized = true;
    
    // fixme defer initialization until an objc-using image is found?
    environ_init();
    tls_init();
    static_init();//注意,这里会自启动,不需要dyld
    runtime_init();
    exception_init();
    cache_init();
    _imp_implementationWithBlock_init();

    _dyld_objc_notify_register(&map_images, load_images, unmap_image);

#if __OBJC2__
    didCallDyldNotifyRegister = true;
#endif
}
static void static_init()
{
    size_t count;
    auto inits = getLibobjcInitializers(&_mh_dylib_header, &count);
    for (size_t i = 0; i < count; i++) {
        inits[i]();
    }
}

三、load和initialize的区别

load

1、在runtime加载类、分类的时候调用,且只调用一次
2、先调用主类的load方法,再调用分类的load方法
3、如果主类有子类,先调用父类的load方法,再调用子类的load方法
4、如果有多个分类有load方法,调用顺序,按照compile source里面的加载顺序调用

initialize

1、是懒加载,在第一次发送消息的时候调用
2、先调用父类的initialize方法,再调用子类的initialize方法
3、如果子类中未实现initialize方法,则会调用父类的initialize方法,因此父类的initialize可能会调用多次
4、Category会覆盖类中的initialize方法,当有多个Category,会执行Compile source最后一个category的initialize

四、 runtime 是什么

runtime 是由CC++ 汇编 实现的⼀套API,为OC语⾔加⼊了⾯向对象,运⾏时的功能

运⾏时(Runtime) 是指将数据类型的确定由编译时推迟到了运⾏时 - 举例⼦? : extension - category 的区别

平时编写的OC代码,在程序运⾏过程中,其实最终会转换成Runtime的C语⾔代码,RuntimeObject-C 的幕后⼯作者

五、⽅法的本质,sel是什么?IMP是什么?两者之间的关系⼜是什么?

sel 是⽅法编号 ~ 在 read_images 期间就编译进⼊了内存

imp 就是我们函数实现指针 ,找 imp 就是找函数的过程

sel 就相当于书本的⽬录 tittle, imp 就是书本的⻚码, 查找具体的函数就是想看这本书⾥⾯具体篇章的内容

1:我们⾸先知道想看什么 ~ tittle (sel)

2:根据⽬录对应的⻚码 (imp)

3:翻到具体的内容

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

1:快速查找 (objc_msgSend)~ cache_t 缓存消息

2:慢速查找~ 递归⾃⼰| ⽗类 ~ lookUpImpOrForward

3:查找不到消息: 动态⽅法解析 ~ resolveInstanceMethod

4:消息快速转发~ forwardingTargetForSelector

5:消息慢速转发~ methodSignatureForSelector & forwardInvocation

六、能否向编译后的得到的类中增加实例变量?能否向运⾏时创建的类中添加实例变量

1:不能向编译后的得到的类中增加实例变量

2:只要类没有注册到内存还是可以添加

原因:我们编译好的实例变量存储的位置在 ro,⼀旦编译完成,内存结构就完全确定,就⽆法修改。

可以添加属性 + ⽅法

七、[self class][super class]的区别以及原理分析

[self class] 就是发送消息 objc_msgSend,消息接受者是 self ⽅法编号:class

[super class] 本质就是 objc_msgSendSuper, 消息的接受者还是 self ⽅法编号:class

只是 objc_msgSendSuper 会更快,直接跳过 self 的查找。

- (instancetype)init{
    self = [super init];
    if (self){
        NSLog("%@ - %@",[self class],[super class]);
    }
    return self;
}

上面的打印的结果是一样的。

八、内存偏移

用代码来一步步探索吧:

LGPerson

//.h文件

@interface LGPerson : NSObject
    @property (nonatomic, assign) int kc_name;
    @property (nonatomic, copy) NSString *kc_hobby;  // 12
    - (void)saySomething;
@end

//.m文件
#import "LGPerson.h"
@implementation LGPerson
- (void)saySomething{ 
    NSLog(@"%s",__func__);
}
@end

ViewController

- (void)viewDidLoad {
    [super viewDidLoad];
    
    LGPerson *person = [LGPerson alloc];
    [person saySomething];
    
    Class cls = [LGPerson class];
    void *kc = &cls;
    [(__bridge id)kc saySomething];
}

打印结果:

2021-07-26 15:24:50.018164+0800 [6368:6129630] -[LGPerson saySomething]
2021-07-26 15:24:50.018288+0800 [6368:6129630] -[LGPerson saySomething]

问题一:saySomething存在哪里,person是如何访问到的?

saySomething存在类里面,person是实例对象,它跟类有一个关联(isa指针,isa存在实例对象中,指向了类),向person发送了一个消息objc_msgSend,objc_msgSend(之前探索过,它是用汇编实现的,有两个重要的参数isaclass)只关心有没有一个指针能够指向LGPerson这个类,现在personkc都指向了LGPerson,然后通过 地址平移 就可以找到saySomething方法。

下一步探索: 更改LGPerson.m

NSLog(@"%s -- %@",__func__,self.kc_name);

查看打印:

2021-07-26[6830:6150081] -[LGPerson saySomething] -- (null)
2021-07-26[6830:6150081] -[LGPerson saySomething] -- <LGPerson: 0x600000955020>

问题二: 为啥会打印<LGPerson: 0x600000955020>呢?

person 是如何访问到 kc_name 的,是通过访问 settergetter 方法,也是内存平移的方式。

image.png

lldb 调试:

(lldb) x/4gx person
0x6000030855a0: 0x000000010702d658 0x0000000107028038 //person:isa happy
0x6000030855b0: 0x0000000000000000 0x0000000000000000
(lldb) p person
(LGPerson *) $1 = 0x00006000030855a0
(lldb) po 0x000000010702d658
LGPerson
(lldb) po 0x0000000107028038
happy

person 通过 +0x8,访问到 saySomething; kc 模仿 person,也拿kc(是在当前函数栈帧中加过来的cls的地址)的地址+0x8,查看 kc+0x8在栈里面的地址是谁,

(lldb) p &cls
(Class *) $3 = 0x00007ffee8bd8000
(lldb) p &person
(LGPerson **) $4 = 0x00007ffee8bd8008

由上面打印可以看出为什么打印 LGPerson

九、压栈

struct kc_struct{
    NSNumber *num1;
    NSNumber *num2;
} kc_struct;

- (void)viewDidLoad { //(id self, SEL _cmd)
    [super viewDidLoad]; //struct {object class}
    
    LGPerson *person = [LGPerson alloc];
    
    struct kc_struct kc_s = {@(10),@(20)};
    
    LGPerson *person1 = [LGPerson alloc];
    person1.kc_name = @"happy";
    [person1 saySomething];
    
}

打印:

(lldb) p &person
(LGPerson **) $0 = 0x00007ffee7b95158
(lldb) p/x &kc_s
(kc_struct *) $1 = 0x00007ffee7b95148
(lldb) p &person1
(LGPerson **) $2 = 0x00007ffee7b95140
(lldb) p kc_s.num1
(__NSCFNumber *) $3 = 0x9ed6c4d5f4468662 (int)10
(lldb) p &$3
(NSNumber **) $4 = 0x00007ffee7b95148
(lldb) p kc_s.num2
(__NSCFNumber *) $5 = 0x9ed6c4d5f4468782 (int)20
(lldb) p &$5
(NSNumber **) $6 = 0x00007ffee7b95150
(lldb) 

压栈过程示意图:

image.png

打印所有的入栈情况:

struct kc_struct{
    NSNumber *num1;
    NSNumber *num2;
} kc_struct;

- (void)viewDidLoad { //(id self, SEL _cmd)
    [super viewDidLoad]; //struct {object class}
   
    Class cls = [LGPerson class];
    void  *kc = &cls;
    [(__bridge id)kc saySomething];
    
    LGPerson *person = [LGPerson alloc];
    person.kc_name = @"happy";
    [person saySomething];
    
    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-07-26 20:28:10.809120+0800  -[LGPerson saySomething] - <ViewController: 0x7fe489d055e0>
2021-07-26 20:28:10.809218+0800  -[LGPerson saySomething] - happy
2021-07-26 20:28:10.809294+0800  0x7ffee3bfa148 - 0x7ffee3bfa158
2021-07-26 20:28:10.809382+0800  0x7ffee3bfa178 : <ViewController: 0x7fe489d055e0>
2021-07-26 20:28:10.809447+0800  0x7ffee3bfa170 : viewDidLoad
2021-07-26 20:28:10.809523+0800  0x7ffee3bfa168 : ViewController
2021-07-26 20:28:10.809623+0800  0x7ffee3bfa160 : <ViewController: 0x7fe489d055e0>
2021-07-26 20:28:10.809706+0800  0x7ffee3bfa158 : LGPerson
2021-07-26 20:28:10.809796+0800  0x7ffee3bfa150 : <LGPerson: 0x7ffee3bfa158>

补充:

结构体是从后往前压栈; 参数是从前往后压栈;