iOS7 最佳實踐:一個天氣應用案例(下)
開始
你有兩個選擇開始本教程:您可以使用在本教程的第1部分你已完成的專案,或者你可以在這裡下載第1部分已完成的專案。
在前面的教程中你建立了你的App的天氣模型 – 現在你需要使用OpenWeatherMap API為你的App來獲取一些資料。你將使用兩個類抽象資料抓取、分析、儲存:WXClient
和WXManager
。
WXClient
的唯一責任是建立API請求,並解析它們;別人可以不用擔心用資料做什麼以及如何儲存它。劃分類的不同工作職責的設計模式被稱為關注點分離。這使你的程式碼更容易理解,擴充套件和維護。
與ReactiveCocoa工作
確保你使用SimpleWeather.xcworkspace
WXClient.h
並增加imports
Objective-C
12 | @import CoreLocation;#import <ReactiveCocoa/ReactiveCocoa/ReactiveCocoa.h> |
1 | 注意:您可能之前沒有見過的@import |
在WXClient.h
中新增下列四個方法到介面申明:
12345 | @import Foundation;-(RACSignal*)fetchJSONFromURL:(NSURL*)url;-(RACSignal*)fetchCurrentConditionsForLocation:(CLLocationCoordinate2D)coordinate;-(RACSignal*)fetchHourlyForecastForLocation:(CLLocationCoordinate2D)coordinate;-(RACSignal*)fetchDailyForecastForLocation:(CLLocationCoordinate2D)coordinate; |
ReactiveCocoa(RAC)是一個Objective-C的框架,用於函式式反應型程式設計,它提供了組合和轉化資料流的API。代替專注於編寫序列的程式碼 – 執行有序的程式碼佇列 – 可以響應非確定性事件。
- 對未來資料的進行組合操作的能力。
- 減少狀態和可變性。
- 用宣告的形式來定義行為和屬性之間的關係。
- 為非同步操作帶來一個統一的,高層次的介面。
- 在KVO的基礎上建立一個優雅的API。
例如,你可以監聽username
屬性的變化,用這樣的程式碼:
123 | [RACAble(self.username) subscribeNext:^(NSString*newName){NSLog(@"%@",newName);}]; |
subscribeNext
這個block會在self.username
屬性變化的時候執行。新的值會傳遞給這個block。
您還可以合併訊號並組合資料到一個組合資料中。下面的示例取自於ReactiveCocoa的Github頁面:
Objective-C12345678 | [[RACSignal combineLatest:@[RACAble(self.password),RACAble(self.passwordConfirmation)] reduce:^(NSString*currentPassword,NSString*currentConfirmPassword){return[NSNumber numberWithBool:[currentConfirmPassword isEqualToString:currentPassword]];}] subscribeNext:^(NSNumber*passwordsMatch){self.createEnabled=[passwordsMatch boolValue];}]; |
RACSignal物件捕捉當前和未來的值。訊號可以被觀察者連結,組合和反應。訊號實際上不會執行,直到它被訂閱。
這意味著呼叫[mySignal fetchCurrentConditionsForLocation:someLocation];
不會做什麼,但建立並返回一個訊號。你將看到之後如何訂閱和反應。
開啟WXClient.m
加入以下imports:
12 | #import "WXCondition.h"#import "WXDailyForecast.h" |
在imports下,新增私有介面:
Objective-C12345 | @interfaceWXClient()@property(nonatomic,strong)NSURLSession*session;@end |
這個介面用這個屬性來管理API請求的URL session。
新增以下init
放到到@implementation
和@end
之間:
1234567 | -(id)init{if(self=[superinit]){NSURLSessionConfiguration*config=[NSURLSessionConfigurationdefaultSessionConfiguration];_session=[NSURLSession sessionWithConfiguration:config];}returnself;} |
使用defaultSessionConfiguration
為您建立session。
1 | 注意:如果你以前沒有了解過NSURLSession,看看我們的[NSURLSession教程](http://www.raywenderlich.com/51127/nsurlsession-tutorial),瞭解更多資訊。 |
構建訊號
你需要一個主方法來建立一個訊號從URL中取資料。你已經知道,需要三種方法來獲取當前狀況,逐時預報及每日預報。
不是寫三個獨立的方法,你可以遵守DRY(Don’t Repeat Yourself)的軟體設計理念,使您的程式碼容易維護。
第一次看,以下的一些ReactiveCocoa部分可能看起來相當陌生。別擔心,你會一塊一塊理解他。
增加下列方法到WXClient.m
:
12345678910111213141516171819202122 | -(RACSignal*)fetchJSONFromURL:(NSURL*)url{NSLog(@"Fetching:%@",url.absoluteString);// 1return[[RACSignal createSignal:^RACDisposable*(id<RACSubscriber>subscriber){// 2NSURLSessionDataTask*dataTask=[self.session dataTaskWithURL:url completionHandler:^(NSData*data,NSURLResponse*response,NSError*error){// TODO: Handle retrieved data}];// 3[dataTask resume];// 4return[RACDisposable disposableWithBlock:^{[dataTask cancel];}];}] doError:^(NSError*error){// 5NSLog(@"%@",error);}];} |
通過一個一個註釋,你會看到程式碼執行以下操作:
- 返回訊號。請記住,這將不會執行,直到這個訊號被訂閱。
- fetchJSONFromURL:
建立一個物件給其他方法和物件使用;這種行為有時也被稱為工廠模式。 - 建立一個NSURLSessionDataTask(在iOS7中加入)從URL取資料。你會在以後新增的資料解析。
- 一旦訂閱了訊號,啟動網路請求。
- 建立並返回RACDisposable物件,它處理當訊號摧毀時的清理工作。
- 增加了一個“side effect”,以記錄發生的任何錯誤。side effect不訂閱訊號,相反,他們返回被連線到方法鏈的訊號。你只需新增一個side effect來記錄錯誤。
1 | 如果你覺得需要更多一些背景知識,看看由AshFurrow編寫的[這篇文章](http://www.teehanlax.com/blog/getting-started-with-reactivecocoa/),以便更好地瞭解ReactiveCocoa的核心概念。 |
在-fetchJSONFromURL:
中找到// TODO: Handle retrieved data
,替換為:
12345678910111213141516171819 | if(!error){NSError*jsonError=nil;idjson=[NSJSONSerialization JSONObjectWithData:data options:kNilOptions error:&jsonError];if(!jsonError){// 1[subscriber sendNext:json];}else{// 2[subscriber sendError:jsonError];}}else{// 2[subscriber sendError:error];}// 3[subscriber sendCompleted]; |
- 當JSON資料存在並且沒有錯誤,傳送給訂閱者序列化後的JSON陣列或字典。
- 在任一情況下如果有一個錯誤,通知訂閱者。
- 無論該請求成功還是失敗,通知訂閱者請求已經完成。
-fetchJSONFromURL:
方法有點長,但它使你的特定的API請求方法變得很簡單。
獲取當前狀況
還在WXClient.m
中,新增如下方法:
1234567891011 | -(RACSignal*)fetchCurrentConditionsForLocation:(CLLocationCoordinate2D)coordinate{// 1NSString*urlString=[NSString stringWithFormat:@"http://api.openweathermap.org/data/2.5/weather?lat=%f&lon=%f&units=imperial",coordinate.latitude, coordinate.longitude];NSURL*url=[NSURL URLWithString:urlString];// 2return[[self fetchJSONFromURL:url] map:^(NSDictionary*json){// 3return[MTLJSONAdapter modelOfClass:[WXConditionclass] fromJSONDictionary:json error:nil];}];} |
- 使用
CLLocationCoordinate2D
物件的經緯度資料來格式化URL。 - 用你剛剛建立的建立訊號的方法。由於返回值是一個訊號,你可以呼叫其他ReactiveCocoa的方法。 在這裡,您將返回值對映到一個不同的值 – 一個NSDictionary例項。
- 使用
MTLJSONAdapter
來轉換JSON到WXCondition
物件 – 使用MTLJSONSerializing
協議建立的WXCondition
。
獲取逐時預報
現在新增根據座標獲取逐時預報的方法到WXClient.m
:
1234567891011121314151617 | -(RACSignal*)fetchHourlyForecastForLocation:(CLLocationCoordinate2D)coordinate{NSString*urlString=[NSString stringWithFormat:@"http://api.openweathermap.org/data/2.5/forecast?lat=%f&lon=%f&units=imperial&cnt=12",coordinate.latitude, coordinate.longitude];NSURL*url=[NSURL URLWithString:urlString |