简介
- OC是 运行时机制 ,最主要的就是 消息机制
- OC函数属于 动态调用过程 ,在编译的时候并不能决定真正调用哪个函数,只有在真正运行的时候才会根据函数的名称找到对应的函数来调用
- 需导入头文件
#import
- 查看最终生成代码
clang -rewrite-objc main.m
- 解决消息机制方法提示步骤: 查找build setting -> 搜索msg用
内容五大区
- 栈:不需要手动管理内存,自动管理(存放发放的参数、局部变量的值)
- 堆,需要手动管理内存,自己去释放
- 静态区:全局变量和静态变量的存储是放在一块的,初始化的全局变量和静态变量在一块区域,未初始化的全局变量和未初始化的静态变量在相邻的另一块区域
- 常量区:常量字符串放这里
- 程序代码区:存放函数体的二进制代码
发送消息
- 方法调用的本质,就是让 对象发送消息。
// Person *p = [Person alloc];
Person * p = objc_msgSend(objc_getClass("Person"),sel_registerName("alloc"));
p = objc_msgSend([NSObject class], @selector(alloc));
// p = [p init];
p = objc_msgSend(p, sel_registerName("init"));
p = objc_msgSend(p, @selector(init));
// 调用eat
[p eat];
// 本质:让对象发送消息
objc_msgSend(p, @selector(eat));
调用类方法的方式:两种
第一种通过类名调用
[Person eat];
第二种通过类对象调用
[[Person class] eat];
用类名调用类方法,底层会自动把类名转换成类对象调用
本质:让类对象发送消息
objc_msgSend([Person class], @selector(eat));
- 消息机制原理:对象根据方法编号SEL去映射表查找对应的方法实现
![](file:///var/folders/0r/rg7wk5l93hx06wq9jn7y5slh0000gn/T/cn.wiz.wiznoteformac/WizNote/84162c9f-5769-4da2-bf63-eca9c06ab026/index_files/916bc807-3d97-4318-9f8b-6b502aeb5012.png)
方法调用流程
-
怎么去调用eat方法 ,对象方法:类对象的方法列表 类方法:元类中方法列表
通过isa去对应的类(该类中以及其父类的方法列表中)中查找
注册方法编号
根据方法编号去查找对应方法
找到的只是最终函数实现地址,根据地址去方法区调用对应函数
什么时候会报 unrecognized selector
的异常?
- 当调用该对象上某个方法,而该对象上没有实现这个方法的时候, 可以通过 消息转发 进行解决,如果还是不行就会报
unrecognized selector
异常 - 在最顶层的父类(一般也就NSObject)中依然找不到相应的方法时,程序在运行时会挂掉并抛出异常
unrecognized selector sent to XXX
,但是在这之前,objc的运行时会给出三次拯救程序崩溃的机会
三次拯救程序崩溃的机会
-
Method resolution
objc运行时会调用
+resolveInstanceMethod:
或者+resolveClassMethod:
,让你有机会提供一个函数实现。如果你添加了函数并返回 YES,那运行时系统就会重新启动一次消息发送的过程
如果 resolve 方法返回 NO ,运行时就会移到下一步,消息转发
-
Fast forwarding
如果目标对象实现了
-forwardingTargetForSelector:
,Runtime 这时就会调用这个方法,给你把这个消息转发给其他对象的机会只要这个方法返回的不是nil和self,整个消息发送的过程就会被重启,当然发送的对象会变成你返回的那个对象。
否则,就会继续
Normal Fowarding
。
这里叫Fast,只是为了区别下一步的转发机制。因为这一步不会创建任何新的对象,但
Normal forwarding
转发会创建一个NSInvocation
对象,相对Normal forwarding
转发更快点,所以这里叫Fast forwarding
-
Normal forwarding
这一步是Runtime最后一次给你挽救的机会。
首先它会发送
-methodSignatureForSelector:
消息获得函数的参数和返回值类型。如果
-methodSignatureForSelector:
返回nil,Runtime则会发出-doesNotRecognizeSelector:
消息,程序这时也就挂掉了。如果返回了一个函数签名,Runtime就会创建一个
NSInvocation
对象并发送-forwardInvocation:
消息给目标对象
objc中的类方法和实例方法有什么本质区别和联系
-
类方法:
类方法是属于类对象的
类方法只能通过类对象调用
类方法中的self是类对象
类方法可以调用其他的类方法
类方法中不能访问成员变量
类方法中不能直接调用对象方法
类方法是存储在元类对象的方法缓存中
-
实例方法:
实例方法是属于实例对象的
实例方法只能通过实例对象调用
实例方法中的self是实例对象
实例方法中可以访问成员变量
实例方法中直接调用实例方法
实例方法中可以调用类方法(通过类名)
实例方法是存放在类对象的方法缓存中
runtime如何通过selector找到对应的IMP地址?(分别考虑类方法和实例方法)
每一个类对象中都一个对象方法列表(对象方法缓存)
类方法列表是存放在类对象中isa指针指向的元类对象中(类方法缓存)
方法列表中每个方法结构体中记录着方法的名称,方法实现,以及参数类型,其实selector本质就是方法名称,通过这个方法名称就可以在方法列表中找到对应的方法实现.
当我们发送一个消息给一个NSObject对象时,这条消息会在对象的类对象方法列表里查找
当我们发送一个消息给一个类时,这条消息会在类的Meta Class对象的方法列表里查找
一个objc对象的isa的指针指向什么?有什么作用?
- 每一个对象内部都有一个isa指针,这个指针是指向它的真实类型,根据这个指针就能知道将来调用哪个类的方法
交换方法
开发使用场景,系统自带的方法功能不够,给系统自带的方法扩展一些功能,并且保持原有的功能
方式一:继承系统的类,重写方法.
方式二:使用runtime,交换方法.
-
应用举例
- 需求:给imageNamed方法提供功能,每次加载图片就判断下图片是否加载成功。
@implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
// 方法一:先搞个分类,定义一个能加载图片并且能打印的方法+ (instancetype)sh_imageNamed:(NSString *)name;
// 方法二:交换imageNamed和sh_imageNamed的实现,就能调用imageName,间接调用sh_imageNamed的实现。
UIImage *image = [UIImage imageNamed:@"123"];
}
@end
@implementation UIImage (Image)
// 把类加载进内存的时候调用,只会调用一次
+ (void)load
{
// self -> UIImage
// 获取imageNamed
// 获取哪个类的方法
// SEL:获取哪个方法
Method imageNamedMethod = class_getClassMethod(self, @selector(imageNamed:));
// 获取xmg_imageNamed
Method sh_imageNamedMethod = class_getClassMethod(self, @selector(sh_imageNamed:));
// 交互方法:runtime
method_exchangeImplementations(imageNamedMethod, sh_imageNamedMethod);
// 调用imageNamed => sh_imageNamedMethod
// 调用sh_imageNamedMethod => imageNamed
}
// 不能在分类中重写系统方法imageNamed,因为会把系统的功能给覆盖掉,而且分类中不能调用super.
// 1.加载图片
// 2.判断是否加载成功
+ (UIImage *)sh_imageNamed:(NSString *)name
{
// 这里调用sh_imageNamed,相当于调用imageName
UIImage *image = [UIImage sh_imageNamed:name];
if (image) {
NSLog(@"加载成功");
} else {
NSLog(@"加载失败");
}
return image;
}
@end
交换原理
- 交换之前
![](file:///var/folders/0r/rg7wk5l93hx06wq9jn7y5slh0000gn/T/cn.wiz.wiznoteformac/WizNote/84162c9f-5769-4da2-bf63-eca9c06ab026/index_files/7ac29f40-9972-4bee-9be8-bf4db5117943.png) - 交换之后
![](file:///var/folders/0r/rg7wk5l93hx06wq9jn7y5slh0000gn/T/cn.wiz.wiznoteformac/WizNote/84162c9f-5769-4da2-bf63-eca9c06ab026/index_files/d9b2f12f-993f-4682-973f-05d5b3df8ebd.png)
什么是 method swizzling
(俗称黑魔法)
简单说就是进行方法交换
在Objective-C中调用一个方法,其实是向一个对象发送消息,查找消息的唯一依据是selector的名字。利用Objective-C的动态特性,可以实现在运行时偷换selector对应的方法实现,达到给方法挂钩的目的
每个类都有一个方法列表,存放着方法的名字和方法实现的映射关系,selector的本质其实就是方法名,IMP有点类似函数指针,指向具体的Method实现,通过selector就可以找到对应的IMP
![](file:///var/folders/0r/rg7wk5l93hx06wq9jn7y5slh0000gn/T/cn.wiz.wiznoteformac/WizNote/84162c9f-5769-4da2-bf63-eca9c06ab026/index_files/f3b78e2c-ae96-47aa-a4bc-6bb8944eda05.jpg)
-
交换方法的几种实现方式
- 利用 `method_exchangeImplementations` 交换两个方法的实现 - 利用 `class_replaceMethod` 替换方法的实现 - 利用 `method_setImplementation` 来直接设置某个方法的IMP
![](file:///var/folders/0r/rg7wk5l93hx06wq9jn7y5slh0000gn/T/cn.wiz.wiznoteformac/WizNote/84162c9f-5769-4da2-bf63-eca9c06ab026/index_files/82c98e34-89f3-4b50-961d-748c0fde1202.jpg)
动态添加方法
- 开发使用场景:如果一个类方法非常多,加载类到内存的时候也比较耗费资源,需要给每个方法生成映射表,可以使用动态给某个类,添加方法解决。
隐式参数self,_cmd
经典面试题:有没有使用performSelector,其实主要想问你有没有动态添加过方法。
* Runtime(动态添加方法):OC都是懒加载机制,只要一个方法实现了,就会马上添加到方法列表中.
- 简单使用
@implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
Person *p = [[Person alloc] init];
// 默认person,没有实现run:方法,可以通过performSelector调用,但是会报错。
// 动态添加方法就不会报错
[p performSelector:@selector(run:) withObject:@10];
}
@end
@implementation Person
// 没有返回值,1个参数
// void,(id,SEL)
void aaa(id self, SEL _cmd, NSNumber *meter) {
NSLog(@"跑了%@", meter);
}
// 任何方法默认都有两个隐式参数,self,_cmd(当前方法的方法编号)
// 什么时候调用:只要一个对象调用了一个未实现的方法就会调用这个方法,进行处理
// 作用:动态添加方法,处理未实现
+ (BOOL)resolveInstanceMethod:(SEL)sel
{
// [NSStringFromSelector(sel) isEqualToString:@"eat"];
if (sel == NSSelectorFromString(@"run:")) {
// 动态添加run方法
// class: 给哪个类添加方法
// SEL: 添加哪个方法,即添加方法的方法编号
// IMP: 方法实现 => 函数 => 函数入口 => 函数名(添加方法的函数实现(函数地址))
// type: 方法类型,(返回值+参数类型) v:void @:对象->self :表示SEL->_cmd
class_addMethod(self, sel, (IMP)aaa, "v@:@");
return YES;
}
return [super resolveInstanceMethod:sel];
}
@end
给分类添加属性
- 开发场景, 给系统的类添加属性的时候,可以使用runtime动态添加属性方法
*本质:动态添加属性,就是让某个属性与对象产生关联
原理
- 原理:给一个类声明属性,其实本质就是给这个类添加关联,并不是直接把这个值的内存空间添加到类存空间。
- 用例:
@implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
// 给系统NSObject类动态添加属性name
NSObject *objc = [[NSObject alloc] init];
objc.name = @"sh";
}
@end
@interface NSObject (Property)
// @property分类:只会生成get,set方法声明,不会生成实现,也不会生成下划线成员属性
@property NSString *name;
@end
// 定义关联的key
static const char *key = "name";
@implementation NSObject (Property)
- (void)setName:(NSString *)name
{
// 让这个字符串与当前对象产生联系,Associated(联合的,关联的)
// 第一个参数:给哪个对象添加关联(属性)
// 第二个参数:关联的key,通过这个key获取,属性名称
// 第三个参数:关联的value,属性值
// 第四个参数:关联的策略
objc_setAssociatedObject(self, key, name, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}
- (NSString *)name
{ // 根据关联的key,获取关联的值。
return objc_getAssociatedObject(self, key);
}
@end
怎么添加属性、方法等?
ivar表示成员变量
class_addIvar
class_addMethod
class_addProperty
class_addProtocol
class_replaceProperty
增加实例变量?
-
能否向编译后得到的类中增加实例变量?能否向运行时创建的类中添加实例变量?为什么?
不能向编译后得到的类中增加实例变量;
能向运行时创建的类中添加实例变量;
分析如下:
因为编译后的类已经注册在runtime中,类结构体中的 objc_ivar_list
实例变量的链表和 instance_size
实例变量的内存大小已经确定,同时runtime 会调用 class_setIvarLayout
或 class_setWeakIvarLayout
来处理 strong weak
引用,所以不能向存在的类中添加实例变量
运行时创建的类是可以添加实例变量,调用 class_addIvar
函数,但是得在调用 objc_allocateClassPair
之后,objc_registerClassPair
之前,原因同上。
关联的对象dealloc
-
使用runtime Associate方法关联的对象,需要在主对象dealloc的时候释放么?
无论在MRC下还是ARC下均不需要
被关联的对象在生命周期内要比对象本身释放的晚很多,它们会在被 NSObject -dealloc 调用的
object_dispose()
方法中释放
补充:对象的内存销毁时间表,分四个步骤
调用 -release :引用计数变为零,对象正在被销毁,生命周期即将结束.不能再有新的 __weak 弱引用,否则将指向 nil.调用 [self dealloc]
父类调用 -dealloc,继承关系中最直接继承的父类再调用 -dealloc
如果是 MRC 代码 则会手动释放实例变量们(iVars)
继承关系中每一层的父类 都再调用 -dealloc
- NSObject 调 -dealloc, 只做一件事:调用 Objective-C runtime 中的 object_dispose() 方法
- 调用 object_dispose(),为 C++ 的实例变量们(iVars)调用 destructors为 ARC 状态下的 实例变量们(iVars) 调用 -release
解除所有使用 runtime Associate方法关联的对象, 解除所有 __weak 引用
调用 free()
字典转模型
根据字典自动打印属性Str
@implementation NSDictionary (Property)
// isKindOfClass:判断是否是当前类或者子类
// 生成属性代码 => 根据字典中所有key
- (void)createPropertyCode
{
NSMutableString *codes = [NSMutableString string];
// 遍历字典
[self enumerateKeysAndObjectsUsingBlock:^(id _Nonnull key, id _Nonnull value, BOOL * _Nonnull stop) {
NSString *code;
if ([value isKindOfClass:[NSString class]]) {
code = [NSString stringWithFormat:@"@property (nonatomic, strong) NSString *%@;",key];
} else if ([value isKindOfClass:NSClassFromString(@"__NSCFBoolean")]) {
code = [NSString stringWithFormat:@"@property (nonatomic, assign) BOOL %@;",key];
} else if ([value isKindOfClass:[NSNumber class]]) {
code = [NSString stringWithFormat:@"@property (nonatomic, assign) NSInteger %@;",key];
} else if ([value isKindOfClass:[NSArray class]]) {
code = [NSString stringWithFormat:@"@property (nonatomic, strong) NSArray *%@;",key];
} else if ([value isKindOfClass:[NSDictionary class]]) {
code = [NSString stringWithFormat:@"@property (nonatomic, strong) NSDictionary *%@;",key];
}
// @property (nonatomic, strong) NSString *source;
[codes appendFormat:@"\n%@\n",code];
}];
NSLog(@"%@",codes);
}
@end
KVC实现
- 原理,
setValuesForKeysWithDictionary
底层实现
// 1.遍历字典中所有key,去模型中查找有没有对应的属性
[dict enumerateKeysAndObjectsUsingBlock:^(id _Nonnull key, id _Nonnull value, BOOL * _Nonnull stop) {
// 2.去模型中查找有没有对应属性 KVC
// key:source value:来自sh
// [item setValue:@"来自sh" forKey:@"source"]
[item setValue:value forKey:key];
/*
[item setValue:@"来自sh" forKey:@"source"]:内部
1.首先去模型中查找有没有setSource,找到,直接调用赋值 [self setSource:@"来自sh"]
2.去模型中查找有没有source属性,有,直接访问属性赋值 source = value
3.去模型中查找有没有_source属性,有,直接访问属性赋值 _source = value
4.找不到,就会直接报错 setValue:forUndefinedKey:报找不到的错误
*/
}];
@implementation Status
+ (instancetype)statusWithDict:(NSDictionary *)dict
{
Status *status = [[self alloc] init];
[status setValuesForKeysWithDictionary:dict];
return status;
}
@end
KVC字典转模型弊端:必须保证,模型中的属性和字典中的key一一对应。
如果不一致,就会调用
[
setValue:forUndefinedKey:]
报key
找不到的错。
分析:模型中的属性和字典的key不一一对应,系统就会调用
setValue:forUndefinedKey:
报错。解决:重写对象的
setValue:forUndefinedKey:
,把系统的方法覆盖,
就能继续使用KVC,字典转模型了。
- (void)setValue:(id)value forUndefinedKey:(NSString *)key
{
}
RunTime实现
思路:利用运行时,遍历模型中所有属性,根据模型的属性名,去字典中查找key,取出对应的值,给模型的属性赋值。
步骤:提供一个NSObject分类,专门字典转模型,以后所有模型都可以通过这个分类转。
- (void)viewDidLoad {
[super viewDidLoad];
//解析Plist文件
NSString *filePath = [[NSBundle mainBundle] pathForResource:@"status.plist" ofType:nil];
NSDictionary *dict = [NSDictionary dictionaryWithContentsOfFile:filePath];
// 设计模型,创建属性代码 => dict
// [dict[@"user"] createPropertyCode];
// 字典转模型:KVC,MJExtension
StatusItem *item = [StatusItem modelWithDict:dict];
}
/* 类似下面这种写法
class_copyIvarList:获取类中的所有成员属性
Ivar:成员属性的意思
第一个参数:表示获取哪个类中的成员属性
第二个参数:表示这个类有多少成员属性,传入一个Int变量地址,会自动给这个变量赋值
返回值Ivar *:指的是一个ivar数组,会把所有成员属性放在一个数组中,通过返回的数组就能全部获取到。
Ivar ivar;
Ivar ivar1;
Ivar ivar2;
// 定义一个ivar的数组a
Ivar a[] = {ivar,ivar1,ivar2};
// 用一个Ivar *指针指向数组第一个元素
Ivar *ivarList = a;
// 根据指针访问数组第一个元素
ivarList[0];
*/
+ (instancetype)modelWithDict:(NSDictionary *)dict
{
// 0.创建对应的对象
id objc = [[self alloc] init];
// runtime:根据模型中属性,去字典中取出对应的value给模型属性赋值
// 1.获取模型中所有成员变量 key
// 获取哪个类的成员变量
// count:成员变量个数
unsigned int count = 0;
// 获取成员变量数组
Ivar *ivarList = class_copyIvarList(self, &count);
// 遍历所有成员变量
for (int i = 0; i < count; i++) {
//根据角标,从数组取出对应的成员属性
Ivar ivar = ivarList[i];
//获取成员属性名
NSString *ivarName = [NSString stringWithUTF8String:ivar_getName(ivar)];
// 获取成员变量类型
NSString *ivarType = [NSString stringWithUTF8String:ivar_getTypeEncoding(ivar)];
// @\"User\" -> User
ivarType = [ivarType stringByReplacingOccurrencesOfString:@"\"" withString:@""];
ivarType = [ivarType stringByReplacingOccurrencesOfString:@"@" withString:@""];
// 获取key
// 处理成员属性名->字典中的key
// 从第一个角标开始截取
NSString *key = [ivarName substringFromIndex:1];
//根据成员属性名去字典中查找对应的value
// key:user value:NSDictionary
id value = dict[key];
// 二级转换:判断下value是否是字典(字典中还有字典),如果是,字典转换成对应的模型
// 并且是自定义对象才需要转换
if ([value isKindOfClass:[NSDictionary class]] && ![ivarType hasPrefix:@"NS"]) {
// 字典转换成模型 userDict => User模型
// 转换成哪个模型
//根据字符串类名生成类对象
Class modelClass = NSClassFromString(ivarType);
if (modelClass) { // 有对应的模型才需要转
// 把字典转模型
value = [modelClass modelWithDict:value];
}
}
// 三级转换:NSArray中也有字典,把数组中的字典转换成模型.
// 判断值是否是数组
if ([value isKindOfClass:[NSArray class]]) {
// 判断对应类有没有实现字典数组转模型数组的协议
if ([self respondsToSelector:@selector(arrayContainModelClass)]) {
// 转换成id类型,就能调用任何对象的方法
id idSelf = self;
// 获取数组中字典对应的模型
NSString *type = [idSelf arrayContainModelClass][key];
// 生成模型
Class classModel = NSClassFromString(type);
NSMutableArray *arrM = [NSMutableArray array];
// 遍历字典数组,生成模型数组
for (NSDictionary *dict in value) {
// 字典转模型
id model = [classModel modelWithDict:dict];
[arrM addObject:model];
}
// 把模型数组赋值给value
value = arrM;
}
}
// 给模型中属性赋值
if (value) {
[objc setValue:value forKey:key];
}
}
return objc;
}
其他用法
- runtime 如何实现 weak 属性?
-
首先要搞清楚weak属性的特点
- weak策略表明该属性定义了一种 非拥有关系 (nonowning relationship)。为这种属性设置新值时,设置方法既不保留新值,也不释放旧值。此特质同assign类似;
-
然而在属性所指的对象遭到摧毁时,属性值也会清空(nil out)
> 那么runtime如何实现weak变量的自动置nil?
runtime对注册的类,会进行布局,会将 weak 对象放入一个 hash 表中。 用 weak 指向的对象内存地址作为 key,当此对象的引用计数为0的时候会调用对象的 dealloc 方法,假设 weak 指向的对象内存地址是a,那么就会以a为key,在这个 weak hash表中搜索,找到所有以a为key的 weak 对象,从而设置为 nil。
-
weak属性需要在dealloc中置nil么?
- 在ARC环境无论是强指针还是弱指针都无需在 dealloc 设置为 nil , ARC 会自动帮我们处理,即便是编译器不帮我们做这些,weak也不需要在dealloc中置nil,在属性所指的对象遭到摧毁时,属性值也会清空
// 模拟下weak的setter方法,大致如下- (void)setObject:(NSObject *)object
{
objc_setAssociatedObject(self, "object", object, OBJC_ASSOCIATION_ASSIGN);
[object cyl_runAtDealloc:^{
_object = nil;
}];
}
-
一个Objective-C对象如何进行内存布局?(考虑有父类的情况)
所有父类的成员变量和自己的成员变量都会存放在该对象所对应的存储空间中
父类的方法和自己的方法都会缓存在类对象的方法缓存中,类方法是缓存在元类对象中
-
每一个对象内部都有一个isa指针,指向他的类对象,类对象中存放着本对象的如下信息
对象方法列表
成员变量的列表
属性列表
每个 Objective-C 对象都有相同的结构,如下图所示
Objective-C 对象的结构图
ISA指针
根类(NSObject)的实例变量
倒数第二层父类的实例变量
...
父类的实例变量
类的实例变量
- 根类对象就是NSObject,它的super class指针指向nil
- 类对象既然称为对象,那它也是一个实例。类对象中也有一个 isa 指针指向它的元类(meta class),即类对象是元类的实例。元类内部存放的是类方法列表,根元类的isa指针指向自己,`superclass` 指针指向 `NSObject` 类
[图片上传中。。。(6)]
- 下面的代码输出什么?
@implementation Son : NSObject
- (id)init
{
self = [super init];
if (self) {
NSLog(@"%@", NSStringFromClass([self class]));
NSLog(@"%@", NSStringFromClass([super class]));
}
return self;
}
@end
-
答案:都输出 Son
class 获取当前方法的调用者的类
superClass 获取当前方法的调用者的父类
super 仅仅是一个编译指示器,就是给编译器看的,不是一个指针
本质:只要编译器看到super这个标志,就会让当前对象去调用父类方法,本质还是当前对象在调用 -
这个题目主要是考察关于objc中对 self 和 super 的理解:
self 是类的隐藏参数,指向当前调用方法的这个类的实例。而 super 本质是一个编译器标示符,和 self 是指向的同一个消息接受者
当使用 self 调用方法时,会从当前类的方法列表中开始找,如果没有,就从父类中再找;
而当使用 super时,则从父类的方法列表中开始找。然后调用父类的这个方法
调用
[self class]
时,会转化成objc_msgSend
函数
id objc_msgSend(id self, SEL op, ...)
- 调用 `[super class]`时,会转化成 `objc_msgSendSuper` 函数
id objc_msgSendSuper(struct objc_super *super, SEL op, ...)
- 第一个参数是 objc_super 这样一个结构体,其定义如下
struct objc_super {
__unsafe_unretained id receiver;
__unsafe_unretained Class super_class;
};
- 第一个成员是 receiver, 类似于上面的 objc_msgSend函数第一个参数self
- 第二个成员是记录当前类的父类是什么,告诉程序从父类中开始找方法,找到方法后,最后内部是使用 objc_msgSend(objc_super->receiver, @selector(class))去调用, 此时已经和[self class]调用相同了,故上述输出结果仍然返回 Son
- objc Runtime开源代码对- (Class)class方法的实现
-(Class)class { return object_getClass(self);
}
[更多RunTime参考](
https://github.com/ChenYilong/iOSInterviewQuestions/blob/master/01《招聘一个靠谱的iOS》面试题参考答案/《招聘一个靠谱的iOS》面试题参考答案(下).md)##)
利用runTime归档与反归档
思考一个问题,现在一个类中有 100 个属性, 我现在的 应用程序中,需要用到 10个类,这十个类都需要归档。如何实现 编码 和 解码方法?
NSCoding
我们在 NSCoding 的协议方法里面做的操作如下:
编码方法 - (void)encodeWithCoder:(NSCoder *)aCoder
,里面做的操作
根据 属性类型
,对属性进行编码。
加上编码 标记
,(秉着 见名知意的原则 标记值与属性名一样)。
- (nullable instancetype)initWithCoder:(NSCoder *)aDecoder
解码方法里面做的操作
根据 属性类型
以及 编码时加的 标记
, 进行解码。
解码完成后,给 属性赋值(可以使用 kvc 赋值方法)。
因此如果我们:
获取到每个属性类型,就可以 根据类型对属性进行编码。
获取到每个属性名, 就可以在编码的时候添加 标记。
有了标记 和 属性类型,就可以正确的解码
objc/runtime 实现归档反归档
我们需要用到 动态运行时类库中的以下几种类型、函数 来获取 属性名,属性类型
类型
**实例变量 Ivar
**
表示类中的实例变量的一种类型。原类型为 struct objc_ivar *
///An opaque type that represents an instance variable.
typedef struct objc_ivar *Ivar;
struct objc_ivar {
char *ivar_name OBJC2_UNAVAILABLE;
char *ivar_type OBJC2_UNAVAILABLE;
int ivar_offset OBJC2_UNAVAILABLE;
#ifdef __LP64__
int space OBJC2_UNAVAILABLE;
#endif
}
**实例变量指针 Ivar *
**
表示 Ivar 类型指针,我们通常用来声明 Ivar 类型数组。比如:用来存储 类中所有实例变量的数组 varArray。
Ivar *varArray = class_copyIvarList([self class], &count);
函数
获取本类中所有实例变量函数class_copyIvarList
Ivar * class_copyIvarList(Class cls, unsigned int *outCount)
获取一个数组 Ivar *
, 该数组中含有本类中所有的实例变量,但并不会获取父类中声明的实例变量。 当不使用的时候 使用 free()
释放该数组。
**获取实例变量名 ivar_getName
**
const char * ivar_getTypeEncoding( Ivar ivar)
返回一个 实例变量的名称, 为 C 字符串。
**获取实例变量类型 ivar_getTypeEncoding
**
const char * ivar_getTypeEncoding( Ivar ivar)
获取实例变量的 编码类型, 为 C 字符串。
下面我们按照上面归档反归档的分析出来的思路,使用 runtime 中函数,写一个通用的 NSObject 归档反归档分类。
创建一个NSObject 的分类 NSObject+AGCoding
.h 文件
#import @interface NSObject (AGCoding) // 遵守编码协议@end
.m 文件
#import "NSObject+AGCoding.h"#import #import /* 成员变量类型 获取 实例变量的类型 方法 NSLog(@"%s", ivar_getTypeEncoding(array[i])); */static NSString *intType = @"i"; // intstatic NSString *integerType = @"q"; // longstatic NSString *floatType = @"f"; // floatstatic NSString *doubleType = @"d"; // doublestatic NSString *boolType = @"B"; // boolstatic NSString *imageType = @"UIImage"; // UIImage 类型static NSString *stringType = @"NSString"; // NSString 类型// 定义属性字典,用来存储 属性名(key) 类型(value)// 比如: age qstatic NSMutableDictionary *proDic = nil;
@implementation NSObject (AGCoding)// 归档是一个编码的过程- (void)encodeWithCoder:(NSCoder *)aCoder{ // (1). 给字典分配空间 proDic = [NSMutableDictionary dictionary]; // (2). 获取类中所有实例变量 unsigned int count; // 属性个数 // 定义Ivar, copy Ivar *varArray = class_copyIvarList([self class], &count); // (3). for循环,获取属性名称 属性类型 for (int i = 0; i < count; i++) { Ivar var = varArray[i]; // 1. 获取属性名称 : age name image const char *cName = ivar_getName(var); // 属性名c字符串 NSString *proName = [[NSString stringWithUTF8String:cName] substringFromIndex:1]; // OC字符串,并且去掉下划线 _ // 2. 获取属性类型 : NSInteger NSString UIImage 等类型 const char *cType = ivar_getTypeEncoding(var); // 获取变量类型, c 字符串 // 3. kvc 取值, 用于 (5) 中判断是否为 object 类型 id value = [self valueForKey:proName]; // 4. 把属性类型 从 c 转化为 oc 字符串 // c 字符串 转化为 oc 字符串,会加上 @"" // 属性类型 NSString *proType = [NSString stringWithUTF8String:cType]; // oc 字符串 // 5. 如果是OC类型数据,且不属于 NSNumber类,把 @"" 去掉 if ([value isKindOfClass:[NSObject class]] && ![value isKindOfClass:[NSNumber class]]) { // 截取前: @"@\"NSString\"" // 截取后: @\"NSString\" NSUInteger length = proType.length; proType = [proType substringWithRange:NSMakeRange(2, length - 3)]; } // (4). proDic字典赋值 : 属性名(key)_属性类型(value) if (![proName isEqualToString:@"proDic"]) { [proDic setValue:proType forKey:proName]; } // (5). 根据类型进行编码 if ([proType isEqualToString:integerType]) { // integer 类型 [aCoder encodeInteger:[value integerValue] forKey:proName]; } else if ([proType isEqualToString:imageType]) { // image 类型 [aCoder encodeDataObject:UIImagePNGRepresentation( value)]; } else if ([proType isEqualToString:stringType]) { // string 类型 [aCoder encodeObject:value forKey:proName]; } // 若再有类型添加即可 } // for 循环,获取结束 // 释放varArray free(varArray);}// 反归档,是一个解码的过程。- (instancetype)initWithCoder:(NSCoder *)aDecoder{ self = [self init]; if (self) { // 注意:解档时重新给 proDic赋值 [self setProDic]; // 解码,给 属性赋值 // 注意: 根据属性类型解码,根据属性名 kvc 赋值 // (1) 获取 字典中的属性名 for (NSString *proName in proDic) { // (2) 获取 属性类型 NSString *proType = proDic[proName]; // (3) 解码, kvc 赋值 if ([proType isEqualToString:integerType]) { // integer 类型 // 解码 NSInteger number = [aDecoder decodeIntegerForKey:proName]; // 赋值 [self setValue:@(number) forKey:proName]; } else if ([proType isEqualToString:imageType]) { // image 类型 UIImage *image = [UIImage imageWithData:[aDecoder decodeDataObject]]; [self setValue:image forKey:proName]; } else if ([proType isEqualToString:stringType]) { // string 类型 NSString *string = [aDecoder decodeObjectForKey:proName]; [self setValue:string forKey:proName]; } // 若再有类型添加即可 } // for 循环结束 } return self;}// 解码时重新给当前类的proDic赋值- (void)setProDic{ // (1). 给字典分配空间 proDic = [NSMutableDictionary dictionary]; // (2). 获取类中所有实例变量 unsigned int count; // 属性个数 // 定义Ivar, copy Ivar *varArray = class_copyIvarList([self class], &count); // (3). for循环,获取属性名称 属性类型 for (int i = 0; i < count; i++) { Ivar var = varArray[i]; // 1. 获取属性名称 : age name image const char *cName = ivar_getName(var); // 属性名c字符串 NSString *proName = [[NSString stringWithUTF8String:cName] substringFromIndex:1]; // OC字符串,并且去掉下划线 _ // 2. 获取属性类型 : NSInteger NSString UIImage 等类型 const char *cType = ivar_getTypeEncoding(var); // 获取变量类型, c 字符串 // 3. 把属性类型 从 c 转化为 oc 字符串 // c 字符串 转化为 oc 字符串,会加上 @"" // 属性类型 NSString *proType = [NSString stringWithUTF8String:cType]; // oc 字符串 // 获取 @ 的个数 NSArray *array = [proType componentsSeparatedByString:@"@"]; // 4. 如果属性字符串中,多了 @"" 把 @"" 去掉 if (array.count >= 2) { // 截取前: @"@\"NSString\"" // 截取后: @\"NSString\" NSUInteger length = proType.length; proType = [proType substringWithRange:NSMakeRange(2, length - 3)]; } // (4). proDic字典赋值 : 属性名(key)_属性类型(value) if (![proName isEqualToString:@"proDic"]) { [proDic setValue:proType forKey:proName]; } } // for 循环,获取结束// 释放varArray free(varArray);}@end