请选择 进入手机版 | 继续访问电脑版

技术控

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

[其他] The Realm SDK Enables Clean and Easy Separation of Concerns

[复制链接]
被妳弄來沒了 发表于 2016-11-29 18:04:11
191 0

The Realm SDK Enables Clean and Easy Separation of Concerns

The Realm SDK Enables Clean and Easy Separation of Concerns-1-技术控-highlights,recommend,benefits,offering,database

   This is the fourth post in a multi-part series on the Realm Mobile Database; it highlights some of the multi-platform aspects of the the database and the high reusability of the code across devices. If you missed the first post on our custom engine , the second post on the benefits of some of the Realm API types , and the third post on sharing code across platforms , we recommend taking few minutes to read through.
  A third-party SDK might empower you or get in your way. For example, a highly functional but monolithic library will allow you to quickly implement some basic app logic, but create more trouble than value down the road.
   The Realm Mobile Database aims to not only allow you to persist your data, but to help you implement good practices. It does that by offering you a flexible SDK in a number of programming languages (Swift, Java, JavaScript, C#, and more) - it allows you to fine-tune your data objects and be in control all the way up to the higher-level APIs like reacting to changes and talking to your server.
  Any SDK, third-party library, or service you’d like to base your own product on should exhibit the same qualities you aim to have in your own product.
  Separation of Concerns

   If you’ve read the previous posts in this series, you remember that we’ve prepared a little companion project to provide examples for the features highlighted in each article. You can always clone (or download) the project from its GitHub repository and peak inside.
  Let’s take a quick look at how the Realm Swift SDK helps separate concerns in the companion project. We didn’t stick to a certain architecture since you might like a different one; this way you can read through the code and apply what you like best in your own project.
  The project consist of a single Xcode workspace, which features one target for each of Apple’s four platforms: iOS, macOS, tvOS, and watchOS:
   

The Realm SDK Enables Clean and Easy Separation of Concerns

The Realm SDK Enables Clean and Easy Separation of Concerns-2-技术控-highlights,recommend,benefits,offering,database

   Since the Realm Swift SDK supports all four platforms, the project shares the data layer code between all of them. In the previous post we had a look at a data entity called Favorite and figured that it only depends on Foundation and RealmSwift frameworks:
  1. import Foundation
  2. import RealmSwift
  3. class Favorite: Object {
  4.     dynamic var symbol = ":sparkling_heart:"
  5.     var symbolIndex: Int? {
  6.         return Favorite.symbols.index(of: symbol)
  7.     }
  8.     static let symbols = [":sparkling_heart:", ":rocket:", "��"]
  9.     static let noSymbolIndex = -1
  10. }
复制代码
  The data entity class can be, therefore, re-used as-is between platforms - it only makes use of common Foundation types and Realm itself.
  But let’s move beyond the data entity classes.
  In essence, all of the different apps that the project builds are list-based applications. They might look a bit different across platforms but the basic structure is:
  
       
  • a default screen showing a list of repositories, with the ability to filter them (no filtering in the watch app)   
  • a single repository detail screen, with the ability (where available) to mark the selected repository as favorite  
   The default screen presents a list of items - the iOS project uses a UITableView , the macOS project an NSTableView , tvOS a UICollectionView , etc. Here’s how the storyboard looks for iOS:
   

The Realm SDK Enables Clean and Easy Separation of Concerns

The Realm SDK Enables Clean and Easy Separation of Concerns-3-技术控-highlights,recommend,benefits,offering,database

  And here’s the watchOS app:
   

The Realm SDK Enables Clean and Easy Separation of Concerns

The Realm SDK Enables Clean and Easy Separation of Concerns-4-技术控-highlights,recommend,benefits,offering,database

  Regardless of the specific UI components each project makes use of, in the end you’d like to present a list, and therefore you can separate the “presenting a list of repositories” logic from the target-specific UI code.
  This way the project ends up featuring three clean-cut layers:
  1. Data storage and observation layer

   Storing and retrieving data is handled by Realm - the API depends on Apple’s Foundation but no other framework, so your Object subclasses and/or other code that handles your entities can be easily abstracted away from your main project.
  This layer also implements any specifics of your data model that need to be tightly integrated with the code talking straight to your Realm database.
   For example the Repository object describes all the data properties the app persists in Realm:
  1. class Repository: Object {
  2.     dynamic var id: Int = 0
  3.     dynamic var stars: Int = 0
  4.     dynamic var url: String = ""
  5.     ...
  6. }
复制代码
But it also features few simple business rules, such as getting all repositories from the Realm database:
  1. static func all(searchTerm: String? = nil) -> Results<Repository> {
  2.     let realm = try! Realm()
  3.     return realm.objects(Repository.self)
  4.         .filter("name contains[c] %@", searchTerm ?? "")
  5.         .sorted(byProperty: "stars", ascending: false)
  6. }
复制代码
You can check out the companion project to see others, like toggling the repo’s “favorite” status.
  2. Shared application and presentation logic

  This layer implements application logic that can be abstracted away from specific platforms. We already mentioned “presenting a list of repos”, but this layer also handles “reacting to data model changes” and networking.
   A ReposListPresenter class takes care of presenting a list of repositories. It talks to the data layer in order to load a list of repositories and keeps the list up to date.
  It adopts a number of platform-specific protocols to be able to serve as a data source for all project targets. (For your favorite architecture, you might create a generic “data source” protocol and bridge it to the platform-specific ones.)
   Since updating the actual UI includes plenty of platform-specific code, ReposListPresenter provides a generic callback to allow each target to handle UI updates in its own code:
  1. func loadRepos(searchFor term: String? = nil, updated: @escaping (RealmCollectionChange<Results<Repository>>) -> Void) {
  2.     refreshToken?.stop()
  3.     repos = Repository.all(searchTerm: term)
  4.     refreshToken = repos?.addNotificationBlock(updated)
  5. }
复制代码
  loadRepos(searchFor:updated:) searches the Realm database for matching repositories, creates a notification subscription for the result set, and binds change notifications to the updated parameter closure.
  3. Views and view controlling

  Finally we have a look at the target-specific UI code.
   Each target features its own storyboard, using the native controls for that platform. The view controller classes for each platform all use ReposListPresenter and implement their own UI update code in the callback.
   For example, the macOS app uses the NSTableView API:
  1. reposPresenter.loadRepos(searchFor: sender?.stringValue ?? "", updated: { changes in
  2.     switch changes {
  3.         case .initial:
  4.             self.tableView.reloadData()
  5.             
  6.         case .update(_, let deletions, let insertions, let updates):
  7.             self.tableView.beginUpdates()
  8.             self.tableView.insertRows(at: IndexSet(insertions), withAnimation: .slideDown)
  9.             self.tableView.reloadData(forRowIndexes: IndexSet(updates), columnIndexes: IndexSet(integer: 0))
  10.             self.tableView.removeRows(at: IndexSet(deletions), withAnimation: .slideUp)
  11.             self.tableView.endUpdates()
  12.         default: break
  13.     }
  14. })
复制代码
  The NSTableView API allows for handling tables with multiple columns and features macOS-specific update animations.
   The tvOS view controller, on the other hand, uses a collection view to display the repositories on a TV screen and therefore makes use of the UICollectionView API:
  1. reposPresenter.loadRepos(searchFor: sender?.text) { changes in
  2.     switch changes {
  3.         case .initial:
  4.             self.collectionView?.reloadData()
  5.         case .update(_, let deletions, let insertions, let updates):
  6.             let cv = self.collectionView!
  7.             cv.performBatchUpdates({
  8.                 cv.insertItems(at: insertions.map {IndexPath(row: $0, section: 0)})
  9.                 cv.reloadItems(at: updates.map {IndexPath(row: $0, section: 0)})
  10.                 cv.deleteItems(at: deletions.map {IndexPath(row: $0, section: 0)})
  11.             }, completion: nil)
  12.         default: break
  13.     }
  14. }
复制代码
  This piece of code uses UICollectionView.performBatchUpdates(_:completion:) to reflect the data model changes in the UI. You can see that the code is similar in logic but different in syntax compared to the macOS code.
  Since observing for changes and triggering the update closure are abstracted away, the current layer only cares about platform-specific UI code.
  Reactive Apps by Default :rocket:

  You saw how easy it is to separate concerns in the demo project. As the app gets more complicated, the different layers can be abstracted further away into separate frameworks, and, potentially, be re-used in other projects as well.
  It is important to notice that, even in its beginning stage, the project is already implementing a reactive architecture, thanks to the Realm SDK.
   The app is message driven . For example, the ReposListPresenter only reacts to change notifications, while the GitHubAPI class focuses on networking and updating the repositories in the database. The two primary classes that read and write to the database work completely independently (and possibly on different threads), and rely solely on Realm’s notifications to trigger updates.
   The app is resilient , as the Realm SDK is incredibly sturdy and allows you to easily avoid data persistence problems like merge conflicts or lost data. By allowing you to easily decouple classes, any issues you experience in a class that writes the data will not affect your presenter classes.
   Finally the app is responsive , as it maintains a local cache of repositories in its Realm database. As soon as you open the app you can browse and search the locally stored repositories while a fresh list is being grabbed in the background. As soon as the updated data is received and stored on disc, Realm delivers change notifications and the app updates all relevant UI by animating items in or out.
   Last but not least, since the Realm SDK has already turned the project into a reactive app, you can always build further upon this foundation (if you need that). If you’d like to use a third-party reactive framework, you can easily integrate it on top of the current project code - for example  RxSwift  for iOS and  RxJava  in your Android project.
  We Hope You Like What You’re Seeing!

  In this post, you learned about some of the ways that the Realm SDK empowers good architecture. The SDK is powerful and very flexible, so no matter which particular architecture is your favorite you’ll be able to separate concerns, decouple classes, and be sure your data is safe.
  As always, if you have few extra minutes, you are welcome to check out the Realm Mobile Database:
  
       
  • Download the database (it’s free!)   
  • Learn how to useRealm with RxSwift  
  In the next installment in this series, we’re going to look in more detail into Realm’s fine-grained notifications, and why you don’t need a fetched results controller when using a modern database.
  See you next time! :wave:
我要投稿

推荐阅读


回页顶回复上一篇下一篇回列表
手机版/c.CoLaBug.com ( 粤ICP备05003221号 | 文网文[2010]257号 | 粤公网安备 44010402000842号 )

© 2001-2017 Comsenz Inc.

返回顶部 返回列表