ios 经典面试题

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函数中进行断点:


然后通过汇编的方法找到weak的底层是objc_initWeak

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

id
objc_initWeak(id *location, id newObj)
{
    if (!newObj) {
        *location = nil;
        return nil;
    }

    return storeWeak
        (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(oldTable, newTable);

    if (haveOld  &&  *location != oldObj) {
        SideTable::unlockTwo(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(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(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 经典面试题)