valueForKeyPath 奔溃
2020-09-22 本文已影响0人
BabyNeedCare
昨天产品反馈了一个奔溃事件, 因为是做职业教育的, 刚好奔溃在课程上, 因此这里做处理总结。
请问下面代码会奔溃吗?
NSArray *dataArrays =
@[
@{@"code": @"ab", @"name": @"apple"},
@{@"code": @"cd", @"name": @"flame"}
];
NSLog(@"----%@---%@",[dataArrays valueForKeyPath:@"name123455"], [[dataArrays valueForKeyPath:@"name123455"] class]);
打印结果
2020-09-22 11:29:04.134991+0800 mjfail[7041:137274] ----(
"<null>",
"<null>"
)---__NSArrayI
不会奔溃, 只是返回的是<null>类型的数组
那么假设是一个模型对象数组呢?
创建Person类, 只有2个属性, code, name
#import <Foundation/Foundation.h>
NS_ASSUME_NONNULL_BEGIN
@interface Person : NSObject
@property (copy, nonatomic) NSString *code;
@property (copy, nonatomic) NSString *name;
@end
NS_ASSUME_NONNULL_END
NSArray *dataArrays = @[
@{@"code": @"ab", @"name": @"apple"},
@{@"code": @"cd", @"name": @"flame"}
];
NSArray *data = [Person mj_objectArrayWithKeyValuesArray:dataArrays];
NSLog(@"%@", [data valueForKeyPath:@"点点滴滴"]);
这次会奔溃吗?
2020-09-22 11:31:12.927584+0800 mjfail[7073:139529] *** Terminating app due to uncaught exception 'NSUnknownKeyException', reason: '[<Person 0x60000330ad80> valueForUndefinedKey:]: this class is not key value coding-compliant for the key 点点滴滴.'
*** First throw call stack:
(
0 CoreFoundation 0x00007fff23c4f02e __exceptionPreprocess + 350
1 libobjc.A.dylib 0x00007fff50b97b20 objc_exception_throw + 48
2 CoreFoundation 0x00007fff23c4ebf9 -[NSException raise] + 9
3 Foundation 0x00007fff256f3f57 -[NSObject(NSKeyValueCoding) valueForUndefinedKey:] + 225
4 Foundation 0x00007fff256f2bca -[NSObject(NSKeyValueCoding) valueForKey:] + 317
5 Foundation 0x00007fff256f4abb -[NSArray(NSKeyValueCoding) valueForKey:] + 411
6 Foundation 0x00007fff256f4e6a -[NSArray(NSKeyValueCoding) valueForKeyPath:] + 416
7 mjfail 0x0000000100b2bc7b -[ViewController viewDidLoad] + 459
8 UIKitCore 0x00007fff471cdb25 -[UIViewController _sendViewDidLoadWithAppearanceProxyObjectTaggingEnabled] + 83
9 UIKitCore 0x00007fff471d2a7e -[UIViewController loadViewIfRequired] + 1084
10 UIKitCore 0x00007fff471d2e9b -[UIViewController view] + 27
11 UIKitCore 0x00007fff4788742d -[UIWindow addRootViewControllerViewIfPossible] + 150
12 UIKitCore 0x00007fff47886ae4 -[UIWindow _updateLayerOrderingAndSetLayerHidden:actionBlock:] + 232
13 UIKitCore 0x00007fff47887ba1 -[UIWindow _setHidden:forced:] + 362
14 UIKitCore 0x00007fff4789af4d -[UIWindow _mainQueue_makeKeyAndVisible] + 42
15 UIKitCore 0x00007fff47aa669d -[UIWindowScene _makeKeyAndVisibleIfNeeded] + 202
16 UIKitCore 0x00007fff46ddbe81 +[UIScene _sceneForFBSScene:create:withSession:connectionOptions:] + 1405
17 UIKitCore 0x00007fff4784bf1a -[UIApplication _connectUISceneFromFBSScene:transitionContext:] + 1018
18 UIKitCore 0x00007fff4784c25c -[UIApplication workspace:didCreateScene:withTransitionContext:completion:] + 304
19 UIKitCore 0x00007fff473b920d -[UIApplicationSceneClientAgent scene:didInitializeWithEvent:completion:] + 361
20 FrontBoardServices 0x00007fff36555225 -[FBSSceneImpl _callOutQueue_agent_didCreateWithTransitionContext:completion:] + 442
21 FrontBoardServices 0x00007fff3657b598 __86-[FBSWorkspaceScenesClient sceneID:createWithParameters:transitionContext:completion:]_block_invoke.154 + 102
22 FrontBoardServices 0x00007fff3655fd05 -[FBSWorkspace _calloutQueue_executeCalloutFromSource:withBlock:] + 220
23 FrontBoardServices 0x00007fff3657b229 __86-[FBSWorkspaceScenesClient sceneID:createWithParameters:transitionContext:completion:]_block_invoke + 355
24 libdispatch.dylib 0x0000000100e74d48 _dispatch_client_callout + 8
25 libdispatch.dylib 0x0000000100e77cb9 _dispatch_block_invoke_direct + 300
26 FrontBoardServices 0x00007fff365a143e __FBSSERIALQUEUE_IS_CALLING_OUT_TO_A_BLOCK__ + 30
27 FrontBoardServices 0x00007fff365a112c -[FBSSerialQueue _queue_performNextIfPossible] + 441
28 FrontBoardServices 0x00007fff365a163b -[FBSSerialQueue _performNextFromRunLoopSource] + 22
29 CoreFoundation 0x00007fff23bb2221 __CFRUNLOOP_IS_CALLING_OUT_TO_A_SOURCE0_PERFORM_FUNCTION__ + 17
30 CoreFoundation 0x00007fff23bb214c __CFRunLoopDoSource0 + 76
31 CoreFoundation 0x00007fff23bb197c __CFRunLoopDoSources0 + 268
32 CoreFoundation 0x00007fff23bac62f __CFRunLoopRun + 1263
33 CoreFoundation 0x00007fff23babe16 CFRunLoopRunSpecific + 438
34 GraphicsServices 0x00007fff38438bb0 GSEventRunModal + 65
35 UIKitCore 0x00007fff4784fb48 UIApplicationMain + 1621
36 mjfail 0x0000000100b2c0b4 main + 116
37 libdyld.dylib 0x00007fff51a1dc25 start + 1
)
libc++abi.dylib: terminating with uncaught exception of type NSException
奔溃很彻底。
专成对象后, 再使用valueForKeyPath, 找不到对应的属性, 就会奔溃。
可是项目中有很多地方使用到了valueForKeyPath, 在每个对应模型中加指定属性, 范围太大, 方法不可取。
那么, 我想到用运行时拦截方法。
-
创建NSArray的分类
-
在.m文件中实现交换方法
#import "NSArray+updateArrays.h"
#import <objc/runtime.h>
#import "MJExtension.h"
@implementation NSArray (updateArrays)
+ (void)load {
Class class = [self class];
SEL originalSelector = NSSelectorFromString(@"valueForKeyPath:");
SEL swizzledSelector = NSSelectorFromString(@"transiValueForKeyPath:");
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);
}
}
- (nullable id)transiValueForKeyPath:(NSString *)keyPath {
__block BOOL isHaveKeyPath = NO;
[self enumerateObjectsUsingBlock:^(NSObject * _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) {
if ([obj.mj_keyValues.allKeys containsObject:keyPath]){
isHaveKeyPath = YES;
*stop = YES;
}
}];
if (isHaveKeyPath) {
return [self transiValueForKeyPath:keyPath];
} else {
return nil;
}
}
@end
这样就可以完美解决同类型的问题了!