美文网首页
iOS 多线程 - 崩溃case及应对措施

iOS 多线程 - 崩溃case及应对措施

作者: ameerkat | 来源:发表于2019-08-27 17:41 被阅读0次

    在上家公司遇到了好几次多线程崩溃导致的Case,故学习多线程容易崩溃的地方,笔记:

    崩溃点

    1.released twice:

    多个线程同时访问set方法,可能导致被set的对象的多次释放

    
    @property (nonatomic, strong) NSString *target;
    
    //....
    
    dispatch_queue_t queue = dispatch_queue_create("parallel", DISPATCH_QUEUE_CONCURRENT);
    
    for (int i = 0; i < 1000000 ; i++) {
    
        dispatch_async(queue, ^{
    
            self.target = [NSString stringWithFormat:@"ksddkjalkjd%d",i];
    
        });
    
    }
    
    - (void)setUserName:(NSString *)userName {
    
          if (userName != _userName) {
    
              [userName retain];
    
              [_userName release];
    
              _userName = userName;
    
          }
    
    }
    

    2.读写时序有问题

    逻辑上读写以不恰当的时序发生了(1)同时(2)不该发生的读先于写

    3.在非主线程更新UI

    查了一堆资料,Apple文档也只说了“Updating UI on a thread other than the main thread is a common mistake that can result in missed UI updates, visual defects, data corruptions, and crashes.”

    我对于这些问题的理解:

    UIKit相关的一切都是在main thread上处理的,子线程更新UI(访问某个子view的内存)的时候,可能主线程也在访问那块内存(比如拿subviews出来,一个一个绘制),可能正好撞在一起。所以更新UI的操作派发到串行的主线程就不存在可能跟老大哥撞车的问题了。

    处理办法:

    1.属性级别的线程安全:属性原子性

    官方自旋锁实现属性的原子性

    
    static  inline  void reallySetProperty(id  self, SEL _cmd, id newValue,
    
    ptrdiff_t offset, bool atomic, bool  copy, bool mutableCopy)
    
    {
    
     id oldValue;
    
     id *slot = (id*) ((char*)self + offset);
    
     if (copy) {
    
            newValue = [newValue copyWithZone:NULL];
    
    } else  if (mutableCopy) {
    
            newValue = [newValue mutableCopyWithZone:NULL];
    
    } else {
    
     if (*slot == newValue) return;
    
            newValue = objc_retain(newValue);
    
        }
    
     if (!atomic) {
    
            oldValue = *slot;
    
            *slot = newValue;
    
    } else {
    
            spin_lock_t *slotlock = &PropertyLocks[GOODHASH(slot)];
    
            _spin_lock(slotlock);
    
            oldValue = *slot;
    
            *slot = newValue;        
    
            _spin_unlock(slotlock);
    
        }
    
        objc_release(oldValue);
    
    }
    

    2.抛出问题

    该在某一线程的操作没在该线程可以直接在开发期抛出错误:

    void PSPDFAssertIfNotMainThread(void) {
    
        NSAssert(NSThread.isMainThread, 
    
          @"Error: Method needs to be called on the main thread. %@", 
    
          [NSThread callStackSymbols]);
    
    }
    

    3.逻辑级别的线程安全:concurrent dispatch_queue VS @synchronized

    concurrent dispatch_queue:
    
    // header
    
    @property (nonatomic, strong)  NSMutableSet  *delegates;
    
    // in init
    
    _delegateQueue = dispatch_queue_create("com.PSPDFKit.cacheDelegateQueue", 
    
      DISPATCH_QUEUE_CONCURRENT);
    
    - (void)addDelegate:(id<PSPDFCacheDelegate>)delegate {
    
        dispatch_barrier_async(_delegateQueue, ^{
    
            [self.delegates addObject:delegate];
    
        });
    
    }
    
    - (void)removeAllDelegates {
    
        dispatch_barrier_async(_delegateQueue, ^{
    
     self.delegates removeAllObjects];
    
        });
    
    }
    
    - (void)callDelegateForX {
    
     dispatch_sync(_delegateQueue, ^{
    
            [self.delegates enumerateObjectsUsingBlock:^(id<PSPDFCacheDelegate> delegate, NSUInteger idx, BOOL *stop) {
    
                // Call delegate
    
            }];
    
        });
    
    }
    

    如果发生次数不不是一秒几千次可以用@synchronized(有异常抛出处理所以慢一点):

    // header
    
    @property (atomic, copy) NSSet *delegates;
    
    - (void)addDelegate:(id<PSPDFCacheDelegate>)delegate {
    
        @synchronized(self) {
    
     self.delegates = [self.delegates setByAddingObject:delegate];
    
        }
    
    }
    
    - (void)removeAllDelegates {
    
        @synchronized(self) {
    
     self.delegates = nil;
    
        }
    
    }
    
    - (void)callDelegateForX {
    
        [self.delegates enumerateObjectsUsingBlock:^(id<PSPDFCacheDelegate> delegate, NSUInteger idx, BOOL *stop) {
    
            // Call delegate
    
        }];
    
    }
    

    4.自己设计的数据结构可以在修改方法中创造不可变copy(写放提供不可变copy)

    5.不要这样把有时序问题的更新放在下一runloop:

    dispatch_async(dispatch_get_main_queue(), ^{
    
        // Some UIKit call that had timing issues but works fine
    
        // in the next runloop.
    
        [self updatePopoverSize];
    
    });
    

    延迟派发的更新越多,越可能出现新的时序问题,不如找准代码应该执行的位置如:viewdidload-》viewwillappear

    6.自己Subclass NSOperation可以打印更多的调试信息

    参考文章

    Browse Issue Thread-Safe Class Design

    UNDERSTANDING THE IOS MAIN THREAD

    相关文章

      网友评论

          本文标题:iOS 多线程 - 崩溃case及应对措施

          本文链接:https://www.haomeiwen.com/subject/igfyectx.html