0%

TPreventKVC的使用和实现

TPreventKVC 项目的 GitHub 地址: https://github.com/tobedefined/TPreventKVC

实现方式

使用+load方法替换NSObject的 -setValue:forUndefinedKey: / valueForUndefinedKey: / setNilValueForKey: 这几个方法,防止产生 NSUnknownKeyExceptionNSInvalidArgumentException 异常,引发崩溃。

具体实现如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64

@implementation NSObject (PreventKVC)

#pragma mark - HandleKVCErrorBlock

+ (void)setHandleKVCErrorBlock:(HandleKVCErrorBlock)handleBlock {
objc_setAssociatedObject(self, @selector(handleKVCErrorBlock), handleBlock, OBJC_ASSOCIATION_RETAIN);
}

+ (HandleKVCErrorBlock)handleKVCErrorBlock {
return objc_getAssociatedObject(self, _cmd);
}

+ (void)load {
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
method_exchangeImplementations(class_getInstanceMethod(self, @selector(setValue:forUndefinedKey:)),
class_getInstanceMethod(self, @selector(__t_setValue:forUndefinedKey:)));
method_exchangeImplementations(class_getInstanceMethod(self, @selector(valueForUndefinedKey:)),
class_getInstanceMethod(self, @selector(__t_valueForUndefinedKey:)));
method_exchangeImplementations(class_getInstanceMethod(self, @selector(setNilValueForKey:)),
class_getInstanceMethod(self, @selector(__t_setNilValueForKey:)));
});
}

- (void)__t_setValue:(nullable id)value forUndefinedKey:(NSString *)key {
HandleKVCErrorBlock handleBlock = [NSObject handleKVCErrorBlock];
if (handleBlock != nil) {
NSArray <NSString *>*callStackSymbols = @[@"The system version is too low."];
if (@available(iOS 4.0, tvOS 9.0, macOS 10.6, watchOS 2.0, *)) {
callStackSymbols = [NSThread callStackSymbols];
}

handleBlock([self class], key, KVCErrorTypeSetValueForUndefinedKey, callStackSymbols);
}
}

- (nullable id)__t_valueForUndefinedKey:(NSString *)key {
HandleKVCErrorBlock handleBlock = [NSObject handleKVCErrorBlock];
if (handleBlock != nil) {
NSArray <NSString *>*callStackSymbols = @[@"The system version is too low."];
if (@available(iOS 4.0, tvOS 9.0, macOS 10.6, watchOS 2.0, *)) {
callStackSymbols = [NSThread callStackSymbols];
}

handleBlock([self class], key, KVCErrorTypeValueForUndefinedKey, callStackSymbols);
}
return nil;
}

- (void)__t_setNilValueForKey:(NSString *)key {
HandleKVCErrorBlock handleBlock = [NSObject handleKVCErrorBlock];
if (handleBlock != nil) {
NSArray <NSString *>*callStackSymbols = @[@"The system version is too low."];
if (@available(iOS 4.0, tvOS 9.0, macOS 10.6, watchOS 2.0, *)) {
callStackSymbols = [NSThread callStackSymbols];
}

handleBlock([self class], key, KVCErrorTypeSetNilValueForKey, callStackSymbols);
}
[self setValue:nil forKey:key];
}

@end

特点

使用runtime动态替换方法防止在使用KVC的方法时候产生 NSUnknownKeyException & NSInvalidArgumentException 错误引发崩溃。

  • -valueForKey:
  • setValue:forKey:
  • -setValue:forKeyPath:
  • -valueForKeyPath:等等

如何导入

源文件

TPreventKVC文件夹内部的所有文件拖入项目中即可

CocoaPods

CocoaPods是一个Cocoa项目管理器。你可以使用以下命令去安装CocoaPods:

1
$ gem install cocoapods

要使用CocoaPods将TPreventKVC集成到您的Xcode项目中,请在Podfile中加入:

1
pod 'TPreventKVC'

然后运行一下命令:

1
$ pod install

Carthage

Carthage是一个去中心化的依赖管理器,它构建并提供所使用的库的framework。

你可以使用 Homebrew并运行下面的命令安装Carthage

1
2
$ brew update
$ brew install carthage

要将TPreventKVC集成到使用Carthage的Xcode项目中,请在Cartfile中加入:

1
github "tobedefined/TPreventKVC"

运行carthage update构建framework,并将编译的对应平台的TPreventKVC.framework拖入Xcode项目中。

使用方法

简单使用

导入项目之后即可。

运行错误信息获取

在APP的 main.m文件的main()函数中 或者 在APP的didFinishLaunching方法中 加入以下代码可以获得缺失方法的具体信息:

1
2
3
4
[NSObject setHandleKVCErrorBlock:^(__unsafe_unretained Class cls, NSString *key, KVCErrorType errorType) {
// 在这里写你要做的事情
// 比如上传到服务器或者打印log等
}];
Some Definitions

NSObject+PreventKVC.h中有以下定义

1
2
3
4
5
6
7
typedef NS_ENUM(NSUInteger, KVCErrorType) {
KVCErrorTypeSetValueForUndefinedKey = 1,
KVCErrorTypeValueForUndefinedKey = 2,
KVCErrorTypeSetNilValueForKey = 3,
};

typedef void (^ __nullable HandleKVCErrorBlock)(Class cls, NSString *key, KVCErrorType errorType);
  • cls: Class类型;为产生错误的类或对象的Class,可使用NSStringFromClass(cls)返回类名字符串
  • key: NSString *类型;为产生错误的Key
  • errorType: KVCErrorType类型;为产生错误的类型
  • callStackSymbols: NSArray<NSString *> *类型;为调用栈信息