[Objective-C]大揭密-基础框架 Foundation 框架下的常用类介绍

[Objective-C]大揭密-基础框架 Foundation 框架下的常用类介绍

常用的类

字符串(NSString与NSMutableString)

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

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数组

unichar就是unsigned short的别名

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

再来演示其他功能

#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!"];
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的代码中
修改字符串的时候,字符串所包含的字符序列本身就发生了改变
无需重新赋值

比如

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

日期与时间

日期与时间(NSDate)

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

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

日期格式器(NSDateFormatter)

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

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

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

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

代码来

#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,它代表一个语言、国际环境,在不同的语言、国家环境下显示出来的字符串是不一样的。

日历(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方法

以下用代码演示

#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;
}
这里输出的星期的数字会比按照日历的星期数多一,是因为按西方他们是把周天当每周的第一天的
定时器(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方法即可。

Foundation对象复制

对象复制

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复制的副本是不可以被修改的

NSCopying和NSmutableCopying协议

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

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

因此应该如下所示:

#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对象呢?

  1. 这是因为此处的FKDog没有提供对应的不可变类,自然也就无法复制不可变的FKDog对象。如果程序为FKDog提供了不可变类,当然还是应该让FKDog的copyWithZone:返回不可变的FKDog对象。
  2. 如果重写copyWithZone:方法的时候,其父类已经实现了NSCopying协议,并重写过copyWithZone方法,那么子类重写copyWithZone:方法的时候应该先调用父类的copy方法复制从父类得到的成员变量,然后对自类中定义的成员变量进行赋值。
  3. 假如父类已经重写copyWithZone:方法,那么子类重写copyWithZone方法的格式如下:
- (id) copyWithZone:(NSZone*)zone {
    id obj = [super copy];
    //对自类定义的成员变量赋值
    ......
    return obj;
}
深复制和浅复制

首先用代码演示一下概念:

#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;
}

在上面的代码里,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对象的值就可以互相不干扰了。这就是深复制。

setter方法的复制选项

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

下面定义

#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)

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对象。
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;
}
对集合元素整体调用方法
    对于简单的调用集合中的元素的方法,可以通过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;
}
对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自身比较大小的方法是根据字符对应的编码来的。

    后两种方法通过调用代码块或者函数来比较大小,代码块相当于一个匿名函数,因此后面两种方式的本质是一样的,它们都可以通过自定义的比较规则来比较集合元素的大小。
使用枚举遍历器遍历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;
}
快速枚举(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;
}
可变数组(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;
}
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)

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;
}
NSSet判断集合元素重复的标准
    当向NSSet集合中存入一个元素时,NSSet会调用该对象的Hash方法来得到对象的hashCode值,然后根据该值决定该对象在底层Hash表中的存储位置,如果根据hashCode计算出该元素在底层Hash表中的存储位置已经不相同,那么系统自然的将它们存在不同的位置。

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

    因此,HashSet集合判断两个元素相等的标准为:1、两个对象通过isEqual:方法比较返回YES;2、两个对象的hash方法返回值相等。
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;
}
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;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值