[這是第12篇]
導(dǎo)語: NSMutableArray提供的API能解決絕大部分的需求吮龄,但是在實(shí)際iOS開發(fā)中汰蓉,在某些場(chǎng)景下泛源,需要考慮線程安全 或 弱對(duì)象引用 或 刪除元素這三個(gè)問題究珊。
一婴氮、線程安全的NSMutableArray####
NSMutableArray本身是線程不安全的只洒。簡(jiǎn)單來說许帐,線程安全就是多個(gè)線程訪問同一段代碼,程序不會(huì)異常毕谴、不Crash成畦。而編寫線程安全的代碼主要依靠線程同步距芬。
1、不使用atomic修飾屬性
原因有二循帐,如下:
1 ) atomic 的內(nèi)存管理語義是原子性的框仔,僅保證了屬性的setter和getter方法是原子性的,是線程安全的拄养,但是屬性的其他方法离斩,如數(shù)組添加/移除元素等并不是原子操作,所以不能保證屬性是線程安全的瘪匿。
2 ) atomic雖然保證了getter跛梗、setter方法線程安全,但是付出的代價(jià)很大棋弥,執(zhí)行效率要比nonatomic慢很多倍(有說法是慢10-20倍)核偿。
總之:使用nonatomic修飾NSMutableArray對(duì)象就可以了,而使用鎖顽染、dispatch_queue來保證NSMutableArray對(duì)象的線程安全漾岳。
2、打造線程安全的NSMutableArray
在《Effective Objective-C 2.0..》書中第41條:多用派發(fā)隊(duì)列家乘,少用同步鎖中指出:使用“串行同步隊(duì)列”(serial synchronization queue)蝗羊,將讀取操作及寫入操作都安排在同一個(gè)隊(duì)列里,即可保證數(shù)據(jù)同步仁锯。而通過并發(fā)隊(duì)列耀找,結(jié)合GCD的柵欄塊(barrier)來不僅實(shí)現(xiàn)數(shù)據(jù)同步線程安全,還比串行同步隊(duì)列方式更高效业崖。
說明:柵欄塊單獨(dú)執(zhí)行野芒,不能與其他塊并行。知道當(dāng)前所有并發(fā)塊都執(zhí)行完畢双炕,才會(huì)單獨(dú)執(zhí)行這個(gè)柵欄塊狞悲。線程安全的NSMutableArray實(shí)現(xiàn)如下:
//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進(jìn)制的形式輸出內(nèi)存地址,附加前綴0x
NSString* uuid = [NSString stringWithFormat:@"com.jzp.array_%p", self];
//注意:_syncQueue是并行隊(duì)列
_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 - 數(shù)據(jù)操作方法 (凡涉及更改數(shù)組中元素的操作妇斤,使用異步派發(fā)+柵欄塊摇锋;讀取數(shù)據(jù)使用 同步派發(fā)+并行隊(duì)列)
- (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來實(shí)現(xiàn)線程同步;將同步與異步派發(fā)結(jié)合起來站超,可以實(shí)現(xiàn)與普通加鎖機(jī)制一樣的同步行為荸恕,又不會(huì)阻塞執(zhí)行異步派發(fā)的線程;使用同步隊(duì)列及柵欄塊死相,可以令同步行為更加高效融求。
說明2:NSMutableDictionary本身也是線程不全的,實(shí)現(xiàn)線程安全的NSMutableDictionary原理同線程安全的NSMutableArray算撮。(代碼見
QSUseCollectionDemo)
2生宛、線程安全的NSMutableArray使用
//線程安全的NSMutableArray
QSThreadSafeMutableArray *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:@"數(shù)組%d",(int)i+1];
[safeArray addObject:str];
});
}
sleep(1);
NSLog(@"打印數(shù)組");
[safeArray enumerateObjectsUsingBlock:^(id _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) {
NSLog(@"%@",obj);
}];
說明1:先要初始化QSThreadSafeMutableArray對(duì)象县昂,初始化工作不是線程安全的。
說明2:多個(gè)線程幾乎同時(shí)添加數(shù)據(jù)元素陷舅,使用QSThreadSafeMutableArray倒彰,沒有發(fā)生遺漏數(shù)據(jù),也沒有因?yàn)橘Y源競(jìng)爭(zhēng)導(dǎo)致的奔潰蔑赘。而NSMutableArray對(duì)象在同樣情況下會(huì)出問題(遺漏數(shù)據(jù) 或 crash)狸驳。
二、NSMutableArray弱引用對(duì)象####
在iOS中缩赛,容器類是強(qiáng)引用其存儲(chǔ)的元素的,將對(duì)象添加到容器時(shí)撰糠,該對(duì)象的引用計(jì)數(shù)+1酥馍,這很好保證了訪問容器類中元素時(shí),元素是始終存在容器類中阅酪。這種強(qiáng)引用同時(shí)也埋下了造成循環(huán)引用的可能旨袒。實(shí)現(xiàn)容器類中弱引用對(duì)象,是個(gè)考慮的問題术辐。容器類中僅以NSMutableArray為例砚尽,實(shí)現(xiàn)弱引用對(duì)象、
1辉词、NSMutableArray分類實(shí)現(xiàn)
//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中必孤,也是強(qiáng)引用values。想弱引用values瑞躺,只需要使用NSMapTable(iOS 6推出)敷搪,它不僅和字典有相似的數(shù)據(jù)結(jié)構(gòu),還可以指定key是強(qiáng)引用幢哨,value是弱引用赡勘。
2、其他實(shí)現(xiàn)
思路:將需要添加到容器中的對(duì)象捞镰,包裝在另一個(gè)存儲(chǔ)對(duì)它的弱引用的對(duì)象中闸与。
//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
** 說明 **: 我們實(shí)現(xiàn)了弱引用元素,即不希望數(shù)組保留對(duì)象岸售,這是為了解決數(shù)組中循環(huán)引用的問題践樱;但平時(shí)還是默認(rèn)使用強(qiáng)引用數(shù)組元素,因?yàn)槿跻脭?shù)組元素冰评,數(shù)組中元素在釋放映胁,數(shù)組會(huì)出問題。
三甲雅、刪除NSMutableArray中的元素####
1解孙、removeObjectAtIndex VS removeObject
removeObjectAtIndex:刪除指定NSMutableArray中指定index的對(duì)象( index不能越界)坑填。
removeObject:刪除NSMutableArray中所有isEqual:待刪對(duì)象的對(duì)象
說明1:removeObjectAtIndex:最多只能刪除一個(gè)對(duì)象,而removeObject:可以刪除多個(gè)對(duì)象(只要符合isEqual:的都刪除掉)弛姜。
說明2:在NSMutableArray遍歷中使用removeObject:刪除該NSMutableArray內(nèi)部對(duì)象脐瑰,此舉可能引發(fā)誤刪
2、刪除元素錯(cuò)誤做法
下面羅列幾種比較常見錯(cuò)誤的做法
1) 在for in 循環(huán)中刪除數(shù)組內(nèi)部對(duì)象廷臼。
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 循環(huán)中刪除數(shù)組內(nèi)部對(duì)象可能會(huì)引起崩潰苍在。只有一種情況例外,在for in 循環(huán)中荠商,如果刪除的是數(shù)組中最后一個(gè)元素的話寂恬,程序就不會(huì)崩潰,這是因?yàn)楫?dāng)for in 循環(huán)遍歷到最后一個(gè)元素時(shí)莱没,已經(jīng)遍歷結(jié)束了初肉。奔潰時(shí)候報(bào)錯(cuò)如下:
*** Terminating app due to uncaught exception 'NSGenericException', reason: '*** Collection <__NSArrayM: 0x610000045040>
was mutated while being enumerated.'
2)在for循環(huán)遍歷中從前往后刪除
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];
}
}
說明:如果是刪除相同元素,相同元素相鄰饰躲,會(huì)被漏刪牙咏。有些童鞋在for循環(huán)遍歷使用removeObject,可以做到不漏刪嘹裂,這是因?yàn)閞emoveObject本身特點(diǎn)就是刪除數(shù)組中所有isEqual:待刪對(duì)象的對(duì)象妄壶。因之,掩蓋了問題寄狼,那么做也是不對(duì)丁寄。
3、刪除元素正確做法
1)直接使用removeObject(如果是刪除相同元素)
因?yàn)槠浔旧硖攸c(diǎn)就是刪除數(shù)組中所有isEqual:待刪對(duì)象的對(duì)象例嘱,解決刪除相同元素這種問題很適合狡逢,不需要在遍歷時(shí)候使用。
2)在for循環(huán)遍歷從后往前刪除
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];
}
}
四拼卵、其他
Class Clusters(類簇)是抽象工廠模式在iOS下的一種實(shí)現(xiàn)奢浑,Class Clusters僅對(duì)外暴露出簡(jiǎn)單的接口,而隱藏了內(nèi)部多個(gè)私有的類和方法的實(shí)現(xiàn)腋腮。NSMutableArray雀彼、NSMutableDictionary就是Class Clusters(類簇)中代表。
NSMutableArray/NSArray中存儲(chǔ)的元素是允許重復(fù)的即寡,其提供的常用接口的性能有差異徊哑。 indexOfObject: 、containsObject:聪富、removeObject: 都會(huì)遍歷數(shù)組中的元素莺丑,這意味著著調(diào)用這些接口,時(shí)間復(fù)雜度至少是O(n); 而objectAtIndex:、removeLastObject梢莽、firstObject萧豆、lastObject、addObject:這些接口的時(shí)間復(fù)雜度是O(1)昏名。
-
NSArray提供的indexOfObject:inSortedRange:options:usingComparator: 使用的是二分查找涮雷,時(shí)間復(fù)雜度是 O(log n)。
//比indexOfObject:的效率高 NSArray *arr = [NSArray arrayWithObjects:@"5",@"1",@"2",@"4",@"3",nil]; NSLog(@"2 index = %ld",[arr indexOfObject:@"2" inSortedRange:NSMakeRange(0, [arr count]) options:NSBinarySearchingFirstEqual usingComparator:^NSComparisonResult(id _Nonnull obj1, id _Nonnull obj2) { if ([obj1 integerValue] > [obj2 integerValue]) { return NSOrderedDescending; // }else if ([obj1 integerValue] < [obj2 integerValue]) { return NSOrderedAscending; } return NSOrderedSame; }]);
-
NSMutableArray/NSArray的排序轻局。
NSArray *arr = [NSArray arrayWithObjects:@"5",@"1",@"2",@"4",@"3",nil]; //遞減排序 NSArray *sortArray = [arr sortedArrayUsingComparator:^NSComparisonResult(id _Nonnull obj1, id _Nonnull obj2) { if ([obj1 integerValue] > [obj2 integerValue]) { return NSOrderedAscending; // }else if ([obj1 integerValue] < [obj2 integerValue]) { return NSOrderedDescending; } return NSOrderedSame; }]; NSLog(@"sortArray = %@",sortArray); //{"5","4","3","2","1"}
End
我是南華coder洪鸭,一名北漂的初級(jí)iOS程序猿。iOS實(shí)(踐)錄系列是我的一點(diǎn)開發(fā)心得仑扑,希望能夠拋磚引玉览爵。