内容简介:@(iOS总结)[温故而知新]找不到方法iOS系统抛出异常崩溃使用命令:
@(iOS总结)[温故而知新]
1、找不到方法的实现 unrecognized selector sent to instance
1.1、场景对应的Code
#import "UnrecognizedSelectorVC.h" /** 代理协议 */ @protocol UnrecognizedSelectorVCDelegate <NSObject> @optional - (void)notImplementionFunc; @end /** 测试控制器的代理对象 */ @interface UnrecognizedSelectorVCObj : NSObject<UnrecognizedSelectorVCDelegate> @property (nonatomic, strong) NSString *name; @end @implementation UnrecognizedSelectorVCObj @end /** 测试控制器 */ @interface UnrecognizedSelectorVC () @property(nonatomic, weak) id<UnrecognizedSelectorVCDelegate> delegate; @property(nonatomic, copy) NSMutableArray *mutableArray; @end @implementation UnrecognizedSelectorVC - (void)viewDidLoad { [super viewDidLoad]; [self case1]; } /** 场景一:没有实现代理 */ - (void)case1 { UnrecognizedSelectorVCObj* obj = [[UnrecognizedSelectorVCObj alloc] init]; self.delegate = obj; // 崩溃:reason: '-[UnrecognizedSelectorVCObj notImplementionFunc]: unrecognized selector sent to instance 0x2808047f0' [self.delegate notImplementionFunc]; // 解决办法:应该使用下面的代码 if ( [self.delegate respondsToSelector:@selector(notImplementionFunc)] ) { [self.delegate notImplementionFunc]; } } /** 场景二:可变属性使用copy修饰 */ - (void)case2 { NSMutableArray* array = [NSMutableArray arrayWithObjects:@1, @2, @3, nil]; self.mutableArray = array; // 崩溃:reason: '-[__NSArrayI addObject:]: unrecognized selector sent to instance 0x281198a50' [self.mutableArray addObject:@4]; // 解决办法:使用strong修饰或者重写set方法 } /** 场景三:低版本系统使用高版本API */ - (void)case3 { if (@available(iOS 10.0, *)) { [NSTimer scheduledTimerWithTimeInterval:1 repeats:YES block:^(NSTimer * _Nonnull timer) { }]; } else { // Fallback on earlier versions } } @end 复制代码
1.2、原因
找不到方法iOS系统抛出异常崩溃
1.3、解决方案:
- 1、实现
消息转发
的几个方法 - 2、尽量避免使用
performSelector
一系列方法 - 3、
delegate
方法调用前进行respondsToSelector
判断 - 4、
属性
和成员变量
不要重名定义,合理使用synthesize
生成属性的setter
和getter
方法 - 5、在
MRC
模式下,变量的retain
和release
要谨慎,建议采用安全release
方法,即release
的对象置为 nil - 6、在.h中声明的方法如果用不到就去掉,用得到就同时在.m文件中实现
- 7、可变属性(如
NSMutableArray
),不要使用copy
修饰,或者重写set
方法 - 8、使用
高版本的系统方法
的时候做判断
1.4、知识归纳:参考runtime 消息转发
使用命令: thread backtrace
查看线程堆栈
2、KVC造成的crash
2.1、场景对应的Code
#import "KvcCrashVC.h" @interface KvcCrashVCObj : NSObject @property (nonatomic, strong) NSString *name; @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 case1]; } /** 场景一:对象不支持KVC */ - (void)case1 { // reason: '[<NSObject 0x282fe7f90> setValue:forUndefinedKey:]: this class is not key value coding-compliant for the key key.' NSObject* obj = [[NSObject alloc]init]; [obj setValue:@"value" forKey:@"key"]; } /** 场景二:key为nil */ - (void)case2 { // reason: '*** -[KvcCrashVCObj setValue:forKey:]: attempt to set a value for a nil key' KvcCrashVCObj* obj = [[KvcCrashVCObj alloc]init]; // value 为nil不会崩溃 [obj setValue:nil forKey:@"name"]; // key为nil会崩溃(直接写nil编译器会提示警告,更多时候我们传的是变量) [obj setValue:@"value" forKey:nil]; } /** 场景三:key不是object的属性产生的crash */ - (void)case3 { // reason: '[<KvcCrashVCObj 0x2810bfa80> setValue:forUndefinedKey:]: this class is not key value coding-compliant for the key falseKey.' KvcCrashVCObj* obj = [[KvcCrashVCObj alloc]init]; [obj setValue:nil forKey:@"falseKey"]; } @end 复制代码
2.2、原因
给不存在的key(包括key为nil)设置value
-
[obj setValue:@"value" forKey:@"UndefinedKey"]
-
[obj valueForKey:@"UndefinedKey"]
2.3、场景:
2.4、解决方案:
- 1、如果属性存在,利用iOS的反射机制来规避,
NSStringFromSelector(@selector())
将SEL
反射为字符串作为key。这样在@selector()
中传入方法名的过程中,编译器会有合法性检查,如果方法不存在或未实现会报黄色警告。 - 2、重写类的
setValue:forUndefinedKey:
和valueForUndefinedKey:
-(void)setValue:(id)value forUndefinedKey:(NSString *)key{ } -(id)valueForUndefinedKey:(NSString *)key{ return nil; } 复制代码
2.5、知识归纳:参考runtime kvc原理分析
3、EXC_BAD_ACCESS
经过 ARC
的洗礼之后,普通的访问释放对象产生的 EXC_BAD_ACCESS
已经大量减少了,现在出现的 EXC_BAD_ACCESS
有很大一部分来自malloc的对象或者越界访问。
3.1、场景对应的Code
#import "BadAccessCrashVC.h" #import <objc/runtime.h> @interface BadAccessCrashVC (AssociatedObject) @property (nonatomic, strong) UIView *associateView; @end @implementation BadAccessCrashVC (AssociatedObject) - (void)setAssociateView:(UIView *)associateView { objc_setAssociatedObject(self, @selector(associateView), associateView, OBJC_ASSOCIATION_ASSIGN); //objc_setAssociatedObject(self, @selector(associateView), associateView, OBJC_ASSOCIATION_RETAIN_NONATOMIC); } - (UIView *)associateView { return objc_getAssociatedObject(self, _cmd);; } @end @interface BadAccessCrashVC () @property (nonatomic, copy) void(^blcok)(void); @property (nonatomic, weak) UIView* weakView; @property (nonatomic, unsafe_unretained) UIView* unSafeView; @property (nonatomic, assign) UIView* assignView; @end @implementation BadAccessCrashVC - (void)viewDidLoad { [super viewDidLoad]; [self case1]; } /** 悬挂指针:访问没有实现的blcok */ - (void)case1 { self.blcok(); } /** 悬挂指针:对象没有被初始化 */ - (void)case2 { UIView* view = [UIView alloc]; view.backgroundColor = [UIColor blackColor]; [self.view addSubview:view]; } /** 悬挂指针:访问的对象已经被释放掉 */ - (void)case3 { { UIView* view = [[UIView alloc]init]; view.backgroundColor = [UIColor blackColor]; self.weakView = view; self.unSafeView = view; self.assignView = view; self.associateView = view; } // ARC下weak对象释放后会自动置nil,因此下面的代码不会崩溃 [self.view addSubview:self.weakView]; // 野指针场景一:unsafe_unretained修饰的对象释放后,不会自动置nil,变成野指针,因此下面的代码会崩溃 [self.view addSubview:self.unSafeView]; // 野指针场景二:应该使用strong/weak修饰的对象,却错误的使用assign修饰,释放后不会自动置nil [self.view addSubview:self.assignView]; // 野指针场景三:给类添加添加关联变量的时候,类似场景二,应该使用OBJC_ASSOCIATION_RETAIN_NONATOMIC修饰,却错误使用OBJC_ASSOCIATION_ASSIGN [self.view addSubview:self.associateView]; } @end 复制代码
3.2、原因
出现悬挂指针,对象没有被初始化,或者访问的对象被释放
3.3、解决方案:
- 1、
Debug
阶段开启僵尸模式,Release
时关闭僵尸模式 - 2、使用Xcode的
Address Sanitizer
检查地址访问越界 - 3、创建对象的时候记得初始化
- 4、对象的属性使用正确的修饰方式(
strong/weak
) - 5、调用
block
的时候,做判断
4、KVO引起的崩溃
4.1、场景对应的Code
#import "KvoCrashVC.h" @interface KvoCrashVCObj : NSObject @property (nonatomic, strong) NSString *name; @end @implementation KvoCrashVCObj @end @interface KvoCrashVC () @property (nonatomic, strong) KvoCrashVCObj *sObj; @end @implementation KvoCrashVC - (void)viewDidLoad { [super viewDidLoad]; self.sObj = [[KvoCrashVCObj alloc] init]; //#import <XXShield/XXShield.h> // static dispatch_once_t onceToken; // dispatch_once(&onceToken, ^{ // [XXShieldSDK registerStabilityWithAbility:(EXXShieldTypeKVO)]; // }); } - (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event { [self func4]; } /** 观察者是局部变量,会崩溃 */ - (void)func1 { // 崩溃日志:An -observeValueForKeyPath:ofObject:change:context: message was received but not handled. KvoCrashVCObj* obj = [[KvoCrashVCObj alloc] init]; [self addObserver:obj forKeyPath:@"view" options:NSKeyValueObservingOptionNew context:nil]; self.view = [[UIView alloc] init]; } /** 被观察者是局部变量,会崩溃 */ - (void)func2 { // 崩溃日志:An -observeValueForKeyPath:ofObject:change:context: message was received but not handled. KvoCrashVCObj* obj = [[KvoCrashVCObj alloc] init]; [obj addObserver:self forKeyPath:@"name" options:NSKeyValueObservingOptionNew context:nil]; obj.name = @""; } /** 没有实现observeValueForKeyPath:ofObject:changecontext:方法:,会崩溃 */ - (void)func3 { // 崩溃日志:An -observeValueForKeyPath:ofObject:change:context: message was received but not handled. [self.sObj addObserver:self forKeyPath:@"name" options:NSKeyValueObservingOptionNew context:NULL]; self.sObj.name = @"0"; } /** 重复移除观察者,会崩溃 */ - (void)func4 { // 崩溃日志:because it is not registered as an observer [self.sObj addObserver:self forKeyPath:@"name" options:NSKeyValueObservingOptionNew context:NULL]; self.sObj.name = @"0"; [self.sObj removeObserver:self forKeyPath:@"name"]; [self.sObj removeObserver:self forKeyPath:@"name"]; } /** 重复添加观察者,不会崩溃,但是添加多少次,一次改变就会被观察多少次 */ - (void)func5 { [self.sObj addObserver:self forKeyPath:@"name" options:NSKeyValueObservingOptionNew context:NULL]; self.sObj.name = @"0"; } - (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSKeyValueChangeKey,id> *)change context:(void *)context { NSLog(@"keyPath = %@", keyPath); } // 总结:KVO有两种崩溃 // 1、because it is not registered as an observer // 2、An -observeValueForKeyPath:ofObject:change:context: message was received but not handled. @end 复制代码
4.2、原因
添加了观察者,没有在正确的时机移除
4.3、解决方案:
- 1、addObserver和removeObserver一定要成对出现,
- 2、推荐使用FaceBook开源的第三方库 FBKVOController
5、集合类相关崩溃
5.1、场景对应的Code
#import "CollectionCrashVC.h" @interface CollectionCrashVC () @end @implementation CollectionCrashVC - (void)viewDidLoad { [super viewDidLoad]; [self case4]; } /** 场景一:数组越界 */ - (void)case1 { // reason: '*** -[__NSArrayI objectAtIndex:]: index 4 beyond bounds [0 .. 2]' NSArray* array = [[NSArray alloc]initWithObjects:@1, @2, @3, nil]; NSNumber* number = [array objectAtIndex:4]; NSLog(@"number = %@", number); } /** 场景二:向数组中添加nil元素 */ - (void)case2 { // reason: '*** -[__NSArrayM insertObject:atIndex:]: object cannot be nil' NSMutableArray* array = [[NSMutableArray alloc]initWithObjects:@1, @2, @3, nil]; [array addObject:nil]; } /** 场景三:数组遍历的时候使用错误的方式移除元素 */ - (void)case3 { NSMutableArray<NSNumber*>* array = [NSMutableArray array]; [array addObject:@1]; [array addObject:@2]; [array addObject:@3]; [array addObject:@4]; // 不崩溃 [array enumerateObjectsUsingBlock:^(NSNumber * _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) { if (obj.integerValue == 1) { [array removeObject:obj]; } }]; // 崩溃,reason: '*** Collection <__NSArrayM: 0x2829946f0> was mutated while being enumerated.' for (NSNumber* obj in array) { if (obj.integerValue == 2) { [array removeObject:obj]; } } // dispatch_async(dispatch_get_global_queue(0, 0), ^{ // self.view.backgroundColor = [UIColor blueColor]; // }); } /** 场景四:使用setObject:forKey:向字典中添加value为nil的键值对,推荐使用KVC的setValue:nil forKey: */ - (void)case4 { NSMutableDictionary* dictionary = [NSMutableDictionary dictionary]; [dictionary setObject:@1 forKey:@1]; // 不崩溃:value为nil,只会移除key对应的键值对 [dictionary setValue:nil forKey:@1]; // 崩溃:reason: '*** -[__NSDictionaryM setObject:forKey:]: object cannot be nil (key: 1)' [dictionary setObject:nil forKey:@1]; } @end 复制代码
5.2、原因
越界、添加nil、多线程非原子性操作、遍历的同时移除元素
5.3、场景:
- 1、数组越界,访问下标大于数组的个数
- 2、向数组中添加空数据
- 3、多线程环境中,一个线程在读取,一个线程在移除
- 4、一边遍历数组,一边移除数组中的元素
5.4、解决方案:
- 1、给集合类添加
category
重写原来的方法,在内部做判断 - 2、使用
Runtime
把原来的方法替换成自定义的安全方法 - 3、给
NSMutableDictionary
添加元素的时候,使用setObject:forKey:
向字典中添加value为nil的键值对,推荐使用KVC的setValue:nil forKey:
。[mutableDictionary setValue:nil ForKey:@"name"]
不会崩溃,只是从字典中移除name键值对。 - 4、因为
NSMutableArray、NSMutableDictionary
不是线程安全的,所以在多线程环境下要保证读写操作的原子性,使用 加锁 、 信号量 、 GCD串行队列 、 GCD栅栏dispatch_barrier_async
、 CGD组 的dispatch_group_enter
和dispatch_group_leave
6、多线程中的崩溃
6.1、场景对应的Code
#import "ThreadCrashVC.h" @interface ThreadCrashVC () @property (nonatomic, strong) NSMutableArray *array; @end @implementation ThreadCrashVC - (void)viewDidLoad { [super viewDidLoad]; } - (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event { [self case1]; } /** dispatch_group_leave比dispatch_group_enter执行的次数多 */ - (void)case1 { // 崩溃:Thread 1: EXC_BREAKPOINT (code=1, subcode=0x1054f6348) dispatch_group_t group = dispatch_group_create(); dispatch_group_leave(group); } /** 在子线程更新UI */ - (void)case2 { dispatch_async(dispatch_get_global_queue(0, 0), ^{ self.view.backgroundColor = [UIColor redColor]; }); } /** 多个线程同时释放一个对象 */ - (void)case3 { // ==================使用信号量同步后不崩溃================== { dispatch_semaphore_t semaphore = dispatch_semaphore_create(1); __block NSObject *obj = [NSObject new]; dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0), ^{ while (YES) { dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER); obj = [NSObject new]; dispatch_semaphore_signal(semaphore); } }); while (YES) { dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER); obj = [NSObject new]; dispatch_semaphore_signal(semaphore); } } // ==================未同步则崩溃================== { __block NSObject *obj = [NSObject new]; dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0), ^{ while (YES) { obj = [NSObject new]; } }); while (YES) { obj = [NSObject new]; } } } /** 多线程中的数组扩容、浅复制 扩容:数组的地址已经改变,报错was mutated while being enumerated 浅复制:访问僵尸对象,报错EXC_BAD_ACCESS // 知识点:集合类对象和非集合类对象的copy与mutableCopy // [NSArray copy] // 浅复制 // [NSArray mutableCopy] // 深复制 // [NSMutableArray copy] // 深复制 // [NSMutableArray mutableCopy] // 深复制 参考: [Swift数组扩容原理](https://bestswifter.com/swiftarrayappend/) [戴仓薯](https://juejin.im/post/5a9aa633518825556a71d9f3) */ -(void)case4 { { NSArray *array = @[@[@"a", @"b"], @[@"c", @"d"]]; NSArray *copyArray = [array copy]; NSMutableArray *mCopyArray = [array mutableCopy]; NSLog(@"array = %p,copyArray = %p,mCopyArray = %p", array, copyArray, mCopyArray); } { NSMutableArray *array = [NSMutableArray arrayWithObjects:[NSMutableString stringWithString:@"a"],@"b",@"c",nil]; NSArray *copyArray = [array copy]; NSMutableArray *mCopyArray = [array mutableCopy]; NSLog(@"array = %p,copyArray = %p,mCopyArray = %p", array, copyArray, mCopyArray); } dispatch_queue_t queue1 = dispatch_queue_create("queue1", 0); dispatch_queue_t queue2 = dispatch_queue_create("queue2", 0); NSMutableArray* array = [NSMutableArray array]; dispatch_async(queue1, ^{ while (true) { if (array.count < 10) { [array addObject:@(array.count)]; } else { [array removeAllObjects]; } } }); dispatch_async(queue2, ^{ while (true) { // case 1:数组扩容 for (NSNumber* number in array) { NSLog(@"%@", number); } // case 2:数组扩容 NSArray* immutableArray = array; for (NSNumber* number in immutableArray) { NSLog(@"%@", number); } // case 3:数组扩容 [NSArray initWithArray:range:copyItems:] NSArray* immutableArray1 = [array copy]; for (NSNumber* number in immutableArray1) { NSLog(@"%@", number); } // case 4:浅复制访问僵尸对象 NSArray* immutableArray2 = [array mutableCopy]; for (NSNumber* number in immutableArray2) { NSLog(@"%@", number); } } }); } @end 复制代码
6.2、原因
死锁、子线程中更新UI、多个线程同时释放一个对象
6.3、场景
- 1、在子线程中更新UI
- 2、 dispatch_group crash,
dispatch_group_leave
的次数比dispatch_group_enter
次数多。参考: iOS疑难问题排查之深入探究dispatch_group crash - 3、多线程下非线程安全类的使用,如
NSMutableArray
、NSMutableDictionary
。NSCache是线程安全的。 - 4、数据缓存到磁盘和读取。
6.4、解决方案:
多线程遇到需要同步的时候,加锁,添加信号量等进行同步操作。一般多线程发生的Crash,会收到SIGSEGV信号,表明试图访问未分配给自己的内存, 或试图往没有写权限的内存地址写数据。
7、Socket长连接,进入后台没有关闭
当服务器close一个连接时,若client端接着发数据。根据TCP协议的规定,会收到一个RST响应,client再往这个服务器发送数据时,系统会发出一个SIGPIPE信号给进程,告诉进程这个连接已经断开了,不要再写了。而根据信号的默认处理规则,SIGPIPE信号的默认执行动作是terminate(终止、退出),所以client会退出。
长连接socket或重定向管道进入后台,没有关闭导致崩溃的解决办法:
7.1、解决方案:
- 方法一:1、切换到后台是,关闭长连接和管道,回到前台重新创建。
- 方法二:2、使用signal(SIGPIPE,SIG_IGN),将SIGPIP交给系统处理,这么做将SIGPIPE设为SIG_IGN,使客户端不执行默认操作,即不退出。
8、Watch Dog超时造成的crash
主线程执行耗时操作,导致主线程被卡超过一定时间。一般异常编码是0x8badf00d,表示应用发生watch dog超时而被iOS终止,通常是应用花费太多的时间无法启动、终止或者响应系统事件。
8.1、解决方案:
主线程只负责更新UI和事件响应,将耗时操作(网络请求、数据库读写等)异步放到后台线程执行。
9、后台返回NSNull导致的崩溃,多见于 Java 做后台服务器开发语言
9.1、场景对应的Code
// reason: '-[NSNull integerValue]: unrecognized selector sent to instance 0x2098f99b0' NSNull *nullStr = [[NSNull alloc] init]; NSMutableDictionary* dic = [NSMutableDictionary dictionary]; [dic setValue:nullStr forKey:@"key"]; NSNumber* number = [dic valueForKey:@"key"]; 复制代码
- NULL:用于普通类型,例如NSInteger
- nil:用于OC对象(除了类这个对象),给nil对象发送消息不会crash
- Nil:用于Class类型对象的赋值(类是元类的实例,也是对象)
- NSNull:用于OC对象的站位,一般会作为集合中的占位元素,给NSNull对象发送消息会crash的,后台给我们返回的就是NSNull对象
9.2、解决方法
利用消息转发。参考: NullSafe 。当我们给一个NSNull对象发送消息的话,可能会崩溃(null是有内存的),而发送给nil的话,是不会崩溃的。
10、在iOS中捕获异常信息
崩溃主要是由于 Mach
异常、 Objective-C
异常( NSException
)引起的,同时对于 Mach
异常,到了 BSD
层会转换为对应的 Signal
信号,那么我们也可以通过捕获信号,来捕获 Crash
事件。针对 NSException
可以通过注册 NSUncaughtExceptionHandler
捕获异常信息。
/** 一、OC异常处理函数 @param exception exception */ static void uncaught_exception_handler (NSException *exception) { // 异常的堆栈信息 NSArray *stackArray = [exception callStackSymbols]; // 出现异常的原因 NSString *reason = [exception reason]; // 异常名称 NSString *name = [exception name]; NSString *exceptionInfo = [NSString stringWithFormat:@"Exception reason:%@\nException name:%@\nException stack:%@", name, reason, stackArray]; NSLog(@"%@", exceptionInfo); NSMutableArray *tmpArr = [NSMutableArray arrayWithArray:stackArray]; [tmpArr insertObject:reason atIndex:0]; //保存到本地 -- 当然你可以在下次启动的时候,上传这个log [exceptionInfo writeToFile:[NSString stringWithFormat:@"%@/Documents/error.log",NSHomeDirectory()] atomically:YES encoding:NSUTF8StringEncoding error:nil]; /** * 把异常崩溃信息发送至开发者邮件 */ NSString *content = [NSString stringWithFormat:@"==异常错误报告==\nname:%@\nreason:\n%@\ncallStackSymbols:\n%@", name, reason, [stackArray componentsJoinedByString:@"\n"]]; NSMutableString *mailUrl = [NSMutableString string]; [mailUrl appendString:@"mailto:test@qq.com"]; [mailUrl appendString:@"?subject=程序异常崩溃,请配合发送异常报告,谢谢合作!"]; [mailUrl appendFormat:@"&body=%@", content]; // 打开地址 NSCharacterSet *set = [NSCharacterSet URLHostAllowedCharacterSet]; NSString *mailPath = [mailUrl stringByAddingPercentEncodingWithAllowedCharacters:set]; //NSString *mailPath = [mailUrl stringByAddingPercentEscapesUsingEncoding:NSUTF8StringEncoding]; [[UIApplication sharedApplication] openURL:[NSURL URLWithString:mailPath]]; } /** 二、Unix标准的signal机制处理函数 @param sig sig */ static void handleSignal( int sig ) { } - (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions { // OC层中未被捕获的异常,通过注册NSUncaughtExceptionHandler捕获异常信息 NSSetUncaughtExceptionHandler(&uncaught_exception_handler); // 内存访问错误,重复释放等错误就无能为力了,因为这种错误它抛出的是Signal,所以必须要专门做Signal处理 // OC中层不能转换的Mach异常,利用Unix标准的signal机制,注册SIGABRT, SIGBUS, SIGSEGV等信号发生时的处理函数。 signal(SIGSEGV,handleSignal); return YES; } 复制代码
以上所述就是小编给大家介绍的《iOS中常见Crash总结》,希望对大家有所帮助,如果大家有任何疑问请给我留言,小编会及时回复大家的。在此也非常感谢大家对 码农网 的支持!
猜你喜欢:本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们。
Flask Web开发实战
李辉 / 机械工业出版社 / 2018-8-1 / 129
这是一本面向Python程序员的,全面介绍Python Web框架Flask的书。关于本书的详细介绍、相关资源等更多信息可以访问本书的官方主页http://helloflask.com/book了解。 • 国内首本Flask著作,在内容上涵盖完整的Flask Web开发学习路径,在实践上包含完整的Flask Web程序开发流程。同时兼容Python2 .7和Python3.6。 • 内......一起来看看 《Flask Web开发实战》 这本书的介绍吧!