天天看点

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属性取出和改成新值。