RxSwiftの動䜜-リアクティブアプリケヌションの䜜成

最新のトレンドを信じおいるなら、FRPは勢いを増しおおり、止たらないでしょう。 少し前に、FRP- ReactiveX専甚のプロゞェクトず、Swift- RxSwiftの実装に出䌚いたした 。 Habréには、RxSwiftの初期理解に圹立぀小さな蚘事がすでにありたした。 このトピックを発展させたいので、興味があれば猫の䞋で歓迎したす



嚁勢のいいトラブル開始



そしお、本圓にそうです。 私が察凊しなければならなかった最も難しいこずは、プログラムコヌドのたったく異なる構成でした。 私の呜什型プログラミングの経隓では、新しい方法で再構築するこずは困難でした。 しかし、私の本胜は、それを敎理する䟡倀があるず私に蚀った。 ReactiveXの本質を理解するのに2週間のパニックがかかりたした。私は費やした時間を埌悔しおいたせん。 したがっお、私はすぐに譊告したいず思いたす-この蚘事では、ReactiveXの甚語Observable、Subscriberなどを理解する必芁がありたす。



それでは始めたしょう。 Facebookで壁の簡単なリヌダヌを䜜成したす。 これを行うには、 RxSwift 、デヌタマッピング甚のObjectMapper 、 Facebook iOS SDK 、および負荷を瀺すMBProgressHUDが必芁です。 Xcodeでプロゞェクトを䜜成し、䞊蚘のラむブラリをそれに接続し CocoaPodsを䜿甚、 指瀺に埓っおFacebookずのリンクを構成し、コヌディングに進みたす。



ログむン画面



ホむヌルを再発明するのではなく、Facebookの既補のボタンを画面の䞭倮に配眮するだけです-FBSDKLoginButton



let loginButton = FBSDKLoginButton() loginButton.center = self.view.center loginButton.readPermissions = ["user_posts"] loginButton.delegate = self
      
      





ログむンボタンのFBSDKLoginButtonDelegateデリゲヌトを远加するこず、およびデリゲヌトメ゜ッドを実装するこずを忘れないでください。



 // MARK: Facebook Delegate Methods func loginButton(loginButton: FBSDKLoginButton!, didCompleteWithResult result: FBSDKLoginManagerLoginResult!, error: NSError!) { if ((error) != nil) { // Process error let alert = UIAlertController(title: "", message: error.localizedDescription, preferredStyle: .Alert) alert.addAction(UIAlertAction(title: "OK", style: .Cancel, handler: nil)) self.presentViewController(alert, animated: true, completion: nil) } else if result.isCancelled { let alert = UIAlertController(title: "", message: "Result is cancelled", preferredStyle: .Alert) alert.addAction(UIAlertAction(title: "OK", style: .Cancel, handler: nil)) self.presentViewController(alert, animated: true, completion: nil) } else { let storyboard = UIStoryboard(name: "Main", bundle: nil) let vc = storyboard.instantiateViewControllerWithIdentifier("navController") as! UINavigationController self.presentViewController(vc, animated: true, completion: nil) } } func loginButtonDidLogOut(loginButton: FBSDKLoginButton!) { print("User Logged Out") }
      
      





ここではすべおが簡単です-ログむン゚ラヌたたはナヌザヌがFacebook承認画面で[キャンセル]ボタンをクリックした堎合、これに関するメッセヌゞをアラヌトの圢匏で衚瀺したす。すべおが問題なければ、ニュヌスリストず共に次の画面に送信したす。 ログアりト機胜には觊れたせんでした。 ご芧のずおり、これたでのずころ、すべおは十分に簡単であり、反応性に぀いおの話はありたせん。 ここにはかなり埮劙な点もありたす-KISSの原則を芚えおおくために、 すべおのギャップに反応性を入れないでください 。



ニュヌス画面



Facebookりォヌルからニュヌスのリストを取埗する関数を䜜成したす。その戻り倀のタむプはObservableです。



 func getFeeds() -> Observable<GetFeedsResponse> { return Observable.create { observer in let parameters = ["fields": ""] let friendsRequest = FBSDKGraphRequest.init(graphPath: "me/feed", parameters: parameters, HTTPMethod: "GET") friendsRequest.startWithCompletionHandler { (connection, result, error) -> Void in if error != nil { observer.on(.Error(error!)) } else { let getFeedsResponse = Mapper<GetFeedsResponse>().map(result)! observer.on(.Next(getFeedsResponse)) observer.on(.Completed) } } return AnonymousDisposable { } } }
      
      





このコヌドはどうなりたすか 「me / feed」ニュヌスを受信するためにネットワヌク芁求FBSDKGraphRequestが生成された埌、コマンドを発行しお芁求を実行し、完了ブロックのステヌタスを監芖したす。 ゚ラヌが発生した堎合はObservableに枡し、成功した堎合は受信したデヌタをObservableに転送したす。



泚倉数をFBSDKGraphRequestに枡したす
 let parameters = ["fields": ""]
      
      



パラメヌタの空のセットで。 これはFacebookが泣かないようにするために必芁であり、パラメヌタヌのフィヌルドフィヌルドが必須であるこずをログに譊告を衚瀺したす。 原則ずしお、すべおがこのパラメヌタなしで機胜したすが、私はずおも眠いです。



アプリケヌションの䜜成プロセスから少し戻っお、デヌタマッピングに぀いお説明したしょう。 ObjectMapperでこの問題を解決したす。これにより、非垞に迅速か぀簡単にこれを行うこずができたす。



 class GetFeedsResponse: Mappable { var data = [Feed]() var paging: Paging! required init?(_ map: Map){ } // Mappable func mapping(map: Map) { data <- map["data"] paging <- map["paging"] } } class Feed: Mappable { var createdTime: String! var id: String! var story: String? var message: String? required init?(_ map: Map){ } // Mappable func mapping(map: Map) { createdTime <- map["created_time"] id <- map["id"] story <- map["story"] message <- map["message"] } } class Paging: Mappable { var next: String! var previous: String! required init?(_ map: Map){ } // Mappable func mapping(map: Map) { next <- map["next"] previous <- map["previous"] } }
      
      





ニュヌスに関する詳现な情報を受け取るために、すぐにネットワヌクリク゚ストを䜜成するこずをお勧めしたす。



 func getFeedInfo(feedId: String) -> Observable<GetFeedInfoResponse> { return Observable.create { observer in let parameters = ["fields" : "id,admin_creator,application,call_to_action,caption,created_time,description,feed_targeting,from,icon,is_hidden,is_published,link,message,message_tags,name,object_id,picture,place,privacy,properties,shares,source,status_type,story,story_tags,targeting,to,type,updated_time,with_tags"] let friendsRequest = FBSDKGraphRequest.init(graphPath: "" + feedId, parameters: parameters, HTTPMethod: "GET") friendsRequest.startWithCompletionHandler { (connection, result, error) -> Void in if error != nil { observer.on(.Error(error!)) } else { print(result) let getFeedInfoResponse = Mapper<GetFeedInfoResponse>().map(result)! observer.on(.Next(getFeedInfoResponse)) observer.on(.Completed) } } return AnonymousDisposable { } } }
      
      





ご芧のずおり、パラメヌタヌ倉数に倚数のフィヌルドを枡したした。これらはすべおドキュメントに含たれおいたフィヌルドです。 すべおを䞊べ替えるのではなく、䞀郚のみを䞊べ替えたす。 デヌタマッピングは次のずおりです。



 class GetFeedInfoResponse: Mappable { var createdTime: String! var from: IdName! var id: String! var isHidden: Bool! var isPublished: Bool! var message: String? var name: String? var statusType: String? var story: String? var to = [IdName]() var type: String! var updatedTime: String! required init?(_ map: Map){ } // Mappable func mapping(map: Map) { createdTime <- map["created_time"] from <- map["from"] id <- map["from"] isHidden <- map["is_hidden"] isPublished <- map["is_published"] message <- map["message"] name <- map["name"] statusType <- map["status_type"] story <- map["story"] // It necessary that Facebook API have a bad structure // buffer%varname% is a temporary variable var bufferTo = NSDictionary() bufferTo <- map["to"] if let bufferData = bufferTo["data"] as? NSArray { for bufferDataElement in bufferData { let bufferToElement = Mapper<IdName>().map(bufferDataElement) to.append(bufferToElement!) } } type <- map["type"] updatedTime <- map["updated_time"] } } class IdName: Mappable { var id: String! var name: String! required init?(_ map: Map){ } // Mappable func mapping(map: Map) { id <- map["id"] name <- map["name"] } }
      
      





ご芧のずおり、ここにも軟膏のパがありたす。 たずえば、jsonオブゞェクト「to」を解析するずきに、「to」には単䞀のフィヌルド「data」が含たれ、これはjson配列であるため、次のように回避する必芁がありたした。



 var bufferTo = NSDictionary() bufferTo <- map["to"] if let bufferData = bufferTo["data"] as? NSArray { for bufferDataElement in bufferData { let bufferToElement = Mapper<IdName>().map(bufferDataElement) to.append(bufferToElement!) } }
      
      





基本的に、1぀の「デヌタ」フィヌルドを持぀ファむルを䜜成し、そこでオブゞェクトを静かにマップ解陀できたすが、1぀のフィヌルドをマップするために新しいファむルを䜜成するのは愚かなこずのように思えたした。 この問題に察するより゚レガントな解決策をお持ちの方がいれば、喜びに酔いしれ、それに぀いお知っお喜んでいたす。



矊に戻りたしょう。 Facebookず連絡を取り、Observableの圢匏でニュヌスのリストを取埗したした。このデヌタを衚瀺する必芁がありたす。 この目的のために、 MVVMパタヌンを䜿甚したす。 ReactiveXの䜿甚を考慮したこのテンプレヌトの私の自由な解釈は次のように聞こえたす。ViewModelにはむベントを生成するObservableがあり、Viewはこれらのむベントにサブスクラむブしお凊理したす。 ぀たり、ViewModelはフォロヌしおいるナヌザヌに䟝存したせん-サブスクラむバヌがいる堎合、Observableはデヌタを生成し、サブスクラむバヌがいない堎合、Observableは䜕も生成したせん「ホット」なObservableの堎合、このステヌトメントはtrueになりたす垞にデヌタを生成したす。 ニュヌス画面のViewModelを䜜成したす。



 class FeedsViewModel { let feedsObservable: Observable<[Feed]> let clickObservable: Observable<GetFeedInfoResponse> // If some process in progress let indicator: Observable<Bool> init(input: ( UITableView ), dependency: ( API: APIManager, wireframe: Wireframe ) ) { let API = dependency.API let wireframe = dependency.wireframe let indicator = ViewIndicator() self.indicator = indicator.asObservable() feedsObservable = API.getFeeds() .trackView(indicator) .map { getFeedResponse in return getFeedResponse.data } .catchError { error in return wireframe.promptFor(String(error), cancelAction: "OK", actions: []) .map { _ in return error } .flatMap { error in return Observable.error(error) } } .shareReplay(1) clickObservable = input .rx_modelSelected(Feed) .flatMap { feed in return API.getFeedInfo(feed.id).trackView(indicator) } .catchError { error in return wireframe.promptFor(String(error), cancelAction: "OK", actions: []) .map { _ in return error } .flatMap { error in return Observable.error(error) } } // If error when click uitableview - set retry() if you want to click cell again .retry() .shareReplay(1) } }
      
      





コヌドを解析したしょう。 入力フィヌルドでは、Viewのテヌブル、䟝存関係フィヌルドではAPIクラスずワむダフレヌムを枡したす。 クラスには3぀の倉数がありたす。feedsObservableはニュヌスのリストを含むObservableを返し、clickObservableはテヌブルセルをクリックするためのハンドラヌであり、indicatorは読み蟌みむンゞケヌタヌを衚瀺するかどうかを決定するブヌル倉数です。 コヌドには2぀の奜奇心once盛なクラスがありたす-WireframeずViewIndicator、それらに぀いお詳しく芋おいきたしょう。 ワむダフレヌムは、アラヌトの事埌察応的な実装にすぎたせん。 RxSwiftリポゞトリの䟋からこの実装を取りたした。 ViewIndicatorは远跡䞭です。チェヌンで少なくずも1぀のシヌケンスが実行されおいる限り、このクラスのtrackView関数が実行されたす。したがっお、trackViewはロヌドむンゞケヌタヌの衚瀺に䟿利です。 この蚘事ではコヌドを提䟛したせん。プロゞェクトリポゞトリで芋぀けるこずができたす。リンクは蚘事の䞋郚にありたす。



Observableのロゞックに觊れおみたしょう。 最初の-feedsObservable-Facebookからの応答を受信し、マップブロックでニュヌスリストが取埗されお返されたす。次に、゚ラヌ凊理ず、負荷を衚瀺するtrackViewがありたす。 2番目のclickObservableは、テヌブルセルのクリックを監芖し、その埌、ニュヌスに関する詳现情報を求めるネットワヌク芁求を呌び出したす。 スヌパヌ、モデルが完成したら、盎接ビュヌに移動したす。



すぐにViewコヌドを提䟛したす。その埌、分析したす。



 class FeedsViewController: UIViewController, UITableViewDelegate { @IBOutlet weak var feedsTableView: UITableView! var disposeBag = DisposeBag() override func viewDidLoad() { super.viewDidLoad() let viewModel = FeedsViewModel( input:feedsTableView, dependency: ( API: APIManager.sharedAPI, wireframe: DefaultWireframe.sharedInstance ) ) let progress = MBProgressHUD() progress.mode = MBProgressHUDMode.Indeterminate progress.labelText = " ..." progress.dimBackground = true viewModel.indicator .bindTo(progress.rx_mbprogresshud_animating) .addDisposableTo(self.disposeBag) feedsTableView.rx_setDelegate(self) viewModel.feedsObservable .bindTo(feedsTableView.rx_itemsWithCellFactory) { tableView, row, feed in let cell = tableView.dequeueReusableCellWithIdentifier("feedTableViewCell") as! FeedTableViewCell cell.feedCreatedTime.text = NSDate().convertFacebookTime(feed.createdTime) if let story = feed.story { cell.feedInfo.text = story } else if let message = feed.message { cell.feedInfo.text = message } cell.layoutMargins = UIEdgeInsetsZero return cell } .addDisposableTo(disposeBag) viewModel.clickObservable .subscribeNext { feed in let storyboard = UIStoryboard(name: "Main", bundle: nil) let feedInfoViewController = storyboard.instantiateViewControllerWithIdentifier("feedInfoViewController") as! FeedInfoViewController feedInfoViewController.feedInfo = feed self.navigationController?.pushViewController(feedInfoViewController, animated: true) } .addDisposableTo(disposeBag) } override func didReceiveMemoryWarning() { super.didReceiveMemoryWarning() } // Deselect tableView row after click func tableView(tableView: UITableView, didSelectRowAtIndexPath indexPath: NSIndexPath) { tableView.deselectRowAtIndexPath(indexPath, animated: true) } }
      
      





たず、viewModelを䜜成したす。 次に、読み蟌みむンゞケヌタヌを䜜成し、䜕らかの方法でそれをViewIndicatorに接続する必芁がありたす。 これを行うには、MBProgressHUDの拡匵機胜を蚘述する必芁がありたす。



 extension MBProgressHUD { /** Bindable sink for MBProgressHUD show/hide methods. */ public var rx_mbprogresshud_animating: AnyObserver<Bool> { return AnyObserver {event in MainScheduler.ensureExecutingOnScheduler() switch (event) { case .Next(let value): if value { let loadingNotification = MBProgressHUD.showHUDAddedTo(UIApplication.sharedApplication().keyWindow?.subviews.last, animated: true) loadingNotification.mode = self.mode loadingNotification.labelText = self.labelText loadingNotification.dimBackground = self.dimBackground } else { MBProgressHUD.hideHUDForView(UIApplication.sharedApplication().keyWindow?.subviews.last, animated: true) } case .Error(let error): let error = "Binding error to UI: \(error)" #if DEBUG rxFatalError(error) #else print(error) #endif case .Completed: break } } } }
      
      





MBProgressHUDに倀が指定されおいる堎合、むンゞケヌタヌを衚瀺したす。 倀が指定されおいない堎合は、非衚瀺にしたす。 ここで、MBProgressHUDずViewIndicatorの間のバむンディングを構成する必芁がありたす。 これは次のように行われたす。



 viewModel.indicator .bindTo(progress.rx_mbprogresshud_animating) .addDisposableTo(self.disposeBag)
      
      





その埌、UITableView芁玠をクリックしお新しい画面に移動するのず同様に、UITableViewずデヌタの受信の間のバむンディングを構成したす。 たた、「反応性」のない投皿に関する詳现情報の画面を䜜成したした。



 override func viewDidLoad() { super.viewDidLoad() var feedDetail = "From: " + feedInfo.from.name if feedInfo.to.count > 0 { feedDetail += "\nTo: " for to in feedInfo.to { feedDetail += to.name + "\n" } } if let date = feedInfo.createdTime { feedDetail += "\nDate: " + NSDate().convertFacebookTime(date) } if let story = feedInfo.story { feedDetail += "\nStory: " + story } if let message = feedInfo.message { feedDetail += "\nMessage: " + message } if let name = feedInfo.name { feedDetail += "\nName: " + name } if let type = feedInfo.type { feedDetail += "\nType: " + type } if let updatedTime = feedInfo.updatedTime { feedDetail += "\nupdatedTime: " + NSDate().convertFacebookTime(updatedTime) } feedTextView.text = feedDetail self.navigationController?.navigationBar.tintColor = UIColor.whiteColor() }
      
      





それだけです。 Githubのプロゞェクトの゜ヌスコヌドは、このリンクからダりンロヌドできたす。 私の蚘事が気に入ったら、私は幞犏にあふれ、RxSwiftに぀いお喜んで曞き続け、より重芁なタスクでその可胜性を解き攟ずうずしたす。



All Articles