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-sel2、根据目录找到对应的
页码-imp3、通过页码去翻到具体的内容
【面试-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<count; i++) {
void *address = sp - 0x8 * I;
if ( i == 1) {
NSLog(@"%p : %s",address, *(char **)address);
}else{
NSLog(@"%p : %@",address, *(void **)address);
}
}

通过打印可以得知栈中现在保存的相关信息,由于栈是从高地址向低地址进行存储
- 当进行方法调用时,首先传入的是每个函数都会包含的隐藏参数
(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,正好与栈中<ViewController: 0x7fac45514f50> 一致
【面试-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
