前言
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>


