美文网首页
ios 经典面试题

ios 经典面试题

作者: 瞬间完善 | 来源:发表于2020-02-10 14:02 被阅读0次

    1、Runtime是什么?

    Runtime是一套由CC++和汇编实现的一套API,为OC语言加入了面向对象和运行时功能。
    运行时 (Runtime) 是指将数据类型的确定由编译时推迟到了运行时。 (例如extension - category的区别)
    平时写的OC代码,在运行时会被转换成RuntimeC语言代码。RuntimeOC的幕后工作者。

    2、方法的本质,SEL是什么?IMP是什么?两者之间的关系又是什么?

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

      • 快速查找 (objc_msgSend) ~ cache_t缓存消息
      • 慢速查找: 递归自己和父类 ~lookUpImpOrForward
      • 查找不到消息: 进行动态方法解析resolveInstanceMethod
      • 消息快速转发: forwardingTargetForSelector
      • 消息慢速转发 : methodSignatureForSelector & forwardInvocation
      • 未找到消息:程序crash,打印日志
    • SEL是方法编号,在read_images期间就编译进入了内存

    • IMP就是我们函数实现指针,找IMP就是找函数实现过程

    • SEL就相当于书本的目录,IMP就是书本的页码,查找具体的函数就是想看这本书里面具体篇章的内容:

      • 我们首先知道想看什么,也就是title SEL
      • 根据目录对应的页码IMP
      • 翻到我们想看的具体内容

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

    • 不能向编译后得到的类中增加实例变量,因为类在编译时已经将实例变量存储到ro中了,编译完成,内存结构确定,我们无法进行修改。
    • 只要没有注册到内存还是可以添加的
    • 在运行时添加属性和方法

    4、isKindOfClassisMemberOfClass

    • isKindOfClass确定一个对象是否是一个类的成员,或者是继承自该类的成员
    • isMemberOfClass只能确定一个对象是否是当前类的成员,即isMemberOfClass不能检测任何的类都是基于NSObject类这一事实,而isKindOfClass可以
      我们可以进行实例测验:
        BOOL re1 = [(id)[NSObject class] isKindOfClass:[NSObject class]];
        BOOL re2 = [(id)[NSObject class] isMemberOfClass:[NSObject class]];
        BOOL re3 = [(id)[LGPerson class] isKindOfClass:[LGPerson class]];
        BOOL re4 = [(id)[LGPerson class] isMemberOfClass:[LGPerson class]];
        NSLog(@" re1 :%hhd\n re2 :%hhd\n re3 :%hhd\n re4 :%hhd\n",re1,re2,re3,re4);
    
        BOOL re5 = [(id)[NSObject alloc] isKindOfClass:[NSObject class]];
        BOOL re6 = [(id)[NSObject alloc] isMemberOfClass:[NSObject class]];
        BOOL re7 = [(id)[LGPerson alloc] isKindOfClass:[LGPerson class]];//1
        BOOL re8 = [(id)[LGPerson alloc] isMemberOfClass:[LGPerson class]];
        NSLog(@" re5 :%hhd\n re6 :%hhd\n re7 :%hhd\n re8 :%hhd\n",re5,re6,re7,re8);
    

    我们看一下输出结果:

    2020-02-05 10:10:29.892802+0800 LGTest[3088:31663]  
     re1 :1
     re2 :0
     re3 :0
     re4 :0
    2020-02-05 10:10:29.893424+0800 LGTest[3088:31663] 
     re5 :1
     re6 :1
     re7 :1
     re8 :1
    

    我们看一下isKindOfClass的源码:

    + (BOOL)isKindOfClass:(Class)cls {
    // ✅ for循环不断获取self的isa指针以及父类的isa指针
        for (Class tcls = object_getClass((id)self); tcls; tcls = tcls->superclass) {
    // ✅ 将循环获取isa指针指向与cls作对比,如果是这个类的成员,或者是继承自该类的成员,返回YES
            if (tcls == cls) return YES;
        }
        return NO;
    }
    

    我们在看一下isMemberOfClass的源码:

    + (BOOL)isMemberOfClass:(Class)cls {
    // ✅ 只是拿到当前self的isa指向与cls作对比,也就是只能确定这个对象是否是当前类的成员
        return object_getClass((id)self) == cls;
    }
    

    5、[self class][super class]

    首先我们创建两个类,LGPerson继承NSObject,LGStudent继承LGPerson,在LGStudentinit方法中进行打印一下代码:

    NSLog(@"%@",NSStringFromClass([self class]));
    NSLog(@"%@",NSStringFromClass([super class]));
    

    看结果:

    2020-02-05 16:14:44.064160+0800 LGTest[23673:249566] LGStudent
    2020-02-05 16:14:44.064508+0800 LGTest[23673:249566] LGStudent
    

    到这里就有问题了,为什么都是LGStudent?第二个不应该是LGPerson吗?
    我们通过clang生成.cpp文件,拿到以下代码:

    // ✅ 原代码很长,精简之后为以下内容
    NSStringFromClass(objc_msgSend((id)self, sel_registerName("class")));
    NSStringFromClass(objc_msgSendSuper({(id)self, (id)class_getSuperclass(objc_getClass("LGPerson"))}, sel_registerName("class"))));
    

    我们可以看到super关键字底层发送消息不是调用的objc_msgSend,而是objc_msgSendSuper,我们在汇编中找到这样一段代码:

    * id objc_msgSendSuper(struct objc_super *super, SEL op, ...)
     *
     * struct objc_super {
     *     id receiver;
     *     Class cls;   // the class to search
     * }
    

    根据.cpp内容可知receiverself(LGStudentsuper_classclass_getSuperclass(objc_getClass("LGPerson"))LGStudent的父类LGPerson
    调用时先调用LGPersonclass方法,如果没有,就沿继承体系往上找直到NSObjectclass,最后内部是使用objc_msgSend(objc_super->receiver, @selector("class"))去调用,所以输出LGStudent

    • [self class]就是发送消息objc_msgSend,消息接受者self,方法编号class
    • [super class]本质就是objc_msgSendSuper,消息的接收者还是self,方法编号class,只是objc_msgSendSuper会更快,直接跳过self的查找

    6、weak实现原理

    我们开发中经常是使用weak关键字来解决循环引用的问题,原因是被weak引用的对象它的引用计数不会增加,而且在这个对象被释放的时候被weak修饰的变量会自动置空,不会造成野指针的问题,相对来说比较安全。那么weak底层究竟是如何实现的呢?接下来我们一起来探究weak的实现原理。
    我们首先定位weak的代码:
    我们在main函数中进行断点:

    WX20200206-155353@2x.png
    然后通过汇编的方法找到weak的底层是objc_initWeak
    WX20200206-155536@2x.png

    我们下一个objc_initWeak的符号断点,可直接定位到源码:

    id
    objc_initWeak(id *location, id newObj)
    {
        if (!newObj) {
            *location = nil;
            return nil;
        }
    
        return storeWeak<DontHaveOld, DoHaveNew, DoCrashIfDeallocating>
            (location, (objc_object*)newObj);
    }
    

    通过上面的源码我们可以看到是重点函数storeWeak:

    storeWeak(id *location, objc_object *newObj)
    {
        assert(haveOld  ||  haveNew);
        if (!haveNew) assert(newObj == nil);
    
        Class previouslyInitializedClass = nil;
        id oldObj;
        // ✅ 声明新旧两个SideTable
        SideTable *oldTable;
        SideTable *newTable;
    
        // Acquire locks for old and new values.
        // Order by lock address to prevent lock ordering problems. 
        // Retry if the old value changes underneath us.
     retry:
        // ✅ 获取全局SideTables,并复制给oldTable和newTable
        if (haveOld) {
            oldObj = *location;
            oldTable = &SideTables()[oldObj];
        } else {
            oldTable = nil;
        }
        if (haveNew) {
            newTable = &SideTables()[newObj];
        } else {
            newTable = nil;
        }
    
        SideTable::lockTwo<haveOld, haveNew>(oldTable, newTable);
    
        if (haveOld  &&  *location != oldObj) {
            SideTable::unlockTwo<haveOld, haveNew>(oldTable, newTable);
            goto retry;
        }
    
        // Prevent a deadlock between the weak reference machinery
        // and the +initialize machinery by ensuring that no 
        // weakly-referenced object has an un-+initialized isa.
        // ✅ 防止弱引用机制和初始化出现死锁,在弱引用之前确保对象已经初始化成功
        if (haveNew  &&  newObj) {
            Class cls = newObj->getIsa();
            if (cls != previouslyInitializedClass  &&  
                !((objc_class *)cls)->isInitialized()) 
            {
                SideTable::unlockTwo<haveOld, haveNew>(oldTable, newTable);
                class_initialize(cls, (id)newObj);
    
                // If this class is finished with +initialize then we're good.
                // If this class is still running +initialize on this thread 
                // (i.e. +initialize called storeWeak on an instance of itself)
                // then we may proceed but it will appear initializing and 
                // not yet initialized to the check above.
                // Instead set previouslyInitializedClass to recognize it on retry.
                previouslyInitializedClass = cls;
    
                goto retry;
            }
        }
    
        // Clean up old value, if any.
        // ✅ 清空旧值
        if (haveOld) {
            weak_unregister_no_lock(&oldTable->weak_table, oldObj, location);
        }
    
        // Assign new value, if any.
        // ✅ 存储新值,函数weak_register_no_lock
        if (haveNew) {
            newObj = (objc_object *)
                weak_register_no_lock(&newTable->weak_table, (id)newObj, location, 
                                      crashIfDeallocating);
            // weak_register_no_lock returns nil if weak store should be rejected
    
            // Set is-weakly-referenced bit in refcount table.
            if (newObj  &&  !newObj->isTaggedPointer()) {
                newObj->setWeaklyReferenced_nolock();
            }
    
            // Do not set *location anywhere else. That would introduce a race.
            *location = (id)newObj;
        }
        else {
            // No new value. The storage is not changed.
        }
        
        SideTable::unlockTwo<haveOld, haveNew>(oldTable, newTable);
    
        return (id)newObj;
    }
    

    我们看源码的前部分都是对SideTable声明的表进行判断,然后我们可以了解到弱引用指针是存在SideTable表中

    • 如果weak指针需要弱引用新的对象newObj就执行存储新值weak_register_no_lock函数
    • 如果weak指针之前弱引用过别的对象oldObj,则调用weak_unregister_no_lock,在oldObjweak_entry_t中移除该weak指针地址
      我们在看一下weak_register_no_lock函数:
    id 
    weak_register_no_lock(weak_table_t *weak_table, id referent_id, 
                          id *referrer_id, bool crashIfDeallocating)
    {
        // ✅ 获取弱引用对象
        objc_object *referent = (objc_object *)referent_id;
        objc_object **referrer = (objc_object **)referrer_id;
    
        if (!referent  ||  referent->isTaggedPointer()) return referent_id;
    
        // ensure that the referenced object is viable
        // ✅ 确保被引用的对象没有在被释放
        bool deallocating;
        if (!referent->ISA()->hasCustomRR()) {
            deallocating = referent->rootIsDeallocating();
        }
        else {
            BOOL (*allowsWeakReference)(objc_object *, SEL) = 
                (BOOL(*)(objc_object *, SEL))
                object_getMethodImplementation((id)referent, 
                                               SEL_allowsWeakReference);
            if ((IMP)allowsWeakReference == _objc_msgForward) {
                return nil;
            }
            deallocating =
                ! (*allowsWeakReference)(referent, SEL_allowsWeakReference);
        }
        // ✅ 正在被释放的对象不能被弱引用
        if (deallocating) {
            if (crashIfDeallocating) {
                _objc_fatal("Cannot form weak reference to instance (%p) of "
                            "class %s. It is possible that this object was "
                            "over-released, or is in the process of deallocation.",
                            (void*)referent, object_getClassName((id)referent));
            } else {
                return nil;
            }
        }
    
        // now remember it and where it is being stored
        // ✅ 在 weak_table 中找到被弱引用对象 referent 对应的 weak_entry,并将 referrer 加入到 weak_entry 中
        weak_entry_t *entry;
        if ((entry = weak_entry_for_referent(weak_table, referent))) {
            append_referrer(entry, referrer);
        } 
        else {
            // 创建了这个数组 - 插入weak_table
            weak_entry_t new_entry(referent, referrer);
            // ✅ 进行扩容
            weak_grow_maybe(weak_table);
            // ✅ 插入weak_entry
            weak_entry_insert(weak_table, &new_entry);
        }
    
        // Do not set *referrer. objc_storeWeak() requires that the 
        // value not change.
    
        return referent_id;
    }
    

    如果对象可以被弱引用,则将引用弱对象所在的weak_table中的weak_entry_t取出,如果weak_entry_t不存在,则会新建一个,然后将指向被弱引用对象地址的指针referrer通过函数append_referrer插入到对应的weak_entry_t引用数组。至此就完成了弱引用。
    如果weak指针在指向obj之前,已经弱引用了其他的对象,则需要先将弱引用从指针对象其他的weak_entry_t的数组中移除。在调用weak_unregister_no_lock函数来做移除操作,我们看一下weak_unregister_no_lock函数源码:

    void
    weak_unregister_no_lock(weak_table_t *weak_table, id referent_id, 
                            id *referrer_id)
    {
        // ✅ 拿到以前弱引用的对象和对象的地址
        objc_object *referent = (objc_object *)referent_id;
        objc_object **referrer = (objc_object **)referrer_id;
    
        weak_entry_t *entry;
    
        if (!referent) return;
        // ✅ 查找到以前弱引用的对象 referent 所对应的 weak_entry_t
        if ((entry = weak_entry_for_referent(weak_table, referent))) {
            // ✅ 在以前弱引用的对象 referent 所对应的 weak_entry_t 的 hash 数组中,移除弱引用 referrer
            remove_referrer(entry, referrer);
            // ✅ 移除元素之后, 要检查一下 weak_entry_t 的 hash 数组是否已经空了
            bool empty = true;
            if (entry->out_of_line()  &&  entry->num_refs != 0) {
                empty = false;
            }
            else {
                for (size_t i = 0; i < WEAK_INLINE_COUNT; i++) {
                    if (entry->inline_referrers[i]) {
                        empty = false; 
                        break;
                    }
                }
            }
            // ✅ 如果 weak_entry_t 的hash数组已经空了,则需要将 weak_entry_t 从 weak_table 中移除
            if (empty) {
                weak_entry_remove(weak_table, entry);
            }
        }
    
        // Do not set *referrer = nil. objc_storeWeak() requires that the 
        // value not change.
    }
    

    到这里对象的弱引用过程已经全部执行完毕。
    我们知道当对象的引用计数为0时,系统会调用对象的dealloc方法进行释放,我们看一下具体的实现:

    - (void)dealloc {
        _objc_rootDealloc(self);
    }
    ***************⏬***************
    void
    _objc_rootDealloc(id obj)
    {
        assert(obj);
    
        obj->rootDealloc();
    }
    ***************⏬***************
    inline void
    objc_object::rootDealloc()
    {
        // ✅ 判断对象是否采用了Tagged Pointer技术
        if (isTaggedPointer()) return;  // fixme necessary?
        // ✅ 判断是否能够进行快速释放,判断一下条件,满足进行 free()
        if (fastpath(isa.nonpointer  &&         // ✅ 对象是否采用了优化的isa计数方式
                     !isa.weakly_referenced  && // ✅ 对象没有被弱引用
                     !isa.has_assoc  &&         // ✅ 对象没有关联对象
                     !isa.has_cxx_dtor  &&      // ✅ 对象没有自定义的C++析构函数
                     !isa.has_sidetable_rc))    // ✅ 对象没有用到sideTable来做引用计数
        {
            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
    * Destroys an instance without freeing memory. 
    * Calls C++ destructors.
    * Calls ARC ivar cleanup.
    * Removes associative references.
    * Returns `obj`. Does nothing if `obj` is nil.
    **********************************************************************/
    /**
     销毁实例而不会释放内存。
     调用C ++析构函数。
     调用ARC ivar清理。
     删除关联引用。
     返回obj。 如果obj为零则不执行任何操作。
     */
    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.
            // ✅ 如果有C++析构函数,则从类中销毁C++析构函数
            if (cxx) object_cxxDestruct(obj);
            // ✅ 如果有关联对象,则移除所有的关联对象,并将其自身从Association Manager的map中移除
            if (assoc) _object_remove_assocations(obj);
            // ✅ 继续清理其它相关的引用
            obj->clearDeallocating();
        }
    
        return obj;
    }
    

    从以上代码我们可以看到最后会继续清理其他相关引用,调用clearDeallocating函数:

    inline void 
    objc_object::clearDeallocating()
    {
        if (slowpath(!isa.nonpointer)) {
            // Slow path for raw pointer isa.
            // ✅ 如果要释放的对象没有采用了优化过的isa引用计数
            sidetable_clearDeallocating();
        }
        else if (slowpath(isa.weakly_referenced  ||  isa.has_sidetable_rc)) {
            // Slow path for non-pointer isa with weak refs and/or side table data.
            // ✅ 如果要释放的对象采用了优化过的isa引用计数,并且有弱引用或者使用了sideTable的辅助引用计数
            clearDeallocating_slow();
        }
    
        assert(!sidetable_present());
    }
    

    clearDeallocating函数内部会根据要释放的对象是否采用了优化过的ISA做引用计数分成两种情况:

    1、如果要释放的对象没有采用了优化过的isa引用计数
    void 
    objc_object::sidetable_clearDeallocating()
    {
        // ✅ 在全局的SideTables中,以this指针(要释放的对象)为key,找到对应的SideTable
        SideTable& table = SideTables()[this];
    
        // clear any weak table items
        // clear extra retain count and deallocating bit
        // (fixme warn or abort if extra retain count == 0 ?)
        table.lock();
        // ✅ 在散列表SideTable中找到对应的引用计数表RefcountMap,拿到要释放的对象的引用计数
        RefcountMap::iterator it = table.refcnts.find(this);
        if (it != table.refcnts.end()) {
            // ✅ 如果要释放的对象被弱引用了,通过weak_clear_no_lock函数将指向该对象的弱引用指针置为nil
            if (it->second & SIDE_TABLE_WEAKLY_REFERENCED) {
                weak_clear_no_lock(&table.weak_table, (id)this);
            }
            // ✅ 从引用计数表中擦除该对象的引用计数
            table.refcnts.erase(it);
        }
        table.unlock();
    }
    
    2、如果要释放的对象采用了优化过的isa引用计数
    NEVER_INLINE void
    objc_object::clearDeallocating_slow()
    {
        assert(isa.nonpointer  &&  (isa.weakly_referenced || isa.has_sidetable_rc));
        // ✅ 在全局的SideTables中,以this指针(要释放的对象)为key,找到对应的SideTable
        SideTable& table = SideTables()[this];
        table.lock();
        
        if (isa.weakly_referenced) {
            // ✅ 要释放的对象被弱引用了,通过weak_clear_no_lock函数将指向该对象的弱引用指针置为nil
            weak_clear_no_lock(&table.weak_table, (id)this);
        }
        // ✅ 使用了sideTable的辅助引用计数,直接在SideTable中擦除该对象的引用计数
        if (isa.has_sidetable_rc) {
            table.refcnts.erase(this);
        }
        table.unlock();
    }
    

    上面两种方法中都用到了weak_clear_no_lock函数,将指向该对象的弱引用指针置为nil,我们在看一下weak_clear_no_lock函数:

    void 
    weak_clear_no_lock(weak_table_t *weak_table, id referent_id) 
    {
        // ✅ 获取被弱引用对象的地址
        objc_object *referent = (objc_object *)referent_id;
        // ✅ 根据对象地址找到被弱引用对象referent在weak_table中对应的weak_entry_t
        weak_entry_t *entry = weak_entry_for_referent(weak_table, referent);
        if (entry == nil) {
            /// XXX shouldn't happen, but does with mismatched CF/objc
            //printf("XXX no entry for clear deallocating %p\n", referent);
            return;
        }
    
        // zero out references
        weak_referrer_t *referrers;
        size_t count;
        // ✅ 找出弱引用该对象的所有weak指针地址数组
        if (entry->out_of_line()) {
            referrers = entry->referrers;
            count = TABLE_SIZE(entry);
        } 
        else {
            referrers = entry->inline_referrers;
            count = WEAK_INLINE_COUNT;
        }
        // ✅ 遍历取出每个weak指针的地址
        for (size_t i = 0; i < count; ++i) {
            objc_object **referrer = referrers[i];
            if (referrer) {
                // ✅ 如果weak指针确实弱引用了对象 referent,则将weak指针设置为nil
                if (*referrer == referent) {
                    *referrer = nil;
                }
                 // ✅ 如果所存储的weak指针没有弱引用对象 referent,这可能是由于runtime代码的逻辑错误引起的,报错
                else if (*referrer) {
                    _objc_inform("__weak variable at %p holds %p instead of %p. "
                                 "This is probably incorrect use of "
                                 "objc_storeWeak() and objc_loadWeak(). "
                                 "Break on objc_weak_error to debug.\n", 
                                 referrer, (void*)*referrer, (void*)referent);
                    objc_weak_error();
                }
            }
        }
        
        weak_entry_remove(weak_table, entry);
    }
    
    • 当一个对象objc被弱指针指向时,这个弱指针会以objc作为键,被存储到sideTable类的weak_table这个散列表上对应的一个weak指针数组里面。
    • 当一个对象objcdealloc方法被调用时,运行时会以objc为键,从sideTableweak_table散列表中,发现对应的weak指针列表,然后将里面的弱指针逐个放置为nil

    相关文章

      网友评论

          本文标题:ios 经典面试题

          本文链接:https://www.haomeiwen.com/subject/thtrxhtx.html