天天看点

OC Foundation框架 字典

NSDictionary用于保存具有映射关系的数据。因此,NSDictionary集合里保存着两组值,一组值用于保存NSDictionary里的key,另一组值用于保存NSDictionary里的value。注意:key和value都可以是任何指针类型的数据,NSDictionary的key不允许重复。

key和value之间存在一对一关系即通过指定的key,总能找到唯一的、确定的value。从NSDictionary中取出数据时,只要给出指定的key,就可以取出对应的value。NSDictionary有类似下图的结构:

OC Foundation框架 字典

所有的key组成一个NSSet集合(key没有顺序,不能重复),实际上,NSDictionary确实包含一个allKeys方法,用于返回NSDictionary所有key组成的集合,但是,NSDictionary把allKeys方法的返回值设为NSArray,说明该方法经过了进一步的转换。

NSDictionary的功能与用法

NSDictionary分别提供了类方法和实例方法来创建NSDictionary,类方法以dictionary开头,实例方法则以init开头:

  • dictionary:创建一个不包含任何key-value对的NSDictionary。
  • dictionaryWithContentsOfFile:/initWithContentsOfFile::读取指定文件的内容,使用指定的文件内容来初始化NSDictionary。该文件通常是由NSDictionary输出生成的。
  • dictionaryWithDictionary:/initWithDictionary::使用已有的NSDictionary包含的key-value对来初始化NSDictionary对象。
  • dictionaryWithObject:forKey::使用单个key-value对来创建NSDictionary对像。
  • dictionaryWithObjects:forKeys:/initWithObjects:forKeys::使用两个NSArray分别指定key、value集合,可以创建包含多个key-value对的NSDictionary。
  • dictionaryWithObjectsAndKeys:/initWithObjectsAndKeys::调用该方法时,需要按value1,key1,value2,key2,…,nil的格式传入多个key-value对。

除此之外,还可以使用如下简化语法来创建NSDictionary对象:

常用的访问该集合的key和value的方法有:

  • count:返回所有key-value对数量。
  • allKeys:返回全部key。
  • allKeysForObject::返回指定value对应的全部key。
  • allValues:返回包含的全部value。
  • objectForKey::获取指定key对应value。
  • objectForKeyedSubscript::通过该方法的支持,允许通过下标法来获取指定key对应的value。
  • valueForKey::获取指定key对应的value。
  • keyEnumerator:返回用于遍历该NSDictionary所有key的NSEnumerator对象。
  • objectEnumerator:该方法返回用于遍历该NSDictionary所有value的NSEnumerator对象。
  • enumerateKeysAndObjectsUsingBlock::使用指定的代码块来迭代执行该集合中所有的key-value对。
  • enumerateKeysAndObjectsWithOptions:usingBlock::使用指定的代码块来迭代执行该集合中所有的key-value对。可以传入一个额外的NSEnumerationOptions参数。
  • writeToFile:atomically::将该NSDictionary对象的数据写入指定文件。

示例程序:

该程序为了能更清晰地看到key-value对的详情,为NSDictionary类扩展了一个print类别,在该类别中扩展了一个print方法,用于打印NSDictionary中key-value对的详情。

该类接口部分:

#import <Foundation/Foundation.h>

@interface NSDictionary (print)
-(void) print;
@end
           

实现部分:

#import "NSDictionary+print.h"

@implementation NSDictionary (print)
-(void) print {
    NSMutableString* result = [NSMutableString stringWithString:@"{"];
    //快速枚举法遍历
    //循环计数器将依次等于每个key
    for(id key in self) {
        [result appendString: [key description]];
        [result appendString:@"="];
        //使用下标法根据key获取对应的vakue
        [result appendString:[self[key] description]];
        [result appendString:@", "];
    }
    //获取字符串长度
    NSUInteger len = [result length];
    //去掉字符串最后两个字符
    [result deleteCharactersInRange:NSMakeRange(len - 2, 2)];
    [result appendString:@"}"];
    NSLog(@"%@", result);
}
@end
           

通过key来获取value有两种方法:

  • 调用NSDictionary的objectForKey:方法
  • 直接使用下标法

也就是说,下面两行代码的功能是相同的:

[dictionary objectForKey:key];
dictionary[key];
           

测试:

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        NSDictionary* dict = [NSDictionary dictionaryWithObjectsAndKeys:
                [[User alloc] initWithName:@"Billy" andPass:@"1234"],@"one",
                [[User alloc] initWithName:@"Terasa" andPass:@"2345"],@"two",
                [[User alloc] initWithName:@"Amy" andPass:@"3456"],@"three",
                              [[User alloc] initWithName:@"John" andPass:@"4567"],@"four",
                [[User alloc] initWithName:@"Jimmy" andPass:@"5678"],@"five",
                nil];
        [dict print];
        NSLog(@"dict包含%ld个key-value对", [dict count]);
        NSLog(@"dict的所有key为:%@", [dict allKeys]);
        NSLog(@"<User[name=Billy,pass=1234]>对应的key为:%@",[dict allKeysForObject:[[User alloc] initWithName:@"Billy" andPass:@"1234"]]);
        //获取遍历dict所有value的枚举器
        NSEnumerator* en = [dict objectEnumerator];
        id value;
        //使用枚举器来遍历dict中所有value
        while(value = [en nextObject]) {
            NSLog(@"%@", value);
        }
        //使用指定代码块来迭代执行该集合中所有的key-vakue对
        [dict enumerateKeysAndObjectsUsingBlock:
         //该集合包含多少个key-value对,下面的代码块就执行相应的次数
         ^(id key, id value, BOOL* stop) {
            NSLog(@"key的值为:%@",key);
            [value say:@"Bill"];
        }];
    }
    return 0;
}
           

输出:

{one=<User[name=Billy,pass=1234]>, five=<User[name=Jimmy,pass=5678]>, three=<User[name=Amy,pass=3456]>, two=<User[name=Terasa,pass=2345]>, four=<User[name=John,pass=4567]>}
dict包含5个key-value对
dict的所有key为:(
    one,
    five,
    three,
    two,
    four
)
<User[name=Billy,pass=1234]>对应的key为:(
    one
)
<User[name=Billy,pass=1234]>
<User[name=Jimmy,pass=5678]>
<User[name=Amy,pass=3456]>
<User[name=Terasa,pass=2345]>
<User[name=John,pass=4567]>
key的值为:one
Billy说:Bill
key的值为:five
Jimmy说:Bill
key的值为:three
Amy说:Bill
key的值为:two
Terasa说:Bill
key的值为:four
John说:Bill
           

对NSDictionary的key排序

方法如下:

  • keysSortedByValueUsingSelector::根据所有value指定方法的返回值对key排序;调用该方法必须返回NSOrderedAscending、NSOrderedDescending、NSOrderedSame这三个枚举值之一。
  • keysSortedByValueUsingComparator::使用指定的代码块来遍历key-value对,并根据执行结果(必须返回NSOrderedAscending、NSOrderedDescending、NSOrderedSame这三个枚举值之一)对所有key进行排序。
  • keysSortedByValueWithOptions:usingComparator::与前一个方法的功能类似,只是可以传入一个额外的NSEnumerationOptions参数。

示例程序:

#import "NSDictionary+print.h"

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        //
        NSDictionary* dict = @{@"one": @"Billy",
                               @"two":@"Teresa",
                               @"three":@"Amy",
                               @"four":@"John"
        };
        //打印dict集合所有元素
        [dict print];
        //获取所有直接调用value的compare:方法对所有key进行排序
        //返回排序好的所有key组成的NSArray
        NSArray* keyArr1 = [dict keysSortedByValueUsingSelector:@selector(compare:)];
        NSLog(@"%@", keyArr1);
        NSArray* keyArr2 = [dict keysSortedByValueUsingComparator:
        //对value进行比较,字符串越长,即可认为value越大
        ^(id value1, id value2){
            //下面定义比较大小的标准:字符串越长,即可认为value越大
            if([value1 length] > [value2 length]) {
                return NSOrderedDescending;
            } else if ([value1 length] < [value2 length]) {
                return NSOrderedAscending;
            } else {
                return NSOrderedSame;
            }
        }];
        NSLog(@"%@", keyArr2);
        //将NSDictionary的内容输出到指定文件中
        [dict writeToFile:@"mydict.txt" atomically: YES];
    }
}
           

先使用compare:方法排序——字符串比较大小直接根据字符对应的编码进行。后面调用代码块对value比较大小,规则是value对应的字符串越长,系统就认为该value越大。程序的输出结果为:

{one=Billy, three=Amy, two=Teresa, four=John}
(
    three,
    one,
    four,
    two
)
(
    three,
    four,
    one,
    two
)
           

打开 mydict.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">
<dict>
	<key>four</key>
	<string>John</string>
	<key>one</key>
	<string>Billy</string>
	<key>three</key>
	<string>Amy</string>
	<key>two</key>
	<string>Teresa</string>
</dict>
</plist>
           

对NSDictionary的key进行过滤

NSDictionary还提供了方法对NSDictionary的所有key执行过滤,这些方法执行完将返回满足条件的key组成的NSSet。方法如下:

  • keysOfEntriesPassingTest::使用代码块迭代处理NSDictionary的每个key-value对。对NSDictionary的key-value对进行过滤,该代码块必须返回BOOL类型的值,只有当该代码块返回YES时,该key才会被保留下来;该代码块可以接受3个参数,其中第一个参数代表正在迭代处理的key,第二个参数代表正在迭代处理的value,第三个参数代表是否还要继续迭代,如果将第三个参数设为NO,那么该迭代会立即停止。
  • keysOfEntriesWithOptions:passingTest::该方法的功能与前一个方法的功能基本相同。只是该方法可以额外传入一个附加的NSEnumerationOptions选项参数。

如下程序示范了对NSDictionary进行过滤。

#import "NSDictionary+print.h"
int main(int argc, const char * argv[]) {
    @autoreleasepool {
        NSDictionary* dict = @{
            @"Billy":[NSNumber numberWithInt: 89],
            @"Teresa":[NSNumber numberWithInt: 69],
            @"Amy":[NSNumber numberWithInt: 78],
            @"John":[NSNumber numberWithInt: 109]
        };
        [dict print];
        //对NSDictionary的所有key进行过滤
        NSSet* keySet = [dict keysOfEntriesPassingTest:
            //使用代码块对NSDictionary的key-value对进行过滤
            ^(id key, id value, BOOL* stop) {
                //当value的值大于80时返回YES
                //这意味着只有value的值大于80的key才会被保存下来
                return (BOOL)([value intValue] > 80);
            }];
        NSLog(@"%@", keySet);
    }
}
           

输出:

{John=109, Billy=89, Amy=78, Teresa=69}
{(
    Billy,
    John
)}
           

使用自定义类作为NSDictionary的key

如果程序打算使用自定义类作为NSDictionary的key,则该自定义类必须满足如下需求:

  • 该自定义类正确重写过isEqual: 和hash方法。所谓正确重写,是指当两个对象通过isEqual:方法判断相等时,两个对象的hash方法返回值也相等。
  • 该自定义类必须实现了copyWithZone:方法,该方法最好返回该对象的不可变副本。

为什么要实现copyWithZone:方法呢?因为对于NSDictionary来说,key是非常关键的,NSDictionary需要根据key来访问value——从这个意义上看,key相当于NSDictionary的索引,如果key本身是可变的,且程序可以通过其他变量来修改NSDictionary的key,这就可能导致NSDictionary的“索引”值被破坏,从而导致NSDictionary的完整性被破坏。

为了让前面的User类作为NSDictionary的key,还需要让该User类实现NSCopying协议(可选的,通常建议实现),并让该User类实现copyWithZone:方法。User实现的copyWithZone:方法如下:

-(id) copyWithZone: (NSZone*) zone {
    NSLog(@"--正在复制--");
    //复制一个对象
    User* newUser = [[[self class] allocWithZone:zone] init];
    //将被复制的对象的实例变量的值赋给新对象的实例变量
    newUser->_name = _name;
    newUser->_pass = _pass;
    return newUser;
}
           

User.h:

#import <Foundation/Foundation.h>

@interface User : NSObject

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

@end
           

User.m:

#import "User.h"

@implementation User

-(id) initWithName:(NSString *) name andPass:(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];
}
-(NSUInteger) hash {
    NSLog(@"===hash===");
    NSUInteger nameHash = self.name == nil ? 0 : [self.name hash];
    NSUInteger passHash = self.pass == nil ? 0 : [self.pass hash];
    return nameHash *31 + passHash;
}
-(id) copyWithZone: (NSZone*) zone {
    NSLog(@"--正在复制--");
    //复制一个对象
    User* newUser = [[[self class] allocWithZone:zone] init];
    //将被复制的对象的实例变量的值赋给新对象的实例变量
    newUser->_name = _name;
    newUser->_pass = _pass;
    return newUser;
}
@end
           

主函数:

#import "User.h"
#import "NSDictionary+print.h"

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        User* u1 = [[User alloc] initWithName:@"Billy" andPass:@"765"];
        NSDictionary* dict = @{
            [[User alloc] initWithName:@"Bill" andPass:@"123"]:@"one",
            u1:@"two",
            [[User alloc] initWithName:@"Bill" andPass:@"123"]:@"three",
            [[User alloc] initWithName:@"Nick" andPass:@"890"]:@"four",
            [[User alloc] initWithName:@"Polly" andPass:@"761"]:@"five"
        };
        //将u1的密码设为nil
        u1.pass = nil;
        //由于NSDictionary并未直接使用u1所指的User作为key
        //而是先复制了u1所指向对象的副本,然后以该副本作为key
        //因此程序将看到dict的key不会受到任何影响
        [dict print];
    }
}
           

当程序尝试使用任何对象作为key时,会先调用copy方法来复制该key的不可变副本,实际是以该副本作为NSDictionary的key。因此,上面程序对u1的pass进行修改时,NSDictionary的所有key并不会受到任何影响。

输出结果:

===hash===
--正在复制--
===hash===
--正在复制--
===hash===
===hash===
--正在复制--
===hash===
--正在复制--
===hash===
===hash===
===hash===
===hash===
{<User[name=Bill,pass=123]>=one, <User[name=Billy,pass=765]>=two, <User[name=Polly,pass=761]>=five, <User[name=Nick,pass=890]>=four}
           

其中两个User对象的name、pass完全相同,但这两个对象通过isEqual:方法会返回YES,并且hash方法返回值也相等,因此只会保留一个。

NSMutableDictionary的功能与用法

NSMutableDictionary继承了NSDictionary,它代表一个key-value对可变的NSDictionary集合。由于NSMutableDictionary可以动态地添加key-value对,因此,创建NSMutableDictionary集合时可以指定初始容量。

NSMutableDictionary主要新增了如下方法:

  • setObject:forKey::设置一个key-value对。如果NSDictionary中没有包含与该key相同的key-value对,那么NSDictionary将会新增一个key-value对;否则该key-value对将覆盖已有的key-value对。
  • setObject:forKeyedSubscript::通过该方法的支持,允许程序通过下标法来设置key-value对。
  • addEntriesFromDictionary::将另一个NSDictionary中所有的key-value对复制到当前NSDictionary中。
  • setDictionary::用另一个NSDictionary中所有的key-value对替换当前NSDictionary中的key-value对。
  • removeObjectForKey::根据key来删除key-value对。
  • removeAllObjects:清空该NSDictionary。
  • removeObjectsForKeys::使用多个key组成的NSArray作为参数,同时删除多个key对应的key-value对。

示范程序:

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        //使用单个key-value对来创建NSMutable Dictionary对象
        NSMutableDictionary* dict = [NSMutableDictionary
            dictionaryWithObjectsAndKeys:
            [NSNumber numberWithInt:89],@"Billy", nil];
        //使用下标法设置key-value对。由于NSDictionary中已经存在该key
        //因此此处设置的value会覆盖前面的value
        dict[@"Billy"] = [NSNumber numberWithInt:99];
        [dict print];
        NSLog(@"再次添加key-value对");
        dict[@"Amy"] = [NSNumber numberWithInt: 67];
        [dict print];
        NSDictionary* dict2 = [NSDictionary
            dictionaryWithObjectsAndKeys:
            [NSNumber numberWithInt:79],@"Amy",
            [NSNumber numberWithInt:86],@"Takumi",
            [NSNumber numberWithInt:12],@"Jimmy",
            nil];
        //将另一个NSDictionary中的key-value对添加到当前NSDictionary中
        [dict addEntriesFromDictionary: dict2];
        [dict print];
        //根据key来删除key-value对
        [dict removeObjectForKey:@"Takumi"];
        [dict print];
        //同时删除多个key对应的key-value对
        [dict removeObjectsForKeys:[NSArray arrayWithObjects:@"Amy",@"Billy", nil]];
        [dict print];
    }
}
           

输出:

{Billy=99}
再次添加key-value对
{Billy=99, Amy=67}
{Jimmy=12, Billy=99, Takumi=86, Amy=79}
{Jimmy=12, Billy=99, Amy=79}
{Jimmy=12}