unrecognized selector sent to instance
person.h
git
@protocol PersonDelegate <NSObject>
- (void)didChangedName;
@end
@interface Person : NSObject
//该方法在.m未实现
- (void)eatFood;
@property (nonatomic, weak) id<PersonDelegate>delegate;
@end
复制代码
ViewController.m
github
#import "ViewController.h"
#import "Person.h"
@interface ViewController ()
@property (nonatomic, strong) NSMutableArray *mutableArray1;
@property (nonatomic, copy) NSMutableArray *mutableArray2;
@end
@implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
[self case4];
}
- (void)case1 {
/*
crash1. eatFood 声明未实现
解决方案:.m实现eatFood方法,或消息转发
*/
Person *person = [[Person alloc] init];
person.delegate = self;
[person eatFood];
}
- (void)case2 {
/*
crash2. 代理未实现
解决方案:
if (person.delegate && [person.delegate respondsToSelector:@selector(didChangedName)]) {
[person.delegate didChangedName];
}
*/
Person *person = [[Person alloc] init];
person.delegate = self;
[person.delegate didChangedName];
}
- (void)case3 {
NSMutableArray *array = [NSMutableArray arrayWithObjects:@"1",@"2",@"3", nil];
self.mutableArray1 = array; //strong修饰的 mutableArray1,此处是NSMutableArray,与array指针地址一致
self.mutableArray2 = array; //copy修饰的mutableArray2,此处是NSArray,是新对象,与array指针地址不一致
[self.mutableArray1 addObject:@"4"];
//下边这行代码致使: -[__NSArrayI addObject:]: unrecognized selector sent to instance 0x600002628ea0
//缘由:copy修饰的NSMutableArray,在51行会将array浅拷贝,self.mutableArray2变成NSArray, [NSArray addObject] crash
//解决方案:用strong修饰或重写setter方法
[self.mutableArray2 addObject:@"4"];
//附:
//[NSArray copy] 浅拷贝,生成NSArray
//[NSArray mutableCopy] 深拷贝, 生成NSMutableArray
//[NSMutableArray copy] 深拷贝,生成是NSArray
//[NSMutableArray mutableCopy] 深拷贝,生成NSMutableArray
}
- (void)case4 {
//低版本调用高版本API
if (@available(iOS 10.0, *)) {
[NSTimer scheduledTimerWithTimeInterval:1 repeats:true block:^(NSTimer * _Nonnull timer) {
}];
} else {
// Fallback on earlier versions
}
}
复制代码
找不到执行的方法(附:给nil对象发消息不会crash,但给非nil对象发未实现的消息会crash)数据库
#import "NSObject+SelectorCrash.h"
@implementation NSObject (SelectorCrash)
- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector {
if ([self respondsToSelector:aSelector]) {
return [self methodSignatureForSelector:aSelector];
}
return [NSMethodSignature signatureWithObjCTypes:"v@:"];
}
- (void)forwardInvocation:(NSInvocation *)anInvocation {
NSLog(@"在 %@ 类中, 调用了没有实现的 %@ 实例方法", [self class], NSStringFromSelector(anInvocation.selector));
}
+ (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector {
if ([self respondsToSelector:aSelector]) {
return [self methodSignatureForSelector:aSelector];
}
return [NSMethodSignature signatureWithObjCTypes:"v@:"];
}
+ (void)forwardInvocation:(NSInvocation *)anInvocation {
NSLog(@"在 %@ 类中, 调用了没有实现的 %@ 类方法", [self class], NSStringFromSelector(anInvocation.selector));
}
@end
复制代码
delegate
,respondsToSelector
判断@interface KVCCrashVCObj : NSObject
@property (nonatomic, copy) NSString *nickname;
@end
@implementation KVCCrashVCObj
- (void)setValue:(id)value forUndefinedKey:(NSString *)key {
}
- (id)valueForUndefinedKey:(NSString *)key {
return nil;
}
@end
@interface KVCCrashVC ()
@end
@implementation KVCCrashVC
- (void)viewDidLoad {
[super viewDidLoad];
self.view.backgroundColor = [UIColor lightGrayColor];
//[self case1];
[self case2];
}
- (void)case1 {
/*
对象不支持kvc
reason: '[<KVCCrashVCObj 0x6000008ccd30> setValue:forUndefinedKey:]: this class is not key value coding-compliant for the key key.'
*/
KVCCrashVCObj *obj = [[KVCCrashVCObj alloc] init];
[obj setValue:@"value" forKey:@"key"];
}
- (void)case2 {
/*
key设置为nil
reason: '*** -[KVCCrashVCObj setValue:forKey:]: attempt to set a value for a nil key'
*/
KVCCrashVCObj *obj = [[KVCCrashVCObj alloc] init];
[obj setValue:nil forKey:@"nickname"];
[obj setValue:@"value" forKey:nil];
}
- (void)case3 {
/*
经过不存在的key取值
reason: '[<KVCCrashVCObj 0x6000019f3150> valueForUndefinedKey:]: this class is not key value coding-compliant for the key key.'
解决方案:
KVCCrashVCObj 重写 - (id)valueForUndefinedKey:(NSString *)key
*/
KVCCrashVCObj *obj = [[KVCCrashVCObj alloc] init];
NSString *nickname = [obj valueForKey:@"key"];
}
@end
复制代码
给不存在的key或nil设置value值, 经过不存在的key取值数组
[obj setValue: @"value" forKey: @"undefinedKey"];
[obj setValue: @"value" forKey: nil];
[obj valueForKey: @"undifinedKey"];
复制代码
[obj setValue:@"value" forKey:NSStringFromSelector(@selector(undifinedKey))];
, 将SEL反射为字符串作为key,这样在@selecor()中传入方法名的时候,编译器会作检查,若是方法不存在会报警告setValue:forUndefinedKey:
和valueForUndefinedKey:
KVOCrashObj* obj = [[KVOCrashObj alloc] init];
/*
1. 观察者:obj
2. 被观察者:self
3. 观察的对象:view
4. context:可选的参数,会随着观察消息传递,
用于区分接收该消息的观察者。通常状况下,只需经过 keyPath 就能够判断接收消息的观察者。可是当父类子类都观察了同一个 keyPath 时,仅靠 keyPath 就没法判断消息该传给子类,仍是传给父类。
5. 须要在观察者类KVOCrashObj里实现 observeValueForKeyPath: ofObject: change: context:,才能接受到被观察者self的view变化
*/
[self addObserver:obj forKeyPath:@"view" options:NSKeyValueObservingOptionNew context:nil];
复制代码
iOS用什么方式实现对一个对象的KVO?(KVO的本质是什么?)安全
答. 当一个对象使用了KVO监听,iOS系统会修改这个对象的isa指针,改成指向一个全新的经过Runtime动态建立的子类,子类拥有本身的set方法实现,set方法实现内部会顺序调用willChangeValueForKey方法、原来的setter方法实现、didChangeValueForKey方法,而didChangeValueForKey方法内部又会调用监听器的observeValueForKeyPath:ofObject:change:context:监听方法。性能优化
#import "KVOCrashVC.h"
@interface KVOCrashObj : NSObject
@property (nonatomic, copy) NSString *nickname;
@end
@implementation KVOCrashObj
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSKeyValueChangeKey,id> *)change context:(void *)context {
NSLog(@"%@ 观察到了%@ 的 %@发生了改变",[self class],[object class],keyPath);
}
@end
@interface KVOCrashVC ()
@property (nonatomic, strong) KVOCrashObj *kvoObj;
@end
@implementation KVOCrashVC
- (void)viewDidLoad {
[super viewDidLoad];
self.view.backgroundColor = [UIColor lightTextColor];
self.kvoObj = [[KVOCrashObj alloc] init];
}
- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {
[self case2];
}
- (void)case1 {
/*
self观察obj的nickname值改变,在self vc没有实现observeValueForKeyPath:ofObject:changecontext:致使crash
reason: '<KVOCrashVC: 0x7f84dc617a20>: An -observeValueForKeyPath:ofObject:change:context: message was received but not handled.
Key path: nickname
Observed object: <KVOCrashObj: 0x60000268a120>
Change: {
kind = 1;
new = "";
}
Context: 0x0'
*/
KVOCrashObj* obj = [[KVOCrashObj alloc] init];
[self addObserver:obj forKeyPath:@"view" options:NSKeyValueObservingOptionNew context:nil];
[obj addObserver:self
forKeyPath:@"nickname"
options:NSKeyValueObservingOptionNew
context:nil];
obj.nickname = @"";
}
- (void)case2 {
/* 重复移除观察者致使崩溃
reason: 'Cannot remove an observer <KVOCrashVC 0x7f8199912f00> for the key path "nickname" from <KVOCrashObj 0x6000004f5780> because it is not registered as an observer.'
*/
[self.kvoObj addObserver:self forKeyPath:@"nickname" options:NSKeyValueObservingOptionNew context:nil];
self.kvoObj.nickname = @"objc.c";
[self.kvoObj removeObserver:self forKeyPath:@"nickname"];
[self.kvoObj removeObserver:self forKeyPath:@"nickname"];
}
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSKeyValueChangeKey,id> *)change context:(void *)context {
NSLog(@"%@ 观察到了%@ 的 %@发生了改变",[self class],[object class],keyPath);
}
@end
复制代码
observeValueForKeyPath: ofObject: change: context:
方法observeValueForKeyPath: ofObject: change: context:
方法addobserver
和removeObserver
必须成对出现#import "BadAccessCrashVC.h"
#import <objc/runtime.h>
@interface BadAccessCrashVC (AssociatedObject)
@property (nonatomic, strong) UIView *associatedView;
@end
@implementation BadAccessCrashVC (AssociatedObject)
- (void)setAssociatedView:(UIView *)associatedView {
/*
self: 关联对象的类
key: 要保证全局惟一,key与关联的对象是一一对应关系,必须全局惟一,一般用@selector(methodName)作为key
value: 要关联的对象
policy:关联策略
OBJC_ASSOCIATION_COPY: 至关于@property(atomic,copy)
OBJC_ASSOCIATION_COPY_NONATOMIC: 至关于@property(nonatomic, copy)
OBJC_ASSOCIATION_ASSIGN: 至关于@property(assign)
OBJC_ASSOCIATION_RETAIN: 至关于@property(atomic, strong)
OBJC_ASSOCIATION_RETAIN_NONATOMIC: 至关于@property(nonatomic, strong)
*/
objc_setAssociatedObject(self, @selector(associatedView), associatedView, OBJC_ASSOCIATION_ASSIGN);
//objc_setAssociatedObject(self, @selector(associatedView), associatedView, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}
- (UIView *)associatedView {
return objc_getAssociatedObject(self, _cmd);
}
@end
@interface BadAccessCrashVC ()
@property (nonatomic, copy) void(^block)(void);
@property (nonatomic, weak) UIView *weakView;
@property (nonatomic, unsafe_unretained) UIView *unsafeView;
@property (nonatomic, assign) UIView *assignView;
@end
@implementation BadAccessCrashVC
- (void)viewDidLoad {
self.view.backgroundColor = [UIColor orangeColor];
[super viewDidLoad];
}
- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {
[self case3];
}
- (void)case1 {
/*
悬挂指针:访问没有实现的Block
Thread 1: EXC_BAD_ACCESS (code=1, address=0x10)
*/
self.block();
}
- (void)case2 {
/*
悬挂指针:对象没有被初始化
Thread 1: EXC_BAD_ACCESS (code=1, address=0x0)
*/
UIView *view = [UIView alloc];
view.backgroundColor = [UIColor redColor];
[self.view addSubview:view];
}
- (void)case3 {
{
UIView *view = [[UIView alloc]init];
view.backgroundColor = [UIColor redColor];
self.weakView = view;
self.unsafeView = view;
self.assignView = view;
self.associatedView = view;
}
//addSubview:nil时不会crash
//如下崩溃都是Thread 1: EXC_BAD_ACCESS (code=EXC_I386_GPFLT)
//不会crash, arc下view释放后,weakView会置为nil,所以这行代码不会崩溃
[self.view addSubview:self.weakView];
//野指针场景一: unsafeunreatin修饰的对象释放后,不会自动置为nil,变成野指针,所以崩溃
[self.view addSubview:self.unsafeView];
//野指针场景二:应该使用strong/weak修饰的对象,却错误的使用了assign,释放后不会置为nil
[self.view addSubview:self.assignView];
//野指针场景三:给类添加关联变量时,相似场景二,应该使用OBJC_ASSOCIATION_RETAIN,却错误的使用了OBJC_ASSOCIATION_ASSIGN
[self.view addSubview:self.associatedView];
}
@end
复制代码
出现悬挂指针、访问未被初始化对象、访问野指针服务器
#import "CollectionCrashVC.h"
@interface CollectionCrashVC ()
@end
@implementation CollectionCrashVC
- (void)viewDidLoad {
[super viewDidLoad];
self.view.backgroundColor = [UIColor yellowColor];
}
- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {
[self case3];
}
- (void)case1 {
/*
数组越界
reason: '*** -[__NSArrayM objectAtIndex:]: index 3 beyond bounds [0 .. 2]
*/
NSMutableArray *array = [NSMutableArray arrayWithObjects:@1, @2, @3, nil];
NSNumber *number = [array objectAtIndex:3];
NSLog(@"number: %@", number);
}
- (void)case2 {
/*
向集合中插入nil元素
reason: '*** -[__NSArrayM insertObject:atIndex:]: object cannot be nil
*/
NSMutableArray *array = [NSMutableArray arrayWithObjects:@1, @2, @3, nil];
[array addObject:nil];
}
- (void)case3 {
NSMutableDictionary *dict = [NSMutableDictionary dictionary];
[dict setObject:@1 forKey:@"1"];
//不会崩溃,value为nil,只会移除对应的键值对
[dict setValue:nil forKey:@"1"];
//崩溃:reason: '*** -[__NSDictionaryM setObject:forKey:]: object cannot be nil (key: 1)'
[dict setObject:nil forKey:@"1"];
}
@end
复制代码
setObject:nil forKey:@"key"
setValue:forKey:
,这个方法向字典中添加nil时,不会崩溃,只会删除对应键值对dispatch_barrier_async
、dispatch_group
markdown
#import "ThreadCrashVC.h"
@interface ThreadCrashVC ()
@end
@implementation ThreadCrashVC
- (void)viewDidLoad {
[super viewDidLoad];
self.view.backgroundColor = [UIColor redColor];
}
- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {
[self case4];
}
- (void)case1 {
//dispatch_group_leave 比dispatch_group_enter多
//Thread 1: EXC_BAD_INSTRUCTION (code=EXC_I386_INVOP, subcode=0x0)
dispatch_group_t group = dispatch_group_create();
dispatch_group_leave(group);
}
- (void)case2 {
//子线程中刷新UI
//ain Thread Checker: UI API called on a background thread: -[UIViewController view]
dispatch_queue_t queue = dispatch_queue_create("com.objc.c", DISPATCH_QUEUE_CONCURRENT);
dispatch_async(queue , ^{
NSLog(@"thread: %@", [NSThread currentThread]);
self.view.backgroundColor = [UIColor yellowColor];
});
}
- (void)case3 {
//使用信号量后不会崩溃
{
dispatch_semaphore_t semaphore = dispatch_semaphore_create(1);
__block NSObject *obj = [[NSObject alloc] init];
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0), ^{
while (YES) {
NSLog(@"dispatch_async -- 1");
dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
obj = [[NSObject alloc] init];
dispatch_semaphore_signal(semaphore);
}
});
while (YES) {
NSLog(@"dispatch_sync -- 2");
dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
obj = [[NSObject alloc] init];
dispatch_semaphore_signal(semaphore);
}
}
//不使用信号量,多线程同时释放对象致使崩溃
{
__block NSObject *obj = [[NSObject alloc] init];
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0), ^{
while (YES) {
NSLog(@"dispatch_async -- 3");
obj = [[NSObject alloc] init];
}
});
while (YES) {
NSLog(@"dispatch_sync -- 4");
obj = [[NSObject alloc] init];
}
}
}
- (void)case4 {
dispatch_queue_t queue1 = dispatch_queue_create("com.objc.c1", DISPATCH_QUEUE_SERIAL);
dispatch_queue_t queue2 = dispatch_queue_create("com.objc.c2", DISPATCH_QUEUE_SERIAL);
NSMutableArray *array = [NSMutableArray array];
dispatch_async(queue1, ^{
NSLog(@"queue1: %@", [NSThread currentThread]);
while (YES) {
if (array.count < 10) {
[array addObject:@(array.count)];
} else {
[array removeAllObjects];
}
}
});
dispatch_async(queue2, ^{
NSLog(@"queue2: %@", [NSThread currentThread]);
while (YES) {
/*
数组地址已经改变
reason: '*** Collection <__NSArrayM: 0x6000020319b0> was mutated while being enumerated.'
*/
for (NSNumber *number in array) {
NSLog(@"queue2 forin array %@", number);
}
/*
reason: '*** Collection <__NSArrayM: 0x600002072d60> was mutated while being enumerated.'
*/
NSArray *array2 = array;
for (NSNumber *number in array2) {
NSLog(@"queue2 forin array2 %@", number);
}
/*
在[NSArray copy]的时候,copy方法内部调用`initWithArray:range:copyItem:`时
NSArray被另外一个线程清空,range不一致致使跑出异常
reason: '*** -[__NSArrayM getObjects:range:]: range {0, 2} extends beyond bounds for empty array'
复制过程当中数组内对象被其它线程释放,致使访问僵尸对象
Thread 4: EXC_BAD_ACCESS (code=1, address=0x754822c49fc0)
*/
NSArray *array3 = [array copy];
for (NSNumber *number in array3) {
NSLog(@"queue2 forin array3 %@", number);
}
/*
复制过程当中数组内对象被其它线程释放,致使访问僵尸对象
Thread 12: EXC_BAD_ACCESS (code=EXC_I386_GPFLT)
*/
NSArray *array4 = [array mutableCopy];
for (NSNumber *number in array4) {
NSLog(@"queue2 forin array4 %@", number);
}
}
});
}
@end
复制代码
多线程遇到数据同步时,须要加锁、信号量等进行同步操做,通常多线程发生的Crash,会收到SIGSEGV。代表试图访问未分配给本身的内存,或视图往没有读写权限的内存中写数据网络
主线程耗时操做,形成主线程被卡超过必定时长,App被系统终止,通常异常编码是0x8badf00d,一般是引用花太长时间没法启动、终止或响应系统事件多线程
主线程作UI刷新和事件响应,将耗时操做(网络请求、数据库读取)放到异步线程
#import "NSNullCrashVC.h"
@interface NSNullCrashVC ()
@end
@implementation NSNullCrashVC
- (void)viewDidLoad {
[super viewDidLoad];
self.view.backgroundColor = [UIColor blueColor];
}
- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {
[self case1];
}
- (void)case1 {
/*
reason: '-[NSNull integerValue]: unrecognized selector sent to instance 0x7fff8004b700'
*/
NSNull *null = [[NSNull alloc] init];
NSMutableDictionary *dict = [NSMutableDictionary dictionary];
[dict setValue:null forKey:@"key"];
NSInteger integer = [[dict valueForKey:@"key"] integerValue];
}
@end
复制代码
NULL
: 用于基本数据类型,如NSIntegernil
: 用于OC对象Nil
: 用于Class类型对象的赋值(类是元类的实例,也是对象)NSNull
: 用于OC对象的占位,通常会作为集合中的占位元素,给NSNull对象发消息会crash,后台给咱们返回的就是NSNull对象利用消息转发,参考:NullSafe, 当咱们给NSNull对象发消息,可能会崩溃(null是有内存的),给nil对象发消息,不会崩溃
#import <objc/runtime.h>
#import <Foundation/Foundation.h>
#ifndef NULLSAFE_ENABLED
#define NULLSAFE_ENABLED 1
#endif
#pragma clang diagnostic ignored "-Wgnu-conditional-omitted-operand"
@implementation NSNull (NullSafe)
#if NULLSAFE_ENABLED
- (NSMethodSignature *)methodSignatureForSelector:(SEL)selector
{
//look up method signature
NSMethodSignature *signature = [super methodSignatureForSelector:selector];
if (!signature)
{
for (Class someClass in @[
[NSMutableArray class],
[NSMutableDictionary class],
[NSMutableString class],
[NSNumber class],
[NSDate class],
[NSData class]
])
{
@try
{
if ([someClass instancesRespondToSelector:selector])
{
signature = [someClass instanceMethodSignatureForSelector:selector];
break;
}
}
@catch (__unused NSException *unused) {}
}
}
return signature;
}
- (void)forwardInvocation:(NSInvocation *)invocation
{
invocation.target = nil;
[invocation invoke];
}
#endif
@end
复制代码
当服务器close一个链接时,若client端接着发数据,根据TCP协议的规定,会收到一个RST响应,client再往这个服务器发送数据时,系统会发出一个SIGPIPE信号给进程,告诉进程这个链接已经断开,不要再写了。而根据信号的默认处理规则,SIGPIPE信号的默认执行动做是terminate(终止、退出),因此client会退出
长链接socket或重定向管道进入后台,没有变比致使崩溃的解决办法
即在程序崩溃前,捕获崩溃信息,用于上报分析
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
//OC层中被捕获的异常,经过注册NSUncaughtExceptionHandler捕获异常信息
NSSetUncaughtExceptionHandler(&uncaughtExceptionhandler);
// 内存访问错误,重复释放等错误就无能为力了,由于这种错误它抛出的是Signal,因此必需要专门作Signal处理
// OC中层不能转换的Mach异常,利用Unix标准的signal机制,注册SIGABRT, SIGBUS, SIGSEGV等信号发生时的处理函数。
signal(SIGSEGV, handleSignal);
return YES;
}
static void uncaughtExceptionhandler (NSException *exception) {
// 异常堆栈信息
NSArray *stackArray = [exception callStackSymbols];
// 出现异常的缘由
NSString *reason = [exception reason];
NSString *name = [exception name];
NSString *exceptionInfo = [NSString stringWithFormat:@"Exception name: %@\nException reason: %@\nException stack: %@", name, reason, stackArray];
NSLog(@"app delegate 异常信息: %@", exceptionInfo);
//存储到本地,下次启动的时候上传
NSMutableArray *temArray = [NSMutableArray arrayWithArray:stackArray];
[temArray insertObject:reason atIndex:0];
[exceptionInfo writeToFile:[NSString stringWithFormat:@"%@/DOcuments/error.log", NSHomeDirectory()] atomically:YES encoding:NSUTF8StringEncoding error:nil];
}
static void handleSignal(int sig) {
}
复制代码
参考