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。
12345678910111213141516171819202122232425262728293031
- (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 上使用同步請求,因為它會整個拖住畫面的渲染,也不能響應用戶的操作,所以通常的用法是:
123456789101112131415161718192021222324252627282930313233343536
- (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。
延伸閱讀:
- ASIHTTPRequest example code
- ASIHTTPRequest 中文文档
Download Image
1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465
- (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}
延伸閱讀:
- ASIHTTPRequest - 使用 download cache
- 让 ASIHTTPRequest 不占用主线程 - keakon 的涂鸦馆
Release
在你的 view controller 的 dealloc 這樣寫:
1234567891011
- (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];。