天天看點

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}