技术控

    今日:130| 主题:49225
收藏本版 (1)
最新软件应用技术尽在掌握

[其他] I create iOS apps - is RxSwift for me?

[复制链接]
别瘠薄闹 发表于 2016-10-4 05:46:26
353 16

立即注册CoLaBug.com会员,免费获得投稿人的专业资料,享用更多功能,玩转个人品牌!

您需要 登录 才可以下载或查看,没有帐号?立即注册

x
Video & transcription below provided by Realm. Realm Swift is a replacement for SQLite & Core Data written for Swift!
   Learn more about Realm
     It is difficult to make the jump from map and filter to presenting view controllers or search bars that need to call an API on the web and populate a table view. To be honest, at first it seems almost like functional or reactive programming has nothing to do with UIKit or NSURLSession.
   In this try! Swift NYC talk, Marin Todorov shows how RxSwift (an async, event based framework) applies in few every day situations of the life of an iOS developer. If you like major pains being solved for you transparently at the price of a single dependence, this talk is for you.
    The Reactive Extensions, Rx , is a library for composing asynchronous and event-based programs using observable sequences and link style query operators. Using Rx, developers represent asynchronous data streams.
    ( Marin, shredding his notes ): I have read the theory many times, but that has never helped me understand what it does for me or in my daily work of developing iOS apps. With the risk of being completely and totally wrong, today I am just going to tell you how I see Rx working in iOS apps.
   The Reactive Side of Rx

   First I am going to talk about the reactive side of Rx, or the ability of having your data pushed to you (instead of you pulling for changes).
   An array, a collection of strings (if it is an array of strings, of course), is handy because you can use a for loop (e.g., to do some work with each of the items, or you can use the more swifty approach by using the forEach method). But it is all the same: you provide a closure, some block of work, and it gets executed on each item separately, one after the other.
    The problem with the arrays is that you do this work in a frozen moment in time. You cannot account for, for example, elements that are being added to your array later. Why would I want to work with things that are not there yet?
   Let’s have a look at a table view controller. You have an array of things and you show them on screen in table view. That is great. However, as soon as the user taps on the plus button and adds a new item (i.e. a fourth item to your array), you have a problem, because your data model has four items, and your UI has three.
    Instead of focusing on your business logic and handling your data, you have to think about notifications, synchronizing your model and UI, etc. It would be handy if there was a way to asynchronously work with older versions of your data, and not just the one in this very moment.
   Observable < Array < String > >

    Rx wraps your data (in this case, an array) into an observable class. This observable class, simplistically, extends your data through time. It takes your data and adds a “time dimension.”
    If we go back to our array example, the observable will define this block of data you want to do with each of the elements, i.e., printing them. It will get executed over all the initial data that you have (the three items that are in this array), but then, when you add a fourth one, this block will fire again. When you add a fifth element to this array, it will again execute. You move from defining how and when things should be happening, and you move (mentally) to defining behaviors . You always would have some data, and you would want to do something with it any time it changes or any time some event happens.
    Things get very linear. This is one of the greatest things that Rx does ( from my point of view ): things get very simple. Instead of thinking: what do I have now? What will I have in the future? What did I have in the past? You only would define a simple behavior. In our example, I have a list of items, I want to see them on screen, in table view. That is it. Let RxSwift do the work every time that you add a new element. It is a very straight line from your data to your UI, in this case.
   Observables can do great things. For example, if you have a text field, it is a very similar situation (even though it is a totally different company we are talking about). Your event, in this case, would be the user typed in a character, and the behavior “show this text in a label, or do something with this text and update the UI.” You define this behavior once and let Rx take care of every time the user types in the new character, just reapply this behavior for you. Your UI can never be out of sync with your data model.
   Handy, simple, linear.
    For a more esoteric example, let’s define a behavior: “the user is scrolling through a table view, and every time they scroll to the bottom of that table view, do a behavior.” This behavior in this case could be: “let’s load 20 more items from the server, and append them to the list that we already have onscreen” (simple, linear behavior). When the user does that, they will have more items when they scroll again to the bottom (and they reach the bottom of these new items being appended), this exact same behavior will get executed for you automatically ( you will have even more items ). It is a very simple way that you start thinking about your code.
   The functional side of Rx

   Let’s have a look at the functional side of these, presumably, great observables.
    In our three different examples, we had an observable type for each of them. For the text field, it has text in it, we used an Observable . If you work with text, an observable string is if you want to have the string in all of its future versions. You wrap your data, the text, into an observable. In our table view controller example, that was an array of strings, Observable> . In our scrolling example, we did not have data; it is an Observable - we are just interested in the event that the user reached the bottom.
   In all of these examples you are working with the same class. This allows you to use the same set of methods on this class (in all possible situations!). You can move away from the data type (i.e., an array, a string, a number or anything else), and you can think of defining the behavior in whole workflows, based on what you can do with observables. Your logic can be easily transferred between projects by copying and pasting files, or of course, extracting those into frameworks, and you can focus on the specific datatype you want to work with in your specific project.
    Let’s have a look at a quick classic example that every first-time Rx presenter has to go through: an application that allows you to enter a query in a text field, and search GitHub for a matching repo.
    Let’s have a look at how we would build this using those, presumably, great observables. There is my text field on the screen. It gives me Observable ; it gives me a string every time that the user types a new character in there. There is a method on the observable class called “filter” that allows me to maybe discard some of those values. It filters out things that I do not like - e.g., I do not like any search queries less than three characters, because they will produce too may irrelevant results. It is a method on the observable. The good thing about it is it also returns an observable. I can get the result of filter and call another method of the observable class on the result of that method. I can chain them, one after the other (which is great): I am going to call it debounce .
   Debounce is a method on the observable that allows me to detect too many events close together, and take the last one. If the user knows part of the name of the repo and types that quickly, I do not want to fire a network request for each of the characters. I want to wait a bit, and take the last one. I am chaining those, because I can call them one after the other.
    I am going to call map on my observable. Map allows me to convert the datatype that I am wrapping into something else. In this case, it is simple: I am taking a string (the search query), and I build a URL, and I build a map NSURLRequest(...) . For each character that I type that already matches all of the previous, I would have a request ready to be fired off to the network.
    FlatMap will allow me to make a network request, wait until it completes and gives back the result. Then I can continue working. That will give me the NSData back, and I can again use map on that to convert the NSData , by using NSJSONSerialization to Array . Then, one more time, map to convert those any objects to Repo(...) .
    This is the workflow of my application ( see slide 14 above ), from a keystroke in the text field through the validation of that input, the networking, the conversion of data, until I have a list of repos that I can bind to a table view, maybe store on disk on Realm (or in any other way). It is good because it is very linear: one thing falls from the other, and I never get confused in what sequence these things happen. I do not have a data source protocol. I do not have a delegate method of protocol. Things are happening sequentially, one after the other, and it is very linear.
   We are going to start with my outlet to my query text field, and Rx will be the observable that gives me the string.
   [code]query.rx_text

  .filter {string in
      return string.characters.count > 3
  }

  .debounce(0.51, scheduler: MainScheduler.instance)

  .map {string in
      let apiURL = NSURL(string: "https://api.github.com/q?=" + string)!
      return NSURLRequest(URL: apiURL)
  }

  .flatMapLatest { request in
      return NSURLSession.sharedSession().rx_data(request)
  }

  .map { data —> Array in
      let json = try NSJSONSerialization.JSONObjectWithData(data, options: [])
      return json as! Array
  }

  .map {object in
      return Repo(object: object)
  }

  .bindTo(tableView.rx_itemsWithCellIdentifier("Cell"))[/code]    We call filter and we supply the code to filter out the bad strings that we do not want. Then we are going to call debounce, and mention in what time interval we want to group those events together. We are going to call our map, taking the string and building a URL, and NS Serial request out of it. And then we are going to call FlatMap, use an URL session to do our request and get data back. Another map to transfer the NSData via NSJASONSerialization (as usual). Another map to take each of those AnyObject and convert them to a repo object that I have defined. In the very last call of this chain, I am going to call .bindTo and say that this list of repos should be bound to my table view, using a CellIdentifier to do all the work.
    ( As you can see, this is bad code, simple and short. However, we can consider a few aspects of it ): you all had 15 minutes of experience with Rx (maybe some have more). You can already see what this code does. It is very sequential, and you can never get confused about the order in which these are going to be executed. If you are a new member on the team, you will understand what this code does. If I have a look at this code six months from now, I am going to understand what it does.
    The best part is that I am not calling methods on an object that do not have anything to do with each other, but I am calling them in a chain. Each of them expects some input and produces some other output. And they are holding hands tight together, once you get it to compile, which is the trick; it is very difficult to introduce any variance into the code. Because once the compiler tells you “this is okay,” then things are running smoothly.
   How any of this is relevant to my iOS applications?
    Let’s take a look at a more elaborate example of presenting a view controller (inherently non-reactive, non-functional, and non-nothing ). We could have a navigation controller with a list of repos, in a table view, and a model view controller which allows the user add the repo manually. They enter all the data via the keyboard, press done, and it should go in .
    The normal way would be to implement a delegate protocol. You will define a protocol with some methods that one of the controllers is going to call on the other one. If you need those to talk back, that gets a little more complicated… but let’s forget all about this .
   We have a universal class, a datatype that allows us to do communication universally between all classes. This is the observable. If your Add Repo view controller has an observable that would emit a value whenever the user is done… that would make things very simple.
    Let’s have a look at the source code. Let’s start with the bar item ( the + button on the top right ). In Rx, tap is an observable that, basically, fires every time that the user taps on the + button.
   [code]addBarItem.rx_tap

    .debounce(0.5, scheduler: MainScheduler.instance)

    .flatMapFirst {[weak self] _ —> Observable in
        let addVC = AddRepoViewController()
        self?.presentViewController(addVC, animated: true, completion: nil)
        return addVC.newRepo.asObservable()
    }

    .doOn {_ in
        self.dismissViewControllerAnimated(true, completion: nil)
    }

    .subscribeNext {repo in
        repos.value.append(repo)
    }

repos.asObservable()
    .bindTo(tableView.rx_itemsWithCellIdentifier("Cell"))[/code]   I am going to use debounce again, which prevents double taps. If the user quickly taps a few times on the + button, instead of opening a few view controllers on the screen, Rx will open it just once.
   Then we are going to do a FlatMap. In the previous example, it allowed us to do a bit of work, and then wait until it completes. This is exactly what I want to do with our view controller that is present modally: present the view controller, and wait until its exposed observable via property would emit back a repo. Next, I want to dismiss the view controller. My final call of the chain: do something with the data. In this case, I am going to take the repo that the observable gave me back and add it to a list of repos. To complete the whole logic, I am going to bind my repos list to the Table View.
    Quickly, run through NVVM ( there was a discussion earlier about what it stands for. Forget about it ). You are going to have some view controllers, you will have some models to drive their data, and this is where you will put all your Rx code ( all the logic, all the calls ). In these last lines in the chain - “since I finally have my data, I want to do something in the UI” - you will make it clear-cut and put it in the view controllers.
    It is easy to separate the logic from bindings to our UI. Since the only logic will be in your view models, you will write your tests to test the view models, and you will never have to instantiate view controls or other craziness in your tests. It is very easy to come to a point where you feel things are separate.
   
       
  • RxSwift is a synchronous-like asynchronous framework.   
  • It has a functional aspect: it allows you to process this asynchronous event (convert things, do things with them).   
  • And it encourages good architecture.   
    Very relevant to iOS development ( yes it is! ).
   Further Reading & Credits

   
       
  • Since Rx is an API that is implemented in various languages, on different platforms: ReactiveX.io will give you instructions how to use the API on with Swift, Java, JS, Skala.   
  • RXSwift.org .   
  • rx-marin.com , where you could find some articles written by me that would help you start with RX.   
   In chronological order: thanks to Ash Furrow, for being extreme inspiration. Jens Ravens, who explained me some of the basics. Florent Pillet, who fixed some of my early code. Junior Bontognali, who is just a friend, amazing, supportive (much Rx in there!). And Krunoslav Zaher, who launched RxSwift.
    I am here thanks to Natasha, who invited me to New York; Realm sent me here :heart:. If you are interested in any of these jobs -we are hiring!.
    See the discussion on Hacker News .
   See full transcription         
I create iOS apps - is RxSwift for me?-1 (framework,difficult,developer,represent,provided)
         Marin Todorov

     Marin Todorov is an independent iOS consultant and publisher. He’s the author of the book “iOS Animations by Tutorials” and runs the “iOS Animations by Emails” newsletter. He started developing on an Apple ][ more than 20 years ago and keeps rocking it today. Meanwhile he has worked in great companies like Monster Technologies and Native Instruments, has lived in 4 different countries, and is one the founding members of the raywenderlich.com tutorial team. Besides crafting code, Marin also enjoys blogging, writing books, teaching, and speaking. He sometimes open sources his code. He walked the way to Santiago.
      Twitter
友荐云推荐




上一篇:Responsive table layout
下一篇:MySQL 8.0 General Tablespaces: File per Database (and no FRM files)
酷辣虫提示酷辣虫禁止发表任何与中华人民共和国法律有抵触的内容!所有内容由用户发布,并不代表酷辣虫的观点,酷辣虫无法对用户发布内容真实性提供任何的保证,请自行验证并承担风险与后果。如您有版权、违规等问题,请通过"联系我们"或"违规举报"告知我们处理。

syliuhui 发表于 2016-10-4 08:46:26
我为别瘠薄闹转身!
回复 支持 反对

使用道具 举报

Beiyee 发表于 2016-10-14 22:51:51
远看是美景,近看想报警。
回复 支持 反对

使用道具 举报

冯楠清 发表于 2016-10-22 19:57:44
青春不在了,青春痘还在!
回复 支持 反对

使用道具 举报

一叶孤城 发表于 2016-10-24 20:33:28
胆子不小啊,居然让我抢到了沙发!
回复 支持 反对

使用道具 举报

踏雪倾天下 发表于 2016-10-25 00:24:13
心里只有你一个频道,最可恨的是还没有广告。
回复 支持 反对

使用道具 举报

Peter.Lin 发表于 2016-10-25 19:24:12
好帖必须得顶起
回复 支持 反对

使用道具 举报

大强长u 发表于 2016-10-25 19:43:33
we are 伐木累
回复 支持 反对

使用道具 举报

aiqiu123 发表于 2016-10-27 07:34:30
拿分 路过
回复 支持 反对

使用道具 举报

丁丹妮 发表于 2016-10-28 04:51:37
胆子不小啊,居然让我抢到了沙发!
回复 支持 反对

使用道具 举报

*滑动验证:
您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

我要投稿

推荐阅读

扫码访问 @iTTTTT瑞翔 的微博
回页顶回复上一篇下一篇回列表手机版
手机版/CoLaBug.com ( 粤ICP备05003221号 | 文网文[2010]257号 )|网站地图 酷辣虫

© 2001-2016 Comsenz Inc. Design: Dean. DiscuzFans.

返回顶部 返回列表