7.1 字符串(NSString与NSMutableString)
NSString:字符序列不可变的字符串
NSMutableString:字符序列可变的字符串
7.1.1 创建字符串
NSString功能:
- 创建字符串: 创建字符串既可以使用以init开头的实例方法,也可以使用以string开 头的类方法,当然也可以直接使用@“”的形式给出字符串直接量
- 读取文件或网络URL来初始化字符串
- 将字符串内容写入文件或URL
- 获取字符串长度,既可以获取字符串内包括的字符个数,也可以获取字符串包括的字节个数
- 获取字符串中的字符或字节,既可获取指定位置的字符,也可以获取指定范围的字符
- 获取字符串对应的C风格字符串
- 连接字符串
- 分隔字符串
- 查找字符串内指定的字符和子串替换字符串
- 比较字符串
- 字符串大小比较
- 对字符串中的字符进行大小写转换
NSString的常见用法:
unichar data[6] = {97, 98, 99, 100, 101, 102};
//使用Unicode数值数组初始化字符串
NSString* str = [[NSString alloc] initWithCharacters:data length:6];
NSLog(@"%@", str);
char* cstr = "hello,ios";
//将C风格的字符串转换为NSString对象
NSString* str2 = [NSString stringWithUTF8String:cstr];
NSLog(@"%@", str2);
//将字符串写入指定文件
[str2 writeToFile:@"myFile.txt" atomically:YES encoding:NSUTF8StringEncoding error:nil];
//读取文件内容,用文件内容初始化字符串
NSString* str3 = [NSString stringWithContentsOfFile:@"myFile.txt" encoding:NSUTF8StringEncoding error:nil];
NSLog(@"%@", str3);
输出结果为:
7.1.2 NSString的常用功能
NSString的常见用法:
NSString* str = @"hello";
NSString* book = @"《疯狂iOS讲义》";
//在str后面追加固定的字符串
//原来的字符串对象并不改变,只是将新生成的字符串重新赋给str指针变量
str = [str stringByAppendingString:@",iOS!"];
NSLog(@"%@", str);
//获取字符串对应的C风格字符串
const char* cstr = [str UTF8String];
NSLog(@"获取的C字符串:%s", cstr);
//在str后面追加带变量的字符串
//原来的字符串对象并不改变,只是将新生成的字符串重新赋给str指针变量
str = [str stringByAppendingFormat:@"%@是一本非常不错的图书。", book];
NSLog(@"%@", str);
NSLog(@"str的字符个数为:%lu", [str length]);
NSLog(@"str按UTF-8字符集解码后字节数为:%lu", [str lengthOfBytesUsingEncoding:NSUTF8StringEncoding]);
//获取str的前10个字符组成的字符串
NSString* s1 = [str substringToIndex:10];
NSLog(@"%@", s1);
//获取str第5个字符之后的字符串
NSString* s2 = [str substringFromIndex:5];
NSLog(@"%@", s2);
//获取str从第5个字符之后,数15个字符组成的字符串
NSString* s3 = [str substringWithRange:NSMakeRange(5, 15)];
NSLog(@"%@", s3);
//获取iOS在str中出现的位置
NSRange pos = [str rangeOfString:@"iOS"];
NSLog(@"iOS在str中出现的开始位置:%ld,长度为:%ld", pos.location, pos.length);
//将str的所有字符大写
str = [str uppercaseString];
NSLog(@"%@", str);
输出结果为:
NSRange:
NSRange并不是一个类,它只是一个结构体,它包括了location和length两个unsigned int整型值,分别代表起始位置和长度。Objecetive-C还提供了NSMakeRange()函数来创建NSRange变量。
7.1.3 可变字符串(NSMutableString)
NSString类是不可变的类,即一旦NSSting对象被创建,包含在这个对象中的字符序列是不可改变的,直至这个对象被销毁。
NSMutableString对象则代表一个字符串序列可变的字符串,而且NSMutableString是NSString的子类,因此NSString的方法,NSMutableString都可以直接使用,NSMutableString对象也可以直接当成NSString对象使用。
NSMutableString提供:appendFormat:、appendString:、deleteCharactersInRange:、insertString:atIndex:、replaceCharactersInRange:withString:、replaceOccurrencesOfString:options:range:、setString:方即可改变该字符串所包含的字符序列。
NSMutableString常见用法:
NSString* book =@"《疯狂iOS讲义》";
//创建一个NSMutableString对象
NSMutableString* str = [NSMutableString stringWithString:@"Hello"];
//追加固定的字符串
//字符串所包含的字符序列本身发生了改变,因此无须重新赋值
[str appendString:@",iOS!"];
NSLog(@"%@", str);
//追加带变量的字符串
//字符串所包含的字符序列本身发生了改变,因此无须重新赋值
[str appendFormat:@"%@是一本非常不错的图书。", book];
NSLog(@"%@", str);
//在指定位置插入字符串
//字符串所包含的字符序列本身发生了改变,因此无须重新赋值
[str insertString:@"fkit.org" atIndex:6];
NSLog(@"%@", str);
//删除从位置6开始之后的12个字符
[str deleteCharactersInRange:NSMakeRange(6, 12)];
NSLog(@"%@", str);
//将从位置6开始数9个字符将这9个字符替换成Objective-C
[str replaceCharactersInRange:NSMakeRange(6, 9) withString:@"Objective-C"];
NSLog(@"%@", str);
输出结果为:
7.2 日期与时间
Objective-C为处理日期、时间提供了NSDate、NSCalendar对象,还提供了日期格式器来处理日期与字符串之间的转换。
7.2.1 日期与时间(NSDate)
NSDate对象代表日期与时间,Objective-C中既提供了类方法来创建NSDate对象,也提供了大量以init开头的方法来初始化NSDate对象。
NSDate常见方法的用途:
//获取代表当前日期、时间的NSDate
NSDate* date1 = [NSDate date];
NSLog(@"%@", date1);
//获取从当前时间开始,一天之后的日期
NSDate* date2 = [[NSDate alloc]
initWithTimeIntervalSinceNow:3600 * 24];
NSLog(@"%@", date2);
//获取从当前时间开始,3天之前的日期
NSDate* date3 = [[NSDate alloc]
initWithTimeIntervalSinceNow:-3 * 3600 * 24];
NSLog(@"%@", date3);
//获取从1970年1月1日开始,20年之后的日期
NSDate* date4 = [NSDate dateWithTimeIntervalSince1970:3600 * 24 * 366 * 20];
NSLog(@"%@", date4);
//获取系统当前的Locale区域设置
NSLocale* cn = [NSLocale currentLocale];
//获取NSDate在当前Locale下对应的字符串
NSLog(@"%@", [date1 descriptionWithLocale:cn]);
//获取两个日期之间较早的日期
NSDate* earlier = [date1 earlierDate:date2];
NSLog(@"较早的日期为:%@", earlier);
//获取两个日期之间较晚的日期
NSDate* later = [date1 laterDate:date2];
NSLog(@"较晚的日期为:%@", later);
//比较两个日期,compare:方法返回NSComparisonResult枚举值,
//该枚举类型包含NSOrderedAscending、NSOrderedSame和
//NSOrderedDescending三个值
//分别代表调用compare:的日期位于被比较日期之前、相同、之后
switch ([date1 compare:date3]) {
case NSOrderedAscending:
NSLog(@"date1位于date3之前");
break;
case NSOrderedSame:
NSLog(@"date1与date3日期相等");
break;
case NSOrderedDescending:
NSLog(@"date1位于date3之后");
break;
}
//获取两个时间之间的时间差
//date1 - date3
NSLog(@"date1与date3之间时间差%g秒"
, [date1 timeIntervalSinceDate:date3]);
//获取指定时间与现在的时间差
//date2 - now
NSLog(@"date2与现在时间差%g秒"
, [date2 timeIntervalSinceNow]);
输出结果为:
创建NSDate的类方法与实例方法基本相似,但NSDate的类方法以date开头,而实例方法以init开头。两个NSDate之间可以比较大小,可以计算时间差,也可以把NSDate转化为复合当前NSLocale的格式字符串。
NSLocale:
NSLocale代表一个语言、国际环境,比如大陆的简体中文,就可以通过NSLocale对象来代表。同样一个日期,在不同的语言、国家环境下,显示出来的字符串是不同的。
7.2.2 日期格式器(NSDateFormatter)
NSDateFormatter代表一个日期格式器,它的功能就是完成NSDate与NSString之间的转换。
使用NSDateFormatter完成NSDate与NSString之间的转换的步骤如下:
1.创建一个NSDateFormatter对象。
2.调用NSDateFormatter的setDateStyle:、setTimeStyle:方法设置格式化日期、时间的风格。 其中,日期、时间风格支持如下几个枚举值。
NSDateFormatterNoStyle:不显示日期、时间的风格。
NSDateFormatterShortStyle:显示“短”的日期,时间风格
NSSDateFormatterMediumStyle:显示“中等”的日期、时间风格。
NSSDateFormatterLongStyle:显示“长”的日期、时间风格。
NSSDateFormatterFullStyle:显示“完整”的日期、时间风格。
如果打算使用自己的格式模版,调用NSDateFormatter的setDateFormate:方法设置日期、时间模版即可。
3.如果需要将NSDate转化为NSString,调用NSDateFormatter的stringFromDate:方法执行格式化即可;如果需要将NSString转化为NSDate,调用NSDateFormatter的dateFromString:方法执行格式化即可。
简单示范一下NSDateFormatter的功能和用法:
//需要被格式化的时间
//获取从1970年1月1日开始,20年之后的日期
NSDate* dt = [NSDate dateWithTimeIntervalSince1970:
3600 * 24 * 366 * 20];
//创建两个NSLocale,分别代表中国、美国
NSLocale* locales[] = {
[[NSLocale alloc] initWithLocaleIdentifier:@"zh_CN"]
,[[NSLocale alloc] initWithLocaleIdentifier:@"en_US"]
};
NSDateFormatter* df[8];
//为上面两个NSLocale创建8个DateFormat对象
for (int i = 0; i < 2; i++) {
df[i * 4] = [[NSDateFormatter alloc] init];
//设置NSDateFormatter的日期、时间风格
[df[i * 4] setDateStyle:NSDateFormatterShortStyle];
[df[i * 4] setTimeStyle:NSDateFormatterShortStyle];
//设置NSDateFormatter的NSLocale
[df[i * 4] setLocale:locales[i]];
df[i * 4 + 1] = [[NSDateFormatter alloc] init];
//设置NSDateFormatter的日期、时间风格
[df[i * 4 + 1] setDateStyle:NSDateFormatterMediumStyle];
[df[i * 4 + 1] setTimeStyle:NSDateFormatterMediumStyle];
//设置NSDateFormatter的NSLocale
[df[i * 4 + 1] setLocale:locales[i]];
df[i * 4 + 2] = [[NSDateFormatter alloc] init];
//设置NSDateFormatter的日期、时间风格
[df[i * 4 + 2] setDateStyle:NSDateFormatterLongStyle];
[df[i * 4 + 2] setTimeStyle:NSDateFormatterLongStyle];
//设置NSDateFormatter的NSLocale
[df[i * 4 + 2] setLocale:locales[i]];
df[i * 4 + 3] = [[NSDateFormatter alloc] init];
//设置NSDateFormatter的日期、时间风格
[df[i * 4 + 3] setDateStyle:NSDateFormatterFullStyle];
[df[i * 4 + 3] setTimeStyle:NSDateFormatterFullStyle];
//设置NSDateFormatter的NSLocale
[df[i * 4 + 3] setLocale:locales[i]];
}
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对象
NSDate* date2 = [df3 dateFromString:dateStr];
NSLog(@"%@", date2);
输出结果为:
其中,日期格式化字符串(dateFormat)涉及的格式占位符有
G // AD,即公元
yy // 显示年份的末尾两位
yyyy // 显示完整年份
M // 1-9月显示一位数字(1 ~ 9),10-12月显示两位数字(10 ~ 12)
MM // 显示两位数组月份(01 ~ 12)
MMM // 英文月份缩写
MMMM // 英文月份全拼
d // 1-9日显示一位数字(1~9),10-31日显示两位数字(10~31)
dd // 日期显示两位数字(01~31)
EEE // 星期几缩写(例如星期二,英文是Tue,中文是周二)
EEEE // 星期几全拼(例如星期二,英文是Tuesday,中文是星期二)
aa // 加上这个,会显示AM/PM
H // 24小时值,0-9小时显示一位数字(0~9),10-12小时显示两位数字(10~12)
HH // 24小时制,显示两位数字
K // 12小时制,0-9小时显示一位数字(0~9),10-12小时显示两位数字(10~12)
KK // 12小时制,显示两位数字
m // 0-9分显示一位数字(0~9),10-59分显示两位数字(10~59)
mm // 显示两位数字(00~59)
s // 0-9分显示一位数字(0~9),10-59分显示两位数字(10~59)
ss // 分钟,显示两位数字(00~59)
S // 显示毫秒
7.2.3 日历(NSCalendar)与日期组件(NSDateComponents)
为了能分开处理NSDate对象所包含的各个字段的数据,Foundation框架提供了NSCalendar对象,该对象包含如下两个常用方法:
- (NSDateComponents*)components:fromDate: :从NSDate提取年、月、日、时、分、秒各时间段的信息。
- dateFromComponents:(NSDateComponents*)comps :使用comps对象包含的年、月、时、分、秒各时间字段的信息来创建NSDate。
NSDateComponents
该对象专门用于封装年、月、日、时、分、秒各时间字段的信息。它只包含了对year、month、date、day、hour、minute、second、week、weekday等各字段的setter和getter方法。
从NSDate对象中分开获取各时间字段的数值的步骤如下:
- 1.创建NSCalendar对象。
- 2.调用NSCalendar的components:fromDate:方法获取NSDate对象中各时间字段的数值,该方法返回一个NSDateComponents对象。
- 3.调用NSDateComponents对象的getter方法来获取各时间字段的数值。
使用各时间字段的数值来初始化NSDate对象的步骤如下:
- 1.创建NSCalendar对象。
- 2.创建一个NSDateComponents对象,调用该对象的setter方法来设置各时间字段的值。
- 3.调用NSCalendar的dateFromComponents:(NSDateComponents*)初始化NSDate对象,该方法会返回一个NSDate对象。
//获取代表公历的Calendar对象
NSCalendar *gregorian = [[NSCalendar alloc] initWithCalendarIdentifier:NSGregorianCalendar];
//获取当前日期
NSDate* dt = [NSDate date];
unsigned unitFlags = NSYearCalendarUnit|NSMonthCalendarUnit|
NSDayCalendarUnit|NSHourCalendarUnit|NSMinuteCalendarUnit|
NSSecondCalendarUnit|NSWeekdayCalendarUnit;
//获取不同时间字段的信息
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对象
NSDateComponents* comp2 = [[NSDateComponents alloc] init];
//设置各个时间字段的数值
comp2.year = 2013;
comp2.month = 4;
comp2.day = 12;
comp2.hour = 15;
comp2.minute = 34;
//通过NSDateComponents所包含的时间字段的数值来恢复NSDateduixiang
NSDate* date = [gregorian dateFromComponents:comp2];
NSLog(@"获取的日期为:%@", date);
输出结果为:
7.2.4 定时器(NSTimer)
使用定时器编程操作步骤:
- 1.调用NSTimer的scheduledTimerWithTimeInterval:invocation:或scheduledTimerWithTimeInterval:target:selector:userInfo:repeats:类方法来创建NSTimer对象。调用该类方法时,需要传入如下参数。
timeInterval:指定每隔多少秒执行一次任务。
invocation或target与selector:指定重复执行的任务。如果指定target和selector参数,则指定用某个对象的特定方法作为重复执行的任务;如果指定invocation参数,该参数需要传入一个NSInvocation对象,其中NSInvocation对象也是封装target和selector的,其实也是指定用某个对象的特定方法作为重复执行任务。
userInfo:该参数用于传入额外的附加信息。
repeats:该参数需要指定一个BOOL值,该参数控制是否需要重复执行任务。
- 2.为第一步编写方法。
- 3.销毁定时器。调用定时器的invalidate方法即可。
测试定时器:
@interface NSTimerTest : NSObject
@property (nonatomic, assign) int count;
- (void) start;
- (void) stop;
@end
@implementation NSTimerTest {
NSTimer* timer;
}
@synthesize count;
- (void) start {
timer = [NSTimer scheduledTimerWithTimeInterval:0.5
target:self //指定以当前对象的info方法作为执行体
selector:@selector(start)
userInfo:nil
repeats:YES]; //指定重复执行
NSLog(@"正在执行第%d次任务", self.count++);
}
- (void) stop {
NSLog(@"取消执行定时器");
[timer invalidate];
}
@end
int main(int grac, char *grav[]) {
@autoreleasepool {
NSTimerTest* test = [[NSTimerTest alloc] init];
for (int i = 0; i < 10; i++) {
[test start];
}
[test stop];
}
return 0;
}
输出结果为:
7.3 对象复制
NSObject类提供了copy和multableCopy方法,通过这两个方法即可复制已有对象的副本。
7.3.1 copy与mutableCopy方法
- copy方法用于复制对象的副本。通常来说,copy方法总是返回对象的不可修改的副本,即使该对象本身是可修改的。
调用NSMutableString的copy方法,将会返回不可修改的字符串对象。
- mutableCopy方法用于复制对象的可变副本。通常来说,mutableCopy方法总是返回该对象可修改的副本,即使被复制的对象本身是不可修改的,调用mutableCopy方法复制出来的副本也是可修改的。
程序调用NSString的mutableCopy方法,将会返回一个NSMutableString对象。
对复制对象的副本无论怎么修改,原对象都不会发生改变。
NSMutableString* book = [NSMutableString
stringWithString:@"疯狂iOS讲义"];
//复制book字符串的可变副本
NSMutableString* bookCopy = [book mutableCopy];
//修改副本,对原字符串没有任何影响
[bookCopy replaceCharactersInRange:NSMakeRange(2, 3)
withString:@"Android"];
//此处看到原字符串的值并没有改变
NSLog(@"book的值为:%@", book);
//字符串副本发生了改变
NSLog(@"bookCopy的值为:%@", bookCopy);
NSString* str = @"fkit";
//复制str(不可变字符串)的可变副本
NSMutableString* strCopy = [str mutableCopy];
//向可变字符串后面追加字符串
[strCopy appendString:@".org"];
NSLog(@"%@", strCopy);
//调用book(可变字符串)的copy方法,程序返回一个不可修改的副本
NSMutableString* bookCopy2 = [book copy];
//由于bookCopy2是不可修改的,因此下面的代码将会出现错误
//[bookCopy2 appendString:@"aa"];
输出结果为:
7.3.2 NSCopying与NSMutableCopy协议
虽然NSObject提供了copy和mutableCopy方法,但自定义类并不能直接调用这两个方法来复制自身。
为了保证一个对象可以调用copy方法来复制自身的不可变副本,通常需要做如下事情:
- 让该类实现NSCopying协议。
- 让该类实现copyWithZone:方法。
为了保证一个对象可以调用mutableCopy方法来复制自身的可变副本,通常需要做如下事情:
- 让该类实现NSMutableCopying协议。
- 让该类实现mutableCopyWithZone:方法。
下面实现类的copy:
@interface FKDog : NSObject <NSCopying>
@property (nonatomic, strong) NSMutableString* name;
@property (nonatomic, assign) int age;
@end
@implementation FKDog
@synthesize name;
@synthesize age;
- (id) copyWithZone:(NSZone*) zone {
NSLog(@"--执行copyWithZone--");
FKDog* dog = [[[self class] allocWithZone:zone] init];
dog.name = self.name;
dog.age = self.age;
return dog;
}
@end
int main(int grac, char *grav[]) {
@autoreleasepool {
FKDog* dog1 = [[FKDog alloc] init];
dog1.name = [NSMutableString stringWithString:@"旺财"];
dog1.age = 20;
FKDog* dog2 = [dog1 copy];
dog2.name = [NSMutableString stringWithString:@"snoopy"];
dog2.age = 12;
NSLog(@"dog1的名字为:%@", dog1.name);
NSLog(@"dog1的年龄为:%d", dog1.age);
NSLog(@"dog2的名字为:%@", dog2.name);
NSLog(@"dog2的年龄为:%d", dog2.age);
}
return 0;
}
协议系统自带!
输出结果为:
- 当程序调用上述类的copy方法来复制自身时,底层实际上调用了copyWithZone:方法来执行实际的复制操作。因为此处的类没有提供对应的不可变类,自然也就无法复制不可变的该类的对象。如果程序为该类提供了不可变类,当然还是应该让该类的copyWithZone:返回不可变的该类的对象。
- 如果重写copyWithZone:方法时,其父类已经实现NSCopying协议,并重写过copyWithZone:方法,那么子类重写copyWithZone:方法应先调用父类的copy方法复制从父类继承得到的成员变量,然后对子类定义的成员变量进行赋值。
假如父类已经重写copyWithZone:方法,那么子类重写copyWithZone:方法的格式如下:
- (void) copyWithZone: (NSZone*) zone {
id obj = [super copy];
//对子类定义的成员变量进行赋值。
...
return obj;
}
7.3.3 浅复制与深复制
浅复制(shallow copy)
当对象的实例变量时指针变量时,如果程序只是复制该指针的地址,而不是真正复制指针所指向的对象,这种方式就被称为“浅复制”。
浅复制出来的两个对象是两个指针变量,他们指向的是同一个地址。
深复制(deep copy)
深复制不仅会复制对象本身,而且会“递归”复制每个指针类型的实例变量,直到两个对象没有任何公共的部分。
将上述的copyWithZone:改为深复制:
- (id) copyWithZone: (NSZone*) zone {
NSLog(@"--执行copyWithZone--");
FKDog* dog = [[[self class] allocWithZone:zone] init];
dog.name = [self.name mutableCopy];
dog.age = self.age;
return dog;
}
上面程序中并没有简单地将被复制对象的name实例变量的值赋给新对象的name实例变量,而是先将原对象name实例变量复制了一份可变副本,再将该可变副本的值赋给新对象的name实例变量。这样就保证了原对象和新对象没有任何公共部分,这就实现了深复制。
7.3.4 setter方法的复制选项
前面介绍合成setter和getter方法时提到可以使用copy指示符,copy指示符就是指定当程序调用setter方法复制时,实际上是将传入参数的副本赋给程序的实例变量。
定义合成getter、setter方法时并没有提供mutableCopy指示符,只有copy指示符。
系统默认的setter方法如下:
例如setName:
- (void) setName: (NSMutableString*) aname {
name = [aname copy];
}
copy方法默认是复制该对象的不可变副本,虽然程序传入的NSMutableString,但程序调用该参数的copy方法得到的是不可变副本。