iOS-线程安全NSMutableArray

作者: 路飞_Luck | 来源:发表于2018-04-15 23:21 被阅读85次
    数组线程安全的思考

    NSMutableArray是线程不安全的,当有多个线程同时对数组进行操作的时候可能导致崩溃或数据错误,下面是对线程安全的几个思路.如有错误,欢迎指正.

    • 对数组的读写都加锁,虽然数组是线程安全了,但失去了多线程的优势

    • 然后又想可以只对写操作加锁然后定义一个全局变量来表示现在有没有写操作,如果有写操作就等写完了在读,那么问题来了如果一个线程先读取数据紧接着一个线程对数组写的操作,读的时候还没有加锁同样会导致崩溃或数据错误,这个方案pass掉.

    • 第三种方案说之前先介绍一下dispatch_barrier_async,dispatch_barrier_async 追加到 queue 中后,会等待 queue 中的任务都结束后,再执行 dispatch_barrier_async 的任务,等 dispatch_barrier_async 的任务结束后,才恢复任务执行, 用dispatch_async和dispatch_barrier_async结合保证NSMutableArray的线程安全,用dispatch_async读和dispatch_barrier_async写(add,remove,replace),当有任务在读的时候写操作会等到所有的读操作都结束了才会写,同样当有写任务时,读任务会等写操作完了才会读,既保证了线程安全又发挥了多线程的优势,但还是有个不足,当我们重写读的方法时dispatch_async是另开辟线程去执行的而且是立马返回的,所以我们不能拿到执行结果,需要去另写一个方法来返回读的结果,但是我们又不想改变调用者的习惯于是又想到了一下方案.

    • 用dispatch_sync和dispatch_barrier_async结合保证NSMutableArray的线程安全,dispatch_sync是在当前线程上执行不会另开辟新的线程,当线程返回的时候就可以拿到读取的结果,我认为这个方案是最完美的选择,既保证的线程安全有发挥了多线程的优势还不用另写方法返回结果,完美~


    数组线程安全的实现

    下面咱们来看一下NSMutableArray线程安全的实现

    • 继承 NSMutableArray创建NSKSafeMutableArray在这个地方遇到了一些坑通过查阅文档发现问题所在:

    在 Cocoa 中有一种奇葩的类存在 Class Clusters。面向对象的编程告诉我们:“类可以继承,子类具有父类的方法”。而 Cocoa 中的 Class Clusters 虽然平时表现的像普通类一样,但子类却没法继承父类的方法。 NSMutableArray就是这样的存在。为什么会这样呢?因为 Class Clusters 内部其实是由多个私有的类和方法组成。虽然它有这样的弊端,但是好处还是不言而喻的。例如,NSNumber 其实也是这种类,这样一个类可以把各种不同的原始类型封装到一个类下面,提供统一的接口。这正设计模式中的抽象工厂模式。

    查看Apple的文档,要继承这样的类需要必须实现其primitive methods方法,实现了这些方法,其它方法便都能通过这些方法组合而成。比如

    需要继承NSMutableArray就需要实现它的以下primitive methods:

    - (void)addObject:(id)anObject;
    - (void)insertObject:(id)anObject atIndex:(NSUInteger)index;
    - (void)removeLastObject;
    - (void)removeObjectAtIndex:(NSUInteger)index;
    - (void)replaceObjectAtIndex:(NSUInteger)index withObject:(id)anObject;
    

    NSArray的primitive methods:

    - (NSUInteger)count;
    - (id)objectAtIndex:(NSUInteger)index;
    

    • BGSafeMutableArray.m文件的实现如下
      先声明一个数组
    #import "BGSafeMutableArray.h"
    
    @interface BGSafeMutableArray() {
        CFMutableArrayRef _array;
    }
    @end
    
    

    初始化方法

    - (id)init {
        self = [super init];
        if (self) {
            _array = CFArrayCreateMutable(kCFAllocatorDefault, 10,  &kCFTypeArrayCallBacks);
        }
        return self;
    }
    

    几个重要方法

    // 获取可变数组数量
    - (NSUInteger)count {
        __block NSUInteger result;
        dispatch_sync(self.syncQueue, ^{
            result = CFArrayGetCount(_array);
        });
        return result;
    }
    
    // 获取第N个位置的对象
    - (id)objectAtIndex:(NSUInteger)index {
        __block id result;
        dispatch_sync(self.syncQueue, ^{
            NSUInteger count = CFArrayGetCount(_array);
            result = index < count ? CFArrayGetValueAtIndex(_array, index) : nil;
        });
        return result;
    }
    
    // 插入对象至指定位置
    - (void)insertObject:(id)anObject atIndex:(NSUInteger)index {
        __block NSUInteger blockIndex = index;
        dispatch_barrier_async(self.syncQueue, ^{
            if (!anObject) {
                return;
            }
            
            NSUInteger count = CFArrayGetCount(_array);
            blockIndex = blockIndex > count ? count : blockIndex;
            
            CFArrayInsertValueAtIndex(_array, index, (__bridge const void *)anObject);
        });
    }
    
    // 删除指定位置上的对象
    - (void)removeObjectAtIndex:(NSUInteger)index {
        dispatch_barrier_async(self.syncQueue, ^{
            NSUInteger count = CFArrayGetCount(_array);
            if (index < count) {
                CFArrayRemoveValueAtIndex(_array, index);
            }
        });
    }
    
    // 添加对象
    - (void)addObject:(id)anObject {
        dispatch_barrier_async(self.syncQueue, ^{
            if (!anObject) {
                return;
            }
            CFArrayAppendValue(_array, (__bridge const void *)anObject);
        });
    }
    
    // 删除最后一个对象
    - (void)removeLastObject {
        dispatch_barrier_async(self.syncQueue, ^{
            NSUInteger count = CFArrayGetCount(_array);
            if (count > 0) {
                CFArrayRemoveValueAtIndex(_array, count-1);
            }
        });
    }
    
    // 替换指定位置的对象
    - (void)replaceObjectAtIndex:(NSUInteger)index withObject:(id)anObject {
        dispatch_barrier_async(self.syncQueue, ^{
            if (!anObject) {
                return;
            }
            
            NSUInteger count = CFArrayGetCount(_array);
            if (index >= count) {
                return;
            }
            
            CFArraySetValueAtIndex(_array, index, (__bridge const void*)anObject);
        });
    }
    

    两个可选方法实现

    // 移除所有对象
    - (void)removeAllObjects {
        dispatch_barrier_async(self.syncQueue, ^{
            CFArrayRemoveAllValues(_array);
        });
    }
    
    // 获取某一个对象的索引位置
    - (NSUInteger)indexOfObject:(id)anObject {
        if (!anObject) {
            return NSNotFound;
        }
        
        __block NSUInteger result;
        dispatch_sync(self.syncQueue, ^{
            NSUInteger count = CFArrayGetCount(_array);
            result = CFArrayGetFirstIndexOfValue(_array, CFRangeMake(0, count), (__bridge const void *)(anObject));
        });
        return result;
    }
    

    私有属性懒加载

    - (dispatch_queue_t)syncQueue {
        static dispatch_queue_t queue = nil;
        static dispatch_once_t onceToken;
        dispatch_once(&onceToken, ^{
            queue = dispatch_queue_create("com.kong.NSKSafeMutableArray", DISPATCH_QUEUE_CONCURRENT);
        });
        return queue;
    }
    
    • 调用
    - (void)viewDidLoad {
        [super viewDidLoad];
        
        BGSafeMutableArray *safeArr = [[BGSafeMutableArray alloc] init];
        
        dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
        for ( int i = 0; i < 10000; i ++) {
            dispatch_async(queue, ^{
                NSLog(@"添加第%d个",i);
                [safeArr addObject:[NSString stringWithFormat:@"%d",i]];
            });
            
            dispatch_async(queue, ^{
                NSLog(@"删除第%d个",i);
                [safeArr removeObjectAtIndex:i];
            });
        }
    }
    

    本文参考关于NSMutableArray线程安全的思考和实现非常感谢该作者

    相关文章

      网友评论

      • IT人故事会:做开发很累,还的学习,之前你这个我也碰到过,但是没记录谢谢了

      本文标题:iOS-线程安全NSMutableArray

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