iOS 面试题分析(一)
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++
构造函数)。 -
+load
是objc
中调用的,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
是由C
和C++
/汇编
实现的⼀套API
,为OC
语⾔加⼊了⾯向对象,运⾏时的功能。 - 是一种运行机制,不是底层。
dyld
、汇编
、objc
、macho
才是底层。 - 平时编写的
OC
代码,在程序运行过程中,其实最终会转换成Runtime
的C
语言代 码,Runtime
是Objective-C
的幕后工作者。
2.3 initialize调用顺序
-
initialize
是在第一次发送消息的时候进行的调用。 -
load
是在load_images的时候调用的,load
比initialize
调用时机早(initialize
在lookupimporforward
慢速消息查找的时候调用)。 -
整个调用顺序
load
>C++构造函数
>initialize
。
2.4 同名分类方法的调用顺序
同名分类方法调用顺序分为两种情况:
- 分类合并到主类的情况,也就是只有一个/或者没有load方法,这个时候整个方法列表一维数组(不考虑其它动态添加方法的情况)。最后编译的同名分类方法会放在主类与其它分类前面,在进行方法二分查找的时候先从中间开始查找,找到对应方法SEL的时候会继续往前查找,直到找到最前面的同名方法。
- 分类没有合并到主类的情况,多个load方法这个时候整个方法列表是一个一个二维数组,后编译加载的分类在数组前面,查找方法的时候从前面开始查找。
- 也就是同名方法最终会找到后编译加载的分类的同名方法,查找过程不一样而已。
在iOS底层探索之类的加载(三): attachCategories分析博客里面也介绍了分类和load方法的一些加载。
2.5 分类和扩展的区别?
首先我们来看看什么是分类和扩展
category
: 类别/分类
- 专门用来给类添加新的方法
- 不能给类添加成员属性,添加了成员变量,也无法取到
注意
:其实可以通过runtime
给分类添加属性- 分类中用
@property
定义变量,只会生成变量的getter,setter
方法的声明,不能生成方法实现和带下划线的成员变量
。
extension
:类扩展
- 可以说成是特殊的分类,也称作
匿名分类
- 可以给类添加
成员属性
,但是是私有变量
- 可以给类添加方法,也是
私有方法
分类我们已经很熟悉了,这里就不必过多赘述了,下面介绍下扩展
extension
匿名扩展类扩展,我们平时用的是非常多的,如下
震惊what ? 什么,这就是扩展吗?天天用居然不知道!
是的,这就是扩展,平时用的是非常之多,但是很多人都不知道。
注意
:类扩展要放在声明之后,实现之前,否则会报错。
更多内容持续更新
🌹 喜欢就点个赞吧👍🌹
🌹 觉得有收获的,可以来一波,收藏+关注,评论 + 转发,以免你下次找不到我😁🌹
🌹欢迎大家留言交流,批评指正,互相学习😁,提升自我🌹