Objective-C是一门动态性非常强的编程语言;
Objective-C的动态性是由Runtime API提供的;
Runtime API提供的接口基本都是C语言的,源码由C/C++ /汇编语言编写;
isa详解
- instance的isa指向class
当调用对象方法时,通过instance的isa找到class,继而在class中寻找对应的方法,并进行调用 - class的isa指向meta-class
当调用类方法时,通过class的isa找到meta-class,继而在meta-class中找到对应的方法,并进行调用
isa的数据结构
在arm64架构之前,isa就是普通的指针,存储着class/meta-class的地址;
在arm64架构后,对isa进行了优化,变为了union结构,使用位域来存储更多的信息;
union isa_t
{
Class cls;
uintptr_t bits;
struct {
#define ISA_MASK 0x000000fffffff8U
....
uintptr_t nonpointer : 1; //0,代表普通指针 1,代表优化过 使用位域
uintptr_t has_assoc : 1; //是否设置过关联对象,如果没有释放会更快
uintptr_t has_cxx_dtor : 1; //是否有c++的析构函数,如果没有释放会更快
uintptr_t shiftcls : 33;//存储着class meta-class对象的内存地址
uintptr_t magic : 6; //用于调试时分辨对象是否完成初始化
uintptr_t weakly_referenced : 1; //是否有被弱引用,如果没有释放会更快
uintptr_t deallocating : 1; //对象是否正在释放
uintptr_t has_sidetable_rc : 1; //引用计数器是否过大,如果过大 引用计数会存储在一个Slide Table中
uintptr_t extra_rc : 19; //里面存储的值是引用计数器-1
}
}
对isa进行 &ISA_MASK的运算后才得到class/meta-class的地址;
使用不同的掩码即可得到不同的信息;
故优化后isa能存储了更加多的信息
tip: 对于class/meta-class 的地址最后三位都是0;
通过lldb中使用: p/x person->isa //获取地址信息
x 0x... //获取存储的信息
class的结构
class中主要有 isa、 superclass、 属性、 对象方法、 协议、 成员变量
meta-class其实是一种特殊的class,结构与class一致
struct objc_class
{
Class isa;
Class superclass;
cache_t cache; //方法缓存
class_data_bits_t bits; //用于存储具体的类信息
}
通过bits & FAST_DATA_MASK得到class_rw_t
struct class_rw_t
{
uint32_t flags;
uint32_t version;
const class_ro_t *ro;
method_list_t *methods; //方法列表,二维列表,包含了分类
property_list_t *propertries; //属性列表,二维列表,包含了分类
const protocol_list_t *protocols; //协议列表,二维列表,包含了分类
Class firstSubclass;
Class nextSiblingClass;
char *demangledName;
}
struct class_ro_t
{
uint32_t flags;
uint32_t instanceStart;
uint32_t instanceSize; //instance占用的内存空间
const uint8_t * ivarLayout;
const char *name; //类名
method_list_t *baseMethodList; //方法列表,一维数组
property_list_t *baseProtocols; //协议列表,一维数组
const ivar_list_t * ivars; //成员变量列表
}
详细看看method_list_t里面的信息
struct method_t
{
SEL name; //函数名
const char *types; //编码(包含函数的返回值 参数等信息)
IMP imp; //指向函数的指针
}
SEL代表方法/函数名,一般叫做选择器,底层结构跟char *类似
可以通过sel_getName和NSStringFromSelector()转成字符串
不同类中相同名字的方法,所对应的方法选择器是相同的
objc_class中的cache_t cache 是用来缓存曾经调用的方法,它的结构是一个散列表
struct cache_t
{
struct bucket_t *buckets; //散列表
mask_t _mask; //散列表长度-1
mask_t _occupied; //已经缓存方法数量
}
struct bucket_t {
cache key_t _key; //SEL作为key
IMP _imp; //函数的内存地址
}
存储时,
使用@selector(methodName) & _mask 得出索引值,将bucket_t存入该索引下;
如果该索引下已经存在bucket_t,则索引-1地寻找下一个为空的
缺点:通过牺牲空间来换取效率
获取时,
通过进行 @selector(methodName) & _mask 得出匹配方法的索引;
! 当得出相同的索引 则索引-1 继续寻找匹配的方法;
同时这也是散列表构建的核心(散列表,也称为哈希表或哈希映射)。
msg_send的原理
msg_send就是OC中方法调用的底层实现,即OC的消息机制,给receiver(方法调用者)发送一条消息(selector方法名)
msg_send有三大阶段:消息发送、动态方法解析、消息转发;
- 消息发送
对象方法:
先通过isa指针找到类对象,从类对象的方法列表中寻找匹配的方法;
若类对象中找不到,则通过superclass指针找到类对象的父类,从其方法列表中寻找匹配的方法;
当全部父类均未找到匹配的方法,则进行下一个阶段-动态方法解析
类方法:
先通过isa指针找到元类对象,从元类对象的方法列表中寻找匹配的类方法;
若元类对象中找不到,则通过superclass指针找到元类对象的父类,从其方法列表中寻找匹配的方法;
当全部父类均未找到匹配的方法,则进行下一个阶段-动态方法解析
消息发送阶段流程图.png
- 动态方法解析
通过开发者实现 +resolveInstanceMethod / +resolveClassMethod进行动态添加方法;
然后再次走一遍消息发送流程;
//开发者手动实现 --
+ (BOOL)resolveInstanceMethod:(SEL)sel {
if (sel == @selector(test)) {
// 获取其他方法
Method otherMethod = class_getInstanceMethod(self, @selector(other));
//动态添加test方法实现
class_addMethod(self, sel, method_getImplementation(otherMethod), method_getTypeEncoding(otherMethod));
return YES;
}
return [super resolveInstanceMethod:sel];
}
+ (BOOL)resolveClassMethod:(SEL)sel {
if (sel == @selector(test2)) {
//获取元类
Class metaClass = objc_getMetaClass("Person");
//获取其他方法
Method otherMethod = class_getClassMethod(self, @selector(other2));
//动态添加实现
class_addMethod(metaClass, sel, method_getImplementation(otherMethod), method_getTypeEncoding(otherMethod));
return YES;
}
return [super resolveClassMethod:sel];
}
- 消息转发
通过开发者实现 forwardingTargetForSelector 返回处理该消息的其他对象;
若无返回或者无处理,则调取 methodSignatureForSelector返回处理该消息的方法签名,以及 forwardInvocation如何处理
1-消息转发的第一层处理
- (id)forwardingTargetForSelector:(SEL)aSelector {
if (aSelector == @selector(test)) {
return [[Cat alloc] init];
}
return [super forwardingTargetForSelector:aSelector];
}
2-消息转发的第二层处理
//NSMethodSignature方法签名,包含返回值类型、参数类型
- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector {
if (aSelector == @selector(test)) {
return [[[Cat alloc] init] methodSignatureForSelector:@selector(test)];
}
return [super methodSignatureForSelector:aSelector];
}
//NSInvocation封装一个方法调用,包括方法调用者、方法名、方法参数
- (void)forwardInvocation:(NSInvocation *)anInvocation {
[anInvocation invokeWithTarget:[[Cat alloc] init]];
}
对于类方法的消息转发,以上方法均有对应类方法(流程一致)
消息转发流程图.png
super
super调用方法时,底层实现是调取 obj_msgSendSuper({self, [Person class]}, @selector(xxx)),故方法的接收者还是self
@implementation Student
- (instancetype)init {
if (self = [super init]) {
NSLog(@"%@",[self class]); // Student
NSLog(@"%@",[self superclass]); // Person
//obj_msgSendSuper({self, [Person class]}, @selector(class))
NSLog(@"%@",[super class]); // Student
NSLog(@"%@",[super superclass]); // Person
}
return self;
}
@end
@implementation NSObject
- (Class)class {
return object_getClass(self);
}
- (Class)superclass {
return class_getSuperclass(object_getClass(self));
}
class
Person *p = [[Person alloc] init];
NSLog(@"%d",[p isKindOfClass:[Person class]]); //1
NSLog(@"%d",[p isMemberOfClass:[Person class]]); //1
NSLog(@"%d",[Person isKindOfClass:[Person class]]); //0
NSLog(@"%d",[Person isMemberOfClass:[Person class]]); //0
NSLog(@"%d",[Person isKindOfClass:[NSObject class]]); //1 -- 基类的元类的superclass = 基类
NSLog(@"%d",[Person isMemberOfClass:object_getClass(Person.class)]); //1
- isKindOfClass的实现是使用 object_getClass,传入对象获取到的是类对象,传入类对象获取到的是元类对象;
- 基类的元类的superclass = 基类
@interface Person : NSObject
@property (nonatomic, copy) NSString *name;
- (void)print;
@end
@implementation Person
- (void)print {
NSLog(@"my name is %@",self.name);
}
@end
- (void)viewDidLoad {
[super viewDidLoad];
// Do any additional setup after loading the view.
NSString *v = @"123";
id cls = [Person class];
void *obj = &cls;
[(__bridge id)obj print];
//输出 -> my name is 123
}
看完上面代码,我们提出两个问题:
- q1: 为什么obj可以调用print成功?
- q2: 为什么self.name 会取出123?
q1:
正常的对象调用方法,
Person *p = [[Person alloc] init];
[p print];
这里面 p 指向-> Person的对象, 包括[isa _name], 其中前八位就是isa
再通过 isa 指向 -> Person class , 从而找到print方法进行执行,
简化而言走向如下 p -> isa -> Person class -> print
而
id cls = [Person class];
void *obj = &cls;
obj 调用方法时,取出其中前八位恰恰就是cls, 而cls又为 Person class的地址, cls充当了isa的作用,
obj -> cls -> Person class
通过类比可得出第二种方式本质与正常对象调用一致,故可以执行print成功
q2
栈空间中,分配的地址是从高到低的
- (void)test {
NSString *a = @"1"; //0x16d9abaa8
NSString *b = @"2"; //0x16d9abaa0
NSString *c = @"3"; //0x16d9aba98
NSLog(@"%p %p %p", &a, &b, &c);
}
2 对象调用方法,通过地址去取成员变量
Person *p的对象中, 前8位是isa 后8位是_name;
故取self.name时,通过isa指针+8访问到_name;
3 例子中代码
NSString *v = @"123";
id cls = [Person class];
void *obj = &cls;
所创建的地址值, obj --+8--> cls --+8--> v
故print方法中访问self.name时, 取isa+8的地址,即cls+8的地址 = NSString *v
runtime应用
//动态创建一个类
Class objc_allocateClassPair(Class superclass, const char *name, size_t extraBytes)
//注册一个类
void objc_registerClassPair(Class cls)
//销毁一个类
void objc_disposeClassPair(Class cls)
//设置isa指向的Class
void object_setClass(id obj, Class cls)
//获取isa指向的class
void object_getClass(id obj)
//判断一个OC对象是否为Class
BOOL object_isClass(id obj)
//判断一个OC对象是否为MetaClass
BOOL object_isMetaClass(id obj)
//获取父类
Class class_getSuperclass(Class cls)
-- 实例变量
//获取一个实例变量
Ivar class_getInstanceVariable(Class cls, const char *name)
//拷贝实例变量列表(最后需要调用free释放)
Ivar *class_copyIvarList(Class cls, unsigned int* outCount)
--想要查看某个类的内部成员可以使用这个方法
//设置和获取成员变量
void object_setIvar(id obj, Ivar ivar, id value)
id object_getIvar(id obj, Ivar ivar)
//动态添加成员变量(已经注册的类是不能动态添加成员变量)
BOOL class_addIvar(Class cls, const char* name, size_t size, uint8_t alignment, const char *types)
//获取成员变量的相关信息
const char* ivar_getName(Ivar v)
const char *ivar_getTypeEncoding(Ivar v)
-- 方法
//获取一个实例方法 类方法
Method class_getInstanceMethod(Class cls, SEL name)
Method class_getClassMethod(Class cls, SEL name)
//方法实现相关
IMP class_getMethodImplementation(Class cls, SEL name)
IMP method_setImplementation(Method m, IMP imp)
void method_exchangeImplementation(Method m1, Method m2)
//拷贝方法列表(最后需要调用free释放)
Method *class_copyMethodList(Class cls, unsinged int *outCount)
//动态添加方法
BOOL class_addMethod(Class cls, SEL name, IMP imp, const char *types)
//动态替换方法
BOOL class_replaceMethod(Class cls, SEL name, IMP imp, const char *types)
//
SEL method_getName(Method m)
IMP method_getImplementation(Method m)
const char* method_getTypeEncoding(Method m)
unsigned int method_getNumberOfArguments(Method m)
char *method_copyReturnType(Method m)
char *method_copyArgumentType(Method m, unsigned int index)
-- 选择器相关
const char *sel_getName(SEL sel)
SEL sel_registerName(const char *str)
//用block作为方法的实现
IMP imp_implementationWithBlock(id block)
id imp_getBlock(IMP anImp)
BOOL imp_removeBlock(IMP anImp)
什么是runtime? 平时项目中有用过么?
- OC是一门动态性比较强的编程语言,允许很多操作推迟到程序运行时再进行
- OC的动态性就是由runtime来支撑和实现的,Runtime是一套C语言的API,封装了很多动态相关的函数
- 平时编写的OC代码,底层都是转成了Runtime API 进行调用
具体的应用:
- 利用关联对象(AssociatedObject)给分类添加属性
- 遍历类的所有成员变量(如 修改非公开的成员变量、字典转模型、自动归解档)
- 交互方法实现(如 交互系统的方法)
- 利用消息转发机制解决方法找不到的问题
网友评论