ASIHTTPRequest 是 Objective-C 上知名的專門用來處理 HTTP requests 的框架,不過,作者似乎已經停止更新了(但是在 iOS 5 還是運作得很正常)。前陣子發現另一個 network framework 叫 AFNetworking,似乎挺不錯的,有空再去試試,雖然我最近寫 mobile app 已經寫到有點煩了…… AFNetworking 是 Gowalla 團隊開發的,對,就是那個被 Facebook 收購然後淒慘地關站的 Gowalla。
Add To Your Xcode Project
詳見:Using ASIHTTPRequest in an iOS project。
直接在 Finder 中把 ASIHTTPRequest 的檔案包含目錄拉進 xcode 裡,然後選擇 “Create groups for any added folders”,目錄的結構會像這樣:
ASINetworkQueue
我通常會在每個 view controller 裡面放一個 ASINetworkQueue 物件,由它來統一處理這個頁面的所有 requests。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
| - (void)viewDidLoad
{
[super viewDidLoad];
requestQueue = [[ASINetworkQueue alloc] init];
// count = 1,就算是非同步,也會是序列執行
requestQueue.maxConcurrentOperationCount = 15;
// ASIHTTPRequest 默認的情況下,queue 中只要有一個 request fail 了,整個 queue 裡的所有 requests 也都會被 cancel 掉
[requestQueue setShouldCancelAllRequestsOnFailure:NO];
// go 只需要執行一次
[requestQueue go];
}
- (void)viewWillDisappear:(BOOL)animated
{
[super viewWillDisappear:animated];
// 離開這個頁面就暫停所有的 requests,之後又回到這個頁面時,可以繼續未完成的請求
[self.requestQueue setSuspended:YES];
}
- (void)viewWillAppear:(BOOL)animated
{
[super viewWillAppear:animated];
// 恢復執行 requests
[self.requestQueue setSuspended:NO];
}
|
Asynchronous Request(非同步請求)
基本上你應該不會想在 mobile 上使用同步請求,因為它會整個拖住畫面的渲染,也不能響應用戶的操作,所以通常的用法是:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
| - (void)followMember
{
NSURL *url = [NSURL URLWithString:@"http://example.com/member/follow"];
/*
帶 POST / GET query 參數的請求要用 ASIFormDataRequest
下載圖片之類的單純的請求就用 ASIHTTPRequest
*/
ASIFormDataRequest *request = [ASIFormDataRequest requestWithURL:url];
/*
通常會把 delegate 指定為當前的 view controller
你把 callback selector 寫在哪裡,delegate 就是哪個
*/
[request setDelegate:self];
[request setDidFinishSelector:@selector(didFollowMember:)];
[request setDidFailSelector:@selector(didServerFail:)];
// POST Query
[request setPostValue:@"1234" forKey:@"user_id"];
[request setPostValue:@"5678" forKey:@"target_id"];
[request setPostValue:@"member" forKey:@"follow_type"];
// 放進 queue 的 request 就是非同步,不需要再特地 [request startAsynchronous] 了
[requestQueue addOperation:request];
}
- (void)didFollowMember:(ASIHTTPRequest *)request
{
NSLog("request 得到 HTTP 200 的 response 之後,就會執行這個 callback");
}
- (void)didServerFail:(ASIHTTPRequest *)request
{
NSLog("server 掛掉、request timeout 或 request 被強制 cancel 掉之後,就會執行這個 callback");
}
|
callback selector 一定會在 main thread 執行,在 iOS 中,如果要進行 UI 操作(例如 [self.tableView reloadData] 之類的),必須是在 main thread。
延伸閱讀:
Download Image
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
| - (void)downloadImage:(NSMutableDictionary *)photoInfo
{
NSURL *photoURL = [NSURL URLWithString:@"http://example.com/images/1234.jpg"];
ASIHTTPRequest *request = [ASIHTTPRequest requestWithURL:photoURL];
[request setDelegate:self];
// cache
[request setDownloadCache:[ASIDownloadCache sharedCache]];
[request setCachePolicy:ASIOnlyLoadIfNotCachedCachePolicy];
[request setCacheStoragePolicy:ASICachePermanentlyCacheStoragePolicy];
// [request setSecondsToCache:60 * 60 * 24 * 30]; // cache 30 天
// time out 自動重試的次數
[request setNumberOfTimesToRetryOnTimeout:2];
// callbacks
[request setDidFinishSelector:@selector(didDownloadImage:)];
[request setDidFailSelector:@selector(didServerFail:)];
[request setDidReceiveResponseHeadersSelector:@selector(dummySelector)];
[request setDidStartSelector:@selector(dummySelector:)];
/*
userInfo 的資料會被帶去 callback methods
而且 request.userInfo 會 retain 它
在這個例子中
一個頁面可能有很多個 UIImageView
你可以把對應的 UIImageView 放進 userInfo
這樣 callback 才知道要把下載下來的圖片放到哪一個 UIImageView
*/
request.userInfo = photoInfo;
[self.requestQueue addOperation:request];
}
- (void)didDownloadImage:(ASIHTTPRequest *)request
{
NSData *imageData = [request responseData];
UIImage *image = [[UIImage alloc] initWithData:imageData];
UIImageView *imageView = [request.userInfo objectForKey:@"imageView"];
if (imageView.subviews.count > 0) {
[imageView.subviews makeObjectsPerformSelector:@selector(removeFromSuperview)];
}
[imageView setImage:image];
[image release];
}
/*
我發現如果沒有指定 setDidReceiveResponseHeadersSelector 和 setDidStartSelector
常常就會發生不明的 crash
所以我就不求甚解地加了這一行
但是老實說我完全不知道為什麼
不過事情都搞到這個地步了
其實我也不是那麼想知道原因
我只想準時下班啊~~~
*/
- (void)dummySelector:(ASIHTTPRequest *)request
{
// do nothing
}
|
延伸閱讀:
Release
在你的 view controller 的 dealloc 這樣寫:
1
2
3
4
5
6
7
8
9
10
11
| - (void)dealloc
{
for (ASIHTTPRequest *request in requestQueue.operations) {
/*
因為 ASIHTTPRequest 並不會 retain 它的 delegate
這樣可以避免 delegate 被 release 之後
request callback 回來卻找不到東西執行而 crash
*/
[request clearDelegatesAndCancel];
}
}
|
因為 request 被 cancel 之後,會執行 didFailSelector 這個 callback,所以你應該設置 [request setDelegate:nil],這樣就不會執行 callback 了。而 [request clearDelegatesAndCancel] 其實就是 [request setDelegate:nil]; [request cancel];。
原文出自:http://gibuloto.com/blog/asihttprequest/