iOS底层原理之部分面试题分析
Runtime Asssociate方法关联的对象,是否需要在dealloc中释放?
不需要释放
分析
我们知道当一个对象销毁的时候会调用 dealloc 方法,那么我们先看下 dealloc 都进行了哪些操作。
dealloc函数调用了_objc_rootDealloc函数

_objc_rootDealloc函数调用rootDealloc函数
void
_objc_rootDealloc(id obj)
{
ASSERT(obj);
obj->rootDealloc();
}
rootDealloc函数查看
inline void
objc_object::rootDealloc()
{
if (isTaggedPointer()) return; // fixme necessary?
if (fastpath(isa.nonpointer &&
!isa.weakly_referenced &&
!isa.has_assoc &&
!isa.has_cxx_dtor &&
!isa.has_sidetable_rc))
{
assert(!sidetable_present());
free(this);
}
else {
object_dispose((id)this);
}
}
从 rootDealloc 函数中我们看到了判断isa相关属性的地方,实际上当一个对象存在会进入 else 中,即 object_dispose 函数
object_dispose函数查看
id
object_dispose(id obj)
{
if (!obj) return nil;
objc_destructInstance(obj);
free(obj);
return nil;
}
通过 objc_destructInstance 函数找到对象,然后 free ,我们看 objc_destructInstance 函数
objc_destructInstance函数

重点查看 _object_remove_assocations 函数
_object_remove_assocations函数分析

类、分类方法同名时调用顺序是怎样的?
当 非+load 方法同名时,分类的方法在类的方法前面( 注意不是覆盖 ),因为 分类的方法是在类realize之后 attach进去的 ,所以 优先分类,其次类
当 +load 方法同名时, 优先类,其次分类
分类与类的扩展
分类
- 专门用来给类添加新的方法
- 不能添加属性,但是可以通过runtime动态添加属性(因为我们在前面的篇章中分析过,分类底层代码中有属性列表)
- 分类中
@property定义的变量只会生成setter以及getter方法的声明,但是不会生成对应的方法实现以及带有下划线的成员变量
作为一个开发者,有一个学习的氛围跟一个交流圈子特别重要,这有个iOS交流群:642363427,不管你是小白还是大牛欢迎入驻 ,分享BAT,阿里面试题、面试经验,讨论技术!
类的扩展
@property
什么是Runtime?
runtime是由C和C++汇编实现的一套API,为OC语言添加了面向对象和运行时功能。
- 运行时:将数据类型的确定由编译阶段推迟到了运行阶段。我们平时所写的OC代码,最终转换为runtime的C语言代码。
方法的本质是什么?SEL、IMP是什么?两者之间的关系是什么?
方法的本质
方法的本质是 消息的发送 ,涉及到消息发送的流程有
objc_msgSend
lookUpImpOrForward
resolveInstanceMethod
forwardingTargetForSelector
mesthodSignatureForSelector & forwardInvocation
SEL、IMP
- sel:方法编号,类比一本书的目录
- imp:方法函数指针地址,类比一本书的页数
- sel与imp关系:sel是方法编号,通过sel找到imp的函数指针地址,通过imp就能找到函数的实现
能否向编译后的类中添加实例变量?能否向运行时创建的类添加实例变量?
编译后实例变量存储到 ro 中,一旦编译完成,内存结构就完全确定了,无法再次修改
[self class] 与 [super class]的区别
我们先看以下如下代码打印结果,其中self是LGTeacher类,LGTeacher继承于LGPerson,LGPerson继承于NSObject

从打印结果中我们看到无论是 [self class] 还是 [super class] 的结果是一样的,为什么呢?
分析
- 我们知道任何方法调用都会隐藏两个参数,即
(id self , sel _cmd),其中self是消息接收者。对于[self class]来说,它的消息接收者是自身LGTeacher没什么可说的,所以打印的是LGTeacher。 - 首先我们要知道
super只是关键字,它意思是说从父类调用方法,因此[super class]就是直接调用的就是父类的class方法,它的本质是objc_msgSendSuper,只是objc_msgSendSuper速度更快,直接跳过self。但需要注意的是,[super class]的消息接受者依然是LGTeacher,所以最终打印的是LGTeacher。
内存偏移相关问题
我们先准备代码,定义 IFPerson 类,代码如下


我们再看ViewController代码

从上述代码中我们延伸出两个问题: 代码是否崩溃 、 doSomething打印结果是什么 。先不回答这两个问题,我们运行代码看结果如何,运行结果如下图

从运行结果中我们可以看出 代码不会崩溃且运行结果也出来了 .
[(__bridge id)kc doSomething] 为什么不会崩溃?
首先我们知道对于一个对象,它的指针地址指向的是 isa ,同时 isa 地址指向 当前的class ,所以 kc 指向的是 IFPerson 的 isa ,而 person 的指针指向的 也是isa ,这样它们都是 isa 从 cache_t 中查找 doSomething 方法,因此不会崩溃。
为什么 [(__bridge id)kc doSomething] 打印的结果是 ViewController ?
- 从打印结果中
[person doSomething]打印出出来shifx是没有什么问题的,毕竟给person.name赋值shifx,但是[(__bridge id)kc doSomething]打印的结果是ViewController呢?要解决这个问题首先我们需要知道person能够找到name是指针从isa内存平移了8个字节移动到了name。那么对于kc来说,它也需要指针平移,但是为什么平移后的结果是viewController呢?这就需要明白栈地址是从高到低存储的,且是先进后出,由于前面先调用了[super viewDidLoad]方法,且viewDidLoad的隐藏参数是(id self, IMP _cmd),所以self会先入栈,其次是cls->kc->person,出栈的顺序刚好相反,由于[(__bridge id)kc doSomething]时需要指针平移,自然指向了self(即ViewController),所以打印的结果是ViewController。 - 为了验证我们上面分析是否正确,我们修改代码位置,将声明
IFPerson *person = [[IFPerson alloc] init]放在[super viewDidLoad]之后,即

此时我们按照我们上面的分析 self 会先入栈,其次是 person -> cls -> kc ,猜测 [(__bridge id)kc doSomething] 打印结果应该是 IFPerson (person的isa指向其Class) ,我们运行代码结果
可以看出我们的分析是正确的。

浙公网安备 33010602011771号