ios 经典面试题
1、Runtime是什么?
Runtime是一套由C
、C++
和汇编实现的一套API
,为OC
语言加入了面向对象和运行时功能。
运行时 (Runtime
) 是指将数据类型的确定由编译时推迟到了运行时。 (例如extension
- category
的区别)
平时写的OC
代码,在运行时会被转换成Runtime
的C
语言代码。Runtime
是OC
的幕后工作者。
2、方法的本质,SEL
是什么?IMP
是什么?两者之间的关系又是什么?
-
方法的本质是发送消息,发送消息又有以下几个流程:
- 快速查找 (
objc_msgSend
) ~cache_t
缓存消息 - 慢速查找: 递归自己和父类 ~
lookUpImpOrForward
- 查找不到消息: 进行动态方法解析
resolveInstanceMethod
- 消息快速转发:
forwardingTargetForSelector
- 消息慢速转发 :
methodSignatureForSelector & forwardInvocation
- 未找到消息:程序crash,打印日志
- 快速查找 (
-
SEL
是方法编号,在read_images
期间就编译进入了内存 -
IMP
就是我们函数实现指针,找IMP
就是找函数实现过程 -
SEL
就相当于书本的目录,IMP
就是书本的页码,查找具体的函数就是想看这本书里面具体篇章的内容:- 我们首先知道想看什么,也就是title
SEL
- 根据目录对应的页码
IMP
- 翻到我们想看的具体内容
- 我们首先知道想看什么,也就是title
3、能否向编译后得到的类中增加实例变量?能否向运行时创建的类中增加实例变量?
- 不能向编译后得到的类中增加实例变量,因为类在编译时已经将实例变量存储到
ro
中了,编译完成,内存结构确定,我们无法进行修改。 - 只要没有注册到内存还是可以添加的
- 在运行时添加属性和方法
4、isKindOfClass
和isMemberOfClass
-
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
,在LGStudent
类init
方法中进行打印一下代码:
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
内容可知receiver
是self(LGStudent
,super_class
是 class_getSuperclass(objc_getClass("LGPerson"))
即LGStudent
的父类LGPerson
调用时先调用LGPerson
的class
方法,如果没有,就沿继承体系往上找直到NSObject
的class
,最后内部是使用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
: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
,在oldObj
的weak_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
指针数组里面。 - 当一个对象
objc
的dealloc
方法被调用时,运行时会以objc
为键,从sideTable
的weak_table
散列表中,发现对应的weak
指针列表,然后将里面的弱指针逐个放置为nil
。