015*:面试题
一:Runtime Asssociate方法关联的对象,需要在dealloc中释放?
不需要
当我们对象释放时,会调用dealloc
- 1、C++函数释放 :
objc_cxxDestruct
- 2、移除关联属性:
_object_remove_assocations
- 3、将弱引用自动设置nil:
weak_clear_no_lock(&table.weak_table, (id)this);
- 4、引用计数处理:
table.refcnts.erase(this)
- 5、销毁对象:
free(obj)
所以,关联对象
不需要我们手动移除,会在对象析构即dealloc
时释放
dealloc 源码
dealloc
-> _objc_rootDealloc
-> rootDealloc
-> object_dispose
(释放对象)-> objc_destructInstance
-> _object_remove_assocations
dealloc
的源码实现2:_objc_rootDealloc
3:进入rootDealloc
源码实现,发现其中有关联属性时设置bool值
,当有这些条件时,需要进入else流程
4:进入object_dispose
源码实现,主要是销毁实例对象
5:objc_destructInstance
6:进入_object_remove_assocations
源码,关联属性的移除,主要是从全局哈希map中找到相关对象的迭代器,然后将迭代器中关联属性,从头到尾的移除
2:类的方法 和 分类方法 重名,如果调用,是什么情况?
如果同名方法是普通方法
,包括initialize
-- 先调用分类方法
因为分类的方法是在类realize之后 attach进去的
,插在类的方法的前面,所以优先调用分类的方法
(注意:不是分类覆盖主类!!)
initialize
方法什么时候调用? initialize
方法也是主动调用,即第一次消息时
调用,为了不影响整个load,可以将需要提前加载的数据
写到initialize
中
如果同名方法是load
方法 -- 先 主类load
,后分类load
(分类之间,看编译的顺序)
参考 load_images
原理分析
3:Runtime是什么?
-
runtime
是由C和C++
汇编实现的一套API
,为OC语言加入了面向对象、以及运行时的功能
-
运行时是指将
数据类型的确定由编译时 推迟到了 运行时
- 举例:extension 和 category 的区别
-
平时编写的OC代码,在程序运行的过程中,其实最终会转换成runtime的C语言代码,
runtime是OC的幕后工作者
1、category 类别、分类
-
专门用来给类添加新的方法
-
不能给类添加成员属性
,添加了成员属性,也无法取到 -
注意:其实
可以通过runtime 给分类添加属性
,即属性关联,重写setter、getter方法 -
分类中用
@property
定义变量,只会生成
变量的setter、getter
方法的声明
,不能生成方法实现 和 带下划线的成员变量
2、extension 类扩展
-
可以说成是
特殊的分类
,也可称作匿名分类
-
可以
给类添加成员属性
,但是是私有变量
-
可以
给类添加方法
,也是私有方法
4:方法的本质,sel是什么?IMP是什么?两者之间的关系又是什么?
-
方法的本质:
发送消息
,消息会有以下几个流程-
快速查找(
objc_msgSend
) - cachelookup缓存消息中查找 -
慢速查找 - 递归自己|父类 -
lookUpImpOrForward
-
查找不到消息:动态方法解析 -
resolveInstanceMethod
-
消息快速转发 -
forwardingTargetForSelector
-
消息慢速转发 -
methodSignatureForSelector & forwardInvocation
-
-
sel
是方法编号
- 在read_images
期间就编译进了内存 -
imp
是函数实现指针
,找imp就是找函数的过程
-
sel
相当于 一本书的目录title
-
sel
相当于 书本的页码
-
查找
具体的函数
就是想看这本书具体篇章的内容-
1、首先知道想看什么,即目录 title - sel
-
2、根据目录找到对应的页码 - imp
-
3、通过页码去翻到具体的内容
-
5:能否向编译后得到的类中增加实例变量?能否向运行时创建的类中添加实例变量
-
1、
不能
向编译后的得到的类中增加实例变量 -
2、
只要类没有注册到内存还是可以添加的
-
3、可以
添加属性+方法
【原因】:编译好的实例变量存储的位置是ro,一旦编译完成,内存结构就完全确定了
6: [self class]和[super class]的区别以及原理分析
-
[self class]
就是发送消息objc_msgSend
,消息接收者是self
,方法编号class
-
[super class]
本质就是objc_msgSendSuper
,消息的接收者还是self
,方法编号class
,在运行时,底层调用的是_objc_msgSendSuper2
【重点!!!】 -
只是
objc_msgSendSuper2
会更快,直接跳过self的查找
- (Class)class { return object_getClass(self); } Class object_getClass(id obj) { if (obj) return obj->getIsa(); else return Nil; }
其底层是获取对象的isa
,当前的对象是LGTeacher
,其isa是同名的LGTeacher
,所以[self class]
打印的是LGTeacher
super
虽然可以直接调用方法
,但是它并不是对象
,只是
语法关键字
。真正的调用者,是当前对象
。
superClass
是(id)class_getSuperclass(objc_getClass("HTPerson"))
所以,最完整的回答如下
-
[self class]
方法调用的本质是发送消息
,调用class的消息流程,拿到元类的类型
,在这里是因为类已经加载到内存,所以在读取时是一个字符串类型,这个字符串类型是在map_images
的readClass
时已经加入表中,所以打印为LGTeacher
-
[super class]
打印的是LGTeacher
,原因是当前的super是一个关键字,在这里只调用objc_msgSendSuper2
,其实他的消息接收者和[self class]
是一模一样的,所以返回的是LGTeacher
7:Runtime是如何实现weak的,为什么可以自动置nil
-
1、通过
SideTable
找到我们的weak_table
-
2、
weak_table
根据referent
找到或者创建weak_entry_t
-
3、然后
append_referrer(entry,referrer)
将我的新弱引用的对象加进去entry -
4、最后
weak_entry_insert
,把entry
加入到我们的weak_table
8:指针平移
// ViewController @implementation ViewController - (void)viewDidLoad { [super viewDidLoad]; Class cls = [LGPerson class]; void *kc = &cls; // 拿到对象的指针地址,赋值给kc指针。 [(__bridge id)kc saySomething]; LGPerson *person = [LGPerson alloc]; [person saySomething]; } @end // LGPerson.h @interface LGPerson : NSObject @property (nonatomic, copy) NSString *lg_name; @property (nonatomic, copy) NSString *lg_hobby; - (void)saySomething; @end // LGPerson.m @implementation LGPerson - (void)saySomething { NSLog(@"%s",__func__); } @end
下面我们运行代码
首先我们要明白方法的调用,我们创建LGPerson对象,对象结构中bits存有saySomething方法,创建person对象,它的isa指向了LGPerson对象,通过指针平移找到saySomething方法进行调用
,如下图9:指针存在哪里?
alloc
的对象 都在堆
中
指针
、对象
在栈
中,例如person
指向的空间在堆
中,person
所在的空间在栈
中
临时变量
在栈
中
属性值
在堆
,属性随对象是在栈
中
注意:
堆
是从小到大
,即低地址
->高地址
栈
是从大到小
,即从高地址
->低地址
分配
函数隐藏参数
会从前往后
一直压,即 从高地址
->低地址
开始入栈,
结构体内部
的成员
是从低地址
->高地址
一般情况下,内存地址有如下规则
0x60
开头表示在 堆
中
0x70
开头的地址表示在 栈
中
0x10
开头的地址表示在全局区域
中
该代码块在内存中的表现形式如下图
图中可以看到,栈里面存放的是非对象的基本数据类型,堆内存存放着oc对象
c中存的是一个16进制的值。64位的值。8直接的值。
引用: