基本
- Objective-C是一门动态性比较强的编程语言,跟C、C++等语言有着很大的不同
- Objective-C的动态性是由Runtime API来支撑的
- Runtime API提供的接口基本都是C语言的,源码由C\C++\汇编语言编写
涉及基础
类族:最基础的类型需实际去探究。
arm64架构之前,isa就是一个普通的指针,存储着对象、Class、Meta-Class对象的内存指针。
-
arm64之后
isa
指针的变化:- 对isa进行了优化,变成了一个共用体(
union
)结构,还是用位域来存储更多的信息(33位存类信息)。如下图: - 下图
struct
内部的: 数字
表示所占位数(位域);(如下图,总共占64位,其中shiftcls : 33 从右到左占33位):所以Mask以后类地址和元类地址后三位一定是0,换算成16进制,则最后一位是0或8
位域详解
- 对isa进行了优化,变成了一个共用体(
位运算详解
- 左移运算符 <<
- x << n:x(二进制)向左移动n位(如:1 << 2,实际:Ob0001 << 2 = 0b0100 即十进制4)
- 右移运算符 >>
- x >> n:x(二进制)向右移动n位(如:8 >> 2,实际:Ob1000 >> 2 = 0b0010 即十进制2)
- 或运算符 |
- x | y:x(二进制)和y(二进制)只要对应的2个二进制位有一个为1时,则结果就为1(如:1 | 2,实际:0b0001 | 0b0010 = 0b0011 即十进制3)
- 与运算符 &
- x & y:x(二进制)和y(二进制)只有对应的2个二进制位都为1时,其结果才为1(如:15 & 2,实际:0b1111 & 0b0010 = 0b0010 即十进制2)
- 取反运算符 ~
- x:x(二进制)的所有二进制位都进行取反,0变1,1变0(如:2,实际:~0b0010 = 0b1101 即十进制13)
位运算对应isa指针的实际应用
例如isa位域的操作:
实例:
union Test { //共用体:开辟所有属性中占用最大字节的内存,每个属性使用共同的空间,值会覆写。
char bits; //(char内存中占一个字节,占8位)
struct { //相当于说明,占4位
char isMan : 1;
char isDead : 1;
char age : 2;
};
} test;
#define kIsMan (1 << 0)
//设置对应isMan的值,而其他位不变
- (void)setIsMan:(BOOL)isMan {
if (isMan) {
test.bits |= kIsMan;
}else {
test.bits &= (~kIsMan);
}
}
//取出对应isMan的值
- (BOOL)isMan {
return !!(test.bits & kIsMan); //保证取出的值为BOOL值
}
方法本质
-
类的底层结构
- 其中的
bits
如最上位域所示,用64个字节,存取了非常多的信息 - 其中
class_rw_t
里面的methods、properties、protocols是二维数组,是可读可写的,包含了类的初始内容、分类的内容,(如特别说明:methods是二维数组,存储了本类和和分类的方法数组,其方法数组里才是对应的method_t
); - 其中
class_ro_t
里面的baseMethodList、baseProtocols、ivars、baseProperties是一维数组,是只读的,包含了类的初始内容。
- 其中的
-
方法的底层结构
- 其中方法的
types
参数的编码表如下图所示:
- 其中方法的
-
cache_t cache
底层结构及原理说明:
- 原理说明:(注:
_mask
等于_buckets
的长度-1)- 存储时:会
@selector(方法名) & _mask
,就可得到一个小于_buckets
的长度的下标,如果此下标的里面为NULL
则存取,反之(此下标-1 或 等于最大下标继续-1 ,直到等于此下标时结束)直到找到NULL存。 - 读取是:同理
@selector(方法名) & _mask
,找到下标并判断里面内容的key是否等于@selector(方法名)
,有则返回,反之(此下标-1 或 等于最大下标继续-1 ,直到等于此下标时结束)如找到则返回,否则返回NULL。 - 注意点:1. 当初始空间不够时,则重新开辟(初始值*2)的空间并清空内部所有缓存。2. &出来的下标可能相同,所以需要判断内部的key是否等于自身。
- 存储时:会
- 原理说明:(注:
消息转发
参考文章
[[MessageSend new] sendMessage:@"Hello"];
//等同于
objc_msgSend([MessageSend new], @selector(sendMessage:), @"Hello");
步骤:
- 首先通过[Message new]对象的isa指针找打它对应的class。
- 在class的cache list 查找是否有sendMessage方法,有则返回,否则往下走。
- 在class的method list 查找是否有sendMessage方法。
- 如果没有就去它的superclass里继续查找(先cache、再method list)。
- 一旦查找到对应的函数方法,就去执行它的实现IMP。
- 如果一直没有找到,就执行消息转发。
- 消息转发机制:
-
动态方法解析:为指定方法(sel)添加方法实现(IMP)
+(BOOL)resolveInstanceMethod:(SEL)sel; //对象方法-动态解析 +(BOOL)resolveClassMethod:(SEL)sel; //类方法-动态解析
-
快速转发:为方法指定备援的接受者(即类对象)
- (id)forwardingTargetForSelector:(SEL)aSelector; //针对对象方法 + (id)forwardingTargetForSelector:(SEL)aSelector; //针对类方法
-
慢速转发:手动生成方法签名并转发给另一对象,分两步(方法签名+指定备胎对象)
//针对对象方法 - (NSMethodSignature*)methodSignatureForSelector:(SEL)aSelector; - (void)forwardInvocation:(NSInvocation *)anInvocation; //针对类方法 + (NSMethodSignature*)methodSignatureForSelector:(SEL)aSelector; + (void)forwardInvocation:(NSInvocation *)anInvocation;
-
消息未处理:避免崩溃
- (void)doesNotRecognizeSelector:(SEL)aSelector; //针对对象方法 + (void)doesNotRecognizeSelector:(SEL)aSelector; //针对类方法
-
注:越往下花费的代价越大。
重点:对象方法和类方法传递的对象不同,一个传递类对象,一个传递元类对象。!!!!
-
实例代码
//1. 动态方法解析 + (BOOL)resolveInstanceMethod:(SEL)sel { NSString *name = NSStringFromSelector(sel); if ([name isEqualToString:@"sendMessage:"]) { //C 的写法:则指定方法为C写法 return class_addMethod(self, sel, (IMP)testFundation, "v@:"); //OC的写法:则指定方法为OC写法 // return class_addMethod(self, sel, class_getMethodImplementation(self, @selector(testFundation)), "v@:"); } return [super resolveInstanceMethod:sel]; //默认用父 } //2. 快速转发 - (id)forwardingTargetForSelector:(SEL)aSelector { NSString *name = NSStringFromSelector(aSelector); if ([name isEqualToString:@"sendMessage:"]) { TEst *test = [[TEst alloc] init]; if ([test respondsToSelector:aSelector]) { //相应方法 return test; } } return [super forwardingTargetForSelector:aSelector]; } //3.1 慢速转发-方法签名 - (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector { NSString *name = NSStringFromSelector(aSelector); if ([name isEqualToString:@"sendMessage:"]) { return [NSMethodSignature signatureWithObjCTypes:"v@:@"]; } return [super methodSignatureForSelector:aSelector]; } //3.2 慢速转发(不实现方法签名,不会到这里来)你可以尽情的处理---用于避免崩溃或做其他操作。 - (void)forwardInvocation:(NSInvocation *)anInvocation { SEL sel = [anInvocation selector]; TEst *test = [[TEst alloc] init]; if ([test respondsToSelector:sel]) { //相应方法 // anInvocation.target = test; //这样申明不行,下边得行 [anInvocation invokeWithTarget:test]; }else { [super forwardInvocation:anInvocation]; } } //4. 消息未处理:避免崩溃 - (void)doesNotRecognizeSelector:(SEL)aSelector { if (DEBUG) { NSLog(@"我没找到任何方法"); } } //动态解析方法--采用OC的写法 - (void) testFundation { NSLog(@"我倒这里来了--OC"); } //动态解析方法--采用C的写法 void testFundation() { NSLog(@"我倒这里来了--C"); }
super本质
//objc_super2在最新官方源码中的结构体定义
struct objc_super2 {
id receiver; //接受者
Class current_class; //当前类,内部实现是会取它的superclass
};
//作用:从消息接受者的superclass类开始找对象方法或类方法
(void *)objc_msgSendSuper2)({
(id)self,
(id)class_getSuperclass(objc_getClass("Student"))
}, sel_registerName("class"))
//sel_registerName("class") == @selector(class)
//结构体-两个成员:{self 和 class_getSuperclass("Student") == 父类}
+ (Class)class {
return self;
}
- (Class)class {
return object_getClass(self);
}
+ (Class)superclass {
return self->superclass;
}
- (Class)superclass {
return [self class]->superclass;
}
//探究:super的本质
NSLog(@"[self class] %@\n",[self class]);
NSLog(@"[self superclass] %@\n",[self superclass]);
NSLog(@"[super class] %@\n",[super class]);
NSLog(@"[super superclass] %@\n",[super superclass]);
/**
//打印结果:
testApp[1650:33448] [self class] Student
testApp[1650:33448] [self superclass] Person
testApp[1650:33448] [super class] Student
testApp[1650:33448] [super superclass] Person
*/
//解释:接受者为self,方法为class,然class是NSObject的对象方法,而class内部代码为“object_getClass(self)”故在这种情况下:==[self class]
- 总结:
super
的本质是,调用objc_msgSendSuper()消息发送
,从消息接受者的superclass类开始找对象方法或类方法。
探究isMemberOfClass
andisKindOfClass
-
源码
//直接用self的元类对象和参数进行比较 + (BOOL)isMemberOfClass:(Class)cls { return self->ISA() == cls; } //直接用self的类对象和参数比较 - (BOOL)isMemberOfClass:(Class)cls { return [self class] == cls; } //用self及其父类的元类对象和参数进行比较 + (BOOL)isKindOfClass:(Class)cls { for (Class tcls = self->ISA(); tcls; tcls = tcls->superclass) { if (tcls == cls) return YES; } return NO; } //用self及其父类的类对象和参数比较 - (BOOL)isKindOfClass:(Class)cls { for (Class tcls = [self class]; tcls; tcls = tcls->superclass) { if (tcls == cls) return YES; } return NO; }
-
实际运用
//前提:Student 是 Person的子类 NSLog(@"-isKindOfClass %d \n",[self isKindOfClass:[Person class]]); //1 //Student是Person的子类 NSLog(@"-isMemberOfClass %d \n",[self isMemberOfClass:[Person class]]); //0 是直接[self class] 比较参数,故Student不匹配Person NSLog(@"+isKindOfClass %d \n",[[self class] isKindOfClass:[Person class]]); //0 因为类对象和元类对象不匹配 NSLog(@"+isMemberOfClass %d \n",[[self class] isMemberOfClass:[Person class]]); //0 因为类对象和元类对象不匹配 NSLog(@"+isKindOfClass %d \n",[NSObject isKindOfClass:[Person class]]); //0 因为类对象和元类对象不匹配 NSLog(@"+isMemberOfClass %d \n",[NSObject isMemberOfClass:[Person class]]); //0 因为类对象和元类对象不匹配 NSLog(@"+isKindOfClass %d \n",[NSObject isKindOfClass:[NSObject class]]); //1 特殊---因为按源码最终的superclass是类对象[NSObject class],故可以相等 NSLog(@"+isKindOfClass %d \n",[NSObject isKindOfClass:object_getClass([Person class])]); //0 因为类对象和元类对象不匹配 NSLog(@"+isMemberOfClass %d \n",[NSObject isMemberOfClass:object_getClass([Person class])]); //0 因为类对象和元类对象不匹配
Runtime部分常用方法介绍
- 类
-
Class objc_allocateClassPair(Class superclass, const char *name, size_t extraBytes)
:动态创建一个类(参数:父类,类名,额外的内存空间) -
void objc_registerClassPair(Class cls)
:注册一个类(要在类注册之前添加成员变量 -
void objc_disposeClassPair(Class cls)
:销毁一个类 -
Class object_getClass(id obj)
:获取isa指向的Class -
Class object_setClass(id obj, Class cls)
:设置isa指向的Class -
BOOL object_isClass(id obj)
:判断一个OC对象是否为Class -
BOOL class_isMetaClass(Class cls)
:判断一个Class是否为元类 -
Class class_getSuperclass(Class cls)
:获取父类
-
- 成员变量
-
Ivar class_getInstanceVariable(Class cls, const char *name)
:获取一个实例变量信息 -
Ivar *class_copyIvarList(Class cls, unsigned int *outCount)
:拷贝实例变量列表(最后需要调用free释放) -
void object_setIvar(id obj, Ivar ivar, id value)
:设置成员变量的值(基本类型需用(__bridge id)(void *)
进行转换,否则编译错误) -
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)
:获取成员变量的名字(需转成NSString才是真正的) -
const char *ivar_getTypeEncoding(Ivar v)
:获取成员变量的类型信息
-
- 属性
-
objc_property_t class_getProperty(Class cls, const char *name)
:获取一个属性 -
objc_property_t *class_copyPropertyList(Class cls, unsigned int *outCount)
:拷贝属性列表(最后需要调用free释放) -
BOOL class_addProperty(Class cls, const char *name, const objc_property_attribute_t *attributes, unsigned int attributeCount)
:动态添加属性 -
void class_replaceProperty(Class cls, const char *name, const objc_property_attribute_t *attributes, unsigned int attributeCount)
:动态替换属性 -
const char *property_getName(objc_property_t property)
:获取属性的名字(需转成NSString才是真正的) -
const char *property_getAttributes(objc_property_t property)
:获取属性的类型信息
-
- 方法
-
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_exchangeImplementations(Method m1, Method m2)
:交换两个方法的实现 -
Method *class_copyMethodList(Class cls, unsigned int *outCount)
:拷贝方法列表(最后需要调用free释放) -
BOOL class_addMethod(Class cls, SEL name, IMP imp, const char *types)
:添加方法的实现 -
IMP 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)
:获取一个方法的类型编码(需free释放) -
char *method_copyArgumentType(Method m, unsigned int index)
:获取一个方法某个位置参数的类型编码(需free释放) -
const char *sel_getName(SEL sel)
:获取方法的名字(需转成NSString才是真正的) -
SEL sel_registerName(const char *str)
:字符注册一个方法名 -
IMP imp_implementationWithBlock(id block)
:用闭包封装一个方法的实现(不太常用) -
id imp_getBlock(IMP anImp)
:获取一个实现的闭包(不常用) -
BOOL imp_removeBlock(IMP anImp)
:移除一个实现的闭包(不常用)
-
方法交换实际应用
//方法事件点击:---- UIControl分类
+ (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];
}
//拦击数组所有添加、插入的事件:---- NSMutableArray分类
+ (void)load {
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
// 类簇:NSString、NSArray、NSDictionary,真实类型是其他类型
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];
}
//拦击字典所有赋值、取值的事件:---- NSMutableDictionary分类
+ (void)load
{
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
//赋值NSMutableDictionary真正基类是__NSDictionaryM
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);
//获值时的真正基类是__NSDictionaryI
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)key {
if (!key) return;
[self mj_setObject:obj forKeyedSubscript:key];
}
- (id)mj_objectForKeyedSubscript:(id)key {
if (!key) return nil;
return [self mj_objectForKeyedSubscript:key];
}
面试题
1、讲一下OC的消息机制?
(详情见上)OC中的方法调用其实都是转成了objc_msgSend函数的调用,给receiver(方法调用者)发送了一条消息(selector方法名)
objc_msgSend底层有3大阶段
消息发送(当前类、父类中查找)、动态方法解析、消息转发
2、什么是Runtime?平时项目中有用过么?
解释:
OC是一个门动态性比较强的编程语言,允许很多操作推迟到程序运行时再进行,
OC的动态性就是由Runtime来支撑和实现的,Runtime是一套C语言的API,封装了很多动态性的函数,
平时编写的OC代码,底层都是转换成了Runtime API进行的。具体应用:
利用关联对象(AssociatedObject)给分类添加属性
变量类的所有成员变量(修复textfield的占位文字颜色、字典转模型、自动归档和解档)
交换方法的实现(交换系统的方法,如:重写reload方法实现空白占位图自动消失和出现)
利用消息转发机制解决方法找不到的异常问题。(防Crash处理)
....