OC是一门动态语言
动态语言是指程序可以在运行时可以改变其结构:添加新的函数、属性,删除已有的函数、属性等结构上的变化,在运行时做类型的检查。
编译时:源代码被编译成机器可以识别的代码的过程。
运行时:用户可以运行编译过的程序,程序运行的过程。
OC的动态性由runtime支持的。
runtime是一个C语言的库,提供API创建类、添加方法、删除方法、交换方法等。
isa指针(is a what?)
objc_object *id;
struct objc_object {
Class isa;
�}
objc_class *Class;
struct objc_class {
super_class,
name,
version,
info,
instance_size,
ivars,
methodLists,
cache,
protocols
}
isa指向流程
实例对象isa指向类对象
类对象isa指向元类
类对象superClass指向父类指向的类对象
所有元类isa指向NSObject对象的元类(根元类)
根元类isa指向自己
根元类的superClass指向NSObject的类对象
元类的superClass指向对应父类的元类
Runtime 消息发送机制:示例图
对象方法调用
IMG_0111.PNG类方法调用
IMG_0112.PNG1)iOS调用一个方法时,实际上会调用objc_msgSend(receiver, selector, arg1, arg2, ...),该方法第一个参数是消息接收者,第二个参数是方法名,剩下的参数是方法参数。
2)iOS调用一个方法时,会先去该类的方法缓存列表里面查找是否有该方法,如果有直接调用,否则走第3)步;
3)去该类的方法列表里面找,找到直接调用,把方法加入缓存列表;否则走第4)步;
4)沿着该类的继承链继续查找,找到直接调用,把方法加入缓存列表;否则消息转发流程;
Person 类:
@interface Person : NSObject
-(void)sayHello;
-(void)sayString:(NSString*)str;
@end
@implementation Person
- (instancetype)init{
if (self = [super init]) {
NSLog(@"%s",__func__);
}
[self copyContent];
return self;
}
-(void)sayHello{
NSLog(@"Hello");
}
-(void)sayString:(NSString*)str{
NSLog(@"sayString---%@",str);
}
@end
//Son类
@interface Son : Person
@end
@implementation Son
stancetype)init{
if (self = [super init]) {
NSLog(@"%s",__func__);
}
return self;
}
@end
//使用
Son *son = [[Son alloc] init];
[son sayString:@"Hello"];
objc_msgSend(son, sel_registerName("sayHello"));
objc_msgSend(son, @selector(sayHello));
void (*glt_msgsend)(id, SEL, NSString *) = (void (*)(id, SEL, NSString *))objc_msgSend;
glt_msgsend(son, @selector(sayString:), @"123");
//打印:
sayString---Hello
2022-05-13 12:19:48.643281+0800 BasicKnowledge[49582:1277604] Hello
2022-05-13 12:19:48.643383+0800 BasicKnowledge[49582:1277604] Hello
2022-05-13 12:19:48.643497+0800 BasicKnowledge[49582:1277604] sayString---123
//Son 没有这个方法时,向它的父类查找
Runtime消息转发机制
IMG_0113.PNG1.消息动态解析
+ (BOOL)resolveInstanceMethod:(SEL)selector;
+ (BOOL)resolveIClassMethod:(SEL)selector;
IMG_0114.PNG
2消息接受者重定向
- (id)forwardingTargetForSelector:(SEL)selector;
IMG_0115.PNG
3消息重定向
- (void)forwardInvocation:(NSInvocation *)anInvocation;
- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector;
IMG_0116.PNG
IMG_0117.PNG
4最后消息未能处理的时候,还会调用
- (void)doesNotRecognizeSelector:(SEL)aSelector抛出异常
1)动态消息解析。检查是否重写了resolveInstanceMethod 方法,如果返回YES则可以通过class_addMethod 动态添加方法来处理消息,否则走第2)步
2)消息target转发。forwardingTargetForSelector 用于指定哪个对象来响应消息。如果返回nil 则走第3)步;
3)消息转发。这步调用 methodSignatureForSelector 进行方法签名,这可以将函数的参数类型和返回值封装。如果返回 nil 执行第四步;否则返回 methodSignature,则进入 forwardInvocation ,在这里可以修改实现方法,修改响应对象等,如果方法调用成功,则结束。否则执行第4)步
4)报错 unrecognized selector sent to instance。
怎么在项目里全局解决"unrecognized selector sent to instance"这类crash?
可以在消息转发(forwardingTargetForSelector)里面处理,防止崩溃,具体处理办法是:
+ (void)defenderSwizzlingClassMethod:(SEL)originalSelector withMethod:(SEL)swizzledSelector withClass:(Class)targetClass {
swizzlingClassMethod(targetClass, originalSelector, swizzledSelector);
}
+ (void)defenderSwizzlingInstanceMethod:(SEL)originalSelector withMethod:(SEL)swizzledSelector withClass:(Class)targetClass {
swizzlingInstanceMethod(targetClass, originalSelector, swizzledSelector);
}
// 交换两个类方法的实现
void swizzlingClassMethod(Class class, SEL originalSelector, SEL swizzledSelector) {
Method originalMethod = class_getClassMethod(class, originalSelector);
Method swizzledMethod = class_getClassMethod(class, swizzledSelector);
BOOL didAddMethod = class_addMethod(class,
originalSelector,
method_getImplementation(swizzledMethod),
method_getTypeEncoding(swizzledMethod));
if (didAddMethod) {
class_replaceMethod(class,
swizzledSelector,
method_getImplementation(originalMethod),
method_getTypeEncoding(originalMethod));
} else {
method_exchangeImplementations(originalMethod, swizzledMethod);
}
}
// 交换两个对象方法的实现
void swizzlingInstanceMethod(Class class, SEL originalSelector, SEL swizzledSelector) {
Method originalMethod = class_getInstanceMethod(class, originalSelector);
Method swizzledMethod = class_getInstanceMethod(class, swizzledSelector);
BOOL didAddMethod = class_addMethod(class,
originalSelector,
method_getImplementation(swizzledMethod),
method_getTypeEncoding(swizzledMethod));
if (didAddMethod) {
class_replaceMethod(class,
swizzledSelector,
method_getImplementation(originalMethod),
method_getTypeEncoding(originalMethod));
} else {
method_exchangeImplementations(originalMethod, swizzledMethod);
}
}
//建立 NSObject catgory
+ (void)load {
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
// 拦截 `+forwardingTargetForSelector:` 方法,替换自定义实现
[NSObject defenderSwizzlingClassMethod:@selector(forwardingTargetForSelector:)
withMethod:@selector(defend_forwardingTargetForSelector:)
withClass:[NSObject class]];
// 拦截 `-forwardingTargetForSelector:` 方法,替换自定义实现
[NSObject defenderSwizzlingInstanceMethod:@selector(forwardingTargetForSelector:)
withMethod:@selector(defend_forwardingTargetForSelector:)
withClass:[NSObject class]];
});
}
// 自定义实现 `+defend_forwardingTargetForSelector:` 方法
+ (id)defend_forwardingTargetForSelector:(SEL)aSelector {
SEL forwarding_sel = @selector(forwardingTargetForSelector:);
// 获取 NSObject 的消息转发方法
Method root_forwarding_method = class_getClassMethod([NSObject class], forwarding_sel);
// 获取 当前类 的消息转发方法
Method current_forwarding_method = class_getClassMethod([self class], forwarding_sel);
// 判断当前类本身是否实现第二步:消息接受者重定向
BOOL realize = method_getImplementation(current_forwarding_method) != method_getImplementation(root_forwarding_method);
// 如果没有实现第二步:消息接受者重定向
if (!realize) {
// 判断有没有实现第三步:消息重定向
SEL methodSignature_sel = @selector(methodSignatureForSelector:);
Method root_methodSignature_method = class_getClassMethod([NSObject class], methodSignature_sel);
Method current_methodSignature_method = class_getClassMethod([self class], methodSignature_sel);
realize = method_getImplementation(current_methodSignature_method) != method_getImplementation(root_methodSignature_method);
// 如果没有实现第三步:消息重定向
if (!realize) {
// 创建一个新类
NSString *errClassName = NSStringFromClass([self class]);
NSString *errSel = NSStringFromSelector(aSelector);
NSLog(@"*** Crash Message: +[%@ %@]: unrecognized selector sent to class %p ***",errClassName, errSel, self);
NSString *className = @"CrachClass";
Class cls = NSClassFromString(className);
// 如果类不存在 动态创建一个类
if (!cls) {
Class superClsss = [NSObject class];
cls = objc_allocateClassPair(superClsss, className.UTF8String, 0);
// 注册类
objc_registerClassPair(cls);
}
// 如果类没有对应的方法,则动态添加一个
if (!class_getInstanceMethod(NSClassFromString(className), aSelector)) {
class_addMethod(cls, aSelector, (IMP)Crash, "@@:@");
}
// 把消息转发到当前动态生成类的实例对象上
return [[cls alloc] init];
}
}
return [self defend_forwardingTargetForSelector:aSelector];
}
// 自定义实现 `-defend_forwardingTargetForSelector:` 方法
- (id)defend_forwardingTargetForSelector:(SEL)aSelector {
SEL forwarding_sel = @selector(forwardingTargetForSelector:);
// 获取 NSObject 的消息转发方法
Method root_forwarding_method = class_getInstanceMethod([NSObject class], forwarding_sel);
// 获取 当前类 的消息转发方法
Method current_forwarding_method = class_getInstanceMethod([self class], forwarding_sel);
// 判断当前类本身是否实现第二步:消息接受者重定向
BOOL realize = method_getImplementation(current_forwarding_method) != method_getImplementation(root_forwarding_method);
// 如果没有实现第二步:消息接受者重定向
if (!realize) {
// 判断有没有实现第三步:消息重定向
SEL methodSignature_sel = @selector(methodSignatureForSelector:);
Method root_methodSignature_method = class_getInstanceMethod([NSObject class], methodSignature_sel);
Method current_methodSignature_method = class_getInstanceMethod([self class], methodSignature_sel);
realize = method_getImplementation(current_methodSignature_method) != method_getImplementation(root_methodSignature_method);
// 如果没有实现第三步:消息重定向
if (!realize) {
// 创建一个新类
NSString *errClassName = NSStringFromClass([self class]);
NSString *errSel = NSStringFromSelector(aSelector);
NSLog(@"*** Crash Message: -[%@ %@]: unrecognized selector sent to instance %p ***",errClassName, errSel, self);
NSString *className = @"CrachClass";
Class cls = NSClassFromString(className);
// 如果类不存在 动态创建一个类
if (!cls) {
Class superClsss = [NSObject class];
cls = objc_allocateClassPair(superClsss, className.UTF8String, 0);
// 注册类
objc_registerClassPair(cls);
}
// 如果类没有对应的方法,则动态添加一个
if (!class_getInstanceMethod(NSClassFromString(className), aSelector)) {
class_addMethod(cls, aSelector, (IMP)Crash, "@@:@");
}
// 把消息转发到当前动态生成类的实例对象上
return [[cls alloc] init];
}
}
return [self defend_forwardingTargetForSelector:aSelector];
}
// 动态添加的方法实现
static int Crash(id slf, SEL selector) {
return 0;
}
Category
objc_category *Category;
struct objc_category {
category_name,
class_name,
instance_methods,
class_methods,
protocols
}
在程序运行时
实例方法整合到主类中
类方法整合到元类中
协议同时整合到主类和元类中
在类的+laod方法中可以调用category里声明的方法吗?
可以,因为附加Category到类的工作先于+load方法的执行
类和category的+load方法调用的顺序
先类,后category。而各个category的+load方法按照编译的顺序执行
关联对象存在哪?
所有的关联对象都由AssociationsManager管理,AssociationsManager里面由一个静态AssociationsHashMap来存储所有的关联对象的。
在category里可以添加属性吗?
category中只能添加方法,不能添加实例变量。类的内存大小是在编译时确定的,而category是在运行时被添加的,此时再添加实例变量会破坏内存结构。
在category中添加属性,通过关联对象实现setter、getter方法。
类和category的同名方法调用的顺序
category并不是完全替换掉主类的同名方法,只是类的方法列表中会出现名字一样的方法且category的方法会排在前面,多个category中的同名方法按编译的顺序排。runtime查找方法按照顺序,一旦找到就return。
遍历类的方法列表,列表里最后一个同名的方法,即是原方法。
category
运行时决议
有单独的.h和.m文件
可以为系统类添加分类
看不到源码的类可以添加分类
只能添加方法,不能添加实例变量
extension
编译时决议
以声明的方式存在,寄生于主类.m文件
不可以为系统类添加extension
没有.m源码的类不可以extension
可以添加方法,可添加实例变量,默认为@private
KVC
是一种可以通过key来访问类属性的机制。而不是通过调用Setter、Getter方法访问。
可以在运行时动态访问和修改对象的属性
// 赋值
[person1 setValue:@"jack" forKey:@"name"];
// 取值
NSString *name = [person1 valueForKey:@"name"];
forKeyPath 是对更“深层”的对象进行访问。如数组的某个元素,对象的某个属性。
[myModel setValue:@"beijing" forKeyPath:@"address.city"];
// 返回所有对象的name属性值
NSArray *names = [array valueForKeyPath:@"name"];
(区别)NSMutableDictionary中的setObject:forKey:与 setVale:forKey:方法有什么区别?问?
答:1.setObject:forKey:中的value是不能够为nil的 ,不然会报错
2.setVale:forKey:中的value是可以为nil的 ,当value为nil时,会自动调用removeObject:forKey方法。
3..setVale:forKey: 中的key参数类型只能是NSString类型的,setObject:forKey:中的key可以任何类型。
setValue:ForKey:(kvc 原理)
按照setKey、_setKey的顺序查找方法,找到了就传递参数,调用方法
如果没找到,则查看accessInstanceVariableDirectly方法的返回值,如果为NO(默认是YES)就不再继续往下执行,直接调用setValue:forUndefinedKey抛出NSUnknownKeyException异常
如果返回值为YES,则按照_key、_isKey、key、isKey的顺序查找成员变量,找到了就直接赋值
如果没找到,则调用setValue:forUndefinedKey抛出异常
valueForKey:
按照getKey、_getKey的顺序查找方法,找到了就直接调用方法
如果没找到,则查看accessInstanceVariableDirectly方法的返回值,如果为NO(默认是YES)就不再继续往下执行,直接调用value:forUndefinedKey抛出NSUnknownKeyException异常
如果返回值为YES,则按照_key、_isKey、key、isKey的顺序查找成员变量,找到了就直接取值
如果没找到,则调用value:forUndefinedKey抛出异常
KVO
KVO:key value observing,键值监听,可以监听对象某个属性值的变化
给对象添加监听
通过runtime动态创建一个子类,修改对象的isa指向子类
子类重写set方法,内部执行顺序
willChangeValueForKey
[super setKey]
didChangeValueForKey
在didChangeValueForKey中调用KVO的回调方法:observeValueForKeyPath:ofObject:change:context:
归档解档(NSCoding)
1.归档是指用某种格式来保存一个或多个对象,以便以后还原这些对象的过程。归档是将数据持久化的一种方式(所谓数据持久化,就是指在IOS开发过程中,将数据保存到本地,能够让程序的运行更加流畅)。
2.归档与解档是iOS中一种序列化与反序列化的方式。想要归档的数据对象,需要遵守NSCoding协议,并且该对象对应的类必须提供encodeWithCoder:和initWithCoder:方法。
3.归档就是将临时数据保存成本地文件。
4.归档的缺点:归档的形式来保存数据,只能一次性归档保存以及一次性解压。所以只能针对小量数据,而且对数据操作比较笨拙,即如果想改动数据的某一小部分,还是需要解压整个数据或者归档整个数据。
通过class_copyIvarList获得对象的属性列表
通过ivar_getName(ivar)获取到属性的C字符串名称
NSString *key = [NSString stringWithUTF8String:name];转成对应的OC名称
利用KVC进行归档 [corder encodeObject: [self valueForKey:key] forKey: key];
解档 id value = [coder decodeObjectForKey];
利用KVC进行赋值[self setValue:value ForKey:key];
XML归档
1.局限:数据类型只支持 NSString、NSDictionary、NSArayy、NSData、NSNumber(如果你想的话,可以将基本数据类型转换为NSNumber再进行归档)。
2.比较方便,设置好归档路径,一句话归档,一句话解档。
3.归档文件格式:一般保存.plist文件。
/**** NSString和NSMutableString XML归解档 ****/
NSString *str = @"hello world";
NSString *path = [[NSHomeDirectory() stringByAppendingPathComponent:@"Desktop"]stringByAppendingPathComponent:@"hello.txt"];
// atomically:这个参数意思是如果为YES,则保证文件的写入原子性。就是说会先创建一个临时文件,直到文件内容写入成功再导入到目标文件里.如果为NO,则直接写入目标文件里.
[str writeToFile:path atomically:YES encoding:NSUTF8StringEncoding error:nil];
// 这里会覆盖原来的内容
[@"hello world 2" writeToFile:path atomically:YES encoding:NSUTF8StringEncoding error:nil];
NSString *str2 = [NSString stringWithContentsOfFile:path encoding:NSUTF8StringEncoding error:nil];
/**** NSData和NSMutableData XML归解档 ****/
// 任何对象都可以转化为NSData
NSData *data = [@"hello world" dataUsingEncoding:NSUTF8StringEncoding];
NSString *path2 = [[NSHomeDirectory() stringByAppendingPathComponent:@"Desktop"]stringByAppendingPathComponent:@"data.txt"];
//归档
[data writeToFile:path2 atomically:YES];
//解档
[NSData dataWithContentsOfFile:path2];
/**** NSArray及NSMutableArray XML归解档 ****/
NSArray *array = @[@"test",@"test2"];
NSString *path3 = [[NSHomeDirectory() stringByAppendingPathComponent:@"Desktop"]stringByAppendingPathComponent:@"array.plist"];
// 归档
[array writeToFile:path3 atomically:YES];
// 解档
NSArray *array = [NSArray arrayWithContentsOfFile:path3];
/**** NSArray XML归解档 ****/
NSDictionary *dic = [NSDictionary dictionaryWithObjectsAndKeys:@"one",@"1",@"two",@"2", nil];
NSString *path4 = [[NSHomeDirectory() stringByAppendingPathComponent:@"Desktop"]stringByAppendingPathComponent:@"dic.plist"];
// 归档
[dic writeToFile:path4 atomically:YES];
// 解档
NSDictionary *ddic = [NSDictionary dictionaryWithContentsOfFile:path3];
网友评论