Objective-C 基础入门(三) 读写文件与回调

目录

 

四、通过 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


四、通过 NSString 和 NSData 读写文件

1.通过 NSString 写入文件

在将字符串对象写入文件时,要指定字符串编码。字符串编码是描述字符和代表的数字之间的映射关系。常用的编码有 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

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]);

2.通过 NSString 读取文件

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

3.将 NSData 对象的数据写入文件

NSData 对象代表内存中的某块缓冲区,保存了相应字节数的数据。例如可以让程序通过某个网址下载数据,并将得到的数据保存在 NSDate 实例中,然后将数据写入文件。

Objective-C 基础入门(三) 读写文件与回调_第1张图片

writeToFile:options:error 方法设置参数通过 option 传入,常见的设置是使用 NSDataWritingAtomic 应用原子操作,处理新下载文件替换旧文件,下载时断电等情况,要么生成一个完整的文件,要么不改变原有状态。所以改进后应为 option:NSDataWritingAtomic

NSURLConnection 已经过期, NSURLSession是NSURLConnection 的替代者

 4.从文件读取数据到 NSData 对象

NSData *readData = [NSData dataWithContentsOfFile:@"/tmp/google.png"];
NSLog(@"The file has %lu bytes",(unsigned long)[readData length]);

五、回调

本章创建一个事件驱动的程序,这个程序能保持运行、等待事件发生,并作出相应的处理,除非人为主动关闭,程序不会自己退出,会一直在后台等待时间的发生。

回调(callback)就是讲一段可执行的代码和一个特定的事件绑定起来,事件发生即调用。Objective-C 中有四种途径实现回调:

  • 目标 - 动作对一个对象发送一个回调,程序开始等待前要求“当事件发生时,向指定的对象发送某个特定的消息”。
  • 辅助对象一个对象发送多个回调。程序开始等待前要求“当事件发生时,向遵守相应协议的辅助对象发送消息”。常见的辅助对象:委托对象 数据源
  • 通知可以处理发给多个对象的回调,OC 中提供一种成为 通知中心 的对象,在程序开始等待前可以告知通知中心:“某个对象正在等待某些特定的通知,通知出现时向指定对象发送特定的消息”。
  • Block 对象block 是一段可执行的代码,程序开始等待前声明一个 Block 对象,当事件发生时执行这段 Block 对象。

1.运行循环

事件驱动的程序需要有一个对象专门等待事件的发生。OS X 系统有一个名为 NSRunLoop (运行循环)的类,会持续等待并在的定的事件发生时触发回调。创建循环的代码如下:

#import 
int main()
{
    @autoreleasepool{
        [[NSRunLoop currentRunLoop] run];
    }
    return 0;
}

程序运行后没有返回 run 方法,此时一直在运行循环等待着事件发生,可以选择 Product - Stop 结束循环。

2.目标 - 动作对

首先创建 计时器,设定延迟、目标和动作指定延迟时间后,计时器回想设定的目标发送指定的消息。下面创建一个拥有 NSRunLoop 对象和 NSTimer 对象的程序。每隔2秒 NSTimer 对象会向其目标发送指定的动作消息。此外还要创建一个 BNRLogger 类,将被设置为 NSTimer 对象的目标。此处目标 - 动作对 BNRLogger - updateLastTime

Objective-C 基础入门(三) 读写文件与回调_第2张图片

在 BNRLogger.h 中,声明相应的动作方法:

#import 
@interface BNRLogger : NSObject
@property (nonatomic) NSDate *lastTime;
-(NSString *)lastTimeString;
-()updateLastTime:(NSTimer *)t;
@end

Objective-C 基础入门(三) 读写文件与回调_第3张图片

所有的 BNRLogger 共享一个 dateFormatter 实例,所以声明为 static。

在 main.m 中,创建一个 BNRLogger 实例,让它成为 NSTimer 的目标,将其 action 设置为 updateLastTime。

Objective-C 基础入门(三) 读写文件与回调_第4张图片

其中使用了 @selector 语句传递动作消息的名称给相应方法。此时需要传递相应的实参,不能只传递方法的名字。计时器很简单,“只是在指定的时刻触发事件,这种情况适合用目标 - 动作对来实现回调”

selector 选择器:

当一个方法需要一个选择器做为实参(就像 scheduledTimerInterval:target:selector:userInfo:repeats:那样)时,需要进行方法的查询,如果使用方法的实际名称进行查询,那么会导致查询速度过慢。实际上编译器会为每个使用过的方法附上一个唯一的数字(即成为选择器),运行时通过数字查询索要调用的方法。通过编译指令 @selector 可以得到与方法名相对应的选择器。

3.辅助对象

四.3中使用了 NSURLConnection 的实例方法从 Web 服务器获取数据。会有以下两个问题:

  • 获取数据时会阻塞主线程,用户界面失去响应
  • 某些情况下无法实现回调,如 Web 服务器要求提供用户名密码

所以通常会以 异步 模式使用 NSURLConnection,此时 NSURLConnection 不会一次发送全部数据,而是分多次发送块状数据发送过程中会发生:得到数据、要求提供认证信息 或 获取数据失败 等事件,程序要准备好响应这些事件。

此时 BNRLogger 实例将会成为 NSURLConnection 的辅助对象。

Objective-C 基础入门(三) 读写文件与回调_第5张图片

创建一个  NSURLConnection 对象,设置 BNRLogger 实例为它的委托对象(delegate)

然后在 BNRLogger 中实现响应特定时事件的回调方法。不需要自己去声明这些方法,它们已经在协议中声明过了。协议是一系列预先写好的方法声明。

Objective-C 基础入门(三) 读写文件与回调_第6张图片

 作为 NSURLConnection 的委托对象,BNRLogger 需要相应三条消息,来自 NSURLConnectionDataDelegate 和 NSURLConnectionDelegate 协议。

 以 异步 模式使用 NSURLConnection,还需要使用一个 NSMutableData 实例。

Objective-C 基础入门(三) 读写文件与回调_第7张图片

 4.通知

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 通知,然后通知中心会将该通知转发给相关的程序中的很多对象。这些对象都可以通过通知中心将自己注册成为观察者

Objective-C 基础入门(三) 读写文件与回调_第8张图片

Objective-C 基础入门(三) 读写文件与回调_第9张图片

 向通知中心注册观察者时,可以指定某个特定的通知名(例如:NSSystemTimeZoneDidChangeNotification)以及通知发布的来源(比如只想收到这个窗口大小调整的通知),两个参数都可以设置为 nil,可会接收到所有对象发出的每条通知。

5.回调与对象所有权

不论哪种类型的回调,必须注意陷入强引用循环的风险。

Objective-C 基础入门(三) 读写文件与回调_第10张图片

所以在编写代码时要遵循以下原则:

  • 对象成为另一个对象的目标,在 dealloc 方法中目标指针赋为 nil:
-(void)dealloc{
    [buttonThatKeepsSendingMeMessages setTarget:nil];
}
  • 对象成为另一个对象的委托对象或数据源,在 dealloc 方法中取消相应的关联:
-(void)dealloc{
    [windowThatBossesMeAround setDelegate:nil];
    [tableViewThatBegsForData setDataSource:nil];
}
  • 通知中心如果将某个对象注册为观察者,通常应该在释放该对象时将其移除通知中心:
-(void)dealloc{
    [[NSNotificationCenter defaultCenter] removeObserver:self];
}

 6.Block 对象

与 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 变量

Objective-C 基础入门(三) 读写文件与回调_第11张图片

这段代码实现的功能是去除数组中所有字符串中的元音字符。

Objective-C 基础入门(三) 读写文件与回调_第12张图片

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 对象,可以将与回调相关的代码写在同一代码段中。

Objective-C 基础入门(三) 读写文件与回调_第13张图片

传递 Block 对象

在 main.m 中,调用 enumerateObjectsUsingBlock: 传入 devowelizer。

[oldStrings enumerateObjectsUsingBlock:devowelizer];

enumerateObjectsUsingBlock:方法要求传入的 Block 对象的三个实参类型是固定的,这三个参数是传入 Block 的参数,系统已经为其设置好了相应的变量,可以在 block 代码中利用这三个参数实现定制功能。

  1. 第一个是 id 指针,指向当前(枚举)的对象
  2. 第二个是 NSUInteger,其值是当前对象在数组中的索引
  3. 第三个是 BOOL 指针,默认为 NO,如果运行过程中如果触发某条件后将该值设为 YES,则数组对象会在执行完当前的 Block 对象后终止枚举。如下例:

Objective-C 基础入门(三) 读写文件与回调_第14张图片

使用 typedef 修饰 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指向的内容不影响原变量)

在 Block 变量中使用 self

如果需要写一个使用 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 方式调用。

 

你可能感兴趣的:(Objective-C)