iOS 基礎集合類 – iPhone手機開發技術文章 iPhone軟體開發教學課程

本文參閱 objc.io 文章 https://www.objc.io/issue-7/collections.html 而進行的總結。全文中文翻譯:https://www.cocoachina.com/applenews/devnews/2014/0122/7735.html

測試環境設置: xcode5 ios7 64位 編譯設置release為-Ofast;Vectorize loops and unroll loops 設置關閉

1、集合類一般都有兩個版本:可變類型和不可變類型。其中不可變類型是完全線程安全的,而可變類型不能保證這一點。所以在API接口中,不應該返回可變類型的集合參數。有時候更有效的做法是:在程序內部保持一個可變類型的集合類對象,而返回一個復制的,不可變類型的集合類對象以供外部訪問。

2、NSArray

①縮寫的創建語法格式

  //這兩種創建 NSArray 的方式是相同的
    NSArray *array_1 = @[@"hello",@"world"];
    NSArray *array_2 = [NSArray arrayWithObjects:@"hello",@"world", nil];

②NSArray的對集合元素的下標操作

  //這兩種方式獲取 NSArray 中元素是相同的
    NSLog(@"print = %@",array_1[0]);
    NSLog(@"print = %@",[array_1 objectAtIndex:0]);

③在通過對一個array復制來創建一個可變數組的時候,由於源數組array可能為nil,所以創建的時候要考慮到nil的情況。

其正確的創建方式有:

 NSArray *array = @[@"string1",@"string2"];
        //方式1
        NSMutableArray *mutableArray_1 = [array mutableCopy];
        if (!mutableArray_1) {
            mutableArray_1 = [NSMutableArray array];
        }
        //通過 三元運算符 ?: 簡化方式1的寫法
        NSMutableArray *mutableArray_2 = [array mutableCopy]?:[NSMutableArray array];
        
        //方式2
        NSMutableArray *mutableArray_3 = [NSMutableArray arrayWithArray:array];

④對於創建NSMatableArray,介於代碼的可讀性,不要使用NSMutableArray *mutableArray
= [@[]mutableCopy];
這種方式創建;而要采用

NSMutableArray *mutableArray
= [NSMutableArray array];這種形式。
⑤可以通過
NSArray* reverseArray = array_1.reverseObjectEnumerator.allObjects;獲得一個數組的反序形式。
3、數組排序
大體上,OC中常用的數組排序有以下幾種方法:sortedArrayUsingSelector: sortedArrayUsingComparator: sortedArrayUsingDescriptors:

①使用sortedArrayUsingSelector: 進行排序(簡單排序)

NSArray *array = @[@"John Appleseed", @"Tim Cook", @"Hair Force One", @"Michael Jurewitz"];
        NSArray *sortedArray = [array sortedArrayUsingSelector:@selector(localizedCaseInsensitiveCompare:)];
        
        [sortedArray enumerateObjectsUsingBlock:^(id obj, NSUInteger idx, BOOL *stop) {
            NSLog(@"%zi --- %@",idx,obj);
        }];
        
        
        NSArray *numbers = @[@9, @5, @11, @3, @1];
        NSArray *sortedNumbers = [numbers sortedArrayUsingSelector:@selector(compare:)];
        [sortedNumbers enumerateObjectsWithOptions:NSEnumerationConcurrent usingBlock:^(id obj, NSUInteger idx, BOOL *stop) {
            NSLog(@"%zi --- %@",idx,obj);
        }];

輸出結果:

2014-01-24 10:26:24.270 Foundation[871:303] 0 --- Hair Force One
2014-01-24 10:26:24.273 Foundation[871:303] 1 --- John Appleseed
2014-01-24 10:26:24.275 Foundation[871:303] 2 --- Michael Jurewitz
2014-01-24 10:26:24.276 Foundation[871:303] 3 --- Tim Cook
2014-01-24 10:26:24.277 Foundation[871:303] 0 --- 1
2014-01-24 10:26:24.277 Foundation[871:1003] 1 --- 3
2014-01-24 10:26:24.277 Foundation[871:1907] 2 --- 5
2014-01-24 10:26:24.280 Foundation[871:303] 3 --- 9
2014-01-24 10:26:24.280 Foundation[871:1003] 4 --- 11

當然,除瞭使用字符串自帶的compare 方法,也可以自定義實現compare方法,進行對象的比較。例如:
新建一個Person類:

#import 

@interface Person : NSObject

@property(nonatomic,strong)NSString* name;
@property(nonatomic)int age;

-(void)sortArray;
@end

#import "Person.h"

@implementation Person
+(Person*)personWithAge:(int)age withName:(NSString*)name
{
    Person* person = [[Person alloc]init];
    person.age = age;
    person.name = name;
    return person;
}

//自定義排序方法
-(NSComparisonResult)comparePerson:(Person *)person{
    //默認按年齡排序
    NSComparisonResult result = [[NSNumber numberWithInt:person.age] compare:[NSNumber numberWithInt:self.age]];
    //註意:基本數據類型要進行數據轉換
    
    //如果年齡一樣,就按照名字排序
    if (result == NSOrderedSame) {
        result = [self.name compare:person.name];
    }
    return result;
}

-(void)sortArray
{
    Person *p1 = [Person personWithAge:23 withName:@"zhangsan"];
    Person *p2 = [Person personWithAge:21 withName:@"lisi"];
    Person *p3 = [Person personWithAge:24 withName:@"wangwu"];
    Person *p4 = [Person personWithAge:24 withName:@"liwu"];
    Person *p5 = [Person personWithAge:20 withName:@"liwu"];
    NSArray *array = [NSArray arrayWithObjects:p1,p2,p3,p4,p5, nil];
    NSArray *sortedArray = [array sortedArrayUsingSelector:@selector(comparePerson:)];
    [sortedArray enumerateObjectsUsingBlock:^(id obj, NSUInteger idx, BOOL *stop) {
        NSLog(@"%zi --- %@ --- %d",idx,[obj name],[obj age]);
    }];
}

@end

調用執行 sortArray 方法。
運行結果:

2014-01-24 10:59:55.417 Foundation[1234:303] 0 --- liwu --- 24
2014-01-24 10:59:55.422 Foundation[1234:303] 1 --- wangwu --- 24
2014-01-24 10:59:55.424 Foundation[1234:303] 2 --- zhangsan --- 23
2014-01-24 10:59:55.425 Foundation[1234:303] 3 --- lisi --- 21
2014-01-24 10:59:55.425 Foundation[1234:303] 4 --- liwu --- 20

註意到上面排序結果中是年齡是降序的,那麼如果想要是升序結果的話,需要修改的地方是:

NSComparisonResult result = [[NSNumber numberWithInt:self.age] compare:[NSNumber numberWithInt:person.age]];

②使用sortedArrayUsingComparator:進行排序(利用block語法)
利用block可以大大簡化代碼,例如:(接上面的例子)

-(void)sortArray_1
{
    Person *p1 = [Person personWithAge:23 withName:@"zhangsan"];
    Person *p2 = [Person personWithAge:21 withName:@"lisi"];
    Person *p3 = [Person personWithAge:24 withName:@"wangwu"];
    Person *p4 = [Person personWithAge:24 withName:@"liwu"];
    Person *p5 = [Person personWithAge:20 withName:@"liwu"];
    NSArray *array = [NSArray arrayWithObjects:p1,p2,p3,p4,p5, nil];
    
    NSArray *sortedArray = [array sortedArrayUsingComparator:^NSComparisonResult(id obj1, id obj2) {
        NSComparisonResult result = [[NSNumber numberWithInt:[obj1 age]] compare:[NSNumber numberWithInt:[obj2 age]]];
        if (result == NSOrderedSame) {
            result = [[obj1 name] compare:[obj2 name]];
        }
        return result;
    }];
    
    [sortedArray enumerateObjectsUsingBlock:^(id obj, NSUInteger idx, BOOL *stop) {
        NSLog(@"%zi --- %@ --- %d",idx,[obj name],[obj age]);
    }];
}

運行輸出結果:

2014-01-24 13:09:24.963 Foundation[1581:303] 0 --- liwu --- 20
2014-01-24 13:09:24.968 Foundation[1581:303] 1 --- lisi --- 21
2014-01-24 13:09:24.968 Foundation[1581:303] 2 --- zhangsan --- 23
2014-01-24 13:09:24.969 Foundation[1581:303] 3 --- liwu --- 24
2014-01-24 13:09:24.970 Foundation[1581:303] 4 --- wangwu --- 24

③使用sortedArrayUsingDescriptors:
進行排序(高級排序)
想象出現這樣的一種排序情況:Person類裡有另外一個類的變量,比如說Person類除瞭name,age變量,還有一輛車Car類型,Car類裡有個name屬性。對Person對象進行排序,有這樣的要求:按照Car的name排序,如果是同一輛車,也就是Car的name相同,那麼再按照年齡進行排序,如果年齡也相同,最後按照Person的name進行排序。
對於此類情況的排序,需要用到排序描述器。例如:
添加一個Car類

#import "Car.h"
@implementation Car

+(Car *)initWithName:(NSString *)name{
    Car *car = [Car alloc] init];
    car.name = name;
    return car;
}

@end

在Person類中添加一個Car對象。

+(Person *)personWithAge:(int)age withName:(NSString *)name withCar:(Car *)car{
    Person *person = [[Person alloc] init];
    person.age = age;
    person.name = name;
    person.car = car;
    return person;
}

主要代碼如下:

-(void)sortArray_2
{
    //首先來3輛車,分別是奧迪、勞斯萊斯、寶馬
    Car *car1 = [Car initWithName:@"Audio"];
    Car *car2 = [Car initWithName:@"Rolls-Royce"];
    Car *car3 = [Car initWithName:@"BMW"];
    
    //再來5個Person,每人送輛車,分別為car2、car1、car1、car3、car2
    Person *p1 = [Person personWithAge:23 withName:@"zhangsan" withCar:car2];
    Person *p2 = [Person personWithAge:21 withName:@"zhangsan" withCar:car1];
    Person *p3 = [Person personWithAge:24 withName:@"lisi" withCar:car1];
    Person *p4 = [Person personWithAge:23 withName:@"wangwu" withCar:car3];
    Person *p5 = [Person personWithAge:23 withName:@"wangwu" withCar:car2];
    
    //加入數組
    NSArray *array = [NSArray arrayWithObjects:p1,p2,p3,p4,p5, nil];
    
    //構建排序描述器
    NSSortDescriptor *carNameDesc = [NSSortDescriptor sortDescriptorWithKey:@"car.name" ascending:YES];
    NSSortDescriptor *personNameDesc = [NSSortDescriptor sortDescriptorWithKey:@"name" ascending:YES];
    NSSortDescriptor *personAgeDesc = [NSSortDescriptor sortDescriptorWithKey:@"age" ascending:YES];
    
    //把排序描述器放進數組裡,放入的順序就是你想要排序的順序
    //我這裡是:首先按照年齡排序,然後是車的名字,最後是按照人的名字
    NSArray *descriptorArray = [NSArray arrayWithObjects:personAgeDesc,carNameDesc,personNameDesc, nil];
    
    NSArray *sortedArray = [array sortedArrayUsingDescriptors: descriptorArray];
    [sortedArray enumerateObjectsUsingBlock:^(id obj, NSUInteger idx, BOOL *stop) {
        NSLog(@"%zi --- %@ --- %d --- %@",idx,[obj name],[obj age],[[obj car] name]);
    }];
}

運行輸出結果:

2014-01-24 13:04:06.008 Foundation[1552:303] 0 --- zhangsan --- 21 --- Audio
2014-01-24 13:04:06.012 Foundation[1552:303] 1 --- wangwu --- 23 --- BMW
2014-01-24 13:04:06.013 Foundation[1552:303] 2 --- wangwu --- 23 --- Rolls-Royce
2014-01-24 13:04:06.014 Foundation[1552:303] 3 --- zhangsan --- 23 --- Rolls-Royce
2014-01-24 13:04:06.016 Foundation[1552:303] 4 --- lisi --- 24 --- Audio

*補充:數組的遍歷方式

NSArray *array = @[@"string1",@"string2",@"string3"];
        //方式一:普通的for循環
        for (int i=0; i < [array count]; i++) {
            NSLog(@"%zi --- %@",i,[array objectAtIndex:i]);
        }
        
        //方式二:快輸for循環
        for (id obj in array) {
            NSLog(@"%zi --- %@",[array indexOfObject:obj],obj);
        }
        //方式三:使用block
        //1: enumerateObjectsUsingBlock 正序遍歷
        [array enumerateObjectsUsingBlock:^(id obj, NSUInteger idx, BOOL *stop) {
            NSLog(@"%zi --- %@",idx,obj);
        }];
        
        //2: enumerateObjectsUsingBlock 正序遍歷:NSEnumerationConcurrent  反序遍歷:NSEnumerationReverse
        [array enumerateObjectsWithOptions:NSEnumerationConcurrent usingBlock:^(id obj, NSUInteger idx, BOOL *stop) {
            NSLog(@"%zi --- %@",idx,obj);
        }];
        
        [array enumerateObjectsWithOptions:NSEnumerationReverse usingBlock:^(id obj, NSUInteger idx, BOOL *stop) {
            NSLog(@"%zi --- %@",idx,obj);
        }];
        
        //方式四:利用枚舉
        NSEnumerator* en = [array objectEnumerator];
        id obj = NULL;
        while (obj = [en nextObject]) {
            NSLog(@"%zi --- %@",[array indexOfObject:obj],obj);
        }

4、二分查找(要求數組已經排序)

typedef NS_OPTIONS(NSUInteger, NSBinarySearchingOptions) {
        NSBinarySearchingFirstEqual     = (1UL << 8),
        NSBinarySearchingLastEqual      = (1UL << 9),
        NSBinarySearchingInsertionIndex = (1UL << 10),
};

- (NSUInteger)indexOfObject:(id)obj 
              inSortedRange:(NSRange)r 
                    options:(NSBinarySearchingOptions)opts 
            usingComparator:(NSComparator)cmp;

用法示例:

NSArray *sortedArray = ... // must be sorted
id searchObject = ...
NSRange searchRange = NSMakeRange(0, [sortedArray count]);
NSUInteger findIndex = [sortedArray indexOfObject:searchObject 
                                inSortedRange:searchRange
                                      options:NSBinarySearchingFirstEqual
                              usingComparator:^(id obj1, id obj2)
                              {
                                  return [obj1 compare:obj2];
                              }];

5、枚舉數組 Enumeration

// First variant, using `indexesOfObjectsWithOptions:passingTest:`.
NSIndexSet *indexes = [randomArray indexesOfObjectsWithOptions:NSEnumerationConcurrent 
                                                   passingTest:^BOOL(id obj, NSUInteger idx, BOOL *stop) {
    return testObj(obj);
}];
NSArray *filteredArray = [randomArray objectsAtIndexes:indexes];

// Filtering using predicates (block-based or text)    
NSArray *filteredArray2 = [randomArray filteredArrayUsingPredicate:[NSPredicate predicateWithBlock:^BOOL(id obj, NSDictionary *bindings) {
    return testObj(obj);
}]];

// Block-based enumeration 
NSMutableArray *mutableArray = [NSMutableArray array];
[randomArray enumerateObjectsUsingBlock:^(id obj, NSUInteger idx, BOOL *stop) {
    if (testObj(obj)) {
        [mutableArray addObject:obj];
    }
}];

// Classic enumeration
NSMutableArray *mutableArray = [NSMutableArray array];
for (id obj in randomArray) {
    if (testObj(obj)) {
        [mutableArray addObject:obj];
    }
}

// Using NSEnumerator, old school.
NSMutableArray *mutableArray = [NSMutableArray array];
NSEnumerator *enumerator = [randomArray objectEnumerator];
id obj = nil;
while ((obj = [enumerator nextObject]) != nil) {
    if (testObj(obj)) {
        [mutableArray addObject:obj];
    }
}

// Using objectAtIndex: (via subscripting)
NSMutableArray *mutableArray = [NSMutableArray array];
for (NSUInteger idx = 0; idx < randomArray.count; idx++) {
    id obj = randomArray[idx];
    if (testObj(obj)) {
        [mutableArray addObject:obj];
    }
}

各種枚舉數組元素的效率比較:

Enumeration Method / Time [ms] 10.000.000 elements 10.000 elements
indexesOfObjects:, concurrent 1844.73 2.25
NSFastEnumeration (for in) 3223.45 3.21
indexesOfObjects: 4221.23 3.36
enumerateObjectsUsingBlock: 5459.43 5.43
objectAtIndex: 5282.67 5.53
NSEnumerator 5566.92 5.75
filteredArrayUsingPredicate: 6466.95 6.31

6、NSDictionary

//創建 字典 的兩種方式
        NSDictionary *dic_1 = [NSDictionary dictionaryWithObjectsAndKeys:@"value",@"key",@"value2",@"key2", nil];
        NSDictionary *dic_2 = @{@"key": @"value",@"key2":@"value2"};

//根據key訪問value的兩種方式
        NSLog(@"%@",[dic_1 objectForKey:@"key"]);
        NSLog(@"%@",dic_1[@"key"]);

7、枚舉字典

// Using keysOfEntriesWithOptions:passingTest:,optionally concurrent
NSSet *matchingKeys = [randomDict keysOfEntriesWithOptions:NSEnumerationConcurrent 
                                               passingTest:^BOOL(id key, id obj, BOOL *stop) 
{
    return testObj(obj);
}];
NSArray *keys = matchingKeys.allObjects;
NSArray *values = [randomDict objectsForKeys:keys notFoundMarker:NSNull.null];
__unused NSDictionary *filteredDictionary = [NSDictionary dictionaryWithObjects:values 
                                                                        forKeys:keys];    
    
// Block-based enumeration.
NSMutableDictionary *mutableDictionary = [NSMutableDictionary dictionary];
[randomDict enumerateKeysAndObjectsUsingBlock:^(id key, id obj, BOOL *stop) {
    if (testObj(obj)) {
        mutableDictionary[key] = obj;
    }
}];

// NSFastEnumeration
NSMutableDictionary *mutableDictionary = [NSMutableDictionary dictionary];
for (id key in randomDict) {
    id obj = randomDict[key];
    if (testObj(obj)) {
        mutableDictionary[key] = obj;
    }
}

 // NSEnumeration
 NSMutableDictionary *mutableDictionary = [NSMutableDictionary dictionary];
 NSEnumerator *enumerator = [randomDict keyEnumerator];
 id key = nil;
 while ((key = [enumerator nextObject]) != nil) {
       id obj = randomDict[key];
       if (testObj(obj)) {
           mutableDictionary[key] = obj;
       }
 }

// C-based array enumeration via getObjects:andKeys:
NSMutableDictionary *mutableDictionary = [NSMutableDictionary dictionary];
id __unsafe_unretained objects[numberOfEntries];
id __unsafe_unretained keys[numberOfEntries];
[randomDict getObjects:objects andKeys:keys];
for (int i = 0; i < numberOfEntries; i++) {
    id obj = objects[i];
    id key = keys[i];
    if (testObj(obj)) {
       mutableDictionary[key] = obj;
    }
 }

:其中註意到使用瞭 __unused 修飾,在被__unused
修飾的字段的方法,在程序中沒有被使用到時,編譯器不會報警告。)
效率比較:

Filtering/Enumeration Method Time [ms], 50.000 elements 1.000.000 elements
keysOfEntriesWithOptions:, concurrent 16.65 425.24
getObjects:andKeys: 30.33 798.49*
keysOfEntriesWithOptions: 30.59 856.93
enumerateKeysAndObjectsUsingBlock: 36.33 882.93
NSFastEnumeration 41.20 1043.42
NSEnumeration 42.21 1113.08

8、NSSet 和
NSMutableSet
NSSet 和 NSMutableSet 是保存無序對象的集合類。其中:
NSSet它是一組單值對象的集合,且NSSet實例中元素是無序,同一個對象隻能保存一個;NSSet
對象創建之後就不能對NSSet對象進行添加和刪除操作。
NSMutableSet
是可變集合,NSMutableSet有幾個很強大的方法,例如intersectSet:,minusSet:和unionSet:。

基本的用法:

一.不可變集合NSSet

1.NSSet的初始化

創建一個集合

NSSet *set1 = [[NSSet alloc] initWithObjects:@”one”, @”two”, nil];

通過數組的構建集合

NSArray *array = [NSArrayWithObjects:@”1″, @”2″, @”3″, nil];

NSSet *set2 = [[NSSet alloc] initWithArray:array];

通過已有集合構建集合

NSSet *set3 = [[NSSet alloc] initWithSet:set2];

2.NSSet常用方法

集合中對象的個數

int count = [set3 count];

以數組的形式返回集合中所有的對象

NSArray *allObjects = [set3 allObjects];

返回集合中的任意一個對象

id object = [set3 anyObject];

判斷兩個集合的元素中有包含的對象,包含返回YES,否則為NO

BOOL isContain = [set4 containsObject:@”2″];

判斷兩個集合的元素是否有相等的對象,存在返回YES,否則為NO

BOOL isIntersect = [set4 intersectsSet:set2];

判斷兩個集合的元素是否完全匹配,匹配返回YES,否則為NO

BOOL isEqual = [set4 isEqualToSet:set5];

集合4是否是集合5的子集合,如果是返回YES,否則為NO

BOOL isSubset = [set4 isSubsetOfSet:set5];

創建一個新的集合2,集合2有兩個對象

NSSet *set1 = [NSSet setWithObjects:@”a”,nil];

NSSet *set2 = [set1 setByAddingObject:@”b”];

通過已有的兩個集合,創建新的一個集合

NSSet *set7 = [NSSet setWithObjects:@”a”,nil];

NSSet *set8 = [NSSet setWithObjects:@”z”,nil];

NSSet *set9 = [set7 setByAddingObjectsFromSet:set8];

通過已有的集合和數組對象,創建一個新的集合

NSArray *array = [NSArray arrayWithObjects:@”a”,@”b”,@”c”,nil];

NSSet *set10 = [NSSet setWithObjects:@”z”,nil];

NSSet *set11 = [set10 setByAddingObjectsFromArray:array];

二、可變集合NSMutableSet

常用方法

創建一個空的集合

NSMutableSet *set1 = [NSMutableSet set];

NSMutableSet *set2 = [NSMutableSet setWithObjects:@”1″,@”2″,nil];

NSMutableSet *set3 = [NSMutableSet setWithObjects:@”a”,@”2″,nil];

集合2減去集合3中的元素,集合2最後元素隻有1個

[set2 minusSet:set3];

集合2與集合3中元素的交集,集合2最後元素隻有1個

[set2 intersectSet:set3];

集合2與集合3中的元素的並集,集合2最後元素隻有3個

[set2 unionSet:set3];

將空集合1設置為集合3中的內容

[set1 setSet:set3];

根據數組的內容刪除集合中的對象

[set2 addObjectsFromArray:array];

[set2 removeObject:@”1″];

[set]2 removeAllObjects];

9、NSIndexSet 和 NSMutableIndexSet :存儲一組無符號整數的集合

It can save a collection of unsigned integers in a very efficient
way, especially if it’s only one or a few ranges. As the name “set” already implies, each NSUInteger is
either in the index set or isn’t. If you need to store an arbitrary number of integers that are not unique, better use an NSArray.

將一個整數數組轉換為NSIndexSet:

NSIndexSet *PSPDFIndexSetFromArray(NSArray *array) {
    NSMutableIndexSet *indexSet = [NSMutableIndexSet indexSet];
    for (NSNumber *number in array) {
        [indexSet addIndex:[number unsignedIntegerValue]];
    }
    return [indexSet copy];
}

使用block語法從NSIndexSet中獲取索引:

NSArray *PSPDFArrayFromIndexSet(NSIndexSet *indexSet) {
    NSMutableArray *indexesArray = [NSMutableArray arrayWithCapacity:indexSet.count];
    [indexSet enumerateIndexesUsingBlock:^(NSUInteger idx, BOOL *stop) {
       [indexesArray addObject:@(idx)];
    }];
    return [indexesArray copy];
}

發佈留言