• iOS常见bug


    前言:

    对这两年修复的bug做了个简单的总结。

    一、常见crash名词解释

    SIGSEGV

    一般情况下,SIGSEGV是由于内存地址不合法造成。因为无效的内存访问导致的,一般是指针指向不存在的地址所导致(Invalid memory reference);

    SIGBUS

    一般情况下,SIGBUS是因为内存地址没有对齐导致。

    因为总线出错(bus error)。地址一般是先校验地址对齐再校验其他的,校验地址对齐后会放入数据总线,这时有问题就会报SIGBUS的错误。

    SIGABRT

    异常终止条件,例如abort()。

    二、常见crash/bug分类整理

    1、3、6是修复crash过程中常遇到的crash类型。

    1.   crashName=NSInvalidArgumentException ,crashReason=data parameter is nil

    (1) [aMutableDictionary setObject:nil forKey:]; object can not be nil.
         [__NSDictionaryM removeObjectForKey:nil]: key cannot be nil
     (2) [aString hasSuffix:nil];  nil argument crash.
        [aString hasPrefix:nil];  nil argument crash.
     (3) aString = [NSMutableString stringWithString:nil];nil argument crash.
         aString = [[NSString alloc] initWithString:nil]; nil argument crash.
     (4) aURL = [NSURL fileURLWithPath:nil]; nil argument crash.
     (5) [NSJSONSerialization JSONObjectWithData:data options:0 error:&error];data is nil crash
    (6) [aMutableArray addObject:nil], 添加nil crash。
    (7) UIPasteboard *pasteboard = [UIPasteboard generalPasteboard];
         pasteboard.string = nil;// setString参数nil则crash8if(controller.presentedViewController != nil) {
            [controller dismissViewControllerAnimated:NO completion:^{
                //controller.presentedViewController为nil则presentViewController:方法会crash         
                [controller presentViewController:self.imagePickerController animated:YES completion:nil]; 
            }];
        }else{
            [controller presentViewController:self.imagePickerController animated:YES completion:nil];
        }

    2.  野指针

           (1)  self.dataArr = (NSMutableArray *)mopayConsumeOrderList.list;

             [self.dataArr removeAllObjects];

             其中mopayConsumeOrderList.list是NSArray * 类型,

              运行后出现程序崩溃,原因是self.dataArr和mopayConsumeOrderList.list指向同一段内存,self.dataArr在运行多次之后mopayConsumeOrderList.list指向空时self.dataArr也变成野指针。

            改成:self.dataArr 自己new一个,即:

            self.dataArr = [NSMutableArray  arrayWithArray:self.mopayConsumeOrderList.list];

          (2)属性的内存管理语义设置的_unsafe_unretain,出现野指针;

          (3) ios9.0之前Notification未注销带来的僵尸对象问题。

    3.  数据类型错误带来的unrecognized selector crash

         例1:常见业务调用时传参数应为NSString,业务方传入的数据为Dictionary,崩溃.

         例2:  网络层中将NSData转换成Model,返回的数据和移动之家上定义的数据不一致,带来的崩溃。

         例3:  push 接收到的消息是string类型,当做NSNumber类型处理。

    4. 数组取值越界crash

    5. 关注新技术

     (1)2017年6月开始苹果热修复crash

     (2)ios10以后push使用UNUserNotificationCenter注册push,否则ios10、ios11都有可能crash。

     (3)ios10等如果没在plist里设置key-value描述语,否则相机崩溃(蓝牙、日历、麦克风、相机、相册、通讯录等)

            <key>NSPhotoLibraryUsageDescription</key>
             <string>APP需要您的允许,才能访问相册</string>

    6. 数据竞争带来的crash (SIGSEGV)

       例1:NSMutableDictionary类型用objectForKey取数据和setValueforKey:设置数据,有可能存在数据竞争,需要做保护。

      例2: NSMutableArray存在同样的问题。遍历数组期间,不要对数组进行remove和insert操作,因为数组有保护机制,容易崩溃。可以先拷贝一份出来,对拷贝的数组遍历,对真正需要改变的数组操作。

    7. selector不存在带来的crash (SIGSEGV)

     [self  respondsToSelector:@sel(aSel)]的判断,否则有可能带来的unrecognized selector crash。

    例1.   [toggle addTarget:target action:selector forControlEvents:UIControlEventValueChanged];  // selector是否存在

    例2.   [self performSelector:@selector(aSelector)];   // aSelector是否存在

    8.  存储类型必须是对象类型

               [self.dic setObject:[NSArray arrayWithArray: self.mpShopArr] forKey:@"shopArr"];

     [[NSUserDefaults standardUserDefaults] setObject:self.dic  forKey:[JVAccountManager sharedAccount].edper];崩溃

    由于self.mpShopArr里存的数据结构包含了很多类型,其中一个数据类型是int,不是object,导致第一句没报错、但第二句就报错了。

    最终存到[NSUserDefaults standardUserDefaults] 里的数据类型,无论包装多少层,所有的内容必须是对象类型。

    9. 内存泄漏(bug)

    (1)单例+RACObserve带来内存泄漏。

         [RACObserve(self.viewModel, modulesShouldRefresh) subscribeNext:^(NSNumber * modulesShouldRefresh) {

            @strongify(self);

            if ([modulesShouldRefresh boolValue] == YES) {

                [self setShowLoading:NO];

                [self.highFrequencyTitleView requestHeadInfoForce:@(YES)];

                [self refreshModules:YES];

            }  }];

          由于viewModel设成了不恰当的单例,对其RACObserve导致self(homeVC)不能释放,也就是当切换账号的时候,原来的homeVC没释放,新的homeVC又创建了一个,导致下拉刷新时,原来的没有释放的homeVC触发了接口,现在的homeVC也触发了接口,导致接口请求多次。所以,单例不能随便乱用,除非可以确定对象与APP同生共死生命周期没有问题。

    (2)performSelector: withObject: afterDelay: 必须有合适的时机cancelPreviousPerformRequestsWithTarget:selector:object:,否则会导致内存泄漏。

             可以用weak类型的dispatch_after避免内存泄漏:

                       __weak CRMHomeViewModel *weakSelf = self;

                        dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(delay * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{

                            [weakSelf requestLiveData:YES];

                        });

    (3)    NSTimer会保留其目标对象,如果不加以注意,就会持有保留环,造成内存泄露。

            scheduledTimerWithTimeInterval: target:self selector: userInfo: repeats:,重复模式的计时器,必须手动调用[_aTimer invalidate]方法,才能令其停止。

        self持有timer,timer的target又是self, timer不释放,导致self也不能调用dealloc,所以timer调用invalidate的时机也不好把控。ios10以后,使用block来打破保留环,定义一个弱引用,令其指向self,然后使块捕获这个引用,而不直接去捕获普通的self变量。也就是说,self不会为计时器所保留。当块开始执行时,立刻生成strong引用,以保证实例在执行期间持续存活:

    __weak SLVTimerTestViewController *weakSelf = self; 
    _aTimer = [NSTimer scheduledTimerWithTimeInterval:5.0 repeats:YES block:^(NSTimer* timer){ 
      SLVTimerTestViewController *strongSelf = weakSelf;  
      [strongSelf timer_invoke]; 
    }];

    三、crash防护

    参考《Baymax:网易iOS App运行时Crash自动防护实践》,并进行了一些调研和实践,对以下5种常见crash进行了分析,并写出了对应的防护代码。写好的代码已经过初步测试。前进的一小步,记录下来。对应的4篇博客:

    1.  [crash详解与防护] unrecognized selector crash
    2.  [crash详解与防护] Container类型的crash和NSString类型的crash

    3.  [crash详解与防护] NSTimer crash

    4.  [crash详解与防护] NSNotification crash
  • 相关阅读:
    大端与小端编号方法的区别
    socket、listen 等函数的打电话隐喻
    windows 网络编程报错 error LNK2019
    有符号数与无符号数之间的转换
    C++ 代码命名建议
    编写启发式代码的方法
    求给定数目的前 n 个素数
    不使用 “+” 实现加法操作
    二叉搜索树中两个节点的旋转
    Python玩转硬件:TPYBoard-Micropython开发板大盘点
  • 原文地址:https://www.cnblogs.com/Xylophone/p/7429373.html
Copyright © 2020-2023  润新知