一、runtime
runtime 是由C、C++、汇编实现的一套API,为OC语言加入了面向对象,运行时的功能。
运行时(runtime)是指将数据类型的确定由编译时推迟到了运行时。如,extension-category 的区别
平时编写的OC代码,在程序运行过程中,其实最终会转换成Runtime的C语言代码,‘Runtime’是‘Objcect-c’的幕后工作者。
二、方法的本质,sel是什么?IMP是什么?两者之间的关系是什么?
方法的本质:发送消息,消息会有以下几个流程
1、快速查找(objc_msgSend)~cache_t 缓存消息
2、慢速查找~递归自己->父类 ~lookUpImpOrForward
3、查找不到消息:动态方法解析~resolveInstanceMethod
4、消息快速转发~forwardingTargetForSelector
5、消息慢速转发~methodSignatureForSelector & forwardInvocation
sel是方法编号~在read_images期间就编译进入了内存
imp就是我们函数实现指针,找imp就是找函数的过程
sel就相当于书本的目录 title
imp 就是书本的页码
查找具体的函数就是想看这本书里面具体篇章的内容
三、能否想编译后的得到的类中增加实例变量?能否向运行时创建的类中添加实例变量?
1:不能向编译后的得到的类中增加实例变量 2:只要类没有注册到内存还是可以添加
原因:我们编译好的实例变量存储的位置在ro,一旦编译完成,内存结构就完全确定了就无法修改
四、isKindOfClass 和 isMemberOfClass

五、[self class] 与 [super class]

有图可知[self class] 与 [super class] 这两个打印出来的结构都是一样,为什么没有将父类打印出来呢?依次分析
[[self class]]:对于这个的打印结果估计都不会意外,还是将寻找流程贴出来


[super class]:这时就可以通过汇编的方式Debug->Debug Workflow -> Always Show Disassemble ,来查看调用流程

然后去找一下 objc_msgSendSuper 的源码


仔细看一下注释就会发现 objc_super 主要有两个变量,然后就可以模拟一下这个环境

[[self class]] 与 [super class] 有什么区别呢?
[[self class]]走的是objc_msgsend,从自己去递归,消息接受者是 self,
[super class] 走的是objc_msgSendSuper,就会跳过自己的查找流程,直接去父类查找,消息接受者还是self。
在 LGSutdent 与父类中并没有去实现 class 方法,所以调用的 class方法其实调用的是 NSObject 里面class 方法

六、weak原理
weak 修饰弱引用,在释放时能够自动置空,用来打破循环引用。
接下来通过汇编找到weak源码分析weak的实现流程 Debug->Debug Workflow -> Always Show Disassemble










总结:Runtime 是如何实现weak的,为什么可以自动置 nil
1:通过Side Table 找到我们的 weak_table
2:weak_talbe 根据referent 找到或者创建 weak_entry_t
3:然后 append_referrer(entry,referrer)将我的新弱引用的对象加进去entry
4:最后weak_entry_insert 把entry加入到我们的weak_table
说明,weak并没有对内存进行管理,只是记录保存,weak进行释放的时候,拿到散列表weak_table,通过objc找到相应局里面entry数组,把它从整个数组中移调,什么时候释放呢,就是进行析构的时候。跟一下这个过程







七、Method_Swizzling 坑点
runtime运用--避免数组越界时崩溃
首先我们先创建一个数组,4个元素,取一个会越界的下标


NSArray+LG.h
+ (void)load;
NSArray+LG.m
#import "NSArray+LG.h"
#import "LGRuntimeTool.h"
#import <objc/runtime.h>
@implementation NSArray (LG)
+ (void)load{
//防止交换一次
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
[LGRuntimeTool lg_methodSwizzlingWithClass:objc_getClass("__NSArrayI") oriSEL:@selector(objectAtIndex:) swizzledSEL:@selector(lg_objectAtIndex:)];
[LGRuntimeTool lg_methodSwizzlingWithClass:objc_getClass("__NSArrayI") oriSEL:@selector(objectAtIndexedSubscript:) swizzledSEL:@selector(lg_objectAtIndexedSubscript:)];
});
}
// 交换的方法
- (id)lg_objectAtIndex:(NSUInteger)index{
if (index > self.count-1) {
NSLog(@"取值越界了:%lu > %lu",(unsigned long)index, self.count-1);
return nil;
}
return [self lg_objectAtIndex:index];
}
- (id)lg_objectAtIndexedSubscript:(NSUInteger)index {
if (index > self.count-1) {
NSLog(@"取值越界了:%lu > %lu",(unsigned long)index, self.count-1);
return nil;
}
return [self lg_objectAtIndexedSubscript:index];
}
LGRuntimeTool.h
交换方法
@param cls 交换对象
@param oriSEL 原始方法编号
@param swizzledSEL 交换的方法编号
*/
+ (void)lg_methodSwizzlingWithClass:(Class)cls oriSEL:(SEL)oriSEL swizzledSEL:(SEL)swizzledSEL;
LGRuntimeTool.m
#import "LGRuntimeTool.h"
#import <objc/runtime.h>
@implementation LGRuntimeTool
+ (void)lg_methodSwizzlingWithClass:(Class)cls oriSEL:(SEL)oriSEL swizzledSEL:(SEL)swizzledSEL{
if (!cls) NSLog(@"传入的交换类不能为空");
Method oriMethod = class_getInstanceMethod(cls, oriSEL);
Method swiMethod = class_getInstanceMethod(cls, swizzledSEL);
method_exchangeImplementations(oriMethod, swiMethod);
}
ViewController.m
#import "ViewController.h"
@interface ViewController ()
@property (nonatomic, strong) NSArray *dataArray;
@end
@implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
self.dataArray = @[@"AA",@"BB",@"CC",@"DD"];
[NSArray load];
NSLog(@"%@",self.dataArray[4]);
NSLog(@"%@",[self.dataArray objectAtIndex:4]);
}
打印
2020-04-11 13:25:13.124037+0800 005---Runtime应用[64014:5227823] 取值越界了:4 > 3
2020-04-11 13:25:13.124195+0800 005---Runtime应用[64014:5227823] (null)
2020-04-11 13:25:13.124037+0800 005---Runtime应用[64014:5227823] 取值越界了:4 > 3
2020-04-11 13:25:13.124195+0800 005---Runtime应用[64014:5227823] (null)
但是要是交换的方法可以多次执行会发生什么,验证一下就修改一下 NSArray+LG.m 里的 load 方法


然后还有一个坑点,通过子类调用一个子类没有父类有的方法,进行方法交换
ViewController.m
#import "ViewController.h"
#import "LGPerson.h"
#import "LGStudent.h"
#import "LGStudent+LG.h"
@interface ViewController ()
@end
@implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
// 黑魔法坑点二: 子类没有实现 - 父类实现
LGStudent *s = [[LGStudent alloc] init];
//此时调用的是 lg_studentInstanceMethod
[s personInstanceMethod];
}
@end
LGStudent.m/.h 里什么也没有
#import "LGStudent.h"
@implementation LGStudent
@end
LGStudent+LG.h
@interface LGStudent (LG)
@end
LGStudent+LG.m
#import "LGStudent+LG.h"
#import "LGRuntimeTool.h"
#import <objc/runtime.h>
@implementation LGStudent (LG)
+ (void)load{
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
[LGRuntimeTool lg_methodSwizzlingWithClass:self oriSEL:@selector(personInstanceMethod) swizzledSEL:@selector(lg_studentInstanceMethod)];
});
}
// personInstanceMethod 我需要父类的这个方法的一些东西
// 给你加一个personInstanceMethod 方法
// imp
- (void)lg_studentInstanceMethod{
//此时调用的是父类中的personInstanceMethod,因此做了方法交换
[self lg_studentInstanceMethod];
NSLog(@"LGStudent分类添加的lg对象方法:%s",__func__);
}
@end
LGPerson.h
#import <Foundation/Foundation.h>
NS_ASSUME_NONNULL_BEGIN
@interface LGPerson : NSObject
- (void)personInstanceMethod;
+ (void)personClassMethod;
@end
LGPerson.m
#import "LGPerson.h"
@implementation LGPerson
- (void)personInstanceMethod{
NSLog(@"person对象方法:%s",__func__);
}
+ (void)personClassMethod{
NSLog(@"person类方法:%s",__func__);
}
@end
LGRuntimeTool.h
@interface LGRuntimeTool : NSObject
/**
交换方法
@param cls 交换对象
@param oriSEL 原始方法编号
@param swizzledSEL 交换的方法编号
*/
+ (void)lg_methodSwizzlingWithClass:(Class)cls oriSEL:(SEL)oriSEL swizzledSEL:(SEL)swizzledSEL;
LGRuntimeTool.m
#import "LGRuntimeTool.h"
#import <objc/runtime.h>
@implementation LGRuntimeTool
+ (void)lg_methodSwizzlingWithClass:(Class)cls oriSEL:(SEL)oriSEL swizzledSEL:(SEL)swizzledSEL{
if (!cls) NSLog(@"传入的交换类不能为空");
Method oriMethod = class_getInstanceMethod(cls, oriSEL);
Method swiMethod = class_getInstanceMethod(cls, swizzledSEL);
method_exchangeImplementations(oriMethod, swiMethod);
}
}
@end
打印结果:

如果此时,我在用父类调用自己的方法
运行结果:

为了避免这个问题,此时我们就要在 LGRuntimeTool.m 类里做一下改动
解决办法:
1.会先尝试给自己添加要交换的方法 2.然后再将父类的IMP给Swizzle
LGRuntimeTool.m
#import "LGRuntimeTool.h"
#import <objc/runtime.h>
@implementation LGRuntimeTool
+ (void)lg_methodSwizzlingWithClass:(Class)cls oriSEL:(SEL)oriSEL swizzledSEL:(SEL)swizzledSEL{
if (!cls) NSLog(@"传入的交换类不能为空");
Method oriMethod = class_getInstanceMethod(cls, oriSEL);
Method swiMethod = class_getInstanceMethod(cls, swizzledSEL);
method_exchangeImplementations(oriMethod, swiMethod);
}
+ (void)lg_betterMethodSwizzlingWithClass:(Class)cls oriSEL:(SEL)oriSEL swizzledSEL:(SEL)swizzledSEL{
if (!cls) NSLog(@"传入的交换类不能为空");
// 一般交换方法: 交换自己有的方法 -- 走下面 因为自己有意味添加方法失败
// 交换自己没有实现的方法:
// 首先第一步:会先尝试给自己添加要交换的方法 :personInstanceMethod (SEL) -> swiMethod(IMP)
// 然后再将父类的IMP给swizzle personInstanceMethod(imp) -> swizzledSEL
//oriSEL:personInstanceMethod
Method oriMethod = class_getInstanceMethod(cls, oriSEL);
Method swiMethod = class_getInstanceMethod(cls, swizzledSEL);
// 尝试添加 向LGStudent 的 oriSEL(personInstanceMethod)添加 swiMethod(lg_studentInstanceMethod)方法
BOOL success = class_addMethod(cls, oriSEL, method_getImplementation(swiMethod), method_getTypeEncoding(oriMethod));
if (success) {// 自己没有 - 交换 - 没有父类进行处理 (重写一个)
//如果自己没有 personInstanceMethod ,并且添加 lg_studentInstanceMethod 实现成功时,
//就对 swizzledSEL(lg_studentInstanceMethod)替换为 oriMethod(personInstanceMethod)
class_replaceMethod(cls, swizzledSEL, method_getImplementation(oriMethod), method_getTypeEncoding(oriMethod));
}else{ // 自己有
method_exchangeImplementations(oriMethod, swiMethod);
}
}
@end
LGStudent+LG.m
+ (void)load{
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
[LGRuntimeTool lg_betterMethodSwizzlingWithClass:self oriSEL:@selector(personInstanceMethod) swizzledSEL:@selector(lg_studentInstanceMethod)];
});
}
运行结果:

要是子类LGStudent 调用了一个子类和父类都没有实现的方法呢要怎么避免崩溃呢
就相当于只在子类有个申明
代码: LGStudent.h
@interface LGStudent : LGPerson
- (void)helloword;
@end
LGStudent+LG.m
#import "LGStudent+LG.h"
#import "LGRuntimeTool.h"
#import <objc/runtime.h>
@implementation LGStudent (LG)
+ (void)load{
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
[LGRuntimeTool lg_betterMethodSwizzlingWithClass:self oriSEL:@selector(helloword) swizzledSEL:@selector(lg_studentInstanceMethod)];
});
}
- (void)lg_studentInstanceMethod{
NSLog(@"LGStudent分类添加的lg对象方法:%s",__func__);
}
ViewController.m
#import "ViewController.h"
#import "LGPerson.h"
#import "LGStudent.h"
#import "LGStudent+LG.h"
@interface ViewController ()
@end
@implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
LGStudent *s = [[LGStudent alloc] init];
[s helloword];
}
打印结果:出现了死循环

为了避免这个问题,这是我们在做替换的时候要做一个判断,判断要替换的方法是否有实现IMP,判断实现不存在时,就对没有实现的方法(SEL),添加一个实现(IMP)
实现:
在 LGRuntimeTool.m 中重新写一个方法
+ (void)lg_bestMethodSwizzlingWithClass:(Class)cls oriSEL:(SEL)oriSEL swizzledSEL:(SEL)swizzledSEL{
if (!cls) NSLog(@"传入的交换类不能为空");
Method oriMethod = class_getInstanceMethod(cls, oriSEL);
Method swiMethod = class_getInstanceMethod(cls, swizzledSEL);
if (!oriMethod) {
// 在oriMethod为nil时,替换后将swizzledSEL复制一个不做任何事的空实现,代码如下:
class_addMethod(cls, oriSEL, method_getImplementation(swiMethod), method_getTypeEncoding(swiMethod));
method_setImplementation(swiMethod, imp_implementationWithBlock(^(id self, SEL _cmd){ }));
}
// 一般交换方法: 交换自己有的方法 -- 走下面 因为自己有意味添加方法失败
// 交换自己没有实现的方法:
// 首先第一步:会先尝试给自己添加要交换的方法 :personInstanceMethod (SEL) -> swiMethod(IMP)
// 然后再将父类的IMP给swizzle personInstanceMethod(imp) -> swizzledSEL
//oriSEL:personInstanceMethod
BOOL didAddMethod = class_addMethod(cls, oriSEL, method_getImplementation(swiMethod), method_getTypeEncoding(swiMethod));
if (didAddMethod) {
class_replaceMethod(cls, swizzledSEL, method_getImplementation(oriMethod), method_getTypeEncoding(oriMethod));
}else{
method_exchangeImplementations(oriMethod, swiMethod);
}
}
运行结果:

八、内存偏移
先给出一段代码 LGPerson.h
@interface LGPerson : NSObject
- (void)saySomething;
@end
LGPerson.m
#import "LGPerson.h"
@implementation LGPerson
- (void)saySomething{
NSLog(@"NB %s - %@",__func__,self.age);
}
@end
ViewController.m
- (void)viewDidLoad {
[super viewDidLoad];
id pcls = [LGPerson class];
void *pp= &pcls;
[(__bridge id)pp saySomething];
// p -> LGPerson 实例对象
LGPerson *p = [LGPerson alloc];
[p saySomething];
NSLog(@"面试题");
}
这时会打印什么呢?

两个结果一模一样,这个要怎么解释呢,画一幅图

接下来再多打印一点其他,self.name
LGPerson.m
#import "LGPerson.h"
@implementation LGPerson
- (void)saySomething{
NSLog(@"NB %s - %@",__func__, self.name);
}
@end
打印结果:

很神奇为什么打印的结果是这样的呢?

栈的当前结构如图,当前pp指向ISA,当访问name时,类型为NSString,8字节,所以就会向下偏移,8个字节就到了VC。这就是传说中的野指针,才会打印出这个结果。