天天看點

OC Foundation架構 數組

NSArray代表元素有序、可重複的一個集合,集合中的每個元素都有其對應的順序索引。

NSArray的功能與用法

NSArray提供了類方法與執行個體方法來建立NSArray,兩種建立方式需要傳入的參數基本相似,隻是類方法以array開頭,示例方法以init開頭。下面介紹幾個常見的方法:

  • array:建立一個不包含任何元素的空NSArray。
  • arrayWithContentsOfFile:/initWithContentsOfFile::讀取檔案内容來建立NSArray。
  • arrayWithObject:/initWithObject::建立隻包含指定元素的NSArray。
  • arrayWithObjects:/initWithObjects::建立包含指定N個元素的NSArray。

此外,還可以使用如下簡化文法來建立NSArray對象:

參考NSArray類的文檔,可以看到NSArray集合的方法大緻包含如下幾類:

  • 查詢集合元素在NSArray中的索引。
  • 根據索引值取出NSArray集合中的元素。
  • 對集合元素整體調用方法。
  • 對NSArray集合進行排序。
  • 取出NSArray集合中的部分集合組成新集合。

示例:

#import <Foundation/Foundation.h>

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        NSArray* array = [NSArray arrayWithObjects:@"Billy",@"Peter",@"Tommy",@"Jimmy",@"Jerry", nil];
        NSLog(@"第一個元素:%@", [array objectAtIndex:0]);
        NSLog(@"索引為1的元素:%@", [array objectAtIndex:1]);
        NSLog(@"最後一個元素:%@", [array lastObject]);
        //擷取從索引為2的元素開始,以及後面3個元素組成的新集合
        NSArray* arr1 = [array objectsAtIndexes:[NSIndexSet indexSetWithIndexesInRange: NSMakeRange(2, 3)]];
        NSLog(@"%@", arr1);
        //擷取元素在集合中的位置
        NSLog(@"Tommy的位置為:%ld", [array indexOfObject:@"Tommy"]);
        //擷取元素在集合指定範圍中的位置
        NSLog(@"在2~5範圍Billy的位置為:%ld", [array indexOfObject:@"Billy" inRange: NSMakeRange(2, 3)]);
        //向數組的最後追加一個元素
        //原NSArray本身并未改變,隻是将傳回新的NSArray賦給array
        array = [array arrayByAddingObject:@"Bob"];
        //向array數組的最後追加另一個數組的所有元素
        //原NSArray本身并未改變,隻是将傳回新的NSArray賦給array
        array = [array arrayByAddingObjectsFromArray: [NSArray arrayWithObjects:@"Amy", @"John", nil]];
        for (int i = 0; i < array.count; ++i) {
            NSLog(@"%@", [array objectAtIndex: i]);
            //NSLog(@"%@", array[i]);
        }
        //擷取5~8的元素
        NSArray* arr2 = [array subarrayWithRange: NSMakeRange(5, 3)];
        //将NSArray集合元素寫入檔案
        [arr2 writeToFile:@"myFile.txt" atomically:YES];
    }
    return 0;
}
           

建立NSArray對象時可以直接傳入多個元素,其中最後一個nil表示NSArray元素結束,它并不會存入NSArray集合。

如下的語句作用是相同的,但後一種隻能在iOS5.0以上使用。

NSLog(@"%@", [array objectAtIndex: i]);
NSLog(@"%@", array[i]);
           

運作程式,看到下面結果:

第一個元素:Billy
索引為1的元素:Peter
最後一個元素:Jerry
(
    Tommy,
    Jimmy,
    Jerry
)
Tommy的位置為:2
在2~5範圍Billy的位置為:9223372036854775807
Billy
Peter
Tommy
Jimmy
Jerry
Bob
Amy
John
           

上述程式中9223372036854775807是常量NSNotFound的值。

程式使用了兩種方法向NSArray集合後面追加了元素:

  • 使用arrayByAddingObject:方法追加單個元素
  • 使用arrayWithObjects:方法将另一個數組中的所有元素追加到原數組的後面。

不管使用上面哪個方法,對原有NSArray對象都不會産生任何修改,程式隻是傳回一個新的NSArray對象。

打開myFile.txt,可以看見檔案内容如下:

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<array>
	<string>Bob</string>
	<string>Amy</string>
	<string>John</string>
</array>
</plist>
           

NSArray判斷指定元素位于NSArray集合中的索引的标準隻有一條:隻有某個集合元素與被查找元素通過isEqual: 方法比較傳回YES時,才可以認為該NSArray集合包含該元素,并不需要兩個元素是同一個元素。

示例程式:

//  User.h

#import <Foundation/Foundation.h>

@interface User : NSObject

@property (nonatomic, copy) NSString* name;
@property (nonatomic, copy) NSString* pass;
-(id) initWithName: (NSString*) aName pass: (NSString*) aPass;
-(void) say: (NSString*) content;

@end
           
//  User.m

#import "User.h"

@implementation User

-(id) initWithName:(NSString *) name pass:(NSString *) pass {
    if (self = [super init]) {
        self->_name = name;
        self->_pass = pass;
    }
    return self;
}
-(void) say: (NSString*) content {
    NSLog(@"%@說:%@",self.name, content);
}
//重寫isEqual: 方法,重寫比較方法為
//如果兩個User的name、pass相等,既可以認為它們相等
-(BOOL) isEqual: (id) other {
    if(self == other) {
        return YES;
    }
    if([other class] == User.class) {
        User* target = (User*) other;
        return [self.name isEqualToString: target.name] && [self.pass isEqualToString: target.pass];
    }
    return NO;
}
//重寫description方法,可以看到User狀态
-(NSString*) description {
    return [NSString stringWithFormat:@"<User[name=%@,pass=%@]>", self.name, self.pass];
}

@end
           

由于程式希望直接看到User對象的内部狀态,是以,程式還為User類重寫了description方法。

示例:

#import "User.h"

int main(int argc, const char * argv[]) {
    @autoreleasepool {
       NSArray* array = @[
           [[User alloc] initWithName:@"Billy" pass:@"1234"],
           [[User alloc] initWithName:@"Amy" pass:@"2345"],
           [[User alloc] initWithName:@"Mary" pass:@"3456"],
           [[User alloc] initWithName:@"John" pass:@"4567"],
           [[User alloc] initWithName:@"Lary" pass:@"5678"],];
       //查找指定新User對象在集合中的索引
        User* newUser = [[User alloc] initWithName:@"Mary" pass:@"3456"];
        NSUInteger pos = [array indexOfObject: newUser];
        NSLog(@"newUser的位置為:%ld", pos);
        NSLog(@"%@", array[pos]);
    }
    return 0;
}
           

程式傳回索引應為2。

對集合元素整體調用方法

NSArray允許對集合中所有的元素或部分元素整體調用方法,如果隻是簡單地調用集合元素的方法,則可以通過NSArray的如下兩種方法來實作。

  • makeObjectsPerformSelector::依次調用NSArray集合中每個元素的指定方法,該方法需要傳入一個SEL參數,用于指定調用哪個方法。
  • makeObjectsPerformSelector:withObject::依次調用NSArray集合中每個元素的指定方法,該方法第一個SEL參數用于指定調用哪個方法,第二個參數用于調用集合元素的方法時傳入參數。

如果希望對集合中的所有元素進行隐式周遊,并使用集合元素來執行某段代碼,則可以通過NSArray的如下方法來完成。

  • enumerateObjectsUsingBlock::周遊集合中的所有元素,并依次使用元素來執行指定的代碼塊。
  • enumerateObjectWithOptions:usingBlock::周遊集合中的所有元素,并依次使用元素來執行指定的代碼塊。該方法可以額外傳入一個參數,用于控制周遊的選項,如反向周遊。
  • enumerateObjectsAtIndexes:options:usingBlock::周遊集合中指定範圍内的元素,并依次使用元素來執行指定的代碼塊。該方法可以額外傳入一個參數,用于控制周遊的選項,如反向周遊。

上面3個方法都需要傳入一個代碼塊參數,該代碼塊必須帶3個參數,第一個參數代表正在周遊的集合元素,第二個參數代表正在周遊的集合元素的索引,第三個參數就是用于周遊集合元素的代碼塊。

示例程式:

#import <Foundation/Foundation.h>
#import "User.h"

int main(int argc, char* argv[]) {
    @autoreleasepool {
        NSArray* array = @[
            [[User alloc] initWithName:@"Billy" pass:@"1234"],
            [[User alloc] initWithName:@"Amy" pass:@"2345"],
            [[User alloc] initWithName:@"Mary" pass:@"3456"],
            [[User alloc] initWithName:@"John" pass:@"4567"],
            [[User alloc] initWithName:@"Lary" pass:@"5678"],];
        //對集合元素整體調用方法
        [array makeObjectsPerformSelector:@selector(say:)
            withObject:@"Good morning! The weather out there is fantastic!"];
        NSString* content = @"Look! There‘s a Porsche 911!";
        //疊代集合内指定範圍内的元素,并使用該元素來執行塊
        [array enumerateObjectsAtIndexes:
            [NSIndexSet indexSetWithIndexesInRange: NSMakeRange( 2, 2)]
            //options:NSEnumerationConcurrent//第2、3兩位正序輸出
            options:NSEnumerationReverse//第2、3兩位倒序輸出
            //代碼塊的第一個參數代表正在周遊的集合元素
            //代碼塊的第二個參數代表正在周遊的集合元素的索引
            //代碼塊的第三個參數用于控制是否停止周遊,将參數設定為NO即可停止周遊
            usingBlock: ^(id obj, NSUInteger idx, BOOL* stop) {
                NSLog(@"正在處理第%ld個元素:%@", idx , obj);
                [obj say:content];
            }];
    }
}
           

輸出結果:

Billy說:Good morning! The weather out there is fantastic!
Amy說:Good morning! The weather out there is fantastic!
Mary說:Good morning! The weather out there is fantastic!
John說:Good morning! The weather out there is fantastic!
Lary說:Good morning! The weather out there is fantastic!
正在處理第3個元素:<User[name=John,pass=4567]>
John說:Look! There‘s a Porsche 911!
正在處理第2個元素:<User[name=Mary,pass=3456]>
Mary說:Look! There‘s a Porsche 911!
           

不難看出,NSArray既可以對集合中所有元素整體調用某個方法,也可以通過NSIndexSet來控制隻對集合中部分元素疊代調用指定的代碼塊。雖然程式隻是調用NSArray的方法,但底層實際上會使用循環進行疊代處理調用每個集合元素,是以,這些方法都可以稱為疊代方法。

對NSArray進行排序

NSArray提供了大量的方法對集合元素進行排序,這些排序方法都以sorted開頭,最常用的排序方法如下:

  • sortedArrayUsingFunction:context::使用排序函數對集合元素進行排序,函數必須傳回NSOrderedDescending、NSOrderedAscending、NSorderedSame這些枚舉值,用于代表集合元素的大小。該方法傳回一個排序好的新NSArray對象。
  • sortedArrayUsingSelector::使用集合元素自身的方法對集合元素進行排序,函數必須傳回NSOrderedDescending、NSOrderedAscending、NSorderedSame這些枚舉值,用于代表集合元素的大小。該方法傳回一個排序好的新NSArray對象。
  • sortedArrayUsingComparator::使用代碼塊對集合元素進行排序,函數必須傳回NSOrderedDescending、NSOrderedAscending、NSorderedSame這些枚舉值,用于代表集合元素的大小。該方法傳回一個排序好的新NSArray對象。

實際上sortedArrayUsingComparator:方法時sortedArrayUsingFunction:context:方法的簡化版本。

示例程式:

#import <Foundation/Foundation.h>
//定義比較函數,根據兩個對象的intValue進行比較
NSComparisonResult intSort (id num1, id num2, void* context) {
    int v1 = [num1 intValue];
    int v2 = [num2 intValue];
    if(v1 < v2) {
        return NSOrderedAscending;
    } else if (v1 > v2) {
        return NSOrderedDescending;
    } else {
        return NSOrderedSame;
    }
}
int main(int argc, const char * argv[]) {
    @autoreleasepool {
        //初始化一個元素為NSString的NSArray對象
        NSArray* array1 = @[@"Billy",@"John",@"Tommy",@"Amy",@"Barry",@"James"];
        //使用集合元素的compare: 方法進行排序
        array1 = [array1 sortedArrayUsingSelector:@selector(compare:)];
        NSLog(@"%@", array1);
        //初始化一個元素為NSNumber的NSArray對象
        NSArray* array2 = @[
            [NSNumber numberWithInt:20],
            [NSNumber numberWithInt:12],
            [NSNumber numberWithInt:-7],
            [NSNumber numberWithInt:50],
            [NSNumber numberWithInt:19]
        ];
        array2 = [array2 sortedArrayUsingFunction:intSort context:nil];
        NSLog(@"%@", array2);
        //使用代碼塊對集合元素進行排序
        NSArray* array3 = [array2 sortedArrayUsingComparator:^(id obj1, id obj2){
            //該代碼塊根據集合元素的intValue進行比較
            if([obj1 intValue] > [obj2 intValue]) {
                return NSOrderedDescending;
            } else if ([obj1 intValue] < [obj2 intValue]) {
                return NSOrderedAscending;
            } else {
                return NSOrderedSame;
            }
        }];
        NSLog(@"%@", array3);
    }
    return 0;
}
           

集合元素本身可以比較大小,而且直接利用集合元素比較大小的方法進行排序的方法稱為自然排序;對于通過比較函數或代碼塊來指定自定義比較規則的方式,則被稱為定制排序。

使用枚舉器周遊NSArray集合元素

除了根據集合元素的索引來周遊元素,還可以調用NSArray對象的如下兩個方法來傳回枚舉器。

  • objectEnumerator :傳回NSArray集合的順序枚舉器。
  • reverseObjectEnumerator :傳回NSArray集合的逆序枚舉器。

上面兩個方法都傳回一個NSEnumerator枚舉器,該枚舉器隻包含如下兩個方法:

  • allObjects:擷取被枚舉集合中的所有元素。
  • nextObject:擷取被枚舉集合中的下一個元素。

一般來說,借助nextObject方法即可對集合元素進行枚舉:程式可以采用循環不斷擷取nextObject方法的傳回值,直到該方法的傳回值為nil結束循環。

示例程式:

#import <Foundation/Foundation.h>

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        //讀入前面寫入磁盤的檔案,用檔案内容初始化NSArray集合
        NSArray* array = [NSArray arrayWithContentsOfFile:@"myFile.txt"];
        //擷取NSArray的順序枚舉器
        NSEnumerator* en = [array objectEnumerator];
        id object;
        while(object = [en nextObject]) {
            NSLog(@"%@" , object);
        }
        NSLog(@"下面逆序周遊");
        //擷取NSArray的逆序枚舉器
        en = [array  reverseObjectEnumerator];
        while (object = [en nextObject]) {
            NSLog(@"%@" , object);
        }
    }
    return 0;
}
           

輸出:

Bob
Amy
John
下面逆序周遊
John
Amy
Bob
           

快速枚舉

OC還提供了一種快速枚舉方法來周遊集合(包括NSArray、NSSet、NSDictionary等集合)。

文法格式如下:

for(type variableName in collection) {
	//variableName自動疊代通路每個元素...
}
           

如果使用快速枚舉來周遊NSDictionary對象,那麼快速枚舉中循環計數器将依次代表NSDictionary的每個key的值。

示例程式:

#import <Foundation/Foundation.h>

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        NSArray* array = [NSArray arrayWithContentsOfFile:@"/Users/zhangbotian/Desktop/iOS/實驗程式文本檔案/myFile.txt"];
        for (id object in array) {
            NSLog(@"%@" , object);
        }
    }
    return 0;
}
           

使用快速枚舉周遊數組元素無須獲得數組長度,也無須根據索引來通路數組元素。快速枚舉的本質是一個for-each循環,它自動循環疊代數組的每個元素,當每個元素都被疊代一次後,for-each循環自動結束。

可變數組

NSArray代表元素不可變集合,一旦建立成功,程式就不能向集合中添加新的元素,不能删除集合中已有的元素,也不能替換集合元素。

注意:NSArray隻是儲存對象的指針,它隻保證這些位址不會改變,但指針指向的變量是可變的。

NSArray有一個子類:NSMutableArray,是以它可以作為NSArray使用,同時,它也代表一個集合元素可變的集合。

NSMutableArray主要新增了如下方法:

  • 添加集合元素:add開頭
  • 删除集合元素:remove開頭
  • 替換集合元素:replace開頭
  • 對集合本身排序:sort開頭

注意:NSMutableArray同樣提供sortedArrayUsingFunction:context:、sortedArrayUsingSelector:、sortedArrayUsingComparator:三個排序方法,差別很容易了解:NSArray傳回一個新的、排序好的NSArray對象,而NSMutableArray的排序方法是對集合本身排序。

示例程式:

#import <Foundation/Foundation.h>
NSString* NSCollectionToString (NSArray* array) {
    NSMutableString* result = [NSMutableString stringWithString:@"["];
    for (id obj in array) {
        [result appendString: [obj description]];
        [result appendString:@", "];
    }
    NSInteger len = [result length];
    //去掉字元串最後兩個字元
    [result deleteCharactersInRange: NSMakeRange(len - 2, 2)];
    [result appendString:@"]"];
    return result;
}
int main(int argc, const char * argv[]) {
    @autoreleasepool {
        NSMutableArray* array = [NSArray arrayWithContentsOfFile:@"/Users/zhangbotian/Desktop/iOS/實驗程式文本檔案/myFile.txt"];
        [array addObject: @"Mike"];
        NSLog(@"最後追加一個元素後:%@", NSCollectionToString(array));
        [array addObjectsFromArray:[NSArray arrayWithObjects:@"Max",@"Charlie" ,nil]];
        NSLog(@"最後追加兩個元素後:%@", NSCollectionToString(array));
        //向集合的指定位置插入一個元素
        [array insertObject:@"Katy" atIndex: 2];
        NSLog(@"在索引為2插入一個元素後:%@", NSCollectionToString(array));
        [array insertObjects:[NSArray arrayWithObjects:@"Hawk",@"Alice", nil] atIndexes:[NSIndexSet indexSetWithIndexesInRange: NSMakeRange(3, 2)]];
        NSLog(@"在插入多個元素後:%@", NSCollectionToString(array));
        [array removeLastObject];//删除最後一個元素
        NSLog(@"在删除最後一個元素後:%@", NSCollectionToString(array));
        [array removeObjectAtIndex:5];//
        NSLog(@"在删除索引為5的元素後:%@", NSCollectionToString(array));
        [array removeObjectsInRange:NSMakeRange(2, 3)];//
        NSLog(@"在删除索引為2~4的元素後:%@", NSCollectionToString(array));
        //替換索引為2的元素
        [array replaceObjectAtIndex:2 withObject:@"Omar"];
        NSLog(@"在替換索引為2的元素後:%@", NSCollectionToString(array));
    }
    return 0;
}
           

輸出結果:

最後追加一個元素後:[Bob, Amy, John, Mike]
最後追加兩個元素後:[Bob, Amy, John, Mike, Max, Charlie]
在索引為2插入一個元素後:[Bob, Amy, Katy, John, Mike, Max, Charlie]
在插入多個元素後:[Bob, Amy, Katy, Hawk, Alice, John, Mike, Max, Charlie]
在删除最後一個元素後:[Bob, Amy, Katy, Hawk, Alice, John, Mike, Max]
在删除索引為5的元素後:[Bob, Amy, Katy, Hawk, Alice, Mike, Max]
在删除索引為2~4的元素後:[Bob, Amy, Mike, Max]
在替換索引為2的元素後:[Bob, Amy, Omar, Max]
           

NSArray的KVC與KVO

NSArray允許對集合中的所有元素進行整體的KVC編碼。它提供了如下兩個方法:

  • setValue:forKey::将NSArray集合中所有元素的指定key對應屬性或執行個體變量設定為value。
  • valueForKey::傳回該NSArray集合中所有元素的指定key組成的NSArray對象。

此外,NSArray還為對集合中所有元素或部分元素進行KVO程式設計提供了如下方法:

  • addObserver:forKeyPath:options:context::為集合中的所有元素添加KVO螢幕。
  • removeObserver:forKeyPath::為集合中的所有元素删除KVO螢幕。
  • addObserver:toObjectsAtIndexes:forKeyPath:context::為集合中指定索引處的元素添加KVO螢幕。
  • removeObserver:fromObjectsAtIndexes:forKeyPath::為集合中指定索引處的元素删除KVO監聽器。

示例程式:

#import "User.h"
#import <Foundation/Foundation.h>
//定義一個函數,該函數用于把NSArray集合轉換為字元串
NSString* NSCollectionToString (NSArray* array) {
    NSMutableString* result = [NSMutableString stringWithString:@"["];
    for (id obj in array) {
        [result appendString: [obj description]];
        [result appendString:@", "];
    }
    NSInteger len = [result length];
    //去掉字元串最後兩個字元
    [result deleteCharactersInRange: NSMakeRange(len - 2, 2)];
    [result appendString:@"]"];
    return result;
}
int main(int argc , char * argv[]) {
    @autoreleasepool {
        NSArray* array = @[
            [[User alloc] initWithName:@"Billy" pass:@"1234"],
            [[User alloc] initWithName:@"Amy" pass:@"2345"],
            [[User alloc] initWithName:@"Mary" pass:@"3456"],
            [[User alloc] initWithName:@"John" pass:@"4567"],
            [[User alloc] initWithName:@"Lary" pass:@"5678"],];
        id newArr = [array valueForKey:@"name"];
        NSLog(@"%@", NSCollectionToString(newArr));
        //對集合所有元素整體進行KVC程式設計
        //将所有集合元素的name屬性改為“New Name”
        [array setValue:@"New Name" forKey:@"name"];
        NSLog(@"%@", NSCollectionToString(array));
    }
    return 0;
}
           

運作結果:

[Billy, Amy, Mary, John, Lary]
[<User[name=New Name,pass=1234]>, <User[name=New Name,pass=2345]>, <User[name=New Name,pass=3456]>, <User[name=New Name,pass=4567]>, <User[name=New Name,pass=5678]>]
           

兩個方法分别将name屬性取出和改成新值。