OC 的消息机制
OC 的消息机制: OC中的方法调用其实都是转成了objc_msgSend函数的调用,给receiver(方法调用者)发送了一条消息(selector方法名)
Objective-C是一门动态性比较强的编程语言,跟C、C++等语言有着很大的不同
Runtime API提供的接口基本都是C语言的,源码由C\C++\汇编语言编写
isa详解
共用体(union)
定义:所有参数占用同一块内存
struct {
int year
int month
int day
}
// 其中year 、 month、day分别占用自己的内存,每个占用4个字节
union {
int year
int month
int day
}
// 其中year 、 month、day是同一块内存,一共占用4个字节
定义一个共用体,并使用
union {
char bits; // 一个字节
// 结构体仅仅是增加了可读性,完全可以删掉,结构体也只占一位,位置完全有Mask决定
struct {
char tall : 1; // 只占一位,在最右边
char rich : 1; // 只占一位
char handsome : 1; // 只占一位
char thin : 1; // 只占一位, 在最左边
// 0000 thin / handsome / rich / tall
};
} _tallRichHandsome;
// bits 和 结构体,都是一个字节,使用同样的一块一字节内容,相互影响,但是因为只是用bits,所以struct只是为了可读性,没有作用
// `1 << 0` 表示`1左移0位` 即 `0b 0000 0001`
// `mask`表示掩码,执行`按位与`操作
#define MJTallMask (1<<0)
#define MJRichMask (1<<1)
#define MJHandsomeMask (1<<2)
#define MJThinMask (1<<3)
// .h
#import <Foundation/Foundation.h>
@interface MJPerson : NSObject
- (void)setTall:(BOOL)tall;
- (void)setRich:(BOOL)rich;
- (void)setHandsome:(BOOL)handsome;
- (void)setThin:(BOOL)thin;
- (BOOL)isTall;
- (BOOL)isRich;
- (BOOL)isHandsome;
- (BOOL)isThin;
@end
// .m
#import "MJPerson.h"
// `mask`表示掩码
#define MJTallMask (1<<0)
#define MJRichMask (1<<1)
#define MJHandsomeMask (1<<2)
#define MJThinMask (1<<3)
@interface MJPerson()
{
union {
char bits;
struct {
char tall : 1;
char rich : 1;
char handsome : 1;
char thin : 1;
};
} _tallRichHandsome;
}
@end
@implementation MJPerson
- (void)setTall:(BOOL)tall
{
if (tall) {
_tallRichHandsome.bits |= MJTallMask;
} else {
// 按位取反
_tallRichHandsome.bits &= ~MJTallMask;
}
}
- (BOOL)isTall
{
// 两个"!",将类型强转为BOOL类型
return !!(_tallRichHandsome.bits & MJTallMask);
}
- (void)setRich:(BOOL)rich
{
if (rich) {
_tallRichHandsome.bits |= MJRichMask;
} else {
_tallRichHandsome.bits &= ~MJRichMask;
}
}
- (BOOL)isRich
{
// 两个"!",将类型强转为BOOL类型
return !!(_tallRichHandsome.bits & MJRichMask);
}
- (void)setHandsome:(BOOL)handsome
{
if (handsome) {
_tallRichHandsome.bits |= MJHandsomeMask;
} else {
_tallRichHandsome.bits &= ~MJHandsomeMask;
}
}
- (BOOL)isHandsome
{
// 两个"!",将类型强转为BOOL类型
return !!(_tallRichHandsome.bits & MJHandsomeMask);
}
- (void)setThin:(BOOL)thin
{
if (thin) {
_tallRichHandsome.bits |= MJThinMask;
} else {
_tallRichHandsome.bits &= ~MJThinMask;
}
}
- (BOOL)isThin
{
// 两个"!",将类型强转为BOOL类型
return !!(_tallRichHandsome.bits & MJThinMask);
}
@end
isa本质
在
arm64架构之前,isa就是一个普通的指针,存储着Class、Meta-Class对象的内存地址
从
arm64架构开始,对isa进行了优化,变成了一个共用体(union)结构,还使用位域(二进制位)来存储更多的信息
根据共用体知识,这里的内容全部存储在uintptr_t bits中,结构体只是为了更方便阅读,没有任何作用(可以删掉),共用体后面的数字代表占了多少位
// ARM64 simulators have a larger address space, so use the ARM64e
// scheme even when simulators build for ARM64-not-e.
# if __has_feature(ptrauth_calls) || TARGET_OS_SIMULATOR(模拟器)
# define ISA_MASK 0x007ffffffffffff8ULL
# else
# define ISA_MASK 0x0000000ffffffff8ULL
可以根据ISA_MASK,推断出要取出共用体中的33位,用来存储类对象、元类对象的地址
nonpointer :
0代表普通的指针,存储着Class、Meta-Class对象的内存地址1代表优化过,使用位域存储更多的信息
has_assoc(has_associatedObject)是否有设置过关联对象,如果没有,释放时会更快
has_cxx_dtor是否有C++的析构函数(.cxx_destruct),如果没有,释放时会更快
shiftcls存储着Class、Meta-Class对象的内存地址信息
magic用于在调试时分辨对象是否未完成初始化
weakly_referenced是否有被弱引用指向过,如果没有,释放时会更快
deallocating对象是否正在释放
extra_rc(extra_retainCount) 里面存储的值是引用计数器的数值减去1(如果引用计数器存储的是10,这里存的就是9)
has_sidetable_rc引用计数器是否过大无法存储在isa中 如果为1,那么引用计数会存储在一个叫SideTable的类的属性中
释放代码
inline void
objc_object::rootDealloc()
{
if (isTaggedPointer()) return; // fixme necessary?
// 可以看到是有判断的,没有释放更快
if (fastpath(isa.nonpointer &&
!isa.weakly_referenced &&
!isa.has_assoc &&
#if ISA_HAS_CXX_DTOR_BIT
!isa.has_cxx_dtor &&
#else
!isa.getClass(false)->hasCxxDtor() &&
#endif
!isa.has_sidetable_rc))
{
assert(!sidetable_present());
free(this);
}
else {
object_dispose((id)this);
}
}
Class的结构(类对象和元类对象的结构体)
成员变量和属性的定义
@interfacePerson :NSObject
{
//定义成员变量
int_age;
}
// 属性
@property(nonatomic,strong) NSString *name;
Class 的结构
class_rw_t的结构
struct class_rw_t {
// Be warned that Symbolication knows the layout of this structure.
uint32_t flags;
uint32_t version;
const class_ro_t *ro;
method_array_t methods;
property_array_t properties;
protocol_array_t protocols;
}
class_ro_t的结构
-
class_rw_t里面的methods、properties、protocols是二维数组,是可读可写的,包含了类的初始内容、分类的内容。class_ro_t的内容会被放到方法实现类数组methods的栈底,分类的方法会加载到栈顶的位置 -
class_ro_t里面的baseMethodList、baseProtocols、ivars、baseProperties是一维数组,是只读的,包含了类的初始内容
method_t是对方法\函数的封装
IMP代表函数的具体实现
SEL代表方法名(就是个名字),一般叫做选择器,底层结构跟char *类似
- 可以通过
@selector()和sel_registerName()获得- 可以通过
sel_getName()和NSStringFromSelector()转成字符串- 不同类中相同名字的方法,所对应的方法选择器是相同的
type包含了函数返回值、参数编码的字符串(iOS中提供了一个叫做@encode的指令,可以将具体的类型表示成字符串编码)
方法缓存
[person test]相当于给person发一个test消息,person去对应的类对象的method_array_t* methods去遍历查找方法,但是因为methods是一个二维数据,如果没找到还要通过superClass指针去父类里面找,遍历查找的效率还是比较底的
Class内部结构中有个方法缓存cache_t,用散列表(哈希表)<底层是个动态数组>来缓存曾经调用过的方法,可以提高方法的查找速度
-
散列表使用函数名(SEL)和mask_t _mask(散列表长度减一)按位与的方式生成key,如果key相同了,直接将key数值减一,存到散列表中,取出过程中是先去取数据,如果发现取出的数据跟当前的key不一致,也要减一,如果减到零,就变成_mask(散列表最大值),继续减一 -
达到快速查找方法的实现。以空间换取时间
-
一旦动态数组扩容,会清空里面的数据
-
父类的方法也会缓存到当前的类对象中
objc_msgSend执行流程
OC中的方法调用(消息机制),其实都是转换为objc_msgSend函数的调用, 就是给方法调用者(reciver)发送消息
objc_messageSend 三大阶段
1.消息发送
2.动态方法解析
3.消息转发
objc_msgSend执行流程 – 源码跟读
objc_msgSend执行流程01-消息发送
objc_msgSend执行流程02-动态方法解析
动态添加方法
+ (BOOL)resolveClassMethod:(SEL)sel {
if (sel == @selector(test)) {
// 在元类方法列表里面存放的类方法,如果找不到,会转向最后的父类查找对象方法
// Method method = class_getInstanceMethod(self, @selector(other));
Method method = class_getClassMethod(self, @selector(other));
class_addMethod(object_getClass(self),
sel,
method_getImplementation(method),
method_getTypeEncoding(method));
// 从源码可以看出, 这里返回YES或者NO都可以
return YES;
}
return [super resolveClassMethod:sel];
}
从源码可以看出,在调用完动态方法解析后,会调用goto函数,再次进行
消息发送,因为已经在类中添加了方法,所以可以在类对象(或元类对象中找到方法)
class_addMethod(object_getClass(self),
sel,
method_getImplementation(method),
method_getTypeEncoding(method));
objc_msgSend的执行流程03-消息转发
当前类无法处理消息,就将消息转发给其他类进行处理
- (id)forwardingTargetForSelector:(SEL)aSelector
{
if (aSelector == @selector(test)) {
// objc_msgSend([[MJCat alloc] init], aSelector)
return [[MJCat alloc] init];
}
return [super forwardingTargetForSelector:aSelector];
}
// 方法签名:返回值类型、参数类型
- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector
{
if (aSelector == @selector(test:name:)) {
return [NSMethodSignature signatureWithObjCTypes:"v@:"];
}
return [super methodSignatureForSelector:aSelector];
}
// NSInvocation封装了一个方法调用,包括:方法调用者、方法名、方法参数
// anInvocation.target 方法调用者
// anInvocation.selector 方法名
// [anInvocation getArgument:NULL atIndex:0]
- (void)forwardInvocation:(NSInvocation *)anInvocation
{
// anInvocation.target = [[MJCat alloc] init];
// [anInvocation invoke];
//[anInvocation invokeWithTarget:[[MJCat alloc] init]];
NSLog(@"%@",NSStringFromSelector(anInvocation.selector));
}
/// 有返回值的处理
- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector
{
if (aSelector == @selector(test:)) {
// return [NSMethodSignature signatureWithObjCTypes:"v20@0:8i16"];
return [NSMethodSignature signatureWithObjCTypes:"i@:i"];
// return [[[MJCat alloc] init] methodSignatureForSelector:aSelector];
}
return [super methodSignatureForSelector:aSelector];
}
- (void)forwardInvocation:(NSInvocation *)anInvocation
{
// 参数顺序:receiver、selector、other arguments
// int age;
// [anInvocation getArgument:&age atIndex:2];
// NSLog(@"%d", age + 10);
// anInvocation.target == [[MJCat alloc] init]
// anInvocation.selector == test:
// anInvocation的参数:15
// [[[MJCat alloc] init] test:15]
[anInvocation invokeWithTarget:[[MJCat alloc] init]];
int ret;
[anInvocation getReturnValue:&ret];
NSLog(@"%d", ret);
}
super的本质
#import "Student.h"
@implementation Student
- (instancetype)init
{
self = [super init];
if (self) {
NSLog(@"[self class] = %@", [self class]); // Student
NSLog(@"[self superclass] = %@", [self superclass]); // Person
NSLog(@"--------------------------------");
// objc_msgSendSuper({self, [MJPerson class]}, @selector(class));
NSLog(@"[super class] = %@", [super class]); // Student
NSLog(@"[super superclass] = %@", [super superclass]); // Person
}
return self;
}
@end
- 将
Student中的方法[super run]转换成CPP代码,来探究super的本质
// 转换前
- (void)run
{
[super run];
}
// 转换后
objc_msgSendSuper (
(__rw_objc_super){
self,
class_getSuperclass(objc_getClass("Student"))
}, @selector(run)
);
2.将转换后的代码进行抽取、查找得出如下代码
struct __rw_objc_super arg = {
self,
class_getSuperclass(objc_getClass("Student")) // 根据函数可以看出是Peron
}
objc_msgSendSuper(arg, @selector(run))
3.查找
objc_super的源码
struct objc_super {
__unsafe_unretained _Nonnull id receiver; // 消息接受者
__unsafe_unretained _Nonnull Class super_class; // 消息接受者的父类
};
#endif
4.根据上面的代码和源码可以得出
[super run ]转换后的代码
objc_msgSendSuper({
self,
person
},
@selector(run));
objc_msgSendSuper({self,person}, @selector(run));
5.查看
objc_msgSendSuper的源码,在message.h中,找到如下的注释
// 从父类的方法列表里开始查找方法实现
the superclass at which to start searching for the method implementation.
结论:super方法,只是从父类对象开始找出方法实现,但是receiver还是当前类
[super message]的底层实现
- 1.消息接收者仍然是子类对象
- 2.从父类开始查找方法的实现
回到开始的问题:[super class] 的打印值,就牵扯到class的方法实现,在NSObject.mm 中查看 class、superclass的源码
+ (Class)class {
return self;
}
- (Class)class {
return object_getClass(self);
}
+ (Class)superclass {
return self->getSuperclass();
}
- (Class)superclass {
return [self class]->getSuperclass();
}
结论:[super class] 打印出来的是recever,也就是student
isKindOfClass 和 isMemberOfClass
在NSObject.mm 中找到源码
// 判断cls对象,是否是self的元类
+ (BOOL)isMemberOfClass:(Class)cls {
return self->ISA() == cls;
}
// 判断cls是否是[self class]
- (BOOL)isMemberOfClass:(Class)cls {
return [self class] == cls;
}
// 判断cls对象,是否是self的元类、或者self的元类的父类
+ (BOOL)isKindOfClass:(Class)cls {
for (Class tcls = self->ISA(); tcls; tcls = tcls->getSuperclass()) {
if (tcls == cls) return YES;
}
return NO;
}
// 判断cls是否是[self class],或者[self class]的父类
- (BOOL)isKindOfClass:(Class)cls {
for (Class tcls = [self class]; tcls; tcls = tcls->getSuperclass()) {
if (tcls == cls) return YES;
}
return NO;
}
runtime基本函数
API
类
动态创建一个类(参数:父类,类名,额外的内存空间)
Class objc_allocateClassPair(Class superclass, const char *name, size_t extraBytes)
注册一个类(要在类注册之前添加成员变量),成员变量在class_ro_t结构体中是只读的,没法在创建完类后动态添加成员变量
void objc_registerClassPair(Class cls)
销毁一个类
void objc_disposeClassPair(Class cls)
获取isa指向的Class
Class object_getClass(id obj)
设置isa指向的Class
Class object_setClass(id obj, Class cls)
判断一个OC对象是否为Class
BOOL object_isClass(id obj)
判断一个Class是否为元类
BOOL class_isMetaClass(Class cls)
获取父类
Class class_getSuperclass(Class cls)
成员变量
Ivar *class_copyIvarList(Class cls, unsigned int *outCount) 常用,使用最后要调用free释放
属性
方法
- 查看类的属性、方法、成员变量、协议
- 字典转模型
// .h
@interface NSObject (Json)
+ (instancetype)mj_objectWithJson:(NSDictionary *)json;
@end
// .m
#import "NSObject+Json.h"
#import <objc/runtime.h>
@implementation NSObject (Json)
+ (instancetype)mj_objectWithJson:(NSDictionary *)json
{
id obj = [[self alloc] init];
unsigned int count;
Ivar *ivars = class_copyIvarList(self, &count);
for (int i = 0; i < count; i++) {
// 取出i位置的成员变量
Ivar ivar = ivars[i];
NSMutableString *name = [NSMutableString stringWithUTF8String:ivar_getName(ivar)];
[name deleteCharactersInRange:NSMakeRange(0, 1)];
// 设值
id value = json[name];
if ([name isEqualToString:@"ID"]) {
value = json[@"id"];
}
[obj setValue:value forKey:name];
}
free(ivars);
return obj;
}
@end
- 替换方法实现
#import "UIControl+Extension.h"
#import <objc/runtime.h>
@implementation UIControl (Extension)
// 交换按钮点击事件,会调用sendAction:to:forEvent:方法,交换方法实现
+ (void)load
{
// hook:钩子函数
Method method1 = class_getInstanceMethod(self, @selector(sendAction:to:forEvent:));
Method method2 = class_getInstanceMethod(self, @selector(mj_sendAction:to:forEvent:));
// 会清空缓存
method_exchangeImplementations(method1, method2);
}
- (void)mj_sendAction:(SEL)action to:(id)target forEvent:(UIEvent *)event
{
NSLog(@"%@-%@-%@", self, target, NSStringFromSelector(action));
// 调用系统原来的实现
[self mj_sendAction:action to:target forEvent:event];
// if ([self isKindOfClass:[UIButton class]]) {
// // 拦截了所有按钮的事件
//
// }
}
@end
查看 method_exchangeImplementations 的源码
void method_exchangeImplementations(Method m1, Method m2)
{
if (!m1 || !m2) return;
mutex_locker_t lock(runtimeLock);
IMP imp1 = m1->imp(false);
IMP imp2 = m2->imp(false);
SEL sel1 = m1->name();
SEL sel2 = m2->name();
// 交换两个method的imp
m1->setImp(imp2);
m2->setImp(imp1);
// 冲洗缓存,这个方法会清空缓存
flushCaches(nil, __func__, [sel1, sel2, imp1, imp2](Class c){
return c->cache.shouldFlush(sel1, imp1) || c->cache.shouldFlush(sel2, imp2);
});
adjustCustomFlagsForMethodChange(nil, m1);
adjustCustomFlagsForMethodChange(nil, m2);
}
通过源码可以看出method_exchangeImplementations本质是交换两个method中的imp
object_getClass: 本质是获取isa指向的class
object_setClass: 设置isa指向的class
Person *person = [[Person alloc] init];
[person run];
object_setClass(person, [Car class]);
[person run];
// 打印结果:可以发现对象的isa指针指向的类对象,被替换掉了
-[JXPerson run]
-[Car run]
交换方法实现
设置动态数组,添加nil防止崩溃
#import "NSMutableArray+Extension.h"
#import <objc/runtime.h>
@implementation NSMutableArray (Extension)
+ (void)load
{
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
// 类簇:NSString、NSArray、NSDictionary,真实类型是其他类型
//类簇是Foundation框架中广泛使用的设计模式。类簇将一些私有的、具体的子类组合在一个公共的、抽象的超类下面,以这种方法来组织类可以简化一个面向对象框架的公开架构,而又不减少功能的丰富性。
Class cls = NSClassFromString(@"__NSArrayM");
Method method1 = class_getInstanceMethod(cls, @selector(insertObject:atIndex:));
Method method2 = class_getInstanceMethod(cls, @selector(mj_insertObject:atIndex:));
method_exchangeImplementations(method1, method2);
});
}
- (void)mj_insertObject:(id)anObject atIndex:(NSUInteger)index
{
if (anObject == nil) return;
[self mj_insertObject:anObject atIndex:index];
}
@end
设置字典的key为nil的情况
#import "NSMutableDictionary+Extension.h"
#import <objc/runtime.h>
@implementation NSMutableDictionary (Extension)
+ (void)load
{
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
Class cls = NSClassFromString(@"__NSDictionaryM");
Method method1 = class_getInstanceMethod(cls, @selector(setObject:forKeyedSubscript:));
Method method2 = class_getInstanceMethod(cls, @selector(mj_setObject:forKeyedSubscript:));
method_exchangeImplementations(method1, method2);
Class cls2 = NSClassFromString(@"__NSDictionaryI");
Method method3 = class_getInstanceMethod(cls2, @selector(objectForKeyedSubscript:));
Method method4 = class_getInstanceMethod(cls2, @selector(mj_objectForKeyedSubscript:));
method_exchangeImplementations(method3, method4);
});
}
- (void)mj_setObject:(id)obj forKeyedSubscript:(id<NSCopying>)key
{
if (!key) return;
[self mj_setObject:obj forKeyedSubscript:key];
}
- (id)mj_objectForKeyedSubscript:(id)key
{
if (!key) return nil;
return [self mj_objectForKeyedSubscript:key];
}
@end