Objective-C 学习笔记 07 - 回调和协议

本文介绍了Objective-C中实现回调的三种方式:Target-action、Helper objects(如代理和数据源)以及Notification。详细讨论了NSTimer的用法,并通过NSURLConnection的异步请求解释了回调的重要性。此外,还讲述了系统时区变化时的通知机制以及对象释放的注意事项。最后,讨论了协议在定义UITableView数据源中的应用。
摘要由CSDN通过智能技术生成

使用Objective-C通常有三种方式实现回调:

Target-action:当某事件发生时,向指定的对象发送某个特定的消息。只做一件事时使用。

Helper objects:当某事件发生时,向遵循相应协议的辅助对象发送消息。代理和数据源是常见的辅助对象。功能较复杂是使用。

Notification:某个对象正在等待某些特定的通告,当其中的某个通告出现时,向指定的对象发送特定的消息。当某事件发生时,相关的对象会向通告中心发布通告,然后再有通告中心将通告转发给正在等待该通告的对象。要触发多个回调对象时使用。

Target-action

NSTimer使用就是这个机制。创建该对象时,要设定延时,目标和动作。在指定的延时时间后,该对象会向设定的目标发送指定的消息。

新建工程Callbacks,添加类Logger

#import <Foundation/Foundation.h>

@interface Logger : NSObject
- (void)sayOuch:(NSTimer *)t;
@end

#import "Logger.h"

@implementation Logger

- (void)sayOuch:(NSTimer *)t
{
    NSLog(@"Ouch!");
}

@end
创建logger实例和NSTimer实例,指定当timer超时是,向logger发送sayOuch:消息,这里需要用到 @selector。

#import <Foundation/Foundation.h>
#include "Logger.h"

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        
        Logger *logger = [[Logger alloc] init];
        __unused NSTimer *timer = [NSTimer scheduledTimerWithTimeInterval:2.0 target:logger selector:@selector(sayOuch:) userInfo:nil repeats:YES];
        
        [[NSRunLoop currentRunLoop] run];
        
    }
    return 0;
}
程序执行效果如下,定时器每2秒超时一次,调用logger的sayOuch方法,打印"Ouch!"

2015-01-30 21:45:13.905 Callbacks[4612:55160] Ouch!
2015-01-30 21:45:15.902 Callbacks[4612:55160] Ouch!
2015-01-30 21:45:17.900 Callbacks[4612:55160] Ouch!
2015-01-30 21:45:19.901 Callbacks[4612:55160] Ouch!

Helper objects

NSTimer比较简单,完成了一项任务:在指定的时刻触发时间。但是类似通过NSURLConnection的sendSynchronousRequest:returningResponse:error:方法从Web服务器获取数据这样的操作就会有两个问题:

1. 获取数据时会阻塞主线程

2. 某些情况下无法实现回调,例如,Web服务器要求用户提供用户名和密码时。

鉴于以上的原因,通常会以异步的模式来使用NSURLConnection。程序会先要求NSURLConnection获取数据,然后等待回调。回调会在以下时间发生时触发:得到数据、Web服务器要求提供认证信息或获取数据失败等。

        NSURL *url = [NSURL URLWithString:@"http://www.baidu.com/img/bdlogo.png"];
        NSURLRequest *request = [NSURLRequest requestWithURL:url];
        __unused NSURLConnection *fetchConn = [[NSURLConnection alloc] initWithRequest:request delegate:logger startImmediately:YES];
修改Logger类,使用NSMutableData来保存服务器返回的数据

#import <Foundation/Foundation.h>

@interface Logger : NSObject
{
    NSMutableData *incomingData;
}
- (void)sayOuch:(NSTimer *)t;
@end

实现回调函数,接收数据,完成数据接收和接收数据失败:

- (void)connection:(NSURLConnection *)connection didReceiveData:(NSData *)data
{
    NSLog(@"received %lu bytes", [data length]);
    
    if (!incomingData)
    {
        incomingData = [[NSMutableData alloc] init];
    }
    
    [incomingData appendData:data];
}

- (void)connectionDidFinishLoading:(NSURLConnection *)connection
{
    NSLog(@"Got it all!");
    
    NSString *string = [[NSString alloc] initWithData:incomingData encoding:NSUTF8StringEncoding];
    NSLog(@"string has %lu characters", [string length]);
}

- (void)connection:(NSURLConnection *)connection didFailWithError:(NSError *)error
{
    NSLog(@"connection failed: %@", [error localizedDescription]);
    incomingData = nil;
}
程序执行结果如下:

received 5331 bytes
incomingData has 5331 bytes
Got it all!

Notification

当用户修改Mac系统的时区设置时,程序中的很多对象可能需要知道系统发生了这一变化。这些对象都可以通过通知中心将自己注册为观察者。

        [[NSNotificationCenter defaultCenter] addObserver:logger selector:@selector(zoneChange:) name:NSSystemTimeZoneDidChangeNotification object:nil];
在logger类中添加回调方法

- (void)zoneChange:(NSNotification *)note
{
    NSLog(@"the system time zone has changed!");
}

回调与对象所有权

无论哪种类型的回调,如果代码编写不正确,都可能使得等待回调的对象得不到正确的释放。编码时应遵守以下规则:

1. 通告中心不拥有其下的观察者,在释放对象时将其移出通告中心

- (void)dealloc
{
    [[NSNotificationCenter defaultCenter] removeObserver:self];
}
2. 对象不拥有其下的委托对象或数据源对象,如果某对象是另一个对象的委托对象或数据源对象,那么该对象应该在dealloc方法中取消相应的关联

- (void)dealloc
{
    [windowThatBossesMeAround setDelegate:nil];
    [tableViewThatBegsForData setDataSource:nil];
}
3. 对象不拥有其目标,如果某个新创建的对象是另一个对象的目标,那么该对象应该在其dealloc方法中将相应的目标指针赋值为nil

- (void)dealloc
{
    [buttonThatKeepsSendingMeMessages setTarget:nil];
}

Protocols

iOS应用程序经常会使用UITableView实例来显示数据,但是UITableView对象自身并不包含要显示的数据,必须从其它对象处获取。因此,程序必须告诉UITableView对象,某个类是它的数据源角色。使用协议可以指定某个对象时UITableView的数据源。这个类需要实现相应的必须方法(required method)并选择性的实现部分可选方法(optional method)。

UITableView的数据源协议是UITableViewDataSource。

@interface TerrificViewController : UIViewController <UITableViewDataSource>
...
@end
这段代码意思是:TerrificViewController是UIViewController的子类,并遵守UITableViewDataSource协议。

如果某个类要遵守多个协议,则都写在尖括号内并以逗号分隔。

@interface TerrificViewController : UIViewController <UITableViewDataSource, UITableViewDelegate, UITextFieldDelegate>
...
@end








评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值