iOS底层原理(18)--面试题
上一节有提到关联对象,现在对关联对象的释放
做一下补充:
一、关联对象释放的流程
1、dealloc
- (void)dealloc {
_objc_rootDealloc(self);
}
2、_objc_rootDealloc
void _objc_rootDealloc(id obj)
{
ASSERT(obj);
obj->rootDealloc();
}
3、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);
}
}
4、object_dispose
id object_dispose(id obj)
{
if (!obj) return nil;
objc_destructInstance(obj);
free(obj);
return nil;
}
5、objc_destructInstance
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.
if (cxx) object_cxxDestruct(obj);
if (assoc) _object_remove_assocations(obj);
obj->clearDeallocating();
}
return obj;
}
6、_object_remove_assocations
void _object_remove_assocations(id object)
{
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);
}
}
// release everything (outside of the lock).
for (auto &i: refs) {
i.second.releaseHeldValue();
}
}
二、load_images
的调用流程
在 dyld
中调用 mapImages 、loadImages
1、 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();
}
2、prepare_load_methods
void prepare_load_methods(const headerType *mhdr)
{
size_t count, i;
runtimeLock.assertLocked();
classref_t const *classlist =
_getObjc2NonlazyClassList(mhdr, &count); // why
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);
}
}
2.1 搜集类方法
2.1.1、 schedule_class_load
static void schedule_class_load(Class cls)
{
if (!cls) return;
ASSERT(cls->isRealized()); // _read_images should realize
if (cls->data()->flags & RW_LOADED) return;
// Ensure superclass-first ordering
schedule_class_load(cls->superclass);
add_class_to_loadable_list(cls);
cls->setInfo(RW_LOADED);
}
2.1.2、add_class_to_loadable_list
void add_class_to_loadable_list(Class cls)
{
IMP method;
loadMethodLock.assertLocked();
method = cls->getLoadMethod();
if (!method) return; // Don't bother if cls has no +load method
if (PrintLoading) {
_objc_inform("LOAD: class '%s' scheduled for +load",
cls->nameForLogging());
}
if (loadable_classes_used == loadable_classes_allocated) { // 扩容
loadable_classes_allocated = loadable_classes_allocated*2 + 16;
loadable_classes = (struct loadable_class *)
realloc(loadable_classes,
loadable_classes_allocated *
sizeof(struct loadable_class));
}
loadable_classes[loadable_classes_used].cls = cls;
loadable_classes[loadable_classes_used].method = method;
loadable_classes_used++;
}
2.1.3、getLoadMethod
IMP
objc_class::getLoadMethod()
{
runtimeLock.assertLocked();
const method_list_t *mlist;
ASSERT(isRealized());
ASSERT(ISA()->isRealized());
ASSERT(!isMetaClass());
ASSERT(ISA()->isMetaClass());
mlist = ISA()->data()->ro()->baseMethods();
if (mlist) {
for (const auto& meth : *mlist) {
const char *name = sel_cname(meth.name);
if (0 == strcmp(name, "load")) {
return meth.imp;
}
}
}
return nil;
}
2.2、搜集分类
2.2.1 、add_category_to_loadable_list
void add_category_to_loadable_list(Category cat)
{
IMP method;
loadMethodLock.assertLocked();
method = _category_getLoadMethod(cat);
// Don't bother if cat has no +load method
if (!method) return;
if (PrintLoading) {
_objc_inform("LOAD: category '%s(%s)' scheduled for +load",
_category_getClassName(cat), _category_getName(cat));
}
if (loadable_categories_used == loadable_categories_allocated) {
loadable_categories_allocated = loadable_categories_allocated*2 + 16;
loadable_categories = (struct loadable_category *)
realloc(loadable_categories,
loadable_categories_allocated *
sizeof(struct loadable_category));
}
loadable_categories[loadable_categories_used].cat = cat;
loadable_categories[loadable_categories_used].method = method;
loadable_categories_used++;
}
2.2.2、 _category_getLoadMethod
IMP
_category_getLoadMethod(Category cat)
{
runtimeLock.assertLocked();
const method_list_t *mlist;
mlist = cat->classMethods;
if (mlist) {
for (const auto& meth : *mlist) {
const char *name = sel_cname(meth.name);
if (0 == strcmp(name, "load")) {
return meth.imp;
}
}
}
return nil;
}
3、call_load_methods
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;
}
3.1 call_class_loads
static void call_class_loads(void)
{
int i;
// Detach current loadable list.
struct loadable_class *classes = loadable_classes;
int used = loadable_classes_used;
loadable_classes = nil;
loadable_classes_allocated = 0;
loadable_classes_used = 0;
// Call all +loads for the detached list.
for (i = 0; i < used; i++) {
Class cls = classes[i].cls;
load_method_t load_method = (load_method_t)classes[i].method;
if (!cls) continue;
if (PrintLoading) {
_objc_inform("LOAD: +[%s load]\n", cls->nameForLogging());
}
(*load_method)(cls, @selector(load));
}
// Destroy the detached list.
if (classes) free(classes);
}
调用顺序
load 、c++、main
void _objc_init(void)
{
static bool initialized = false;
if (initialized) return;
initialized = true;
// fixme defer initialization until an objc-using image is found?
environ_init();
tls_init();
static_init();//注意,这里会自启动,不需要dyld
runtime_init();
exception_init();
cache_init();
_imp_implementationWithBlock_init();
_dyld_objc_notify_register(&map_images, load_images, unmap_image);
#if __OBJC2__
didCallDyldNotifyRegister = true;
#endif
}
static void static_init()
{
size_t count;
auto inits = getLibobjcInitializers(&_mh_dylib_header, &count);
for (size_t i = 0; i < count; i++) {
inits[i]();
}
}
三、load和initialize的区别
load
1、在runtime加载类、分类的时候调用,且只调用一次
2、先调用主类的load方法,再调用分类的load方法
3、如果主类有子类,先调用父类的load方法,再调用子类的load方法
4、如果有多个分类有load方法,调用顺序,按照compile source里面的加载顺序调用
initialize
1、是懒加载,在第一次发送消息的时候调用
2、先调用父类的initialize方法,再调用子类的initialize方法
3、如果子类中未实现initialize方法,则会调用父类的initialize方法,因此父类的initialize可能会调用多次
4、Category会覆盖类中的initialize方法,当有多个Category,会执行Compile source最后一个category的initialize
四、 runtime 是什么
runtime
是由C
和 C++
汇编 实现的⼀套API,为OC语⾔加⼊了⾯向对象,运⾏时的功能
运⾏时(Runtime) 是指将数据类型的确定由编译时推迟到了运⾏时 - 举例⼦? : extension - category 的区别
平时编写的OC代码,在程序运⾏过程中,其实最终会转换成Runtime的C语⾔代码,Runtime
是 Object-C
的幕后⼯作者
五、⽅法的本质,sel是什么?IMP是什么?两者之间的关系⼜是什么?
sel
是⽅法编号 ~ 在 read_images
期间就编译进⼊了内存
imp
就是我们函数实现指针 ,找 imp
就是找函数的过程
sel
就相当于书本的⽬录 tittle, imp
就是书本的⻚码,
查找具体的函数就是想看这本书⾥⾯具体篇章的内容
1:我们⾸先知道想看什么 ~ tittle (sel)
2:根据⽬录对应的⻚码 (imp)
3:翻到具体的内容
⽅法的本质:发送消息 , 消息会有以下⼏个流程
1:快速查找 (objc_msgSend
)~ cache_t 缓存消息
2:慢速查找~ 递归⾃⼰| ⽗类 ~ lookUpImpOrForward
3:查找不到消息: 动态⽅法解析 ~ resolveInstanceMethod
4:消息快速转发~ forwardingTargetForSelector
5:消息慢速转发~ methodSignatureForSelector & forwardInvocation
六、能否向编译后的得到的类中增加实例变量?能否向运⾏时创建的类中添加实例变量
1:不能向编译后的得到的类中增加实例变量
2:只要类没有注册到内存还是可以添加
原因:我们编译好的实例变量存储的位置在 ro
,⼀旦编译完成,内存结构就完全确定,就⽆法修改。
可以添加属性 + ⽅法
七、[self class]
和[super class]
的区别以及原理分析
[self class]
就是发送消息 objc_msgSend
,消息接受者是 self
⽅法编号:class
[super class]
本质就是 objc_msgSendSuper
, 消息的接受者还是
self
⽅法编号:class
只是 objc_msgSendSuper
会更快,直接跳过 self
的查找。
- (instancetype)init{
self = [super init];
if (self){
NSLog("%@ - %@",[self class],[super class]);
}
return self;
}
上面的打印的结果是一样的。
八、内存偏移
用代码来一步步探索吧:
LGPerson
类
//.h文件
@interface LGPerson : NSObject
@property (nonatomic, assign) int kc_name;
@property (nonatomic, copy) NSString *kc_hobby; // 12
- (void)saySomething;
@end
//.m文件
#import "LGPerson.h"
@implementation LGPerson
- (void)saySomething{
NSLog(@"%s",__func__);
}
@end
ViewController
类
- (void)viewDidLoad {
[super viewDidLoad];
LGPerson *person = [LGPerson alloc];
[person saySomething];
Class cls = [LGPerson class];
void *kc = &cls;
[(__bridge id)kc saySomething];
}
打印结果:
2021-07-26 15:24:50.018164+0800 [6368:6129630] -[LGPerson saySomething]
2021-07-26 15:24:50.018288+0800 [6368:6129630] -[LGPerson saySomething]
问题一:saySomething
存在哪里,person
是如何访问到的?
saySomething
存在类里面,person
是实例对象,它跟类有一个关联(isa
指针,isa
存在实例对象中,指向了类),向person
发送了一个消息objc_msgSend
,objc_msgSend
(之前探索过,它是用汇编实现的,有两个重要的参数isa
和class
)只关心有没有一个指针能够指向LGPerson
这个类,现在person
和kc
都指向了LGPerson
,然后通过 地址平移 就可以找到saySomething
方法。
下一步探索:
更改LGPerson.m
NSLog(@"%s -- %@",__func__,self.kc_name);
查看打印:
2021-07-26[6830:6150081] -[LGPerson saySomething] -- (null)
2021-07-26[6830:6150081] -[LGPerson saySomething] -- <LGPerson: 0x600000955020>
问题二: 为啥会打印<LGPerson: 0x600000955020>
呢?
person
是如何访问到 kc_name
的,是通过访问 setter
和 getter
方法,也是内存平移的方式。
lldb 调试:
(lldb) x/4gx person
0x6000030855a0: 0x000000010702d658 0x0000000107028038 //person:isa happy
0x6000030855b0: 0x0000000000000000 0x0000000000000000
(lldb) p person
(LGPerson *) $1 = 0x00006000030855a0
(lldb) po 0x000000010702d658
LGPerson
(lldb) po 0x0000000107028038
happy
person
通过 +0x8
,访问到 saySomething
;
kc
模仿 person
,也拿kc
(是在当前函数栈帧中加过来的cls
的地址)的地址+0x8
,查看 kc
+0x8
在栈里面的地址是谁,
(lldb) p &cls
(Class *) $3 = 0x00007ffee8bd8000
(lldb) p &person
(LGPerson **) $4 = 0x00007ffee8bd8008
由上面打印可以看出为什么打印 LGPerson
。
九、压栈
struct kc_struct{
NSNumber *num1;
NSNumber *num2;
} kc_struct;
- (void)viewDidLoad { //(id self, SEL _cmd)
[super viewDidLoad]; //struct {object class}
LGPerson *person = [LGPerson alloc];
struct kc_struct kc_s = {@(10),@(20)};
LGPerson *person1 = [LGPerson alloc];
person1.kc_name = @"happy";
[person1 saySomething];
}
打印:
(lldb) p &person
(LGPerson **) $0 = 0x00007ffee7b95158
(lldb) p/x &kc_s
(kc_struct *) $1 = 0x00007ffee7b95148
(lldb) p &person1
(LGPerson **) $2 = 0x00007ffee7b95140
(lldb) p kc_s.num1
(__NSCFNumber *) $3 = 0x9ed6c4d5f4468662 (int)10
(lldb) p &$3
(NSNumber **) $4 = 0x00007ffee7b95148
(lldb) p kc_s.num2
(__NSCFNumber *) $5 = 0x9ed6c4d5f4468782 (int)20
(lldb) p &$5
(NSNumber **) $6 = 0x00007ffee7b95150
(lldb)
压栈过程示意图:
打印所有的入栈情况:
struct kc_struct{
NSNumber *num1;
NSNumber *num2;
} kc_struct;
- (void)viewDidLoad { //(id self, SEL _cmd)
[super viewDidLoad]; //struct {object class}
Class cls = [LGPerson class];
void *kc = &cls;
[(__bridge id)kc saySomething];
LGPerson *person = [LGPerson alloc];
person.kc_name = @"happy";
[person saySomething];
NSLog(@"%p - %p",&person,kc);
// 隐藏参数 会压入栈帧
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);
}
}
}
打印内容:
2021-07-26 20:28:10.809120+0800 -[LGPerson saySomething] - <ViewController: 0x7fe489d055e0>
2021-07-26 20:28:10.809218+0800 -[LGPerson saySomething] - happy
2021-07-26 20:28:10.809294+0800 0x7ffee3bfa148 - 0x7ffee3bfa158
2021-07-26 20:28:10.809382+0800 0x7ffee3bfa178 : <ViewController: 0x7fe489d055e0>
2021-07-26 20:28:10.809447+0800 0x7ffee3bfa170 : viewDidLoad
2021-07-26 20:28:10.809523+0800 0x7ffee3bfa168 : ViewController
2021-07-26 20:28:10.809623+0800 0x7ffee3bfa160 : <ViewController: 0x7fe489d055e0>
2021-07-26 20:28:10.809706+0800 0x7ffee3bfa158 : LGPerson
2021-07-26 20:28:10.809796+0800 0x7ffee3bfa150 : <LGPerson: 0x7ffee3bfa158>
补充:
结构体是从后往前压栈; 参数是从前往后压栈;
转载自:https://juejin.cn/post/6989212790435086373