iOS 底层探索 文章汇总
目录
- 一、前言
- 二、类的扩展
- 三、关联对象
一、前言
前面的文章我们分析了类的加载流程,知道了类在有分类
,动态添加方法、协议、属性
的情况下才会生成rwe
。那么这篇文章我们探索类的扩展和方法的动态绑定
。
二、类的扩展
代码如下:
@interface NATeacher : NSObject
@end
@interface NATeacher ()// 类的扩展
@end
@implementation NATeacher
@end
注意:类的扩展只能放在类的声明之后,类的实现之前
最常见的类的扩展:ViewController
类的扩展
#import "ViewController.h"
@interface ViewController () // ViewController类的扩展
@end
@implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
// Do any additional setup after loading the view.
}
@end
分类 VS 类扩展
类扩展的本质
在类扩展中添加属性和方法
@interface NATeacher : NSObject
@end
@interface NATeacher ()// 类的扩展
@property (nonatomic, copy) NSString *ext_name;
- (void)ext_instanceMethod;
+ (void)ext_classMethod;
@end
@implementation NATeacher
- (void)ext_instanceMethod {
}
+ (void)ext_classMethod {
}
@end
生成cpp
文件:
-
cd
当前代码所在的目录 clang -rewrite-objc main.m -o main2.cpp
类、类扩展、属性如下:
#ifndef _REWRITER_typedef_NATeacher
#define _REWRITER_typedef_NATeacher
typedef struct objc_object NATeacher;
typedef struct {} _objc_exc_NATeacher;
#endif
extern "C" unsigned long OBJC_IVAR_$_NATeacher$_ext_name;
struct NATeacher_IMPL {
struct NSObject_IMPL NSObject_IVARS;
NSString *_ext_name;
};
对应的方法实现如下:
static void _I_NATeacher_ext_instanceMethod(NATeacher * self, SEL _cmd) {
}
static void _C_NATeacher_ext_classMethod(Class self, SEL _cmd) {
}
static NSString * _I_NATeacher_ext_name(NATeacher * self, SEL _cmd) { return (*(NSString **)((char *)self + OBJC_IVAR_$_NATeacher$_ext_name)); }
extern "C" __declspec(dllimport) void objc_setProperty (id, SEL, long, id, bool, bool);
static void _I_NATeacher_setExt_name_(NATeacher * self, SEL _cmd, NSString *ext_name) { objc_setProperty (self, _cmd, __OFFSETOFIVAR__(struct NATeacher, _ext_name), (id)ext_name, 0, 1); }
- 从上面的代码中可以看到为属性自动生成的
set、get
方法 - 因此写到类扩展中的属性和方法和写到类声明中表现完全相同
-
cpp
中_method_list_t
里面属性的set、get
都有两个:
查看macho中类扩展的数据加载情况
代码准备如下:
NATeacher.h
@interface NATeacher : NSObject
- (void)na_instanceMethod;
@end
NATeacher.m
#import "NATeacher.h"
@implementation NATeacher
+ (void)load {
}
- (void)na_instanceMethod {
}
- (void)ext_instanceMethod {
}
+ (void)ext_classMethod {
}
@end
NATeacher+NAEXT.h
#import "NATeacher.h"
@interface NATeacher ()
- (void)ext_instanceMethod;
+ (void)ext_classMethod;
@end
- 实现
+load
方法将NATeacher
设置为非懒加载类方便研究 - 新建
NATeacher+NAEXT.h
头文件用于放分类的方法
readClass
方法打印如下:
- 可见扩展中的方法和主类中的方法一样在编译期作为主类的一部分一起编译进类中
三、关联对象
代码如下:
@interface LGPerson (LG)
@property (nonatomic, copy) NSString *cate_name;
@end
LGPerson *person = [LGPerson alloc];
person.cate_name = @"KC";
NSLog(@"cate_name:%@",person.cate_name);
上面的代码编译不会报错,运行会Crash
,因为系统只会生成属性set、get
方法的声明,不会生成方法的实现
在分类中添加如下方法以实现属性的set、get
方法:
@implementation LGPerson (LG)
- (void)setCate_name:(NSString *)cate_name {
//1: 对象,2: 标识符,3: value,4: 属性关联策略
objc_setAssociatedObject(self, "cate_name", cate_name, OBJC_ASSOCIATION_COPY_NONATOMIC);
}
- (NSString *)cate_name {
return objc_getAssociatedObject(self, "cate_name");
}
@end
- 现在就能正常运行代码进行属性
set、get
方法的调用了。
1. 关联对象底层方法调用情况
进入objc_setAssociatedObject
方法
void
objc_setAssociatedObject(id object, const void *key, id value, objc_AssociationPolicy policy)
{
SetAssocHook.get()(object, key, value, policy);
}
static ChainedHookFunction SetAssocHook{_base_objc_setAssociatedObject};
static void
_base_objc_setAssociatedObject(id object, const void *key, id value, objc_AssociationPolicy policy)
{
_object_set_associative_reference(object, key, value, policy);
}
所以关联对象底层调用的是_object_set_associative_reference
方法。
现在分析关联对象方法将属性的value
存到了哪里??
2. 主类属性value
存储情况
在前面clang
生成的cpp
文件中属性的设置方法调用的是objc_setProperty
方法
static void _I_NATeacher_setExt_name_(NATeacher * self, SEL _cmd, NSString *ext_name) {
objc_setProperty (self, _cmd, __OFFSETOFIVAR__(struct NATeacher, _ext_name), (id)ext_name, 0, 1);
}
void objc_setProperty(id self, SEL _cmd, ptrdiff_t offset, id newValue, BOOL atomic, signed char shouldCopy)
{
bool copy = (shouldCopy && shouldCopy != MUTABLE_COPY);
bool mutableCopy = (shouldCopy == MUTABLE_COPY);
reallySetProperty(self, _cmd, newValue, offset, atomic, copy, mutableCopy);
}
static inline void reallySetProperty(id self, SEL _cmd, id newValue, ptrdiff_t offset, bool atomic, bool copy, bool mutableCopy)
{
if (offset == 0) {
object_setClass(self, newValue);
return;
}
id oldValue;
id *slot = (id*) ((char*)self + offset);
if (copy) {
newValue = [newValue copyWithZone:nil];
} else if (mutableCopy) {
newValue = [newValue mutableCopyWithZone:nil];
} else {
if (*slot == newValue) return;
newValue = objc_retain(newValue);
}
if (!atomic) {
oldValue = *slot;
*slot = newValue;
} else {
spinlock_t& slotlock = PropertyLocks[slot];
slotlock.lock();
oldValue = *slot;
*slot = newValue;
slotlock.unlock();
}
objc_release(oldValue);
}
在reallySetProperty
方法中属性的值是放在了slot
地址,其中slot = (char*)self + offset
,offset = __OFFSETOFIVAR__(struct NATeacher, _ext_name)
#define __OFFSETOFIVAR__(TYPE, MEMBER) ((long long) &((TYPE *)0)->MEMBER)
3. 关联对象value
存储情况
进入到_object_set_associative_reference
方法源码
void
_object_set_associative_reference(id object, const void *key, id value, uintptr_t policy)
{
// This code used to work when nil was passed for object and key. Some code
// probably relies on that to not crash. Check and handle it explicitly.
// rdar://problem/44094390
if (!object && !value) return;
if (object->getIsa()->forbidsAssociatedObjects())
_objc_fatal("objc_setAssociatedObject called on instance (%p) of class %s which does not allow associated objects", object, object_getClassName(object));
// 包装了一下 对象
DisguisedPtr disguised{(objc_object *)object};
// 包装一下 policy - value
ObjcAssociation association{policy, value};
// retain the new value (if any) outside the lock.
association.acquireValue();
{
AssociationsManager manager;//不唯一,只是加锁避免多线程影响
AssociationsHashMap &associations(manager.get());//唯一
if (value) {// try_emplace:如果map中没有键,则将键、值对插入到映射中。
auto refs_result = associations.try_emplace(disguised, ObjectAssociationMap{});
if (refs_result.second) {
/* it's the first association we make */
object->setHasAssociatedObjects();
}
/* establish or replace the association */
auto &refs = refs_result.first->second; // 空的桶子
auto result = refs.try_emplace(key, std::move(association));
if (!result.second) {
association.swap(result.first->second);
}
} else { // 如果value为空就移除
auto refs_it = associations.find(disguised);
if (refs_it != associations.end()) {
auto &refs = refs_it->second;
auto it = refs.find(key);
if (it != refs.end()) {
association.swap(it->second);
refs.erase(it);
if (refs.size() == 0) {
associations.erase(refs_it);
}
}
}
}
}
// release the old value (outside of the lock).
association.releaseHeldValue();
}
关联对象流程如下:
3.1 关联对象:设值流程
- 创建一个
AssociationsManager
管理类 - 获取唯一的全局静态
哈希Map
- 判断是否插入的关联
valu
e是否为空:
3.1:存在走第4步
3.2:不存在就走:关联对象插入空值流程 - 创建一个空的
ObjcAssociationMap
去取查询的键值对 - 如果发现这个key就插入一个空的
BucketT
进去并返回 - 标记对了存在关联对象
- 用当前的
修饰策略
和value
组成一个ObjcAssociation
替换原来的BucketT
的空 - 标记一下
ObjcAssociationMap
的第一次为false
3.2 关联对象:插入空值流程
- 根据
DisguisedPtr
找到AssociationsHashMap
中的iterator
迭代查询器 - 清理迭代器
- 插入空值就相当于清除关联对象
3.3 关联对象:取值流程
- 创建一个
AssociationsManager
管理类 - 获取唯一的全局静态
哈希Map
- 根据
DisguisedPtr
找到AssociationsHashMap
中的iterator
迭代查询器 - 如果这个迭代器不是最后一个:获取
ObjcAssociationMap
(这里有策略和value
) - 找到
ObjectAssociationsMap
的迭代查询器获取一个经过属性修饰符修饰的value
- 返回
_value