iOS数组操作

iOS 多线程参考:https://juejin.im/entry/58aacac08ac247006e625b8c

导语: NSMutableArray提供的API能解决绝大部分的需求,但是在实际iOS开发中,在某些场景下,需要考虑线程安全 或 弱对象引用 或 删除元素这三个问题。

文章基本 copy ,本文只是记录,修改部分,供自己使用。谢谢。

一、线程安全的NSMutableArray

NSMutableArray本身是线程不安全的。简单来说,线程安全就是多个线程访问同一段代码,程序不会异常、不Crash。而编写线程安全的代码主要依靠线程同步。
1、不使用atomic修饰属性
原因有二,如下:
1 ) atomic 的内存管理语义是原子性的,仅保证了属性的setter和getter方法是原子性的,是线程安全的,但是属性的其他方法,如数组添加/移除元素等并不是原子操作,所以不能保证属性是线程安全的。
2 ) atomic虽然保证了getter、setter方法线程安全,但是付出的代价很大,执行效率要比nonatomic慢很多倍(有说法是慢10-20倍)。
总之:使用nonatomic修饰NSMutableArray对象就可以了,而使用锁、dispatch_queue来保证NSMutableArray对象的线程安全。
2、打造线程安全的NSMutableArray
《Effective Objective-C 2.0..》书中第41条:多用派发队列,少用同步锁中指出:使用“串行同步队列”(serial synchronization queue),将读取操作及写入操作都安排在同一个队列里,即可保证数据同步。而通过并发队列,结合GCD的栅栏块(barrier)来不仅实现数据同步线程安全,还比串行同步队列方式更高效。

GCD的栅栏块作用示意图.png

说明:栅栏块单独执行,不能与其他块并行。知道当前所有并发块都执行完毕,才会单独执行这个栅栏块。线程安全的NSMutableArray实现如下:

//QSThreadSafeMutableArray.h
@interface QSThreadSafeMutableArray : NSMutableArray
@end
//QSThreadSafeMutableArray.m
#import "QSThreadSafeMutableArray.h"
@interface QSThreadSafeMutableArray()
@property (nonatomic, strong) dispatch_queue_t syncQueue;
@property (nonatomic, strong) NSMutableArray* array;
@end
@implementation QSThreadSafeMutableArray
#pragma mark- init 方法
- (instancetype)initCommon{
 self = [super init]; 
if (self) { //%p 以16进制的形式输出内存地址,附加前缀0x 
NSString* uuid = [NSString stringWithFormat:@"com.jzp.array_%p", self];
 //注意:_syncQueue是并行队列 
_syncQueue = dispatch_queue_create([uuid UTF8String], DISPATCH_QUEUE_CONCURRENT);
 } 
return self;
}
- (instancetype)init{ 
self = [self initCommon];
 if (self) { 
_array = [NSMutableArray array]; 
            }
 return self;
}//其他init方法略#pragma mark - 数据操作方法 (凡涉及更改数组中元素的操作,使用异步派发+栅栏块;读取数据使用 同步派发+并行队列)
- (NSUInteger)count{
 __block NSUInteger count; 
dispatch_sync(_syncQueue, ^{ count = _array.count; }); return count;
}
- (id)objectAtIndex:(NSUInteger)index{
 __block id obj;
 dispatch_sync(_syncQueue, ^{
 if (index < [_array count]) {
 obj = _array[index]; 
} }); 
return obj;
}
-(NSEnumerator *)objectEnumerator{
 __block NSEnumerator *enu; dispatch_sync(_syncQueue, ^{ 
enu = [_array objectEnumerator]; 
}); return enu;}
- (void)insertObject:(id)anObject atIndex:(NSUInteger)index{ dispatch_barrier_async(_syncQueue, ^{ 
if (anObject && index < [_array count]) { 
[_array insertObject:anObject atIndex:index]; 
} });
}
- (void)addObject:(id)anObject{ dispatch_barrier_async(_syncQueue, ^{
 if(anObject){ 
[_array addObject:anObject]; 
} });
}
- (void)removeObjectAtIndex:(NSUInteger)index{ dispatch_barrier_async(_syncQueue, ^{
 if (index < [_array count]) {
 [_array removeObjectAtIndex:index]; 
} });
}
- (void)removeLastObject{ dispatch_barrier_async(_syncQueue, ^{ [_array removeLastObject];
 });
}
- (void)replaceObjectAtIndex:(NSUInteger)index withObject:(id)anObject{ dispatch_barrier_async(_syncQueue, ^{ 
if (anObject && index < [_array count]) {
 [_array replaceObjectAtIndex:index withObject:anObject]; 
} });}
- (NSUInteger)indexOfObject:(id)anObject{
 __block NSUInteger index = NSNotFound; dispatch_sync(_syncQueue, ^{
 for (int i = 0; i < [_array count]; i ++) {
 if ([_array objectAtIndex:i] == anObject) {
 index = i; break; }
 } });
 return index;
}
- (void)dealloc{
 if (_syncQueue) { 
_syncQueue = NULL; }}
@end

说明1:使用dispatch queue来实现线程同步;将同步与异步派发结合起来,可以实现与普通加锁机制一样的同步行为,又不会阻塞执行异步派发的线程;使用同步队列及栅栏块,可以令同步行为更加高效。
说明2:NSMutableDictionary本身也是线程不全的,实现线程安全的NSMutableDictionary原理同线程安全的NSMutableArray。(代码见QSUseCollectionDemo)
2、线程安全的NSMutableArray使用
//线程安全的

NSMutableArrayQSThreadSafeMutableArray *safeArray = [[QSThreadSafeMutableArray alloc]init];
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
for (NSInteger i = 0; i < 10; i++) { dispatch_async(queue, ^{
NSString *str = [NSString stringWithFormat:@"数组%d",(int)i+1];
[safeArray addObject:str]; 
});}
sleep(1);
NSLog(@"打印数组");
[safeArray enumerateObjectsUsingBlock:^(
id _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) { NSLog(@"%@",obj);}];

说明1:先要初始化QSThreadSafeMutableArray对象,初始化工作不是线程安全的。
说明2:多个线程几乎同时添加数据元素,使用QSThreadSafeMutableArray,没有发生遗漏数据,也没有因为资源竞争导致的奔溃。而NSMutableArray对象在同样情况下会出问题(遗漏数据 或 crash)。

二、NSMutableArray弱引用对象

在iOS中,容器类是强引用其存储的元素的,将对象添加到容器时,该对象的引用计数+1,这很好保证了访问容器类中元素时,元素是始终存在容器类中。这种强引用同时也埋下了造成循环引用的可能。实现容器类中弱引用对象,是个考虑的问题。容器类中仅以NSMutableArray为例,实现弱引用对象、
1、NSMutableArray分类实现

//NSMutableArray+WeakReferences.h
@interface NSMutableArray (WeakReferences)+ (id)mutableArrayUsingWeakReferences;
+ (id)mutableArrayUsingWeakReferencesWithCapacity:(NSUInteger)capacity;
@end
//NSMutableArray+WeakReferences.m
#import "NSMutableArray+WeakReferences.h"
@implementation NSMutableArray (WeakReferences)
+ (id)mutableArrayUsingWeakReferences 
{
 return [self mutableArrayUsingWeakReferencesWithCapacity:0];
}
+ (id)mutableArrayUsingWeakReferencesWithCapacity:(NSUInteger)capacity { 
CFArrayCallBacks callbacks = {0, NULL, NULL, CFCopyDescription, CFEqual}; 
// Cast of C pointer type 'CFMutableArrayRef' (aka 'struct __CFArray *') to Objective-C pointer type 'id' requires a bridged cast 
return (id)CFBridgingRelease(CFArrayCreateMutable(0, capacity, &callbacks));
 // return (id)(CFArrayCreateMutable(0, capacity, &callbacks));
}
@end

说明1 : 参考自Non-retaining array for delegates
说明2:在NSDictionary/NSMutableDictionary中,也是强引用values。想弱引用values,只需要使用
NSMapTable
(iOS 6推出),它不仅和字典有相似的数据结构,还可以指定key是强引用,value是弱引用。
2、其他实现
思路:将需要添加到容器中的对象,包装在另一个存储对它的弱引用的对象中。

//QSWeakObjectWrapper.h
@interface QSWeakObjectWrapper : NSObject
@property (nonatomic, weak, readonly) id weakObject;
- (id)initWithWeakObject:(id)weakObject;
@end
//QSWeakObjectWrapper.m
#import "QSWeakObjectWrapper.h"
@implementation QSWeakObjectWrapper
- (id)initWithWeakObject:(id)weakObject{
 if (self = [super init]) { 
_weakObject = weakObject;
 } 
return self;
}
@end

**说明 **: 我们实现了弱引用元素,即不希望数组保留对象,这是为了解决数组中循环引用的问题;但平时还是默认使用强引用数组元素,因为弱引用数组元素,数组中元素在释放,数组会出问题。

三、删除NSMutableArray中的元素

1、removeObjectAtIndex VS removeObject
removeObjectAtIndex:删除指定NSMutableArray中指定index的对象( index不能越界)。

removeObject:删除NSMutableArray中所有isEqual:待删对象的对象

说明1:removeObjectAtIndex:最多只能删除一个对象,而removeObject:可以删除多个对象(只要符合isEqual:的都删除掉)。
说明2:在NSMutableArray遍历中使用removeObject:删除该NSMutableArray内部对象,此举可能引发误删.
2、删除元素错误做法
下面罗列几种比较常见错误的做法
1)for in 循环中删除数组内部对象。

NSMutableArray *arr = [[NSMutableArray alloc]initWithObjects:@"1",@"2",@"3",@"3",@"4",@"3",@"5",@"3",@"6",nil];
 for (NSString *str in arr) { 
if ([str isEqualToString:@"3"]) { 
NSInteger index = [arr indexOfObject:@"3"];
 [arr removeObjectAtIndex:index]; 
     }
}

说明:在for in 循环中删除数组内部对象可能会引起崩溃。只有一种情况例外,在for in 循环中,如果删除的是数组中最后一个元素的话,程序就不会崩溃,这是因为当for in 循环遍历到最后一个元素时,已经遍历结束了。奔溃时候报错如下:
*** Terminating app due to uncaught exception 'NSGenericException', reason: '*** Collection <__NSArrayM: 0x610000045040> was mutated while being enumerated.'

2)for循环遍历中从前往后删除
NSMutableArray *arr = [[NSMutableArray alloc]initWithObjects:@"1",@"2",@"3",@"3",@"4",@"3",@"5",@"3",@"6",nil];
for (NSInteger i = 0; i < [arr count]; i++) {
NSString *str = [arr objectAtIndex:i];
if ([str isEqualToString:@"3"]) {
[arr removeObjectAtIndex:i];
}
}

说明:如果是删除相同元素,相同元素相邻,会被漏删。有些童鞋在for循环遍历使用removeObject,可以做到不漏删,这是因为removeObject本身特点就是删除数组中所有isEqual:待删对象的对象。因之,掩盖了问题,那么做也是不对。
3、删除元素正确做法
1)直接使用removeObject(如果是删除相同元素)
因为其本身特点就是删除数组中所有isEqual:待删对象的对象,解决删除相同元素这种问题很适合,不需要在遍历时候使用。
2)在for循环遍历从后往前删除

NSMutableArray *arr = [[NSMutableArray alloc]initWithObjects:@"1",@"2",@"3",@"3",@"4",@"3",@"5",@"3",@"6",nil];
for (NSInteger i = [arr count] - 1; i >= 0; i--) {
 NSString *str = [arr objectAtIndex:i];
 if ([str isEqualToString:@"3"]) { 
[arr removeObjectAtIndex:i]; 
    }
}

3) 遍历该可变数组copy的不可变数组,来操作可变数组。
copy是浅copy,copy的是指针,所以两数组元素指针相同,那么其实遍历不可变数组和遍历可变数组是一样的。这样我们就可以遍历不可变来操作可变。

NSMutableArray *arr = [[NSMutableArray alloc]initWithObjects:@"1",@"2",@"3",@"3",@"4",@"3",@"5",@"3",@"6",nil];
NSArray *arr1 = [arr copy];
for (NSInteger i =0; i <=  [arr count] - 1; i++) {
 NSString *str = [arr1 objectAtIndex:i];
 if ([str isEqualToString:@"3"]) { 
//[arr removeObjectAtIndex:i]; 
[arr removeObject:str]; 
str = @"sdfsdf";
    }
}

四、篇外

Class Clusters(类簇)是抽象工厂模式在iOS下的一种实现,Class Clusters仅对外暴露出简单的接口,而隐藏了内部多个私有的类和方法的实现。NSMutableArray、NSMutableDictionary就是Class Clusters(类簇)中代表。

源码参考QSUseCollectionDemo

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

推荐阅读更多精彩内容

  • [这是第12篇] 导语: NSMutableArray提供的API能解决绝大部分的需求,但是在实际iOS开发中,在...
    南华coder阅读 12,055评论 20 67
  • *面试心声:其实这些题本人都没怎么背,但是在上海 两周半 面了大约10家 收到差不多3个offer,总结起来就是把...
    Dove_iOS阅读 27,211评论 30 472
  • 1.项目经验 2.基础问题 3.指南认识 4.解决思路 ios开发三大块: 1.Oc基础 2.CocoaTouch...
    阳光的大男孩儿阅读 5,031评论 0 13
  • 从三月份找实习到现在,面了一些公司,挂了不少,但最终还是拿到小米、百度、阿里、京东、新浪、CVTE、乐视家的研发岗...
    时芥蓝阅读 42,366评论 11 349
  • 平淡的一天,有话则长,无话则短! 昨晚写完《吃瓜群众说闲事儿》还真有点小得意,一方面的确把最近断断续续关于这方面的...
    墨语花开时阅读 210评论 2 1