dealloc 释放的对象
首先查看dealloc的底层源码
obj->rootDealloc();
if (isTaggedPointer()) return; // fixme necessary?
// 根据isa中相关存储格式进行判断
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);
}
void *objc_destructInstance(id obj)
{
if (obj) {
// Read all of the flags at once for performance. 判断是否还包含C++方法以及关联对象
bool cxx = obj->hasCxxDtor();
bool assoc = obj->hasAssociatedObjects();
// This order is important.
if (cxx) object_cxxDestruct(obj);
if (assoc) _object_remove_assocations(obj);
obj->clearDeallocating();
}
return obj;
}
根据上述代码得知:
- 先查看isa相关信息来判断是否还包含信息1是0否
-
object_dispose
->objc_destructInstance
来判断是否存在hasCxxDtor
、hasAssociatedObjects
- 如果存在
C++
方法,就从缓存中释放object_cxxDestruct
->object_cxxDestructFromClass
->lookupMethodInClassAndLoadCache
- 如果存在Associate,则从对应的哈希表中查找然后依次删除
- 如果存在
ObjectAssociationMap refs{};
{
AssociationsManager manager;
AssociationsHashMap &associations(manager.get());
AssociationsHashMap::iterator i = associations.find((objc_object *)object);
if (i != associations.end()) {
refs.swap(i->second);
associations.erase(i);
}
}
主类与分类的同名方法调用顺序
- 如果方法为普通方法,包括
initialize
,这时会因为编译时分类方法
存储在主类方法
的前面
,故而优先调用分类
方法,-
initialize
方法也是主动调用,即第一次消息
时调用,为了不影响整个load
,可以将需要提前加载的数据
写到initialize
中 - 其他分类方法通过
attachCategories
进行添加在主类方法之前,但不会覆盖
主类方法
-
- 如果为
load
方法,将先调用主类
的load
方法,再调用分类
的load
方法,可以在load_images
中进行验证
void
load_images(const char *path __unused, const struct mach_header *mh)
{
if (!didInitialAttachCategories && didCallDyldNotifyRegister) {
didInitialAttachCategories = true;
loadAllCategories();
}
// Return without taking locks if there are no +load methods here.
if (!hasLoadMethods((const headerType *)mh)) return;
recursive_mutex_locker_t lock(loadMethodLock);
// Discover load methods
{
mutex_locker_t lock2(runtimeLock);
prepare_load_methods((const headerType *)mh);
}
// Call +load methods (without runtimeLock - re-entrant)
call_load_methods();
}
void prepare_load_methods(const headerType *mhdr)
{
classref_t const *classlist =
_getObjc2NonlazyClassList(mhdr, &count);
for (i = 0; i < count; i++) {
schedule_class_load(remapClass(classlist[i]));
}
category_t * const *categorylist = _getObjc2NonlazyCategoryList(mhdr, &count);
for (i = 0; i < count; i++) {
category_t *cat = categorylist[I];
Class cls = remapClass(cat->cls);
if (!cls) continue; // category for ignored weak-linked class
if (cls->isSwiftStable()) {
_objc_fatal("Swift class extensions and categories on Swift "
"classes are not allowed to have +load methods");
}
realizeClassWithoutSwift(cls, nil);
ASSERT(cls->ISA()->isRealized());
add_category_to_loadable_list(cat);
}
}
void call_load_methods(void)
{
static bool loading = NO;
bool more_categories;
loadMethodLock.assertLocked();
// Re-entrant calls do nothing; the outermost call will finish the job.
if (loading) return;
loading = YES;
void *pool = objc_autoreleasePoolPush();
do {
// 1. Repeatedly call class +loads until there aren't any more
while (loadable_classes_used > 0) {
call_class_loads();
}
// 2. Call category +loads ONCE
more_categories = call_category_loads();
// 3. Run more +loads if there are classes OR more untried categories
} while (loadable_classes_used > 0 || more_categories);
objc_autoreleasePoolPop(pool);
loading = NO;
}
通过prepare_load_methods
得知,先调用_getObjc2NonlazyClassList
,然后才是_getObjc2NonlazyCategoryList
call_load_methods
也是通过遍历先call_class_loads
然后call_category_loads
【面试-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
) - cache_t
缓存消息中查找
慢速查找 - 递归自己|父类 -lookUpImpOrForward
查找不到消息:动态方法解析 - resolveInstanceMethod
消息快速转发 -forwardingTargetForSelector
消息慢速转发 - methodSignatureForSelector
& forwardInvocation
sel
是方法编号
- 在read_images
期间就编译进了内存
imp
是函数实现指针
,找imp
就是找函数
的过程
sel
相当于 一本书的目录title
imp
相当于 书本的页码
查找具体的函数就是想看这本书具体篇章的内容
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
的查找
@interface LGTeacher : LGPerson
@implementation LGTeacher
- (instancetype)init{
self = [super init];
if (self) {
NSLog(@"%@ - %@",[self class],[super class]);
}
return self;
}
通过clang编译成cpp文件可以看出
static instancetype _I_LGTeacher_init(LGTeacher * self, SEL _cmd) {
self = ((LGTeacher *(*)(__rw_objc_super *, SEL))(void *)objc_msgSendSuper)((__rw_objc_super){(id)self, (id)class_getSuperclass(objc_getClass("LGTeacher"))}, sel_registerName("init"));
if (self) {
NSLog((NSString *)&__NSConstantStringImpl__var_folders_5c_6btl4svn6n5bqty614qwd7k80000gp_T_LGTeacher_d541e4_mi_0,((Class (*)(id, SEL))(void *)objc_msgSend)((id)self, sel_registerName("class")),((Class (*)(__rw_objc_super *, SEL))(void *)objc_msgSendSuper)((__rw_objc_super){(id)self, (id)class_getSuperclass(objc_getClass("LGTeacher"))}, sel_registerName("class")));
}
return self;
}
通过实现方法可以看出,[super class]
并不是通过LGTeacher
的父类LGPerson
进行调用,而消息接收者
还是self
即为LGTeacher
,只是结构为__rw_objc_super
struct objc_super {
/// Specifies an instance of a class.
__unsafe_unretained _Nonnull id receiver;
/// Specifies the particular superclass of the instance to message.
#if !defined(__cplusplus) && !__OBJC2__
/* For compatibility with old objc-runtime.h header */
__unsafe_unretained _Nonnull Class class;
#else
__unsafe_unretained _Nonnull Class super_class;
#endif
/* super_class is the first class to search */
};
通过底层源码我们可以发现,super_class
只是内部的一个参数,并非改变消息接收者,因此一开始打印的 NSLog(@"%@ - %@",[self class],[super class]);
结果都为LGTeacher
【面试-7】内存平移问题
下面方法调用打印结果是否一致
- (void)saySomething{
NSLog(@"%s",__func__);
}
在ViewController中调用
Class cls = [LGPerson class];
void *kc = &cls; //
[(__bridge id)kc saySomething];
LGPerson * person = [LGPerson alloc];
[person saySomething];
结果发现两种打印是一致的,原因如下
可以看出是通过
person
的isa
去获取类的相关信息,person
的isa
指向的LGPerson
,而上面通过 *kc = &cls
也可以看出kc
指向的也是LGPerson
,他们都是通过获取LGPerson
的MethodList
去查找saySomething
方法,故而效果一致
@property (nonatomic, copy) NSString *kc_name; // 12
- (void)saySomething{
NSLog(@"%s - %@",__func__,self.kc_name);
}
如果进行上述更改,只增加一个属性的打印结果我们发现:
- 正常情况下,读取属性是通过
内存平移
的方式进行读取,在person
中存储的事isa
以及相关属性
,这时就可以通过person
的首地址
平移8个单位
获取kc_name
的地址从而进行读取,kc_name
没有赋值,打印为null
可以理解 - 如果是通过指针地址方式调用,发现打印了一个
ViewController
,这就很难理解,这时通过栈地址读取的方式来查看相关内容
void *sp = (void *)&self;
void *end = (void *)&person;
long count = (sp - end) / 0x8;
for (long i = 0; i
通过打印可以得知栈中现在保存的相关信息,由于栈是从高地址向低地址进行存储
- 当进行方法调用时,首先传入的是每个函数都会包含的隐藏参数
(id self,sel _cmd)
- 通过实践可以得知结构体成员内部的压栈情况是
低地址
->高地址
,递增
的,故而如果是结构体进栈,则保存情况是后面变量
先入栈 -
super
通过clang
查看底层的编译,是objc_msgSendSuper
,其第一个参数是一个结构体__rw_objc_super(self,class_getSuperclass)
因此可以得出目前栈中所保存的信息为self - _cmd - (id)class_getSuperclass(objc_getClass("ViewController")) - self - cls - kc - person
person
与LGPerson
的关系是 person
是以LGPerson
为模板的实例化
对象,即alloc
有一个指针地址,指向isa
,isa
指向LGPerson
,它们之间关联是有一个isa指向
由于kc
也是指向LGPerson
的关系,编译器会认为 kc
也是LGPerson
的一个实例化
对象,即kc
相当于isa
,即首地址,指向LGPerson
,具有和person
一样的效果,简单来说,我们已经完全将编译器骗过了,即kc
也有kc_name
。由于person
查找kc_name
是通过内存平移8字节
,所以kc
也是通过内存平移8字节
去查找kc_name
,目前kc
的首地址为0x7ffeec381098
,平移0x8
得出0x7ffeec3810a0
,正好与栈中
一致
【面试-8】 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