1. 程式人生 > >iOS7 最佳實踐:一個天氣應用案例(下)

iOS7 最佳實踐:一個天氣應用案例(下)

開始

你有兩個選擇開始本教程:您可以使用在本教程的第1部分你已完成的專案,或者你可以在這裡下載第1部分已完成的專案

在前面的教程中你建立了你的App的天氣模型 – 現在你需要使用OpenWeatherMap API為你的App來獲取一些資料。你將使用兩個類抽象資料抓取、分析、儲存:WXClientWXManager

WXClient的唯一責任是建立API請求,並解析它們;別人可以不用擔心用資料做什麼以及如何儲存它。劃分類的不同工作職責的設計模式被稱為關注點分離。這使你的程式碼更容易理解,擴充套件和維護。

與ReactiveCocoa工作

確保你使用SimpleWeather.xcworkspace

,開啟WXClient.h並增加imports

Objective-C
12 @import CoreLocation;#import <ReactiveCocoa/ReactiveCocoa/ReactiveCocoa.h>
Objective-C
1 注意:您可能之前沒有見過的@import
指令,它在Xcode5中被引入,是由蘋果公司看作是一個現代的,更高效的替代#import。有一個非常好的教程,涵蓋了最新的Objective-C特性-[What’s New in Objective-C and Foundation in iOS 7](http://www.raywenderlich.com/49850/whats-new-in-objective-c-and-foundation-in-ios-7)。

WXClient.h中新增下列四個方法到介面申明:

Objective-C
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屬性的變化,用這樣的程式碼:

Objective-C
123 [RACAble(self.username) subscribeNext:^(NSString*newName){NSLog(@"%@",newName);}];

subscribeNext這個block會在self.username屬性變化的時候執行。新的值會傳遞給這個block。

您還可以合併訊號並組合資料到一個組合資料中。下面的示例取自於ReactiveCocoa的Github頁面:

Objective-C
12345678 [[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:

Objective-C
12 #import "WXCondition.h"#import "WXDailyForecast.h"

在imports下,新增私有介面:

Objective-C
12345 @interfaceWXClient()@property(nonatomic,strong)NSURLSession*session;@end

這個介面用這個屬性來管理API請求的URL session。

新增以下init放到到@implementation@end之間:

Objective-C
1234567 -(id)init{if(self=[superinit]){NSURLSessionConfiguration*config=[NSURLSessionConfigurationdefaultSessionConfiguration];_session=[NSURLSession sessionWithConfiguration:config];}returnself;}

使用defaultSessionConfiguration為您建立session。

Objective-C
1 注意:如果你以前沒有了解過NSURLSession,看看我們的[NSURLSession教程](http://www.raywenderlich.com/51127/nsurlsession-tutorial),瞭解更多資訊。

構建訊號

你需要一個主方法來建立一個訊號從URL中取資料。你已經知道,需要三種方法來獲取當前狀況,逐時預報及每日預報。

不是寫三個獨立的方法,你可以遵守DRY(Don’t Repeat Yourself)的軟體設計理念,使您的程式碼容易維護。

第一次看,以下的一些ReactiveCocoa部分可能看起來相當陌生。別擔心,你會一塊一塊理解他。

增加下列方法到WXClient.m:

Objective-C
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);}];}

通過一個一個註釋,你會看到程式碼執行以下操作:

  1. 返回訊號。請記住,這將不會執行,直到這個訊號被訂閱。 - fetchJSONFromURL:建立一個物件給其他方法和物件使用;這種行為有時也被稱為工廠模式
  2. 建立一個NSURLSessionDataTask(在iOS7中加入)從URL取資料。你會在以後新增的資料解析。
  3. 一旦訂閱了訊號,啟動網路請求。
  4. 建立並返回RACDisposable物件,它處理當訊號摧毀時的清理工作。
  5. 增加了一個“side effect”,以記錄發生的任何錯誤。side effect不訂閱訊號,相反,他們返回被連線到方法鏈的訊號。你只需新增一個side effect來記錄錯誤。
Objective-C
1 如果你覺得需要更多一些背景知識,看看由AshFurrow編寫的[這篇文章](http://www.teehanlax.com/blog/getting-started-with-reactivecocoa/),以便更好地瞭解ReactiveCocoa的核心概念。

-fetchJSONFromURL:中找到// TODO: Handle retrieved data ,替換為:

Objective-C
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];
  1. 當JSON資料存在並且沒有錯誤,傳送給訂閱者序列化後的JSON陣列或字典。
  2. 在任一情況下如果有一個錯誤,通知訂閱者。
  3. 無論該請求成功還是失敗,通知訂閱者請求已經完成。

-fetchJSONFromURL:方法有點長,但它使你的特定的API請求方法變得很簡單。

獲取當前狀況

還在WXClient.m中,新增如下方法:

Objective-C
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];}];}
  1. 使用CLLocationCoordinate2D物件的經緯度資料來格式化URL。
  2. 用你剛剛建立的建立訊號的方法。由於返回值是一個訊號,你可以呼叫其他ReactiveCocoa的方法。 在這裡,您將返回值對映到一個不同的值 – 一個NSDictionary例項。
  3. 使用MTLJSONAdapter來轉換JSON到WXCondition物件 – 使用MTLJSONSerializing協議建立的WXCondition

獲取逐時預報

現在新增根據座標獲取逐時預報的方法到WXClient.m:

Objective-C
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