前言
iOS 底层第18
天的学习。今天主要分享的是对上一次分类关联对象
的一些补充以及一些 runtime
比较重要知识点复习从而给这一个篇章画下一个完美的句号。
分类关联对象补充
objc_getAssociatedObject
- 进入
objc_getAssociatedObject
id
objc_getAssociatedObject(id object, const void *key)
{
return _object_get_associative_reference(object, key);
}
- 再次进入
_object_get_associative_reference
_object_get_associative_reference(id object, const void *key)
{
ObjcAssociation association{};
{
// 析构函数 创建一个 AssociationsManager 管理类
AssociationsManager manager;
// 获取全局唯一静态哈希map
AssociationsHashMap &associations(manager.get());
// 根据 object(key) 找到 iterator
AssociationsHashMap::iterator i = associations.find((objc_object *)object);
// 如果 iterator 不是最后一个
if (i != associations.end()) {
// 获取 ObjectAssociationMap
ObjectAssociationMap &refs = i->second;
// 根据 value_key 再次找到 iterator
ObjectAssociationMap::iterator j = refs.find(key);
if (j != refs.end()) {
// 获取一个经过属性修饰符修饰的value
association = j->second;
association.retainReturnedValue();
}
}
}
// 返回 value
return association.autoreleaseReturnedValue();
}
- 分析
_object_get_associative_reference
代码,其实是2层哈希
,根据key
找value
的过程👇
_object_remove_assocations
- 关联对象后的
属性
何时销毁呢? - 全局搜索
_object_remove_assocations
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, /*deallocating*/true);
obj->clearDeallocating();
}
return obj;
}
- 发现在
objc_destructInstance
有对其进行调研,全局搜索objc_destructInstance
id
object_dispose(id obj)
{
if (!obj) return nil;
objc_destructInstance(obj);
free(obj);
return nil;
}
- 发现在
object_dispose
有对其进行调研,全局搜索object_dispose
inline void
objc_object::rootDealloc()
{
if (isTaggedPointer()) return;
object_dispose((id)this);
}
- 找到了
rootDealloc
// Replaced by NSZombies
- (void)dealloc {
_objc_rootDealloc(self);
}
- 小结:管理对象的生成的
属性
也会在dealloc
时进行销毁
知识点复习
- load 方法调用流程
- [self class]和[super class]的区别以及原理分析
分析前准备
@interface XKStudent : XKPerson
@end
@implementation XKStudent
- (instancetype)init{
if (self == [super init]) {
NSLog(@" \n self class %@ \n super class %@",[self class],[super class]);
return self;
}
return nil;
}
- 输出打印结果
为什么
[self class]
会打印XKStudent
?
- 从底层原理开始分析 进入
self class
源码
- 进入
object_getClass
- 小结:
[self class]
其实在底层调用了objc_msgSend(id receive,sel _cmd)
,在调用方法时,系统把参数self
给隐藏了,这里的self
=objc
,objc->isa => class
。故输入结果为XKStudent
那为什么
[super class]
也会打印XKStudent
?
- 动态运行程序 当调用
[super class]
会来到objc_msgSendSuper2
👇
- 全局搜
objc_msgSendSuper2
objc_msgSendSuper2(struct objc_super * _Nonnull super, SEL _Nonnull op, ...)
// objc_super
struct objc_super {
/// Specifies an instance of a class.
__unsafe_unretained _Nonnull id receiver;
__unsafe_unretained _Nonnull Class super_class; /* super_class is the first class to search */
};
- 可知
objc_msgSendSuper2
参数id receiver
就是[super class]
的隐藏参数self
,
而这里的self
也是objc
,objc->isa => class
。故输入结果为XKStudent
- 而这里的
super
是编译器的关键字,不是参数别被误导了。
- 而这里的
- 小结:
[self class], [super class]
输出都是类
,在底层的本质
都是一样的
发送消息
把self = objc
传进去用来获取isa
。
- 内存偏移面试题分析
代码👇
@implementation ViewController
// viewDidLoad
- (void)viewDidLoad {
[super viewDidLoad];
// Do any additional setup after loading the view.
XKStudent *student = [XKStudent alloc];
[student doSomething];
// 核心代码
Class cls = [XKStudent class];
void *kc = &cls;
[(__bridge id)kc doSomething];
}
@end
// XKStudent
@implementation XKStudent
- (void) doSomething {
NSLog(@"%s ",__func__);
}
@end
- 打印输出👇
为什么
第二段代码 - [(__bridge id)kc doSomething]
也会输出doSomething
?
- 在开始分析前,先要知道为什么实例对象
student
能调用到类(XKStudent)
里面doSomething
这个方法👇
- 由👆可知,实例对象
student
和 指针地址kc
都指向了同一个class
故可输出一样的方法doSomething
代码改进👇
@interface XKStudent : NSObject
// 添加了一个实例变量 name
@property (nonatomic, copy) NSString *name;
- (void) doSomething;
@end
@implementation XKStudent
- (void) doSomething {
NSLog(@"%s name is %@",__func__,self.name);
}
@end
- 打印输出👇
- 输出
null
是因为没有对name
进行赋值。
那为什么
[(__bridge id)kc doSomething]
会输出SKStudent 对象
❓
要明白为什么
你就要先明白 给name
赋值在底层到底做了❓
- 开始分析先给
name
赋值
XKStudent *s = [XKStudent alloc];
s.name = @"小看";
- 打印输出👇
- 进入
lldb
调试
- 我们发现对象首地址内存平移
0x8
再取地址0x0000000131b207f0
与s.name
取地址0x0000000131b207f0
是相同的,由此得知对象的赋值
其实在底层就是一个内存平移
的过程。 - 继续调式
&cls
- 👆得知
cls
取地址后平移0x8
得到的地址0x00007ffeebfe20e8
也与实例对象
取地址0x00007ffeebfe20e8
相同。这下就能解释[(__bridge id)kc doSomething]
会输出SKStudent 对象
- 因为
cls
不是实例对象
,当实例对象+0x8
后,cls
没有分配内存空间,它只能在函数栈
里进行0x8
,所以就来到了SKStudent 实例对象
代码再次改进👇
@interface XKStudent : NSObject
// 新增了一个 age
@property (nonatomic, copy) NSString *age;
@property (nonatomic, copy) NSString *name;
- (void) doSomething;
@end
- 打印输出👇
这是我们发现多了一个
age
,输出的结果也不一样了,那这又是为何会输出<ViewController: 0x7fbe4d406bd0>
?
- 其实这里在内存平移时要访问
name
时,就必须要跨过age
,因此也就是平移了0x10
那为何平移了
0x10
,就会输出<ViewController: 0x7fbe4d406bd0>
?
- 要知道为何必须先要知道
函数栈
到底是如何压栈
的
结构体入栈分析
struct xk_struct {
NSNumber *num1;
NSNumber *num2;
};
- (void)viewDidLoad { // id self, SEL _cmd
[super viewDidLoad]; // 底层结构体 id objc,class
// Do any additional setup after loading the view.
XKStudent *student1 = [XKStudent alloc];
struct xk_struct xk_s = {@10,@11};
XKStudent *student2 = [XKStudent alloc];
}
-
lldb
输出 👇
从输出得知
student1
先入栈,地址0x00007ffee6ed00e8
,后入栈student2
,地址0x00007ffee6ed00d0
。结论:内存地址 由高到低 ↑
lldb
输出结构体内部👇
- 从输出得知
num2 地址
>num1 地址
- 小结: 结构体是倒着
入栈
的,后面的先入栈
参数入栈分析
void xk_Function(id s1, id s2){
NSLog(@"s1 = %p",&s1);
NSLog(@"s2 = %p",&s2);
}
@implementation ViewController
- (void)viewDidLoad { // id self, SEL _cmd
[super viewDidLoad]; // 底层结构体 id objc,class
// Do any additional setup after loading the view.
XKStudent *student1 = [XKStudent alloc];
XKStudent *student2 = [XKStudent alloc];
xk_Function(student1, student2);
}
- 打印输出👇
- 从输出得知
参数s1 地址
>参数 s2 地址
- 小结: 参数传递是顺着
入栈
的,第一个参数先入栈
内存偏移总结
- 解释为何平移
0x10
输出<ViewController: 0x7fbe4d406bd0>
-
[super viewDidLoad]
在底层就是一个struct { id objc , Class superclass}
-
superclass = <ViewController:: 0x7fbe4d406bd0>
先入栈,objc = ViewController
后入栈 ,故平移0x10
会输出<ViewController:: 0x7fbe4d406bd0>