1. class_ro_t的llvm层分析
-
下载llvm源码
-
在llvm源码中搜索class_ro_t,定位class_ro_t结构体
- 找到Read函数的实现
- 什么时候调用Read函数 ClassDescriptorV2::Describe --> Read_class_row --> class_ro->Read
2. 类别(category)
类别(category)
- 专门用来给类添加新的方法
- 不能给类添加成员属性,添加了成员属性,但可以通过runtime用
关联对象
的方式添加属性 - 分类中用
@property
定义变量,只会生成变量的getter,setter方法
的声明,不能生成方法实现和带下划线的成员变量
给类别添加属性,需要重写getter,setter方法
@interface LGPerson (LG)
@property (nonatomic, copy) NSString *cate_name;
@end
@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
关联对象
AssociationsManager 不是唯一的, AssociationsHashMap 才是唯一的
关联对象:设值流程
- objc_setAssociatedObject --> SetAssocHook --> _base_objc_setAssociatedObject --> _object_set_associative_reference
void objc_setAssociatedObject(id object, const void *key, id value, objc_AssociationPolicy policy)
{
SetAssocHook.get()(object, key, value, policy);
}
//SetAssocHook
static ChainedHookFunction<objc_hook_setAssociatedObject> SetAssocHook{_base_objc_setAssociatedObject};
//_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
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<objc_object> 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) {
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 {
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();
}
//try_emplace
std::pair<iterator, bool> try_emplace(const KeyT &Key, Ts &&... Args) {
BucketT *TheBucket;
if (LookupBucketFor(Key, TheBucket)) // 找桶子
return std::make_pair(
makeIterator(TheBucket, getBucketsEnd(), true),
false); // Already in map.
// Otherwise, insert the new element.
TheBucket = InsertIntoBucket(TheBucket, Key, std::forward<Ts>(Args)...);
return std::make_pair(
makeIterator(TheBucket, getBucketsEnd(), true),
true);
}
auto refs_result = associations.try_emplace(disguised, ObjectAssociationMap{})中refs_result的结构:
auto &refs = refs_result.first->second中refs的结构
AssociationsManager 不是单例,可以创建多个,公用AssociationsHashMap所以必须加锁使用保证安全性
AssociationsManager::init不是初始化是类的静态函数,在load_image中调用
请问关联对象需要移除么? 不需要。在对象销毁的时候统一移除
关联对象流程
-
1:创建一个
AssociationsManager
管理类 -
2:获取
唯一
的全局静态哈希Map:AssociationsHashMap
-
3:判断是否插入的
关联值value
是否存在- 3.1:存在走第4步
- 3.2:不存在就走 :
关联对象-插入空流程
-
4:通过
try_emplace
方法,并创建一个空的ObjectAssociationMap
去取查询的键值对: -
5:如果发现
没有
这个key
就插入一个 空的 BucketT
进去并返回true -
6:通过
setHasAssociatedObjects
方法标记对象存在关联对象
即置isa指针
的has_assoc
属性为true
-
7:用当前
policy 和 value
组成了一个ObjcAssociation
替换原来BucketT 中的空
-
8:标记一下
ObjectAssociationMap
的第一次
为false
关联对象插入空流程
-
根据DisguisedPtr找到
AssociationsHashMap
中的iterator迭代查询器
-
清理迭代器
-
如果插入空值,相当于清处
关联对象:取值流程
-
创建一个
AssociationsManager
管理类 -
获取唯一的全局静态哈希Map
-
根据DisguisedPtr找到
AssociationsHashMap
中的iterator迭代查询器
-
如果这个迭代查询器不是最后一个 获取:
ObjectAssociationMap
(这里有策略和Value) -
找到
ObjectAssociationMap
的迭代查询器获取一个经过属性修饰符修饰的value -
返回value
3. 类扩展(extension)
类扩展(extension)
- 可以说是特殊的分类,也称作
匿名分类
。 - 类扩展(extension)的位置:在类的声明之后,实现之前,习惯放置在类实现.m文件的顶部
- 可以给类
添加成员属性
,但是是私有变量 - 可以给类
添加方法
,也是私有方法
main.mm
#import <Foundation/Foundation.h>
#import "LGPerson.h"
#import <objc/runtime.h>
#import <malloc/malloc.h>
@interface Teacher : NSObject
@end
@interface Teacher()
@property(nonatomic,copy) NSString *ext_name;
- (void)ext_instanceMethod;
- (void)ext_classMethod;
@end
@implementation Teacher
- (void)ext_instanceMethod{
}
- (void)ext_classMethod{
}
@end
int main(int argc, const char * argv[]) {
@autoreleasepool {
NSLog(@"Hello world")
}
return 0;
}
重新预编译为cpp文件
clang -rewrite-objc main.mm -o main2.cpp
属性直接写入类,并有setter 和 getter方法:
extern "C" unsigned long OBJC_IVAR_$_Teacher$_ext_name;
struct Teacher_IMPL {
struct NSObject_IMPL NSObject_IVARS;
NSString *_ext_name;
};
static NSString * _I_Teacher_ext_name(Teacher * self, SEL _cmd) { return (*(NSString **)((char *)self + OBJC_IVAR_$_Teacher$_ext_name)); }
extern "C" __declspec(dllimport) void objc_setProperty (id, SEL, long, id, bool, bool);
static void _I_Teacher_setExt_name_(Teacher * self, SEL _cmd, NSString *ext_name) { objc_setProperty (self, _cmd, __OFFSETOFIVAR__(struct Teacher, _ext_name), (id)ext_name, 0, 1); }
扩展的方法查看,在编译时期就直接添加到类的方法列表中,作为了类的一部分
static struct /*_method_list_t*/ {
unsigned int entsize; // sizeof(struct _objc_method)
unsigned int method_count;
struct _objc_method method_list[6];
} _OBJC_$_INSTANCE_METHODS_Teacher __attribute__ ((used, section ("__DATA,__objc_const"))) = {
sizeof(_objc_method),
6,
{{(struct objc_selector *)"ext_instanceMethod", "v16@0:8", (void *)_I_Teacher_ext_instanceMethod},
{(struct objc_selector *)"ext_classMethod", "v16@0:8", (void *)_I_Teacher_ext_classMethod},
{(struct objc_selector *)"ext_name", "@16@0:8", (void *)_I_Teacher_ext_name},
{(struct objc_selector *)"setExt_name:", "v24@0:8@16", (void *)_I_Teacher_setExt_name_},
{(struct objc_selector *)"ext_name", "@16@0:8", (void *)_I_Teacher_ext_name},
{(struct objc_selector *)"setExt_name:", "v24@0:8@16", (void *)_I_Teacher_setExt_name_}}
};
总结: 类的扩展在编译器就已经作为类的一部分编译进来,和分类不同,不会动态加入
4. 相关面试题
Q:如何移除关联对象?
-
移除一个
object
的某个key
的关联对象:调用objc_setAssociatedObject
设置关联对象value
为nil
。objc_setAssociatedObject
函数会调用_object_set_associative_reference
函数,并在该函数中判断传进来的value
是否为nil
,是的话会调用erase(j)
擦除函数,将j
变量擦除。j
即为ObjectAssociationMap
对象里的一对【key: key value: ObjcAssociation(_policy、_value)】。 -
移除一个
object
的所有关联对象:调用函数objc_removeAssociatedObjects
。objc_removeAssociatedObjects
函数会调用_object_remove_assocations
函数,并在该函数中调用对象的erase(i)
擦除函数,将i
变量擦除。i
即为AssociationsHashMap
对象中的一对【key: object value: ObjectAssociationMap】。
Q:如果 object 被销毁,那它所对应的 ObjectAssociationMap 是否也会自动销毁?
答案是肯定的。
当我们对象释放时,会调用dealloc
- C++函数释放 :
objc_cxxDestruct
- 移除关联属性:
_object_remove_assocations
- 将弱引用自动设置nil:
weak_clear_no_lock(&table.weak_table, (id)this);
- 引用计数处理:
table.refcnts.erase(this)
- 销毁对象:
free(obj)
所以,关联对象
不需要我们手动移除,会在对象析构即dealloc
时释放
dealloc的源码查找路径为:dealloc
-> _objc_rootDealloc
-> rootDealloc
-> object_dispose
(释放对象)-> objc_destructInstance
-> _object_remove_assocations
Q:如果没有关联对象,怎么实现 Category 有成员变量的效果?
使用字典。创建一个全局的字典,将self
对象在内存中的地址作为key
。
缺点: 内存泄漏问题:全局变量会一直存储在内存中;
线程安全问题:可能会有多个对象同时访问字典,加锁可以解决;
每添加一个成员变量就要创建一个字典,很麻烦。
#import "Person.h"
@interface Person (Test)
@property (nonatomic, assign) int height;
@end
#import "Person+Test.h"
#import <objc/runtime.h>
@implementation Person (Test)
NSMutableDictionary *heights_;
+ (void)load {
heights_ = [NSMutableDictionary dictionary];
}
- (void)setHeight:(int)height {
NSString *key = [NSString stringWithFormat:@"%@",self];
heights_[key] = @(height);
}
- (int)height {
NSString *key = [NSString stringWithFormat:@"%@",self];
return [heights_[key] intValue];
}
Q: 类的方法 和 分类方法 重名,如果调用,是什么情
-
如果同名方法是
普通方法
,包括initialize
-- 先调用分类方法- 因为
分类的方法是在类realize之后 attach进去的
,插在类的方法的前面,所以优先调用分类的方法
(注意:不是分类覆盖主类!!) initialize
方法什么时候调用?initialize
方法也是主动调用,即第一次消息时
调用,为了不影响整个load,可以将需要提前加载的数据
写到initialize
中
- 因为
-
如果同名方法是
load
方法 -- 先主类load
,后分类load
(分类之间,看编译的顺序)- 原因:参考iOS-底层原理 18:类的加载(上)中的
load_images解析
- 原因:参考iOS-底层原理 18:类的加载(上)中的
Q: Runtime是什么?
-
runtime
是由C和C++
汇编实现的一套API
,为OC语言加入了面向对象、以及运行时的功能
-
运行时是指将
数据类型的确定由编译时 推迟到了 运行时
- 举例:extension 和 category 的区别
-
平时编写的OC代码,在程序运行的过程中,其实最终会转换成runtime的C语言代码,
runtime是OC的幕后工作者
Q:分类和扩展的对比
-
category 类别、分类
- 专门用来
给类添加新的方法
不能给类添加成员属性
,添加了成员属性,也无法取到- 注意:其实
可以通过runtime 给分类添加属性
,即属性关联,重写setter、getter方法 - 分类中用
@property
定义变量,只会生成
变量的setter、getter
方法的声明
,不能生成方法实现 和 带下划线的成员变量
- 专门用来
-
extension 类扩展
- 可以说成是
特殊的分类
,也可称作匿名分类
- 可以
给类添加成员属性
,但是是私有变量
- 可以
给类添加方法
,也是私有方法
- 可以说成是
Q:方法的本质,sel是什么?IMP是什么?两者之间的关系又是什么?
-
方法的本质:
发送消息
,消息会有以下几个流程- 快速查找(
objc_msgSend
) - cache_t缓存消息中查找 - 慢速查找 - 递归自己|父类 -
lookUpImpOrForward
- 查找不到消息:动态方法解析 -
resolveInstanceMethod
- 消息快速转发 -
forwardingTargetForSelector
- 消息慢速转发 -
methodSignatureForSelector & forwardInvocation
- 快速查找(
-
sel
是方法编号
- 在read_images
期间就编译进了内存 -
imp
是函数实现指针
,找imp就是找函数的过程
-
sel
相当于 一本书的目录title
-
imp
相当于 书本的页码
-
查找
具体的函数
就是想看这本书具体篇章的内容- 1、首先知道想看什么,即目录 title - sel
- 2、根据目录找到对应的页码 - imp
- 3、通过页码去翻到具体的内容
Q:能否向编译后得到的类中增加实例变量?能否向运行时创建的类中添加实例变量
- 1、
不能
向编译后的得到的类中增加实例变量 - 2、
只要类没有注册到内存还是可以添加的
,原因是:编译好的实例变量存储的位置是ro,一旦编译完成,内存的结构完全确定了就无法修改 - 3、可以
添加属性+方法
Q:[self class]和[super class]的区别以及原理分析
[self class]
就是发送消息objc_msgSend
,消息接收者是self
,方法编号class
[super class]
本质就是objc_msgSendSuper
,消息的接收者还是self
,方法编号class
,在运行时,底层调用的是_objc_msgSendSuper2
【重点!!!】- 只是
objc_msgSendSuper2
会更快,直接跳过self的查找
- 进入
[self class]
中的class
源码
**
- (Class)class {
return object_getClass(self);
}
Class object_getClass(id obj)
{
if (obj) return obj->getIsa();
else return Nil;
}
class方法查找顺序是:LGTeacher->LGPerson->NSObject
,找到NSObject中的class方法,返回object_getClass(self)
,其底层是获取对象的isa
,当前的self是LGTeacher对象
,其isa是同名的LGTeacher
,所以[self class]
打印的是LGTeacher
- [super class]中,其中
super
是语法的关键字
,可以通过clang
看super
的本质objc_msgSendSuper(__rw_objc_super,sel)
其中第一个参数是消息接收者,是一个__rw_objc_super
结构; [super class]就是拿到__rw_objc_super这个消息接收者的isa
clang -rewrite-objc LGTeacher.m -o LGTeacher.cpp
- 查看init方法
- 底层源码中搜索
__rw_objc_super
,是一个中间结构体
- objc中搜索
objc_msgSendSuper
,查看其隐藏参数
- 找不到定义,但是找到了声明,通过注释了解到是struct objc_super类型,那么我们更换搜索目标,搜索
struct objc_super
的结构
一共有两个参数:分别是id类型的receiver(消息接收者)变量和Class类型的父类变量,此时receiver的值是self(LGTeacher的对象)
,self是init后的实例对象
,实例对象的isa
指向的是本类,即消息接收者是LGTeacher本类
objc_msgSendSuper
,打开汇编调试,objc_msgSendSuper转为了objc_msgSendSuper2
- 查看 objc_msgSendSuper2的函数原型,
从类开始查找,而不是父类
- 查看
objc_msgSendSuper2
的汇编源码,是从superclass
中的cache
中查找方法-class
,底层是在函数内部调用的class->superclass
获取父类,并不是我们上面分析的直接传入的就是父类对象。查找顺序是:LGPerson->NSObject
找到NSObject中的-class方法返回object_getClass(self);
,此时self仍是LGTeacher
其实
_objc_msgSendSuper2
内传入的结构体为objc_super2
struct objc_super2 {
id receiver;
Class current_class;
};
我们可以发现objc_super2
中除了消息接受者receiver
,另一个成员变量current_class
也就是当前类对象。
与我们上面分析的不同_objc_msgSendSuper2
函数内其实传入的是当前类对象,然后在函数内部获取当前类对象的父类,并且从父类开始查找方法。
总结:
-
[self class]
方法调用的本质是发送消息
,调用class的消息流程,拿到元类的类型
,在这里是因为类已经加载到内存,所以在读取时是一个字符串类型,这个字符串类型是在map_images
的readClass
时已经加入表中,所以打印为LGTeacher
-
[super class]
打印的是LGTeacher
,原因是当前的super是一个关键字,在这里只调用objc_msgSendSuper2
,其实他的消息接收者和[self class]
是一模一样的,所以返回的是LGTeacher
Q: Runtime是如何实现weak的,为什么可以自动置nil
- 通过
SideTable
找到我们的weak_table
weak_table
根据referent
找到或者创建weak_entry_t
- 然后
append_referrer(entry,referrer)
将我的新弱引用的对象加进去entry - 最后
weak_entry_insert
,把entry
加入到我们的weak_table
Q:内存平移问题
LGPerson类定义 LGPerson.h
#import <Foundation/Foundation.h>
NS_ASSUME_NONNULL_BEGIN
@interface LGPerson : NSObject
@property (nonatomic, assign) int kc_name;
@property (nonatomic, copy) NSString *kc_hobby; // 12
- (void)saySomething;
@end
NS_ASSUME_NONNULL_END
LGPerson.m
#import "LGPerson.h"
@implementation LGPerson
- (void)saySomething{
NSLog(@"%s - %@",__func__,self.kc_hobby);
}
@end
调用
Class cls = [LGPerson class];
void *kc = &cls;
[( __bridge id)kc saySomething];
LGPerson *person = [LGPerson alloc];
[person saySomething];
通过运行发现,是可以执行的
[person saySomething]
的本质是对象发送消息
,关键是找到类结构中存储的methods
person
的isa
指向类LGPerson
即通过person的首地址 可找到 LGPerson的首地址
,我们可以通过LGPerson的内存平移找到cache
,在cache中查找方法
[(__bridge id)kc saySomething]
中的kc
是来自于LGPerson
这个类,然后有一个指针kc
kc中存储的是isa
,而isa将其指向LGPerson的首地址
saySomething里面有属性 self.kc_name 的打印
- (void)saySomething{
NSLog(@"%s - %@",__func__,self.kc_hobby);
}
Class cls = [LGPerson class];
void *kc = &cls;
[(__bridge id)kc saySomething];
LGPerson *person = [LGPerson alloc];
[person saySomething];
打印结果如下:
其中person方式的kc_name
是由于 self指向person的内存结构
,然后通过内存平移8字节,取出去kc_name
,即self指针首地址平移8字节获得
kc表示8字节指针
,self.kc_name
的获取,相当于 kc首地址的指针也需要平移8字节找kc_name
,那么此时的kc的指针地址是多少?平移8字节获取的是什么?
-
kc
是一个指针,是存在栈
中的,栈是一个先进后出
的结构,参数传入就是一个不断压栈的过程, -
其中
隐藏参数会压入栈
,且每个函数都会有两个隐藏参数(id self,sel _cmd)
,可以通过clang
查看底层编译 -
隐藏参数压栈
的过程,其地址是递减
的,即在栈中,参数会从前往后一直压入栈
-
super通过clang查看底层的编译,是
objc_msgSendSuper
,其第一个参数是一个结构体__rw_objc_super(self,class_getSuperclass)
-
结构体内部
的压栈情况是低地址->高地址
,递增
的,栈中结构体内部
的成员是从`最后一个成员变量开始入栈``, -
栈中
从高地址到低地址
的顺序的:self - _cmd - (id)class_getSuperclass(objc_getClass("ViewController")) - self - cls - kc - person
-
self
和_cmd
是viewDidLoad
方法的两个隐藏参数,是高地址->低地址正向压栈
的 -
class_getSuperClass
和self
为objc_msgSendSuper2
中的结构体成员,是从最后一个成员变量,开始入栈,即反向压栈
kc是指向LGPerson的关系,kc中存储的是
person<LGPerson: 0x7ffee808c1e8>
,
编译器会认为 kc也是LGPerson的一个实例化对象
,即kc中存储的是isa,isa指向LGPerson
,具有和person一样的效果,简单来说,我们已经完全将编译器骗过了,即kc
也有kc_hobby
。由于person查找kc_name是通过内存平移8字节,即找到0x7ffee808c1e8+0x8 = 0x7ffee808c1f0
,所以kc也是通过内存平移8字节去查找kc_hobby 就找到了ViewController
修改类结构和代码
@interface LGPerson : NSObject
@property (nonatomic, assign) int kc_name;
@property (nonatomic, copy) NSString *kc_hobby; // 12
- (void)saySomething;
@end
NS_ASSUME_NONNULL_END
#import "LGPerson.h"
@implementation LGPerson
- (void)saySomething{
NSLog(@"%s - %@",__func__,self.kc_name);
}
@end
- (void)viewDidLoad {
[super viewDidLoad];
// ViewController 当前的类
// self cmd (id)class_getSuperclass(objc_getClass("LGTeacher")) self cls kc person
Class cls = [LGPerson class];
void *kc = &cls; //
LGPerson *person = [LGPerson alloc];
// LGPerson - 0x7ffeea0c50f8 ---> 0x7ffeea0c50f8平移12个字节取kc_name会崩溃
[(__bridge id)kc saySomething]; //崩溃
[person saySomething]; // self.kc_name = nil - (null)
}
[(__bridge id)kc saySomething]; //崩溃
kc中存储的是LGPerson - 0x7ffeea0c50f8 ,进入-saySomething方法中,读取self.kc_name,即是0x7ffeea0c50f8地址往后平移12个字节读取,不能读取到,所以崩溃
修改代码saySomething
- (void)saySomething{
NSLog(@"%s - %x",__func__,self.kc_name);
}
哪些东西在栈里 哪些在堆里
alloc
的对象 都在堆
中指针、对象
在栈
中,例如person指向的空间
在堆
中,person所在的空间在栈中临时变量
在栈
中属性值
在堆
,属性随对象是在栈
中
堆
是从小到大,即低地址->高地址栈是从大到小,即从高地址->低地址分配
- 函数隐藏参数会
从前往后
开始入栈`,- 结构体内部的成员是
最后一个成员开始入栈
一般情况下,内存地址有如下规则
0x60
开头表示在堆
中0x70
开头的地址表示在栈
中0x10
开头的地址表示在全局区域
中