您现在的位置是:首页 >技术交流 >OC学习之——Foundation框架网站首页技术交流

OC学习之——Foundation框架

晓美焰丶 2024-06-17 11:19:19
简介OC学习之——Foundation框架

目录

一、字符串(NSString与NSMutableString)

1.1 NSString字符串及功能

 1.2 NSMutableString 可变字符串

二、日期与时间

2.1 日期与时间(NSDate)

 2.2 日期格式器(NSDateFormatter)

2.3 日历(NSCalendar)与日期组件(NSDateComponents)

2.4 定时器(NSTimer)

三、对象复制

3.1 copy与mutableCopy方法

3.2 NSCopying和NSmutableCopying协议

 3.3 深复制和浅复制

3.4 setter方法的复制选项

四、OC集合概述

五、数组(NSArray和NSMutableArray)

5.1 NSArray的功能与用法

5.1.1 NSArray判断指定元素位置的标准

5.2 对集合元素整体调用方法

5.3 对NSArray进行排序

5.4 使用枚举遍历器遍历NSSArray集合元素

5.5 快速枚举(for...in)

5.6 可变数组(NSMutableArray)

5.7 NSArray的KVC与KVO

六、集合(NSSet与NSMutableSet)

6.1 NSSet的功能与用法

6.2 NSSet判断集合元素重复的标准

6.3 NSMutableSet的功能和用法

6.4 NSCountedSet的功能和用法 

七、有序集合(NSOrderedSet和NSMutableOrderedSet)

八、字典(NSDictionary和NSMutableDictionary)

8.1 NSDictionary的功能和用法

 8.2 对NSDictionary的key排序

8.3 对NSDictionary的key进行过滤

​编辑​​​​​​​

 8.4 使用自定义类作为NSDictionary的key

8.5 NSMutableDictionary的功能和用法


一、字符串(NSString与NSMutableString)

        NSString代表字符序列不可变的字符串,而NSMutable代表字符序列可变的字符串。

1.1 NSString字符串及功能

        通过NSString,我们可以:

        1、创建字符串。2、读取文件或网络URL来初始化字符串,或者将字符串写入文件或URL。3、获取字符串长度,即可获取字符串字符个数,也可获取字符串包括的字节个数。4、获取字符串中的字符或字节,即可获取指定位置的字符,也可获取指定范围的字符。5、获取字符串对应的C风格字符串。6、连接、分隔、查找、替换、比较字符串。7、对字符串中的字符进行大小写转换。

        以下用代码来展示如上的功能:

#import <Foundation/Foundation.h>

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        //使用Unicode数值数组初始化字符串
        unichar data[6] = {97,98,99,100,101,102};
        NSString *str1 = [[NSString alloc] initWithCharacters: data length: 6];
        NSLog(@"%@",str1);

        //将C风格的字符串转换为NSString对象
        char *cstr = "Hello,iOS!";
        NSString *str2 = [NSString stringWithUTF8String: cstr];
        NSLog(@"%@",str2);

        //将字符串写入指定对象
        [str2 writeToFile: @"myFile.txt" atomically:YES encoding:NSUTF8StringEncoding error: nil];

        //读取文件内容,用文件内容初始化字符串
        NSString *str3 = [NSString stringWithContentsOfFile:@"NSStringTest.m"encoding:NSUTF8StringEncoding error:nil];
        NSLog(@"%@",str3);
    }
    return 0;
}

        上面的代码还定义了一个unichar数组,该数组也是一个基本类型,就是unsigned short的别名。

        程序将str2字符串的内容写入了底层的myFile.txt文件,因此,运行此程序就会发现该文件的运行目录下多了一个myFile.txt文件,该文件内容就是str2的字符串。

        以上代码的运行结果为:

接下来我们再用一段代码来演示NSString的其他功能:

#import <Foundation/Foundation.h>

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        NSString *str1 = @"Hello";
        NSString *str2 = @"Hello";
        NSString *book = @"《疯狂iOS》";
        
        //追加字符串
        str1 = [str1 stringByAppendingString: @",iOS!"];
        NSLog(@"str1后接“,iOS”得到的字符串:%@",str1);
        
        str2 = [str2 stringByAppendingString: book];
        NSLog(@"str2后接book得到的字符串:%@",str2);
        
        //获取C风格的字符串
        const char *cstr = [str1 UTF8String];
        NSLog(@"获取的C字符串:%s",cstr);
        
        str1 = [str1 stringByAppendingFormat: @"%@是一本非常不错的书",book];
        NSLog(@"%@",str1);
        
        //获取指定字符
        NSString *s1 = [str1 substringToIndex: 5];
        NSLog(@"s1前5个字符组成的字符串:%@",s1);
        
        NSString *s2 = [str1 substringFromIndex: 10];
        NSLog(@"获取str1从第10个字符开始以后的所有字符:%@",s2);
        
        NSString *s3 = [str1 substringWithRange: NSMakeRange(5,10)];
        NSLog(@"获取str1中第5个到第10个字符:%@",s3);
        
        NSRange pos = [str1 rangeOfString: @"iOS"];
        NSLog(@"iOS在str1中出现的开始位置:%ld,长度为:%ld",pos.location,pos.length);
        
        //对字符进行大小转换
        str1 = [str1 uppercaseString];
        NSLog(@"str1的字符转化为大写:%@",str1);

        //获取字符串长度和字节数
        NSLog(@"str1的字符个数为:%lu",[str1 length]);
        NSLog(@"str1按UTF-8解码后字节数为:%lu",[str1 lengthOfBytesUsingEncoding: NSUTF8StringEncoding]);
    }
    return 0;
}

        上面的代码使用了一个NSRange类型的变量,NSRange并不是一个类,它只是一个结构体,包括了location和length两个unsigned int整型值,分别代表起始位置和长度。

        还有一个NSMakeRange(loc,len),它是一个结构体类型,包含两个参数,loc是起始位置,len是长度。表示字符串要传进来的起始位置和长度。

        以上代码的输出结果为:

        在修改字符串的时候,由于NSString字符串不可改变,因此实际上原来的字符串对象并不改变,只是将新生成的字符串重新赋值给原来的指针变量。例如这句代码:

        str1 = [str1 stringByAppendingString: @",iOS!"];

 1.2 NSMutableString 可变字符串

        NSString字符串是不可变的字符串,即一旦NSString对象被创建,其中的字符序列就不能更改了。而NSMutableString字符串就不一样了,它的字符串序列是可更改的。而且NSMutableString是NSString的子类,因此,上一节说的NSString的方法,NSMutableString都可以直接使用。

        NSMutableString提供了以下的方法来改变字符串字符序列:

appendString:追加固定字符串
appendFormat:追加带变量的字符串
deleteCharactersInRange:删除某范围字符串
insertString: atIndex:在指定位置插入字符串
replaceCharactersInRange: withString:将指定位置的字符串替换为另一个字符串

        接下来用代码来演示如上的功能:

#import <Foundation/Foundation.h>

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        NSString *book = @"《疯狂iOS》";
        NSMutableString *str1 = [NSMutableString stringWithString: @"Hello"];
        
        [str1 appendString: @",iOS!"];//追加固定字符串
        NSLog(@"%@",str1);
        
        [str1 appendFormat:@"%@是一本非常不错的图书",book];//追加带变量的字符串
        NSLog(@"%@",str1);
        
        [str1 insertString:@"fkit.org" atIndex: 6];//在指定位置插入字符串
        NSLog(@"%@",str1);
        
        [str1 deleteCharactersInRange: NSMakeRange(6, 12)];//删除第六到第十二个字符
        NSLog(@"%@",str1);
        
        [str1 replaceCharactersInRange:NSMakeRange(6, 15) withString:@"Objective-C"];//将第六到第十五个字符改为“Objective-C”
        NSLog(@"%@",str1);
    }
    return 0;
}

程序运行的结果为:

        而在NSMutableString的代码中,由于NSMutableString的字符串是可变的,因此修改字符串的时候,字符串所包含的字符序列本身就发生了改变,因此无需重新赋值,比如说上面代码的这一句:

        [str1 appendString: @",iOS!"];

二、日期与时间

2.1 日期与时间(NSDate)

        OC为处理日期、时间提供了NSDate、NSCalendar对象。

        其中,NSDate对象代表日期与时间,OC既提供了类方法来创建NSDate对象,也提供了大量init开头的方法来初始化NSDate对象。创建NSDate的类方法和实例方法基本相似,只是类方法以date开头,实例方法以init开头。

 2.2 日期格式器(NSDateFormatter)

        NSDateFormatter代表一个日期格式器,它的作用就是完成NSDate和NSString之间的转换。在进行转换时,我们首先需要创建一个NSDateFormatter对象,然后调用该对象的setDateStyle:、setTimeStyle:方法设置格式化日期、时间的风格。其中日期、时间风格支持以下几个枚举值:

NSDateFormatterNoStyle不显示日期、时间的风格
NSDateFormatterShortStyle显示“短”的日期、时间的风格
NSDateFormatterLongStyle显示“长”的日期、时间的风格
NSDateFormatterMediumStyle显示“中等”的日期、时间的风格
NSDateFormatterFullStyle显示“完整”的日期、时间的风格

        除了这几个枚举值,我们还可以通过调用setDateFormate:方法设置日期、时间的风格模版。

        如果需要将NSDate转换为NSString,可以调用NSDateFormatter的stringFromDate:方法执行格式化即可;如果需要将NSString转换为NSDate,可以调用NSDateFormatter的dateFromString:方法执行格式化即可。

接下来用代码来演示NSDate和NSDateFormatter的功能:

#import <Foundation/Foundation.h>

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        //NSDate的功能演示
        NSLog(@"----------------------以下是NSDate的功能运行结果--------------------");
        NSDate *date1 = [NSDate date]; //获取当前时间
        NSLog(@"%@",date1);
        
        NSDate *date2 = [[NSDate alloc] initWithTimeIntervalSinceNow:3600 * 24];//获取从当前时间开始的后一天的时间
        NSLog(@"%@",date2);
        
        NSDate *date3 = [[NSDate alloc] initWithTimeIntervalSinceNow:-3 * 3600 * 24];//获取从现在开始三天前的时间
        NSLog(@"%@",date3);
        
        NSDate *date4 = [[NSDate alloc] initWithTimeIntervalSince1970:3600 * 24 * 366 * 20];//获取从1970年1月1日开始往后20年的时间
        NSLog(@"%@",date4);
        
        NSLocale *cn = [NSLocale currentLocale];//NSLocale代表一个语言,这里表示中文
        NSLog(@"%@",[date1 descriptionWithLocale: cn]);//用中文输出date1的时间
        
        NSDate *earlier = [date1 earlierDate: date2];
        NSLog(@"%@",earlier);//获取两个时间中较早的时间
        
        NSDate *later = [date1 laterDate: date2];
        NSLog(@"%@",later);//获取两个时间中较晚的时间
        
        //比较两个日期用:compare:方法,它包括如下三个值
        //三个值分别代表调用compare的日期位于被比较日期之前、相同、之后
        switch([date1 compare: date3]) {
            case NSOrderedAscending: NSLog(@"date1在date3之前");
                break;
            case NSOrderedSame: NSLog(@"date1和date3时间想相同");
                break;
            case NSOrderedDescending: NSLog(@"date1在date3时间之后");
                break;
        }
        
        NSLog(@"date1和date3的时间差是%g秒",[date1 timeIntervalSinceDate: date3]);//获取两个时间的时间差
        NSLog(@"date2与现在的时间差%g秒",[date2 timeIntervalSinceNow]);//获取指定时间和现在的时间差
        
        

        //NSDateFormatter的功能
        NSLog(@"----------------以下是NSDateFormatter的功能的运行结果----------------");
        NSDateFormatter *dt = [NSDate dateWithTimeIntervalSince1970:3600 * 24 * 366 * 20];//格式化时间为从1970年1月1日开始的20年后的时间
        
        NSLocale *locales[] = {[[NSLocale alloc] initWithLocaleIdentifier:@"zh_CN"],[[NSLocale alloc] initWithLocaleIdentifier:@"en_US"]};//创建两个NSLocale分别表示中国、美国
        NSDateFormatter *df[8];//为上面两个NSLocale创建8个NSDateFormatter对象
        
        for (int i = 0; i < 2; i++) {
            df[i * 4] = [[NSDateFormatter alloc] init];
            [df[i * 4] setDateStyle:NSDateFormatterShortStyle];//设置NSDateFormatter的日期、时间风格
            [df[i * 4] setTimeStyle:NSDateFormatterShortStyle];
            [df[i * 4] setLocale: locales[i]];//设置NSDateFormatter的NSLocale
            
            df[i * 4 + 1] = [[NSDateFormatter alloc] init];
            [df[i * 4 + 1]setDateStyle:NSDateFormatterMediumStyle];//设置NSDateFormatter的日期、时间风格
            [df[i * 4 + 1]setDateStyle:NSDateFormatterMediumStyle];
            [df[i * 4 + 1] setLocale: locales[i]];//设置NSDateFormatter的NSLocale
            
            df[i * 4 + 2] = [[NSDateFormatter alloc] init];
            [df[i * 4 + 2] setDateStyle:NSDateFormatterLongStyle];//设置NSDateFormatter的日期、时间风格
            [df[i * 4 + 2] setTimeStyle:NSDateFormatterLongStyle];
            [df[i * 4 + 2] setLocale: locales[i]];//设置NSDateFormatter的NSLocale
            
            df[i * 4 + 3] = [[NSDateFormatter alloc] init];
            [df[i * 4 + 3] setDateStyle:NSDateFormatterFullStyle];//设置NSDateFormatter的日期、时间风格
            [df[i * 4 + 3] setTimeStyle:NSDateFormatterFullStyle];
            [df[i * 4 + 3] setLocale: locales[i]];//设置NSDateFormatter的NSLocale
        }
        for (int i = 0; i < 2; i++) {
            switch (i) {
                case 0: NSLog(@"-----中国日期格式------");
                    break;
                case 1: NSLog(@"-----美国日期格式------");
                    break;
            }
            NSLog(@"SHORT格式的日期格式:%@",[df[i * 4] stringFromDate: dt]);
            NSLog(@"MEDIUM格式的日期格式:%@",[df[i * 4 + 1] stringFromDate: dt]);
            NSLog(@"LONG格式的日期格式:%@",[df[i * 4 + 2] stringFromDate: dt]);
            NSLog(@"FULL格式的日期格式:%@",[df[i * 4 + 3] stringFromDate: dt]);
        }
        NSDateFormatter *df2 = [[NSDateFormatter alloc] init];
        [df2 setDateFormat:@"公元yyyy年MM月DD日HH时mm分"];//设置自定义格式器模版
        NSLog(@"%@",[df2 stringFromDate: dt]);//执行格式化
        NSString *dateStr = @"2013-03-02";
        NSDateFormatter *df3 = [[NSDateFormatter alloc] init];
        [df3 setDateFormat: @"yyyy-MM-DD"];//根据日期字符串的格式设置格式模版
        NSDate *date6 = [df3 dateFromString: dateStr];//将字符串转化为NSDate对象
        NSLog(@"%@",date6);
    }
    return 0;
}

        上面的代码用到了NSLocale,它代表一个语言、国际环境,在不同的语言、国家环境下显示出来的字符串是不一样的。 

上面代码的运行结果:

2.3 日历(NSCalendar)与日期组件(NSDateComponents)

        当需要将年、月、日的数值转换为NSDate的时候,或者从NSDate对象中获取其包含的年、月、日信息时,我们就需要将NSDate对象的各个字段数据分开提取。

        为了能分开NSDate对象包含的各个字段数据,Foundation框架提供了NSCalendar对象,该对象包含了以下两个常用方法:

        1、(NSDateComponents*)components: fromDate: :从NSDate提取年、月、日、时、分、秒各时间字段的信息。

        2、dateFromComponents:(NSDateComponents*)comps:使用comps对象包含的年、月、日、时、分、秒各时间字段的信息来创建NSDate。

        NSDateComponents对象是专门用于封装年、月、日、时、分、秒各时间字段的信息,该对象只包含了对year、month、day、hour、minute、second、week、weekday等各字段的getter和setter方法。

        以下用代码演示NSCalendar和NSDateComponents的功能:

#import <Foundation/Foundation.h>

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        NSCalendar *gregorian = [[NSCalendar alloc] initWithCalendarIdentifier: NSCalendarIdentifierGregorian];//获取代表公历的Calendar对象
        NSDate *dt = [NSDate date];//获取当前日期
        
        unsigned unitFlags = NSCalendarUnitYear | NSCalendarUnitMonth | NSCalendarUnitDay | NSCalendarUnitHour | NSCalendarUnitMinute | NSCalendarUnitSecond | NSCalendarUnitWeekday;//定义一个时间字段的旗标,指定将会获取指定年、月、日、时、分、秒的信息
        NSDateComponents *comp = [gregorian components: unitFlags fromDate: dt];//获取不同时间字段的信息
        
        //获取各时间字段的数值
        NSLog(@"现在是%ld年",comp.year);
        NSLog(@"现在是%ld月",comp.month);
        NSLog(@"现在是%ld日",comp.day);
        NSLog(@"现在是%ld时",comp.hour);
        NSLog(@"现在是%ld分",comp.minute);
        NSLog(@"现在是%ld秒",comp.second);
        NSLog(@"现在是星期%ld",comp.weekday);//这里输出的数字会比按照日历的星期数多一,是因为按西方他们是把周天当每周的第一天的
        
        NSDateComponents *comp2 = [[NSDateComponents alloc] init];//再次创建一个NSDateComponents对象
        
        //设置各时间字段的数值
        comp2.year = 2023;
        comp2.month = 5;
        comp2.day = 10;
        comp2.hour = 18;
        comp2.minute = 15;
        
        //通过NSDateComponents所包含的时间字段的数值来恢复NSDate对象
        NSDate *date = [gregorian dateFromComponents: comp2];
        NSLog(@"获取的日期为:%@",date);
    }
    return 0;
}

        上面代码的运行结果是:

        注:这里输出的星期的数字会比按照日历的星期数多一,是因为按西方他们是把周天当每周的第一天的。

2.4 定时器(NSTimer)

        当程序需要让某个方法重复执行,可以借助OC中的定时器来完成。

        通过调用NSTimer的scheduledTimerWithTimeInterval: invocation: repeats:或scheduledTimerWithTimeInterval: targe:selector: userInfo: repeats:类方法来创建NSTimer对象。调用该方法时需要传入以下参数:

        1、timeInterval:指定每隔多少秒执行一次任务

        2、invocation或target与selector:指定重复执行的任务。如果指定target和selector参数,则指定用某个对象的特定方法作为重复执行的任务;如果指定invocation参数,该参数需要传入一个NSInvocation对象,该对象也是封装target和selector的,其实也是指定用某个对象的特定方法作为重复执行的任务。

        3、userInfo:该参数用于传入额外的附加信息。

        4、repeats:该参数需要指定一个BOOL值,该参数控制是否需要重复执行任务。

        在执行完定时器后,也需要销毁定时器,只要调用定时器的invalidate方法即可。

三、对象复制

3.1 copy与mutableCopy方法

        copy方法用于复制对象的副本,复制下来的该副本是不可修改的,哪怕是调用NSMutableString的copy方法也不可修改。

        而mutableCopy方法复制下来的副本是可修改的,即使被复制的对象原本是不可修改的。例如调用mutableCopy方法复制NSString的,返回的是一个NSMutableString对象。

        因为以上方法返回的是原对象的副本,所以对复制的副本进行修改时,原对象通常不受影响。

 以下用代码演示copy和mutableCopy方法的功能:

#import <Foundation/Foundation.h>

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        //copy与mutableCopy
        NSMutableString *book = [NSMutableString stringWithString: @"疯狂iOS讲义"];//定义一个book字符串
        
        NSMutableString *bookCopy = [book mutableCopy];//用mutableCopy给book复制一个副本
        
        [bookCopy replaceCharactersInRange: NSMakeRange(2, 3) withString: @"Android"];//复制后的bookCopy副本是可以修改的,这里做个修改,对原字符串的值也没有影响
        
        NSLog(@"book的值为:%@",book);//原值
        
        NSLog(@"bookCopy的值为:%@",bookCopy);//副本修改后的值,没有问题
        NSString *str = @"fkit";//定义一个str字符串
        
        NSMutableString *strCopy = [str mutableCopy];//用mutableCopy给str复制一个副本
        
        [strCopy appendString:@".org"];//向可变字符串后面追加字符串
        NSLog(@"%@",strCopy);
        
        NSMutableString *bookCopy2 = [book copy];//用copy方法复制一个book的副本(这个副本不可变)
        [bookCopy2 appendString:@"aa"];//这里会报错,因为copy创建的副本不可变,修改了就崩了
        
    }
    return 0;
}

以上代码的运行结果是:

        在修改用copy复制的bookCopy2时,会发生报错,可见copy复制的副本是不可以被修改的。

3.2 NSCopying和NSmutableCopying协议

        当我们想将自定义类用上一节的两个方法复制副本时,我们可能会直接创建完对象后用”类名* 对象2 = [对象1 copy];“这样的格式来复制副本,但实际上直接这样复制是不对的,会报错说找不到copyWithZone:方法,mutableCopy也是一样。因此我们可以看出,自定义类是不能直接调用这两个方法来复制自身的。

        这是为什么呢?是因为当程序调用copy/mutableCopy方法复制时,程序底层需要调用copyWithZone:/mutableCopyWithZone:方法来完成复制的工作,并返回这两个方法的值。因此为了保证可以复制,需要在自定义类的接口部分声明NSCopying/NSMutableCopying协议,然后再类的实现部分增加copyWithZone:/mutableCopyWithZone:方法,因此,对自定义对象的复制应该如下所示:

先定义一个FKDog的接口部分,在接口部分写入NSCopying协议:

#import <Foundation/Foundation.h>

NS_ASSUME_NONNULL_BEGIN

@interface FKDog : NSObject<NSCopying>

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

@end

NS_ASSUME_NONNULL_END

然后再在实现部分增加copyWithZone:方法:

#import "FKDog.h"

@implementation FKDog

@synthesize name;
@synthesize age;

- (id) copyWithZone:(NSZone *)zone {
    NSLog(@"--执行copyWithZon--");
    //使用zone参数创建FKDog对象
    FKDog *dog = [[[self class] allocWithZone: zone] init];
    dog.name = self.name;
    dog.age = self.age;
    return dog;
}

@end

主函数测试及结果:

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

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        FKDog *dog1 = [FKDog new];//新建一个dog1对象
        dog1.name = [NSMutableString stringWithString:@"旺财"];//用可变字符串的方式将dog1的名字赋值为旺财
        dog1.age = 20;
        FKDog *dog2 = [dog1 copy];//不添加协议这里会报错
        //FKDog *dog3 = [dog1 mutableCopy];//不添加协议的话这里也会报错,原因是自定义类不能直接调用这两个方法来复制自身
        dog2.name = [NSMutableString stringWithString:@"snoopy"];
        dog2.age = 12;
        NSLog(@"dog1的名字是:%@,年龄是:%d",dog1.name,dog1.age);
        NSLog(@"dog2的名字是:%@,年龄是:%d",dog2.name,dog2.age);
    }
    return 0;
}

        有一个问题:前面说copy方法复制的时候,它复制的对象应该是不可变的副本,但是为什么此处调用了copy方法复制后依然是一个可变的FKDog对象呢?

        这是因为此处的FKDog没有提供对应的不可变类,自然也就无法复制不可变的FKDog对象。如果程序为FKDog提供了不可变类,当然还是应该让FKDog的copyWithZone:返回不可变的FKDog对象。

        如果重写copyWithZone:方法的时候,其父类已经实现了NSCopying协议,并重写过copyWithZone方法,那么子类重写copyWithZone:方法的时候应该先调用父类的copy方法复制从父类得到的成员变量,然后对自类中定义的成员变量进行赋值。

        假如父类已经重写copyWithZone:方法,那么子类重写copyWithZone方法的格式如下:

- (id) copyWithZone:(NSZone*)zone {
    id obj = [super copy];
    //对自类定义的成员变量赋值
    ......
    return obj;
}

 3.3 深复制和浅复制

        首先,用代码来演示一下深复制和浅复制的概念:

        我们先定义一个FKDog类,它的接口和实现部分如下所示:

#import <Foundation/Foundation.h>

NS_ASSUME_NONNULL_BEGIN

@interface FKDog : NSObject<NSCopying>

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

@end

NS_ASSUME_NONNULL_END
#import "FKDog.h"

@implementation FKDog

@synthesize name;
@synthesize age;
- (id) copyWithZone:(NSZone *)zone {
    FKDog *dog = [[[self class] allocWithZone:zone] init];
    dog.name = self.name;
    dog.age = self.age;
    return dog;
}

@end

        然后在主函数测试及结果:

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

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        FKDog *dog1 = [FKDog new];
        dog1.name = [NSMutableString stringWithString:@"旺财"];
        dog1.age = 12;
        FKDog *dog2 = [dog1 copy];
        [dog2.name replaceCharactersInRange:NSMakeRange(0, 2) withString: @"snoopy"];
        
        NSLog(@"dog1的名字是:%@",dog1.name);
        NSLog(@"dog2的名字是:%@",dog2.name);
    }
    return 0;
}

         在上面的代码里,明明只是修改了dog2的name属性,为何dog1的属性也会改变呢?这是因为name只是一个指针变量,该变量中存放的是字符串的地址而非字符串本身,因此此时赋值的效果是让dog对象的name与被复制对象的name指向了同一个字符串的地址,因此输出时,它们输出的其实是同一个字符串,一个对象的name属性被改变时,另一个对象的name属性也会相应改变。这就是浅复制,即程序只是复制了该指针的地址而非真正指向的对象。如图所示:

        因此相对的,深复制就是不仅会复制对象本身,而且会“递归”复制每个指针类型的实例变量,直到两个对象没有任何共用的部分。以下是深复制的代码:

接口部分和主函数是和浅复制一模一样的,这里就不详细说了:

接口:

#import <Foundation/Foundation.h>

NS_ASSUME_NONNULL_BEGIN

@interface FKDog : NSObject<NSCopying>

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

@end

NS_ASSUME_NONNULL_END

 主函数:

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

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        FKDog *dog1 = [FKDog new];
        dog1.name = [NSMutableString stringWithString:@"旺财"];
        dog1.age = 12;
        FKDog *dog2 = [dog1 copy];
        [dog2.name replaceCharactersInRange: NSMakeRange(0, 2) withString: @"snoopy"];
        NSLog(@"dog1的名字是:%@",dog1.name);
        NSLog(@"dog2的名字是:%@",dog2.name);
    }
    return 0;
}

深浅复制的差异主要体现在实现部分:

#import "FKDog.h"

@implementation FKDog

@synthesize name;
@synthesize age;

- (id) copyWithZone:(NSZone *)zone {
    NSLog(@"--执行copyWithZone--");
    FKDog *dog = [[[self class] allocWithZone:zone] init];
    dog.name = [self.name mutableCopy];//在这个地方与浅复制不同
    dog.age = self.age;
    return dog;
}

@end

        我们可以看出来,深复制的代码在copyWithZone:方法中给name赋值的时候,并不是直接将被复制对象的name实例变量的值赋给新对象的name实例变量,而是先用mutableCopy了一个副本,然后将副本的值赋给name,这样就保证了被复制的对象和新的对象的name变量没有共用的部分,因此两个对象的name对象的值就可以互相不干扰了。这就是深复制。

3.4 setter方法的复制选项

        前面说到在合成setter和getter方法的时候可以使用copy指示符,该指示符就是指定当程序调用setter方法复制的时候,实际上是将传入参数的副本赋给程序的实例变量。

下面定义一个FKItem类:

#import <Foundation/Foundation.h>

NS_ASSUME_NONNULL_BEGIN

@interface FKItem : NSObject

@property (nonatomic,copy) NSMutableString *name;

@end

NS_ASSUME_NONNULL_END
#import "FKItem.h"

@implementation FKItem

@synthesize name;

@end

主函数测试:

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

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        FKItem *item = [FKItem new];
        item.name = [NSMutableString stringWithString:@"疯狂iOS讲义"];
        [item.name appendString: @"fkit"];
        
    }
    return 0;
}

        在运行的时候,我们会发现,[item.name appendString:@"fkit"];这一句会报错。这是因为:
        如上的代码是用点语法setter方法给item的name赋值,这里的setter方法相当于在实现部分有如下代码:

- (void) setName: (NSMutableString*) aname {
            name = [aname copy];
        }

        而在我们定义name属性的时候用的是copy指示符,复制的是不可变的副本,因此程序赋给FKItem对象的name实例变量的值仍然是不可变字符串。

        定义合成getter、setter方法时并没有提供mutableCopy指示符,因此即使定义实例变量的时候用了可变类型,但只要使用了copy指示符,实例变量得到的值总是不可变对象。

四、OC集合概述

        OC集合类可以用于存储数量不等的多个对象,并且可以实现常用的数据结构,例如栈和队列等,除此之外,OC集合还可以用来保存具有映射关系的关联数组。

        OC的集合大致上可以分为三种体系:

NSArray代表有序、可重复的集合,很像一个数组
NSSet代表无序、不可重复的集合
NSDictionary代表具有映射关系的集合

        在实际编程里,面向的是NSArray(及其子类NSMutableArray)、NSSet(及其子类NSMutableSet)、NSDictionary(及其子类NSMutableDictionary)编程,程序创建的也可能是它们的子类的实例。

        集合类和数组不一样,数组保存的元素既可以是基本类型的值,也可以是对象(实际上是对象的指针变量);而集合里只能保存对象(实际上是对象的指针变量)。

        OC集合中,NSSet集合类似于一个罐子,把一个对象添加到NSSet集合时,NSSet无法记住添加这个元素的顺序,因此NSSet的元素不可以重复。且访问其元素,只能根据元素本身来访问。

        NSArray类似于一个数组,它可以记住每次添加元素的顺序,因此它的元素可以重复,且NSMutableArray的长度可变。访问其中的元素,只需要根据元素的索引来访问。

        NSDictionary集合也像一个罐子,只是它里面的每一项数据都由两个值组成。访问其中的元素,可以根据每项元素的key值来访问其value。

五、数组(NSArray和NSMutableArray)

5.1 NSArray的功能与用法

        NSArray分别提供了类方法和实例方法来创建NSArray,两种创建方式要传入的参数基本相似,只是类方法由array开头,实例方法以init开头。

        创建NSArray对象的几个常见方法:

array创建一个不包含任何元素的空NSArray
arrayWithContentsOfFile:/initWithContentsOfFile:读取文件内容来创建NSArray
arrayWithObject:/initWithObject:创建只包含指定元素的NSArray
arrayWithObjects:/initWithObjects:创建包含指定的n个元素的NSArray

以下用代码演示一下NSArray的用法:

#import <Foundation/Foundation.h>

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        //NSArray集合
        NSArray *arr = [NSArray arrayWithObjects:@"疯狂iOS讲义",@"疯狂111",@"疯狂222",@"疯狂333",@"疯狂444", nil];
        NSLog(@"第一个元素是:%@",[arr objectAtIndex: 0]);
        NSLog(@"索引为1的元素:%@",[arr objectAtIndex: 1]);
        NSLog(@"最后一个元素:%@",[arr lastObject]);
        
        //获取索引从2到5的元素组成的新集合
        NSArray *arr1 = [arr objectsAtIndexes: [NSIndexSet indexSetWithIndexesInRange:NSMakeRange(2, 3)]];
        NSLog(@"%@",arr1);
        
        //获取元素在集合中的位置
        NSLog(@"疯狂111的位置为:%ld",[arr indexOfObject: @"疯狂111"]);
        //获取元素在集合指定范围中的位置
        NSLog(@"疯狂111的位置为:%ld",[arr indexOfObject: @"疯狂111" inRange:NSMakeRange(2, 3)]);
        
        //向数组的末尾追加元素
        //原arr的本身没有变,只是将新返回的NSArray赋给arr
        arr = [arr arrayByAddingObject:@"晓美焰"];//追加单个元素
        arr = [arr arrayByAddingObjectsFromArray:[NSArray arrayWithObjects: @"鹿目圆",@"美树沙耶香", nil]];//将另一个数组中所有元素追加到原数组后面
        for (int i = 0; i < arr.count; i++) {
            NSLog(@"%@",[arr objectAtIndex: i]);//也可简写为:NSLog(@"%@",[array objectAtIndex: i]);
        }
        //获取array数组中索引为5到8的所有元素
        NSArray *arr2 = [arr subarrayWithRange: NSMakeRange(5, 3)];
        //将NSArray集合的元素写入文件
        [arr2 writeToFile: @"myFile.txt" atomically: YES];
        
        for (int j = 0; j < 8; j++) { //也可以用下标法来访问元素
            NSLog(@"%@",arr[j]);
        }
        
    }
    return 0;
}

        在上面的代码中,传入集合的元素中的最后一个是nil,代表NSArray元素结束,其实这个nil元素并不会存入NSArray集合中。

        上面代码还用了一个NSIndexSet集合,这个集合和NSSet的功能基本相似,区别只是NSIndexSet集合主要用于保存索引值,因此它的集合都是NSUInteger对象。

        在iOS 5.0以上的版本可以直接用下标法来访问元素,以下两个代码作用是相同的:

        [array objectAtIndex: i];

        array[i];

上面代码的运行结果:

        对于上面的方法,无论哪种都无法修改NSArray对象(因为NSArray集合本身是不能修改的),程序只是返回一个新的NSArray对象。

5.1.1 NSArray判断指定元素位置的标准

        NSSArray判断指定元素位置的标准只有一条:只有某个集合元素与被查找的元素通过isEqual:方法比较返回YES,即可认为该NSArray集合包含该元素,并不需要两个元素是同一个元素。

下面用代码来证实NSArray的比较机制:

首先先定义一个FKUser类,接口和实现如下:

#import <Foundation/Foundation.h>

NS_ASSUME_NONNULL_BEGIN

@interface FKUser : NSObject

@property (nonatomic,copy) NSString *name;
@property (nonatomic,copy) NSString *pass;

- (id) initWithName: (NSString*) aName pass: (NSString*) aPass;
- (void) say: (NSString*) content;

@end

NS_ASSUME_NONNULL_END
#import "FKUser.h"

@implementation FKUser

@synthesize name;
@synthesize pass;

- (id) initWithName:(NSString *)aName pass:(NSString *)aPass {
    if (self = [super init]) {
        name = aName;
        pass = aPass;
    }
    return self;
}
- (void) say: (NSString*) content {
    NSLog(@"%@说:%@",self.name,content);
}
- (BOOL) isEqual:(id)other {
    if (self == other) {
        return YES;
    }
    if ([other class] == FKUser.class) {
        FKUser *target = (FKUser*)other;
        return [self.name isEqualToString: target.name] && [self.pass isEqualToString: target.pass];
    }
    return NO;
}

//为了直接看到FKUser的内部状态,因此改写了description方法
- (NSString*) description {
    return [NSString stringWithFormat:@"<FKUser[name = %@,pass = %@>",self.name,self.pass];
}

@end

 主函数及运行结果:

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

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        NSArray *array = [NSArray arrayWithObjects:[[FKUser alloc] initWithName: @"sun" pass: @"123"],[[FKUser alloc] initWithName: @"bai" pass: @"345"],[[FKUser alloc] initWithName: @"zhu" pass: @"654"],[[FKUser alloc] initWithName: @"tang" pass: @"178"],[[FKUser alloc] initWithName: @"niu" pass: @"155"], nil];
        FKUser *newUser = [[FKUser alloc] initWithName: @"zhu" pass: @"654"];
        NSUInteger pos = [array indexOfObject: newUser];
        NSLog(@"newUser的位置为:%ld",pos);
    }
    return 0;
}

5.2 对集合元素整体调用方法

        对于简单的调用集合中的元素的方法,可以通过NSArray的如下两种方法:

        1、makeObjectsPerformSelector:依次调用元素中每个元素的指定方法,该方法需要传入一个SEL参数,用于指定调用哪种方法。

        2、makeObjectsPerformSelector: withObject::依次调用NSArray集合中的每个元素的指定方法,该方法第一个SEL参数用于指定调用哪个方法;第二个参数用于调用集合元素的方法时传入参数;第三个参数用于控制是否中止迭代,如果在处理某个元素后,将第三个元素赋为YES,该方法就会中止迭代使用。

        如果希望对集合中的所有元素进行隐式访问,并使用集合元素来执行某一段代码,则可通过NSArray的以下方法来完成。

        1、enumerateObjectsUsingBlock::遍历集合中的所有元素,并依次使用元素来执行指定的代码块。

        2、enumerateObjectsWithOptions: usingBlock::遍历集合中的所有元素,并依次使用元素来执行指定的代码块。该方法可以额外传入一个参数,用于控制遍历的选项,如反向遍历。

        3、enumerateObjectsAtIndexes:options:usingBlock::遍历集合中指定范围内的元素,并依次使用元素来执行指定的代码块。该方法可以传入一个选项参数,用于控制遍历的选项,如反向遍历。

        上面方法都必须传入一个代码块参数,该代码块必须带三个参数,前一个参数代表正在遍历的集合元素,第二个参数代表正在遍历的集合元素的索引。

接下来用代码来演示上面的方法:

定义一个FKUser类:

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

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        NSArray *array = [NSArray arrayWithObjects:[[FKUser alloc] initWithName: @"sun" pass: @"123"],[[FKUser alloc] initWithName: @"bai" pass: @"345"],[[FKUser alloc] initWithName: @"zhu" pass: @"654"],[[FKUser alloc] initWithName: @"tang" pass: @"178"],[[FKUser alloc] initWithName: @"niu" pass: @"155"], nil];
        FKUser *newUser = [[FKUser alloc] initWithName: @"zhu" pass: @"654"];
        NSUInteger pos = [array indexOfObject: newUser];
        NSLog(@"newUser的位置为:%ld",pos);
    }
    return 0;
}
#import "FKUser.h"

@implementation FKUser

@synthesize name;
@synthesize pass;

- (id) initWithName:(NSString *)aName pass:(NSString *)aPass {
    if (self = [super init]) {
        name = aName;
        pass = aPass;
    }
    return self;
}
- (void) say: (NSString*) content {
    NSLog(@"%@说:%@",self.name,content);
}
- (BOOL) isEqual:(id)other {
    if (self == other) {
        return YES;
    }
    if ([other class] == FKUser.class) {
        FKUser *target = (FKUser*)other;
        return [self.name isEqualToString: target.name] && [self.pass isEqualToString: target.pass];
    }
    return NO;
}
- (NSString*) description {
    return [NSString stringWithFormat:@"<FKUser[name = %@,pass = %@>",self.name,self.pass];
}

@end

主函数及运行结果:

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

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        NSArray *array = [NSArray arrayWithObjects:[[FKUser alloc] initWithName: @"sun" pass: @"123"],[[FKUser alloc] initWithName: @"bai" pass: @"345"],[[FKUser alloc] initWithName: @"zhu" pass: @"654"],[[FKUser alloc] initWithName: @"tang" pass: @"178"],[[FKUser alloc] initWithName: @"niu" pass: @"155"], nil];
        [array makeObjectsPerformSelector: @selector(say:) withObject: @"下午好,NSArray真强大!"];
        NSString *content = @"疯狂iOS讲义";
        [array enumerateObjectsAtIndexes:[NSIndexSet indexSetWithIndexesInRange:NSMakeRange(2, 2)] options:NSEnumerationReverse usingBlock:^(id obj,NSUInteger idx,BOOL *stop) {
            NSLog(@"正在处理第%ld个元素:%@",idx,obj);
            [obj say: content];
        }];
    }
    return 0;
}

 

5.3 对NSArray进行排序

        对NSArray排序的常用方法有以下几种:

        

sortedArrayUsingFunction: context:该方法使用排序函数对集合元素进行排序,该排序函数必须返回NSOrderedDescending(降序)、NSOrderedAscending(升序)、NSOrderedSame(同序)这些枚举值,用于代表集合元素的大小。该方法返回一个排好序的新NSArray对象。     
sortedArrayUsingSelector:该方法使用集合元素自身的方法对集合元素进行排序,集合元素的该方法必须返回NSOrderedDescending(降序)、NSOrderedAscending(升序)、NSOrderedSame(同序)这些枚举值,用于代表集合元素的大小。返回一个排好序的新NSArray对象。
sortedArrayUsingComparator:该方法使用代码块对集合元素进行排序,该代码块必须返回NSOrderedDescending(降序)、NSOrderedAscending(升序)、NSOrderedSame(同序)这些枚举值,用于代表集合元素的大小。返回一个排好序的新NSArray对象。

以下用代码来演示一下上述的方法:

#import <Foundation/Foundation.h>

//定义一个比较函数,根据两个对象的intValue进行比较
NSInteger 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 {
        NSArray *array1 = [NSArray arrayWithObjects: @"Objective-C",@"C",@"C++",@"Ruby",@"Perl",@"Python",nil];//初始化一个元素为NSString的NSArray对象
        //使用集合的compare:方法进行排序
        array1 = [array1 sortedArrayUsingSelector: @selector(compare:)];
        NSLog(@"%@",array1);
        
        NSArray *array2 = [NSArray arrayWithObjects: [NSNumber numberWithInt:20],[NSNumber numberWithInt:12],[NSNumber numberWithInt:-8],[NSNumber numberWithInt:50],[NSNumber numberWithInt:19],nil];//初始化一个元素为int的NSArray对象
        //使用intSort函数进行排序
        array2 = [array2 sortedArrayUsingFunction: intSort context: nil];
        NSLog(@"%@",array2);
        
        //使用代码块对array2的元素进行排序
        NSArray *array3 = [array2 sortedArrayUsingComparator: ^(id obj1,id obj2) {
            //该代码块根据集合元素的intValue进行比较
            if ([obj1 intValue] > [obj2 intValue]) {
                return NSOrderedDescending;
            }
            if ([obj1 intValue] < [obj2 intValue]) {
                return NSOrderedAscending;
            }
            return NSOrderedSame;
        }];
        NSLog(@"%@",array3);
    }
    return 0;
}

运行结果:

​​​​​​​

        在上述代码中,我们可以看见第一种方法使用NSString自身的compare:方法进行排序。这是因为NSString自身已经实现了compare:方法,这意味着NSString对象本身就可以比较大小——NSString自身比较大小的方法是根据字符对应的编码来的

        compare:方法的比较是:compare在要比较的字符串中,依次取出对应的数组元素,按ascii码值比较,如果ascii值能比较出结果了,就不往后比较。默认返回值为升序

        后两种方法通过调用代码块或者函数来比较大小,代码块相当于一个匿名函数,因此后面两种方式的本质是一样的,它们都可以通过自定义的比较规则来比较集合元素的大小。

5.4 使用枚举遍历器遍历NSSArray集合元素

        可以调用NSArray对象的如下两个方法来返回枚举器:

        1、objectEnumerator:返回NSArray集合的顺序枚举器。

        2、reverseObjectEnumerator:返回NSArray逆序枚举器。

        上面两个方法都返回一个NSEnumerator枚举器,该枚举器只包含如下两个方法:

        1、allObjects:获取被枚举集合中的所有元素。

        2、nextObject:获取被枚举集合中的下一个元素。

        借助nextObject方法即可对集合元素进行枚举:程序可采用循环不断获取nextObject方法的返回值,直到该方法的返回值为nil结束循环。

用以下代码演示上述方法:

#import <Foundation/Foundation.h>

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        NSArray *array = [NSArray arrayWithObjects: @"晓美焰",@"鹿目圆",@"巴麻美",@"美树沙耶香",nil];//初始化一个NSArray集合
        //获取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;
}

运行结果:

5.5 快速枚举(for...in)

        OC提供了一种快速枚举的方法来遍历集合(包括NSArray、NSSet、NSDictionary等集合),使用快速枚举遍历集合元素的时候,无需获取集合的长度,也无需根据索引来访问集合元素,即可快速枚举自动遍历集合的每个元素。其语法格式如下: 

for (type variableName in collection) {
    //variableName自动迭代访问每个元素
}

        在上面的语法格式中,type是集合元素的类型,variableName是一个形参名,快速枚举将自动将集合元素赋给该变量。如果使用快速枚举来遍历NSDictionary对象,快速枚举中循环计数器依次代表NSDictionary的每个key值。

        快速枚举的本质是一个foreach循环,foreach循环和普通循环不同的是,它无需循环条件,也无需循环迭代语句,这些部分都是由系统来完成的,foreach循环自动迭代数组的每个元素,当每个元素都被迭代一次后,foreach循环自动结束。

代码示例:

#import <Foundation/Foundation.h>

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        NSArray *array = [NSArray arrayWithObjects:@"晓美焰",@"鹿目圆",@"巴麻美",@"美树沙耶香",nil];
        for (id object in array) {
            NSLog(@"%@",object);
        }
    }
    return 0;
}

运行结果:

         

5.6 可变数组(NSMutableArray)

        NSArray代表元素不可变的集合,一旦NSArray创建成功,程序中不能向集合中添加新的元素,不能删除已有的元素,也不能替换集合元素。NSArray只是保存对象的指针,因此,NSArray只保证这些指针变量中的地址不能改变,但指针变量所指向的对象是可改变的。

        NSMutableArray是NSArray的子类,因此它可以当作NSArray使用。它代表一个元素可变的集合,因此它程序可以向它中增添、删除、替换元素。创建NSMutableArray时可以通过参数指定底层数组的初始容量。

        NSMutableArray新增了以下方法:

添加集合元素的方法以add开头
删除集合元素的方法以remove开头
替换集合中元素的方法以replace开头
对集合本身排序的方法以sort开头

        NSMutableArray还提供了sortUsingSelector:、sortUsingComparator:、sortUsingFunction: context:方法,它们与前面介绍的NSArray的排序的方法类似,区别是NSArray的排序的方法返回的是一个新的NSArray对象,而NSMutableArray返回的是排序后的原来的对象。

下面用代码演示:

#import <Foundation/Foundation.h>

//定义一个函数,该函数用于把NSArray集合转换为字符串
//这样方便我们调试的时候看到NSArray集合中的元素
NSString *NSCollectionToString(NSArray *array) {
    NSMutableString *result = [NSMutableString stringWithString:@"["];
    for (id obj in array) {
        [result appendString: [obj description]];
        [result appendString:@","];
    }
    NSUInteger len = [result length];//获取字符串长度
    [result deleteCharactersInRange:NSMakeRange(len - 1, 1)];//去掉最后一个字符
    [result appendString:@"]"];
    return result;
}

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        //初始化NSMutableArray集合
        NSMutableArray *array = [NSMutableArray arrayWithObjects:@"晓美焰",@"鹿目圆",@"巴麻美",@"美树沙耶香", nil];
        
        //向集合最后追加元素
        [array addObject:@"佐仓杏子"];
        NSLog(@"追加一个元素后:%@",NSCollectionToString(array));
        [array addObjectsFromArray:[NSArray arrayWithObjects:@"丘比",@"仁美", nil]];
        NSLog(@"最后追加两个元素后:%@",NSCollectionToString(array));
        
        //向集合指定位置插入元素
        [array insertObject:@"蓓蓓" atIndex:2];
        NSLog(@"在索引为2的位置插入一个元素后:%@",NSCollectionToString(array));
        [array insertObjects:[NSArray arrayWithObjects:@"吼拉姆",@"馒头卡", 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-5的元素后:%@",NSCollectionToString(array));
        
        //替换集合中指定位置的元素
        [array replaceObjectAtIndex:2 withObject:@"Q币"];
        NSLog(@"替换索引为2处的元素后:%@",NSCollectionToString(array));
    }
    return 0;
}

运行结果:

5.7 NSArray的KVC与KVO

        NSArray是一个容纳多个对象的集合,它允许直接对集合中的所有元素进行整体的KVC编码,NSArray提供了如下两个方法:

        1、setValue: forKey::将NSArray集合中所有元素的指定key对应属性或实例变量设置为value。

        2、valueForKey::返回该NSArray集合中所有元素的指定key所组成的NSArray对象。

        除此之外,NSArray还为对集合中的所有元素或部分元素进行KVO编程提供了如下方法:

        1、addObserver: forKeyPath: options: context::为集合中的所有元素添加KVO监听器。

        2、removeObserver: forKeyPath::为集合中的所有元素删除KVO监听器。

        3、addObserver: toObjectsAtIndexes: forKeyPath: options: context::为集合中指定索引处的元素添加KVO监听器。

        4、removeObserver: fromObjectsAtIndexes: forKeyPath::为集合中指定索引处的元素删除KVO监听器。

六、集合(NSSet与NSMutableSet)

6.1 NSSet的功能与用法

        在前面说过,NSSet集合就像一个罐子,把对象放进去后是无序的,因此里面的元素不能重复。NSSet按Hash算法来存储集合中的元素,因此具有很好的存取和查找功能。与NSArray相比,NSSet最大的区别是元素没有索引,因此前面的NSArray的所有关于索引的方法都不能用于NSSet。

        但是NSSet和NSArray还是有相似之处,比如:1、它们都可以通过count方法来获取集合元素的数量。2、都可以使用快速枚举来集合遍历元素。3、都可以通过objectEnumerator方法获取NSEnumerator枚举器对集合元素进行遍历。4、都提供了makeObjectsPerformSelector:、makeObjectsPerformSelector:withObject:方法对集合元素整体调用某个方法,以及enumerateObjectsUsingBlock:、enumerateObjectsWithOptions:usingBlock对集合整体或部分元素迭代执行代码块。5、都提供了valueForKey:和setValue: forKey:方法对集合元素进行KVC编程。6、都提供了集合所有元素和部分元素进行KVC编程的方法。

        在NSSet集合中同样,以set开头的是类方法,以init开头的是实例方法。

接下来用代码来介绍NSSet的各方法:

#import <Foundation/Foundation.h>

//定义一个函数,可以把NSSet集合转化为字符串
//方便我们调试观察结果
NSString *NSCollectionToString(id collection) {
    NSMutableString *result = [NSMutableString stringWithString: @"["];
    for (id obj in collection) {
        [result appendString: [obj description]];
        [result appendString:@","];
    }
    NSUInteger len = [result length];//获取字符串长度
    [result deleteCharactersInRange: NSMakeRange(len - 1, 1)];//去除最后一个字符
    [result appendString: @"]"];
    return result;
}

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        //初始化集合set1和set2
        //在初始化集合set1的时候故意传入两个相同的元素,可以看到结果是只保留了一个
        NSSet *set1 = [NSSet setWithObjects: @"晓美焰", @"晓美焰", @"鹿目圆", @"巴麻美", nil];
        NSLog(@"set1集合中元素个数为%ld", [set1 count]);
        NSLog(@"s1集合:%@",NSCollectionToString(set1));
        NSSet *set2 = [NSSet setWithObjects: @"巴麻美", @"美树沙耶香", @"佐仓杏子",  nil];
        NSLog(@"s2集合:%@", NSCollectionToString(set2));
        
        //向集合中追加单个元素
        set1 = [set1 setByAddingObject: @"丘比"];
        NSLog(@"添加一个元素后:%@", NSCollectionToString(set1));
        
        //获取两个集合的并集
        NSSet *s = [set1 setByAddingObjectsFromSet: set2];
        NSLog(@"set1和set2的并集:%@", NSCollectionToString(s));
        
        //判断两个集合是否有交集
        BOOL b = [set1 intersectsSet: set2];
        NSLog(@"set1和set2是否有交集:%d", b);
        
        //判断一个集合是否是另一个集合的子集
        BOOL bo = [set2 isSubsetOfSet: set1];
        NSLog(@"set2是否是set1的子集:%d", bo);
        
        //判断集合中是否包含某个元素
        BOOL bb = [set1 containsObject: @"鹿目圆"];
        NSLog(@"set1是否包含鹿目圆:%d", bb);
        
        //随机从集合中取出一个元素,但是同时写两个下面的代码输出的结果是相同的
        NSLog(@"set1随机取出一个元素:%@", [set1 anyObject]);
        NSLog(@"set1随机取出一个元素:%@", [set1 anyObject]);
        
        //使用代码块对集合元素进行过滤
        NSSet *filteredSet = [set2 objectsPassingTest: ^(id obj,BOOL *stop) {
            return (BOOL)([obj length] > 3);
        }];
        NSLog(@"set2中的元素长度大于3的集合元素有:%@", NSCollectionToString(filteredSet));
    }
    return 0;
}

运行结果:

6.2 NSSet判断集合元素重复的标准

        当向NSSet集合中存入一个元素时,NSSet会调用该对象的Hash方法来得到对象的hashCode值,然后根据该值决定该对象在底层Hash表中的存储位置,如果根据hashCode计算出该元素在底层Hash表中的存储位置已经不相同,那么系统自然的将它们存在不同的位置。

        如果两个元素的hashCode相同,接下来就要通过isEqual:方法判断两个元素是否相等,如果有两个元素通过isEqual:方法比较返回NO,NSSet依然认为它们不相等,NSSet会把它们都存在底层的Hash表的同一个位置,只是将在这个位置形成链,后面的元素添加失败。

        因此,HashSet集合判断两个元素相等的标准为:1、两个对象通过isEqual:方法比较返回YES;2、两个对象的hash方法返回值相等。

用代码演示:

首先,还是写出FKUser:

#import <Foundation/Foundation.h>

NS_ASSUME_NONNULL_BEGIN

@interface FKUser : NSObject

@property (nonatomic,copy) NSString *name;
@property (nonatomic,copy) NSString *pass;

- (id) initWithName: (NSString*) aName pass: (NSString*) aPass;
- (void) say: (NSString*) content;

@end

NS_ASSUME_NONNULL_END
#import "FKUser.h"

@implementation FKUser

@synthesize name;
@synthesize pass;

- (id) initWithName:(NSString *)aName pass:(NSString *)aPass {
    if (self = [super init]) {
        name = aName;
        pass = aPass;
    }
    return self;
}
- (void) say: (NSString*) content {
    NSLog(@"%@说:%@",self.name,content);
}
- (BOOL) isEqual:(id)other {
    if (self == other) {
        return YES;
    }
    if ([other class] == FKUser.class) {
        FKUser *target = (FKUser*)other;
        return [self.name isEqualToString: target.name] && [self.pass isEqualToString: target.pass];
    }
    return NO;
}
- (NSString*) description {
    return [NSString stringWithFormat:@"<FKUser[name = %@,pass = %@>",self.name,self.pass];
}

@end

 然后是主函数和运行结果:

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

NSString *NSCollectionToString (id array) {
    NSMutableString *result = [NSMutableString stringWithString: @"["];
    for (id obj in array) {
        [result appendString: [obj description]];
         [result appendString: @","];
    }
    NSUInteger len = [result length];
    [result deleteCharactersInRange: NSMakeRange(len - 1, 1)];
    [result appendString: @"]"];
    return result;
}

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        NSSet *set = [NSSet setWithObjects: [[FKUser alloc] initWithName: @"sun" pass: @"123"], [[FKUser alloc] initWithName: @"bai" pass: @"345"], [[FKUser alloc] initWithName: @"sun" pass: @"123"], [[FKUser alloc] initWithName: @"tang" pass: @"178"], [[FKUser alloc] initWithName: @"niu" pass: @"155"], nil];
        NSLog(@"set集合元素的个数:%ld", [set count]);
        NSLog(@"%@", NSCollectionToString(set));
    }
    return 0;
}

 

 

        在主函数为集合添加元素的时候,故意将相同值的元素添加了两遍,结果发现那个元素在集合中真就出现了两遍,但是我们知道NSSet中是不能有重复元素的,这是为什么呢,这是因为该程序只重写了isEqual方法但是没有重写hash方法,然后导致两个新元素的hashCode不相同,使得NSSet认为它们两个不相等,就都存到集合中了。因此,应该再按如下重写hash方法:

- (NSUInteger) hash {
    NSLog(@"===hash===");
    NSUInteger nameHash = name == nil ? 0 : [name hash];
    NSUInteger passHash = pass == nil ? 0 : [pass hash];
    return nameHash * 31 + passHash;
}

        重写hash方法的基本规则:

        1、程序运行过程中,同一个对象多次调用hash方法应该返回相同的值。

        2、当两个对象通过isEqual:方法比较返回YES时,两个对象的hash应该返回相同的值

        3、对象中作为isEqual:比较标准的实例变量,都应该用来hashCode值 

6.3 NSMutableSet的功能和用法

        和前面类似,NSMutableSet和NSSet的区别是前者可变后者不可变。NSMutableSet在NSSet的基础上新增了这几个方法:

addObject:向集合中添加单个元素
removeObject:从集合中删除单个元素
removeAllObject:删除集合中所有元素
addObjectsFromArray:使用NSArray数组作为参数,向NSSet集合中添加参数数组中的所有元素
unionSet:计算两个NSSet元素的并集
minusSet:计算两个NSSet集合的差集
intersectSet:计算两个NSSet集合的交集
setSet:用后一个集合的元素替换已有集合中所有元素

用代码演示上述方法:

#import <Foundation/Foundation.h>

//定义一个函数,可以把NSSet集合转化为字符串
//方便我们调试观察结果
NSString *NSCollectionToString(id collection) {
    NSMutableString *result = [NSMutableString stringWithString: @"["];
    for (id obj in collection) {
        [result appendString: [obj description]];
        [result appendString: @","];
    }
    NSUInteger len = [result length];//获取字符串长度
    [result deleteCharactersInRange: NSMakeRange(len - 1, 1)];//去除最后一个字符
    [result appendString: @"]"];
    return result;
}

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        //创建一个初始容量为10的set集合
        NSMutableSet *set = [NSMutableSet setWithCapacity: 10];
        
        //向集合中添加一个元素
        [set addObject: @"疯狂iOS讲义"];
        NSLog(@"set添加一个元素后:%@", NSCollectionToString(set));
        
        //利用NSArray向集合中添加多个元素
        [set addObjectsFromArray: [NSArray arrayWithObjects: @"疯狂andro讲义", @"疯狂Ajax讲义", @"疯狂XML讲义", nil]];
        NSLog(@"set使用NSArray添加三个元素后:%@", NSCollectionToString(set));
        
        //删除集合中指定元素
        [set removeObject: @"疯狂XML讲义"];
        NSLog(@"set删除一个元素后:%@", NSCollectionToString(set));
        
        NSSet *set2 = [NSSet setWithObjects: @"晓美焰", @"疯狂iOS讲义", nil];
        //计算两个集合的并集
        [set unionSet: set2];
        NSLog(@"set和set2的并集:%@", NSCollectionToString(set));
        //计算两个集合的差集
        [set minusSet: set2];
        NSLog(@"set和set2的差集:%@", NSCollectionToString(set));
        //计算两个集合的交集
        [set intersectSet: set2];
        NSLog(@"set和set2的交集:%@", NSCollectionToString(set));
        //用set2的集合元素替换set集合的所有元素
        [set setSet: set2];
        NSLog(@"用set2的集合元素替换set集合的所有元素:%@", NSCollectionToString(set));
    }
    return 0;
}

运行结果:

6.4 NSCountedSet的功能和用法 

         NSCountedSet是NSMutableSet的子类,它与NSMutableSet集合不同的是:NSCountedSet为每个元素额外维护一个添加次数的状态。当程序向NSCountedSet中添加一个元素的时候,如果NSCountedSet集合中不包含该元素,NSCountedSet接纳该元素,并将该元素的添加次数标记为1;当程序向NSCountedSet中添加一个元素的时候,如果NSCountSet集合中已经包含该元素,NSCountedSet不会接纳该元素,但会将该元素添加次数加一。

        当程序从NSCountedSet中删除元素时,NSCountedSet只是将该元素的添加次数减一,只有当该元素添加次数变为0的时候,该元素才会真正的从NSCountedSet中删除。

        它提供了countForObject:方法来获取指定元素的添加次数。

用下面的代码来演示其方法:

#import <Foundation/Foundation.h>

//定义一个函数,可以把NSSet集合转化为字符串
//方便我们调试观察结果
NSString *NSCollectionToString(id collection) {
    NSMutableString *result = [NSMutableString stringWithString: @"["];
    for (id obj in collection) {
        [result appendString: [obj description]];
        [result appendString: @","];
    }
    NSUInteger len = [result length];//获取字符串长度
    [result deleteCharactersInRange: NSMakeRange(len - 1, 1)];//去除最后一个字符
    [result appendString: @"]"];
    return result;
}

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        NSCountedSet *set = [NSCountedSet setWithObjects: @"疯狂iOS讲义", @"疯狂andro讲义", @"疯狂Ajax讲义", nil];
        
        //向set里添加两次对应字符串
        [set addObject: @"疯狂iOS讲义"];
        [set addObject: @"疯狂iOS讲义"];
        NSLog(@"%@", NSCollectionToString(set));
        NSLog(@"疯狂iOS讲义的添加次数为:%ld", [set countForObject: @"疯狂iOS讲义"]);
        
        //从set中删除对应字符串但不删完
        [set removeObject: @"疯狂iOS讲义"];
        NSLog(@"删除疯狂iOS讲义一次后的结果:%@", NSCollectionToString(set));
        NSLog(@"删除疯狂iOS讲义一次后的添加次数:%ld", [set countForObject: @"疯狂iOS讲义"]);
        
        //从set中删除对应字符串且删完
        [set removeObject: @"疯狂iOS讲义"];
        [set removeObject: @"疯狂iOS讲义"];
        NSLog(@"删除疯狂iOS讲义3次后的结果:%@", NSCollectionToString(set));
        NSLog(@"删除疯狂iOS讲义3次后的添加次数:%ld", [set countForObject: @"疯狂iOS讲义"]);
    }
    return 0;
}

运行结果:

七、有序集合(NSOrderedSet和NSMutableOrderedSet)

        NSOderedSet和NSMutableOrderedSet既具有NSSet集合的特征,又具有NSArray类似的功能。它有以下两个特点:

        1、NSOrderedSet不允许元素重复。

        2、NSOrderedSet可以保持元素的添加顺序,而且每个元素都有索引,可以根据索引来操作元素。

        NSMutableOrderedSet是NSOrderedSet的子类,代表集合元素可变的有序集合。与前面一样,它可以增添,删除,替换,排序元素。

 用代码演示其功能:

#import <Foundation/Foundation.h>

//定义一个函数,可以把NSSet集合转化为字符串
//方便我们调试观察结果
NSString *NSCollectionToString(id collection) {
    NSMutableString *result = [NSMutableString stringWithString: @"["];
    for (id obj in collection) {
        [result appendString: [obj description]];
        [result appendString: @","];
    }
    NSUInteger len = [result length];//获取字符串长度
    [result deleteCharactersInRange: NSMakeRange(len - 1, 1)];//去除最后一个字符
    [result appendString: @"]"];
    return result;
}

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        //创建集合时故意用重复的元素,可看到程序只会保留其中一个
        NSOrderedSet *set = [NSOrderedSet orderedSetWithObjects: [NSNumber numberWithInt: 40], [NSNumber numberWithInt: 12], [NSNumber numberWithInt: -9], [NSNumber numberWithInt: 28], [NSNumber numberWithInt: 12], [NSNumber numberWithInt: 17], nil];
        NSLog(@"%@", NSCollectionToString(set));
        
        //根据索引获取元素
        NSLog(@"set集合中的第一个元素:%@", [set firstObject]);
        NSLog(@"set集合中的最后一个元素:%@", [set lastObject]);
        NSLog(@"set集合中索引为2的元素:%@", [set objectAtIndex: 2]);
        NSLog(@"28在set集合中的索引为:%ld", [set indexOfObject: [NSNumber numberWithInt:28]]);
        
        //对集合进行过滤,获取元素值大于20的元素的索引
        NSIndexSet *indexSet = [set indexesOfObjectsPassingTest: ^(id obj, NSUInteger idx, BOOL *stop) {
            return (BOOL)([obj intValue] > 20);
        }];
        NSLog(@"set中元素值大于20的元素的索引为:%@", indexSet);
    }
    return 0;
}

 运行结果:

八、字典(NSDictionary和NSMutableDictionary)

        NSDictionary用于保存具有映射关系的数据,因此NSDictionary中保存着两组值,一组值用于保存key,另一组用于保存value。key和value都可以是任何引用类型的数据,Map的key不允许重复。

        key和value之间存在单向一对一的关系,即通过指定的key,总能找到唯一的、确定的value。

        NSDictionary包含了一个allKeys方法,用于返回NSDictionary中所有key组成的NSArray集合。

8.1 NSDictionary的功能和用法

        NSDictionary由多组key-value对组成,因此创建NSDictionary时需要同时指定多组key、value对。NSDictionary分别提供了dictionary开头的类方法和init开头的实例方法。下面是创建NSDIctionary常见的几种方法:

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

 以下是访问NSDictionary的几种方法:

         在用代码演示上述功能之前,我们先写一个代码为NSDictionary扩展了一个print类别,在类别中扩展了一个print方法,用于打印NSDictionary中key-value对的详情:

这是接口部分:

#import <Foundation/Foundation.h>

NS_ASSUME_NONNULL_BEGIN

@interface NSDictionary (printf)

- (void) print;

@end

NS_ASSUME_NONNULL_END

实现部分:

#import "NSDictionary+printf.h"

@implementation NSDictionary (printf)

- (void) print {
    NSMutableString *result = [NSMutableString stringWithString: @"{"];//创建一个可变字符串,初始化为左花括号
    //快速枚举遍历调用该方法的对象的所有key元素
    for (id key in self) {
        [result appendString: [key description]];//向最开始的result对象后面追加访问到的key调用的改写过的description方法的返回值
        [result appendString: @"="];
        [result appendString: [self[key] description]];//使用下标访问法根据key获取对应的value
        [result appendString: @","];
    }
    NSUInteger len = [result length];//获取字符串长度
    [result deleteCharactersInRange: NSMakeRange(len - 2, 2)];//去掉字符串最后两个字符
    [result appendString: @"}"];
    NSLog(@"%@", result);
}

@end

        上面的代码演示了NSDictionary的两个基本用法,程序可以使用快速枚举来遍历NSDictionary的所有key。除此之外,程序也可以根据key来获取对应的value。通过key来获取value有如下两种语法:

        1、调用NSDictionary的objectForKey:方法即可根据key来获取对应的value。

        2、直接使用下标法根据key来获取对应的value。使用这个语法获取时实际上就是调用NSDictionary的objectForKeyedSubscript:方法访问。

上面两个方法对应下面两个代码,它们的功能是相同的:

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

        然后又要用到之前写的那个FKUser类,这个类的接口和实现部分如下:

接口:

#import <Foundation/Foundation.h>

NS_ASSUME_NONNULL_BEGIN

@interface FKUser : NSObject

@property (nonatomic,copy) NSString *name;
@property (nonatomic,copy) NSString *pass;

- (id) initWithName: (NSString*) aName pass: (NSString*) aPass;//重写初始化方法
- (void) say: (NSString*) content;//定义一个say方法

@end

NS_ASSUME_NONNULL_END

实现:

#import "FKUser.h"

@implementation FKUser

@synthesize name;
@synthesize pass;

- (id) initWithName:(NSString *) aName pass:(NSString *) aPass {
    if (self = [super init]) {
        name = aName;
        pass = aPass;
    }
    return self;
}
- (void) say: (NSString*) content {
    NSLog(@"%@说:%@", self.name, content);
}

//重写自定义isEqual方法
- (BOOL) isEqual: (id)other {
    if (self == other) {
        return YES;
    }
    if ([other class] == FKUser.class) {
        FKUser *target = (FKUser*) other;
        return [self.name isEqualToString: target.name] && [self.pass isEqualToString: target.pass];
    }
    return NO;
}
//重写description方法
- (NSString*) description {
    return [NSString stringWithFormat:@"<FKUser[name = %@, pass = %@>", self.name, self.pass];
}

@end

然后接下来的主函数就演示了刚刚所说的功能,主函数及运行结果:

#import <Foundation/Foundation.h>
#import "FKUser.h"
#import "NSDictionary+printf.h"

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        //使用多个key-value对初始化创建NSDictionary对象
        NSDictionary *dict = [NSDictionary dictionaryWithObjectsAndKeys: [[FKUser alloc] initWithName: @"晓美焰" pass: @"123"], @"one", [[FKUser alloc] initWithName: @"鹿目圆" pass: @"345"], @"two", [[FKUser alloc] initWithName: @"晓美焰" pass: @"123"], @"three", [[FKUser alloc] initWithName: @"巴麻美" pass: @"178"], @"four", [[FKUser alloc] initWithName: @"美树沙耶香" pass: @"155"], @"five", nil];
        //对dict对象调用print方法,输出value和key的值
        [dict print];
        //调用count方法获得key-value对的数量
        NSLog(@"dict包含%ld个key-value对", [dict count]);
        //调用allKey方法获得所有的key值
        NSLog(@"dict的所有key是:%@", [dict allKeys]);
        //调用allKeysForObject方法获取指定value对应的全部key
        NSLog(@"<FKUser[name = 晓美焰, pass = 123]>对应的所有的key为:%@", [dict allKeysForObject: [[FKUser alloc] initWithName: @"晓美焰" pass: @"123"]]);
        
        //获取遍历dict所有value的枚举器
        NSEnumerator *en = [dict objectEnumerator];
        NSObject *value;
        //使用枚举器遍历dict中的所有value
        while (value = [en nextObject]) {
            NSLog(@"%@", value);
        }
        
        //使用指定代码块来迭代执行该集合中所有key-value对
        [dict enumerateKeysAndObjectsUsingBlock: ^(id key, id value, BOOL *stop) {
            if (![key  isEqual: @"two"]) {
                NSLog(@"key的值为:%@", key);
                [value say: @"圆神怎么你了"];
            } else {
                NSLog(@"key的值为:%@", key);
                [value say: @"我怎么你了"];
            }
        }];
    }
    return 0;
}

 8.2 对NSDictionary的key排序

        NSDictionary还提供了方法对NSDictionary的所有key执行排序,这些方法执行完成后将返回排序完成后所有key组成的NSSArray。NSDictionary提供的排序方法如下:

        1、keysSortedByValueUsingSelector::根据NSDictionary的所有value的指定方法的返回值对key排序;调用value的该方法必须返回NSOrderedAscending(升序)、NSOrderedDesending(降序)、NSOrderedSame(同序)的三个值之一。

        2、keysSortedByValueUsingComparator::该方法使用指定的代码块来遍历key-value对,并根据执行结果返回NSOrderedAscending(升序)、NSOrderedDesending(降序)、NSOrderedSame(同序)的三个值之一来对NSDictionary的所有key排序。

        3、keysSortedByValueWithOptions:usingComparator::与前一个方法的功能相似,只是该方法可以传入一个额外的NSEnumerationOptions参数。

下面用代码演示:

继续要用到前面扩展的print方法,这里就不再写出来了

#import <Foundation/Foundation.h>
#import "NSDictionary+print.h"

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        //使用多个key-value对创建NSDictionary
        NSDictionary *dict = [NSDictionary dictionaryWithObjectsAndKeys: @"OC", @"one", @"Ruby", @"two", @"Python", @"three", @"Perl", @"four", nil];
        [dict print];//调用print打印dict中所有的元素
        
        //
        NSArray *keyArr1 = [dict keysSortedByValueUsingSelector: @selector(compare:)];
        NSLog(@"%@", keyArr1);
        NSArray *keyArr2 = [dict keysSortedByValueUsingComparator: ^(id value1, id value2) {
            if ([value1 length] > [value2 length]) {
                return NSOrderedDescending;
            }
            if ([value1 length] < [value2 length]) {
                return NSOrderedAscending;
            }
            return NSOrderedSame;
        }];
        NSLog(@"%@", keyArr2);
        [dict writeToFile: @"myFile.txt" atomically: YES];
    }
    return 0;
}

8.3 对NSDictionary的key进行过滤

         NSDictionary提供了以下两个过滤方法:

        1、keysOfEntriesPassingTest::使用代码块迭代处理NSDictionary中的每个key-value对,并对其进行过滤,该代码必须返回BOOL类型的值,只有当该代码返回YES的时候,该key才会被保留下来;该代码块可以接受三个参数,第一个参数表示正在迭代处理的key,第二个参数代表正在迭代处理的value,第三个参数代表是否需要继续迭代。

        2、keysOfEntriesWithOptions: passingTest::该方法的功能与前一个方法的功能基本相同,只是该方法额外传入一个NSEnumerationOptions选项参数。

代码演示:

#import <Foundation/Foundation.h>
#import "NSDictionary+print.h"

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        NSDictionary *dict = [NSDictionary dictionaryWithObjectsAndKeys: [NSNumber numberWithInt: 89], @"Objective-C", [NSNumber numberWithInt: 69], @"Ruby", [NSNumber numberWithInt: 75], @"Python", [NSNumber numberWithInt: 109], @"Perl",  nil];
        [dict print];

        //对NSDictionary的所有key过滤
        NSSet *KeySet = [dict keysOfEntriesPassingTest: ^(id key, id value, BOOL *stop) {
        //对NSDictionary的value进行比较
        //当value的值大于80的key才会被保留
            return (BOOL)([value intValue] > 80);
        }];
        NSLog(@"%@", KeySet);
    }
    return 0;
}

​​​​​​​

 8.4 使用自定义类作为NSDictionary的key

        如果程序打算使用自定义类作为key,需要满足以下要求:

        1、该自定义类正确重写过isEqual和hash方法,即当两个对象通过isEqual:方法判断相等时它们的hash方法的返回值也相等。

        2、该自定义类必须实现了copyWithZone:方法,该方法最好能返回对象的不可变副本。

以下是代码演示:

首先还是要有扩展的print,和上面一样就不写了

FKUser的接口和实现,在实现中,要重写isEqual和hash方法

#import <Foundation/Foundation.h>

NS_ASSUME_NONNULL_BEGIN

@interface FKUser : NSObject<NSCopying>

@property (nonatomic,copy) NSString *name;
@property (nonatomic,copy) NSString *pass;

- (id) initWithName: (NSString*) aName pass: (NSString*) aPass;
- (void) say: (NSString*) content;

@end

NS_ASSUME_NONNULL_END
#import "FKUser.h"

@implementation FKUser

@synthesize name;
@synthesize pass;

- (id) initWithName:(NSString *) aName pass:(NSString *) aPass {
    if (self = [super init]) {
        self.name = aName;
        self.pass = aPass;
    }
    return self;
}
- (void) say: (NSString *) content {
    NSLog(@"%@说:%@", self.name, content);
}
- (BOOL) isEqual: (id)object {
    if (self == object) {
        return YES;
    }
    if ([object class] == FKUser.class) {
        FKUser *target = (FKUser*) object;
        return [self.name isEqualToString: target.name] && [self.pass isEqualToString: target.pass];
    }
    return NO;
}
- (NSString*) description {
    return [NSString stringWithFormat: @"<FKUSer[name = %@, pass = %@]>", self.name, self.pass];
}
- (id) copyWithZone: (NSZone *) zone {
    NSLog(@"-----正在复制-----");
    FKUser *newUser = [[[self class] allocWithZone: zone] init];
    newUser.name = self.name;
    newUser.pass = self.pass;
    return newUser;
}

//重写hash方法,重写该方法的比较标准是:
//如果两个FKUser的name、pass相等,两个FKUser的Hash方法返回值相等
- (NSUInteger) hash {
    NSUInteger nameHash = name == nil ? 0 : [name hash];
    NSUInteger passHash = pass == nil ? 0 : [pass hash];
    return nameHash * 31 + passHash;
}

@end

主函数及运行结果:

#import <Foundation/Foundation.h>
#import "NSDictionary+print.h"
#import "FKUser.h"

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        FKUser *u1 = [[FKUser alloc] initWithName: @"晓美焰" pass: @"345"];
        NSDictionary *dict = [NSDictionary dictionaryWithObjectsAndKeys: @"one", [[FKUser alloc] initWithName: @"鹿目圆" pass: @"123"], @"two", u1, @"three", [[FKUser alloc] initWithName: @"鹿目圆" pass: @"123"], @"four", [[FKUser alloc] initWithName: @"巴麻美" pass: @"178"], @"five", [[FKUser alloc] initWithName: @"美树沙耶香" pass: @"155"], nil];
        u1.pass = nil;
        [dict print];
    }
    return 0;
}

8.5 NSMutableDictionary的功能和用法

        NSMutableDictionary继承了NSDictionary,代表一个key-value可变的NSDictionary集合。

用代码演示其方法:

#import <Foundation/Foundation.h>
#import "NSDictionary+print.h"

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        NSMutableDictionary *dict = [NSMutableDictionary dictionaryWithObjectsAndKeys: [NSNumber numberWithInt: 89], @"疯狂iOS讲义", nil];
        
        //使用下标法设置key-value对,由于NSDictionary中存在该key
        //所以此处设置的value会覆盖之前的value
        dict[@"疯狂iOS讲义"] = [NSNumber numberWithInt: 99];
        [dict print];
        NSLog(@"--再次添加key-value对--");
        dict[@"疯狂XML讲义"] = [NSNumber numberWithInt: 69];
        dict[@"疯狂Android讲义"] = [NSNumber numberWithInt: 69];
        [dict print];
        NSDictionary *dict2 = [NSDictionary dictionaryWithObjectsAndKeys: [NSNumber numberWithInt: 79], @"疯狂Ajax讲义", [NSNumber numberWithInt: 89], @"Struts 2.x权威指南", nil];
        
        //将另一个NSDictionary中的key-value对添加到该集合中
        [dict addEntriesFromDictionary: dict2];
        [dict print];
        //根据key来删除key-value对
        [dict removeObjectForKey: @"Struts 2.x权威指南"];
        [dict print];
    }
    return 0;
}

风语者!平时喜欢研究各种技术,目前在从事后端开发工作,热爱生活、热爱工作。