阅读 708

iOS 面试题分析(一)|8月更文挑战

1.回顾

在之前的博客中,对OC底层进行了一系列的探索分析,相信小伙伴们都学到了一定的知识,但是底层源码分析比较枯燥,那么本次就对一些面试题进行分析。 iOS

1.1 补充

在上篇博客iOS底层探索之类的加载(四):类的关联对象AssociatedObject中主要讲了类的扩展类的关联对象移除关联还没有讲,这里就做一点补充。

  • 移除关联对象objc_removeAssociatedObjects
void objc_removeAssociatedObjects(id object) 
{
    if (object && object->hasAssociatedObjects()) {
        _object_remove_assocations(object, /*deallocating*/false);
    }
}
复制代码
  • _object_remove_assocations
void
_object_remove_assocations(id object, bool deallocating)
{
    ObjectAssociationMap refs{};

    {
        AssociationsManager manager;
        AssociationsHashMap &associations(manager.get());
        AssociationsHashMap::iterator i = associations.find((objc_object *)object);
        if (i != associations.end()) {
            refs.swap(i->second);

            // If we are not deallocating, then SYSTEM_OBJECT associations are preserved.
            bool didReInsert = false;
            if (!deallocating) {
                for (auto &ref: refs) {
                    if (ref.second.policy() & OBJC_ASSOCIATION_SYSTEM_OBJECT) {
                        i->second.insert(ref);
                        didReInsert = true;
                    }
                }
            }
            if (!didReInsert)
                associations.erase(i);
        }
    }

    // Associations to be released after the normal ones.
    SmallVector<ObjcAssociation *, 4> laterRefs;

    // release everything (outside of the lock).
    for (auto &i: refs) {
        if (i.second.policy() & OBJC_ASSOCIATION_SYSTEM_OBJECT) {
            // If we are not deallocating, then RELEASE_LATER associations don't get released.
            if (deallocating)
                laterRefs.append(&i.second);
        } else {
            i.second.releaseHeldValue();
        }
    }
    for (auto *later: laterRefs) {
        later->releaseHeldValue();
    }
}

复制代码

上面👆这是移除关联对象的代码,这里就不过多分析源码了,我们看看在哪里调用了

在对象的生命周期,dealloc的时候

  • dealloc
// Replaced by NSZombies
- (void)dealloc {
    _objc_rootDealloc(self);
}
复制代码
  • _objc_rootDealloc
void
_objc_rootDealloc(id obj)
{
    ASSERT(obj);

    obj->rootDealloc();
}
复制代码

对象释放就会去找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);
    }
}
复制代码

这里会判断isa.nonpointer、弱引用isa.weakly_referenced、关联对象isa.has_assoc等等,有的话就进入object_dispose

  • object_dispose
id 
object_dispose(id obj)
{
    if (!obj) return nil;

    objc_destructInstance(obj);    
    free(obj);

    return nil;
}

复制代码

进入objc_destructInstance销毁实例

  • 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;
}
复制代码

C++方法,关联对象方法判断,就去走_object_remove_assocations也就是最开头的那个移除关联对象方法。

所以,关联对象不需要我们手动移除,会在dealloc时自动进行释放

2. iOS面试题分析

2.1 load与c++构造函数调用顺序

  • load是在dyld回调load_images中进行调用的,这个回调是在_objc_init的过程中进行注册的。
  • C++构造函数对于同一个image而言是在load回调后dyld调用的。(并不是绝对的)
  • image内部是先加载所有类的+ load,再加载分类的+ load,最后加载C++全局构造函数。(类load -> 分类load -> C++构造函数)。
  • +loadobjc中调用的,C++全局构造函数是在dyld中调用的。(image内部的顺序默认是按Compile Sources中顺序进行加载的,当然对于有依赖库的image,依赖库+load先被调用)。
  • Dyld初始化image是按Link Binary With Libraries顺序逐个初始化的,从下标1开始,最后再初始化主程序(下标0)。
  • 当然对于同一个image而言C++构造函数在load之后调用并不是绝对的。比如objc系统库,在进行dyld注册回调之前调用了自身库的C++构造函数(自启)。

2.2 runtime是什么?

  • runtime 是由CC++/ 汇编 实现的⼀套API,为OC语⾔加⼊了⾯向对象,运⾏时的功能。
  • 是一种运行机制,不是底层。dyld汇编objcmacho才是底层。
  • 平时编写的OC代码,在程序运行过程中,其实最终会转换成RuntimeC语言代 码,RuntimeObjective-C 的幕后工作者。

2.3 initialize调用顺序

  • initialize是在第一次发送消息的时候进行的调用。

  • load是在load_images的时候调用的,loadinitialize调用时机早(initializelookupimporforward慢速消息查找的时候调用)。

  • 整个调用顺序load > C++构造函数 > initialize

2.4 同名分类方法的调用顺序

同名分类方法调用顺序分为两种情况:

  • 分类合并到主类的情况,也就是只有一个/或者没有load方法,这个时候整个方法列表一维数组(不考虑其它动态添加方法的情况)。最后编译的同名分类方法会放在主类与其它分类前面,在进行方法二分查找的时候先从中间开始查找,找到对应方法SEL的时候会继续往前查找,直到找到最前面的同名方法。
  • 分类没有合并到主类的情况,多个load方法这个时候整个方法列表是一个一个二维数组,后编译加载的分类在数组前面,查找方法的时候从前面开始查找。
  • 也就是同名方法最终会找到后编译加载的分类的同名方法,查找过程不一样而已。

iOS底层探索之类的加载(三): attachCategories分析博客里面也介绍了分类和load方法的一些加载。

2.5 分类和扩展的区别?

首先我们来看看什么是分类和扩展

category: 类别/分类

  • 专门用来给类添加新的方法
  • 不能给类添加成员属性,添加了成员变量,也无法取到
  • 注意:其实可以通过runtime给分类添加属性
  • 分类中用@property定义变量,只会生成变量的 getter,setter方法的声明,不能生成方法实现和带下划线的成员变量

extension:类扩展

  • 可以说成是特殊的分类,也称作匿名分类
  • 可以给类添加成员属性,但是是私有变量
  • 可以给类添加方法,也是私有方法

分类我们已经很熟悉了,这里就不必过多赘述了,下面介绍下扩展

extension

类扩展,我们平时用的是非常多的,如下

匿名扩展

what ? 什么,这就是扩展吗?天天用居然不知道!

是的,这就是扩展,平时用的是非常之多,但是很多人都不知道。

震惊

注意:类扩展要放在声明之后,实现之前,否则会报错。

更多内容持续更新

🌹 喜欢就点个赞吧👍🌹

🌹 觉得有收获的,可以来一波,收藏+关注,评论 + 转发,以免你下次找不到我😁🌹

🌹欢迎大家留言交流,批评指正,互相学习😁,提升自我🌹

文章分类
iOS
文章标签