目录
四、通过 NSString 和 NSData 读写文件
1.通过 NSString 写入文件
NSError
2.通过 NSString 读取文件
3.将 NSData 对象的数据写入文件
4.从文件读取数据到 NSData 对象
五、回调
1.运行循环
2.目标 - 动作对
3.辅助对象
4.通知
5.回调与对象所有权
6.Block 对象
声明 Block 变量
编写 Block 对象
传递 Block 对象
使用 typedef 修饰 Block
外部变量
在 Block 变量中使用 self
在将字符串对象写入文件时,要指定字符串编码。字符串编码是描述字符和代表的数字之间的映射关系。常用的编码有 ASII编码、UTF-8 和 UTF-16。
NSMutableString *str = [NSMutableString new];
for(int i=0;i<10;i++)
{
[str appendString:@"Aaron is cool!\n"];
}
[str writeToFile:@"/tem/cool.txt"
atomically:YES
encoding:NSUTF8StringEncoding
error:NULL];
NSLog(@"done writing /cool.txt");
NSError 表示了写入文件时出现的各种类型错误描述。除了要将执行结果(bool 值)还需将错误描述返回给调用方。可以使用“引用传递”,在调用函数时,传入一个指向某个变量的引用,这个变量可以直接保存或修改相应的值,其内存地址就是要传入的引用。
NSMutableString *str = [NSMutableString new];
for(int i=0;i<10;i++)
{
[str appendString:@"Aaron is cool!\n"];
}
声明一个指向 NSError 对象的指针,但是不创建相应的对象
实际上,只有当发生错误时,才会由 writeToFile 创建相应的 NSError 对象
NSError *error;
BOOL b = [str writeToFile:@"/tem/cool.txt"
atomically:YES
encoding:NSUTF8StringEncoding
error:&error];
检查返回的 Bool 值,如果出错,查询 NSError 对象输出错误描述
if(b)
NSLog(@"done");
else
NSLog(@"writing failed: %@",[error localizedDescription]);
NSError *error;
NSString *str = [[NSString alloc]
initWithContentsOfFile:@"/etc/resolv.conf"
encoding:NSASCIIStringEncoding
error:&error];
如果载入失败返回 nil
if(!str)
NSLog(@"read failed: %@",[error localizedDescription]);
else
NSLog(@"resolv.conf: %@",str);
NSData 对象代表内存中的某块缓冲区,保存了相应字节数的数据。例如可以让程序通过某个网址下载数据,并将得到的数据保存在 NSDate 实例中,然后将数据写入文件。
writeToFile:options:error 方法设置参数通过 option 传入,常见的设置是使用 NSDataWritingAtomic 应用原子操作,处理新下载文件替换旧文件,下载时断电等情况,要么生成一个完整的文件,要么不改变原有状态。所以改进后应为 option:NSDataWritingAtomic。
NSURLConnection 已经过期, NSURLSession是NSURLConnection 的替代者
NSData *readData = [NSData dataWithContentsOfFile:@"/tmp/google.png"];
NSLog(@"The file has %lu bytes",(unsigned long)[readData length]);
本章创建一个事件驱动的程序,这个程序能保持运行、等待事件发生,并作出相应的处理,除非人为主动关闭,程序不会自己退出,会一直在后台等待时间的发生。
回调(callback)就是讲一段可执行的代码和一个特定的事件绑定起来,事件发生即调用。Objective-C 中有四种途径实现回调:
事件驱动的程序需要有一个对象专门等待事件的发生。OS X 系统有一个名为 NSRunLoop (运行循环)的类,会持续等待并在的定的事件发生时触发回调。创建循环的代码如下:
#import
int main()
{
@autoreleasepool{
[[NSRunLoop currentRunLoop] run];
}
return 0;
}
程序运行后没有返回 run 方法,此时一直在运行循环等待着事件发生,可以选择 Product - Stop 结束循环。
首先创建 计时器,设定延迟、目标和动作。指定延迟时间后,计时器回想设定的目标发送指定的消息。下面创建一个拥有 NSRunLoop 对象和 NSTimer 对象的程序。每隔2秒 NSTimer 对象会向其目标发送指定的动作消息。此外还要创建一个 BNRLogger 类,将被设置为 NSTimer 对象的目标。此处目标 - 动作对为 BNRLogger - updateLastTime。
在 BNRLogger.h 中,声明相应的动作方法:
#import
@interface BNRLogger : NSObject
@property (nonatomic) NSDate *lastTime;
-(NSString *)lastTimeString;
-()updateLastTime:(NSTimer *)t;
@end
所有的 BNRLogger 共享一个 dateFormatter 实例,所以声明为 static。
在 main.m 中,创建一个 BNRLogger 实例,让它成为 NSTimer 的目标,将其 action 设置为 updateLastTime。
其中使用了 @selector 语句传递动作消息的名称给相应方法。此时需要传递相应的实参,不能只传递方法的名字。计时器很简单,“只是在指定的时刻触发事件,这种情况适合用目标 - 动作对来实现回调”。
selector 选择器:
当一个方法需要一个选择器做为实参(就像 scheduledTimerInterval:target:selector:userInfo:repeats:那样)时,需要进行方法的查询,如果使用方法的实际名称进行查询,那么会导致查询速度过慢。实际上编译器会为每个使用过的方法附上一个唯一的数字(即成为选择器),运行时通过数字查询索要调用的方法。通过编译指令 @selector 可以得到与方法名相对应的选择器。
四.3中使用了 NSURLConnection 的实例方法从 Web 服务器获取数据。会有以下两个问题:
所以通常会以 异步 模式使用 NSURLConnection,此时 NSURLConnection 不会一次发送全部数据,而是分多次发送块状数据发送过程中会发生:得到数据、要求提供认证信息 或 获取数据失败 等事件,程序要准备好响应这些事件。
此时 BNRLogger 实例将会成为 NSURLConnection 的辅助对象。
创建一个 NSURLConnection 对象,设置 BNRLogger 实例为它的委托对象(delegate):
然后在 BNRLogger 中实现响应特定时事件的回调方法。不需要自己去声明这些方法,它们已经在协议中声明过了。协议是一系列预先写好的方法声明。
作为 NSURLConnection 的委托对象,BNRLogger 需要相应三条消息,来自 NSURLConnectionDataDelegate 和 NSURLConnectionDelegate 协议。
以 异步 模式使用 NSURLConnection,还需要使用一个 NSMutableData 实例。
A对B的变化感兴趣,就注册为B的观察者,当B发生变化时通知A,告知B发生了变化。这就是观察者模式。观察者模式定义了一种一对多的依赖关系,让多个观察者对象同时监听某一个主题对象。这个主体对象在状态上发生变化时,会通知所有观察者对象,使它们能够自动更新自己或者做出相应的一些动作。
相关的类
1、NSNotification
这个类可以理解为一个消息对象,其中有三个成员变量。
1) 这个成员变量是这个消息对象的唯一标识,用于辨别消息对象。
@property (readonly, copy) NSString *name;
2)这个成员变量定义一个对象,可以理解为针对某一个对象的消息。
@property (readonly, retain) id object;
3)这个成员变量是一个字典,可以用其来进行传值。
1 @property (readonly, copy) NSDictionary *userInfo;
2、NSNotificationCenter
概念: 这个类是一个通知中心,使用单例设计,每个应用程序都会有一个默认的通知中心。用于调度通知的发送的接受。
以NSNotificationCenter为中心,观察者往Center中注册对某个主题对象的变化感兴趣,主题对象通过NSNotificationCenter 进行变化广播。所有的观察和监听行为都向同一个中心注册,所有对象的变化也都通过同一个中心向外广播。
当系统的时区设置发生变化时,会向通知中心发布 NSSystemTimeZoneDidChangeNotification 通知,然后通知中心会将该通知转发给相关的程序中的很多对象。这些对象都可以通过通知中心将自己注册成为观察者。
向通知中心注册观察者时,可以指定某个特定的通知名(例如:NSSystemTimeZoneDidChangeNotification)以及通知发布的来源(比如只想收到这个窗口大小调整的通知),两个参数都可以设置为 nil,可会接收到所有对象发出的每条通知。
不论哪种类型的回调,必须注意陷入强引用循环的风险。
所以在编写代码时要遵循以下原则:
-(void)dealloc{
[buttonThatKeepsSendingMeMessages setTarget:nil];
}
-(void)dealloc{
[windowThatBossesMeAround setDelegate:nil];
[tableViewThatBegsForData setDataSource:nil];
}
-(void)dealloc{
[[NSNotificationCenter defaultCenter] removeObserver:self];
}
与 C++ 中的 lamda 函数很相似。Block 对象是一段代码,是在花括号中的一套指令,但是没有函数名,相应的位置只有一个 ^ 符号。与函数不同的是,Block 定义在函数或者方法内部,并能够访问在函数或者方法范围内 Block 之外的任何变量。^ 符号表示这段代码是一个 Block 对象。如下示例:
^(double dividend, double divisor){
NSLog(@"This is an instruction within a block.");
double quotient = dividend / divisor;
return quotient;
}
这段代码中的 Block 对象有两个实参,还返回一个 double 类型的值。Block 对象可以被当成一个实参来传递给可以接受 block 的方法。
这段代码实现的功能是去除数组中所有字符串中的元音字符。
Block 的声明需要包括 Block 的返回类型、Block名称、实参类型。
同在 main.m 中,编写代码,将 Block 对象赋给变量:
...
void(^devowelizer)(id, NSUInteger ,BOOL *);
devowelizer = ^(id string, NSUInteger i ,BOOL *stop){
NSMutableString *newString =[NSMutableString stringWithString:string];
/ 枚举数组中的字符串,将所有出现的元音字母替换成空字符串
for(NSString *s in vowels){
NSRange fullRange = NSMakeRange(0,[newString length]);
[newString replaceOccurencesOfString:s
withString:@""
options:NSCaseInsensitiveSearch
range:fullRange]
}
[devowelizedStrings addObject:newString];
}; / Block 变量赋值结束
...
Block 对象与其他回调:
之前介绍的两种回调机制:辅助对象 与 通知中心,程序可以在特定的事件发生的时候调用 指定的方法。虽然以上的两种回调机制可以很好的完成任务,但是也有一个缺点,即回调的设置代码与回调方法的具体实现无法写在同一段代码中,甚至会出现在不同的文件中。通过 Block 对象,可以将与回调相关的代码写在同一代码段中。
在 main.m 中,调用 enumerateObjectsUsingBlock: 传入 devowelizer。
[oldStrings enumerateObjectsUsingBlock:devowelizer];
enumerateObjectsUsingBlock:方法要求传入的 Block 对象的三个实参类型是固定的,这三个参数是传入 Block 的参数,系统已经为其设置好了相应的变量,可以在 block 代码中利用这三个参数实现定制功能。
一般在实现文件的顶部,或者头文件内使用 typedef。
#import
typedef void (^ArrayEnumerationBlock)(id, NSUInteger, BOOL *);
int main()
{
autoreleasepool{
...
/声明 Block 变量
void (^devowelizer)(id, NSUInteger, BOOL *);
ArrayEnumerationBlock devowelizer;
/将 Block 对象赋给变量
devowelizer = ^(id string, NSUInteger i, BOOL *stop){
...
匿名 Block 对象,参考直接使用 lamda 函数的用法。
Block 对象通常会在其代码中使用外部创建的其他变量,在 block 内部可以直接使用外部变量。非局部变量会以 const 变量被拷贝并存储到 block 中,也就是说在 block 中是只读的。如果尝试在 block 内部给外部变量赋值,会抛出编译器错误。
1)在block内部可以访问block外部的变量
2)在block内部不可以修改block "外部的变量"
3)给外部变量加上 __block
关键字,则这个局部变量可以在block内部进行修改
注意:
1、静态变量和全局变量在加和不加__block
都会直接引用变量地址。也就意味着在没有加__block
关键字的情况下可以修改变量的值。
2、常量变量(NSString *a=@"hello";
a
为变量,@“hello”
为常量。)
不加__block
类型,block会直接取常量值(浅拷贝)。
加__block
类型,block会去引用变量的地址。(如:a变量,a = @"abc".可以任意修改a指向的内容不影响原变量)
如果需要写一个使用 self 的 Block 对象,就必须多做一些工作来避免造成强引用循环。当 BNREmployee 实例调用到 Block 对象时,实例有一个指向 Block 对象(myBlock)的指针,这个 Block 对象会捕获 self,所以它有一个指回 BNREmployee 实例的指针,所以会陷入强引用循环。
所以需要在 Block 对象外声明一个 __weak 指针,将这个指针指向 Block 实例的 self。最后在 Block 中使用这个新的指针。
__weak BNREmployee *weakSelf = self; /弱引用
myBlock = ^{
BNREmployee *innerSelf = weakSelf; /局部强引用
NSLog(@"Employee: %@",innerSelf);
};
此处如果在使用多线程时,self 指向的对象可能在 Block 执行的时候被释放,所以不能直接使用 weakSelf,而是通过 innerSelf 创建一个局部强引用,如果 innerSelf 复制成功,则此时 BNREmployee 就不会被析构。并且由于 innerSelf 是内部的局部变量,在 Block 运行结束后会正常释放。
注意:如果在 Block 中使用了对象的实例变量,此时也相当于直接使用了 self 指针,所以强调也要通过 innerSelf.employeeID 方式调用。