ææ°ã®ãã¬ã³ããä¿¡ããŠãããªãã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ããªã²ãŒããè¿œå ããããšãããã³ããªã²ãŒãã¡ãœãããå®è£
ããããšãå¿ããªãã§ãã ããã
ããã§ã¯ãã¹ãŠãç°¡åã§ã-ãã°ã€ã³ãšã©ãŒãŸãã¯ãŠãŒã¶ãŒã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){ }
ãã¥ãŒã¹ã«é¢ãã詳现ãªæ
å ±ãåãåãããã«ãããã«ãããã¯ãŒã¯ãªã¯ãšã¹ããäœæããããšããå§ãããŸãã
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){ }
ã芧ã®ãšãããããã«ãè»èã®ããšããããŸãã ããšãã°ã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>
ã³ãŒãã解æããŸãããã å
¥åãã£ãŒã«ãã§ã¯ã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() }
ãŸããviewModelãäœæããŸãã 次ã«ãèªã¿èŸŒã¿ã€ã³ãžã±ãŒã¿ãŒãäœæããäœããã®æ¹æ³ã§ãããViewIndicatorã«æ¥ç¶ããå¿
èŠããããŸãã ãããè¡ãã«ã¯ãMBProgressHUDã®æ¡åŒµæ©èœãèšè¿°ããå¿
èŠããããŸãã
extension MBProgressHUD { 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ã«ã€ããŠåãã§æžãç¶ããããéèŠãªã¿ã¹ã¯ã§ãã®å¯èœæ§ã解ãæŸãšããšããŸãã