I'm using RxSwift with MVVM and I'm found myself a bit confused. Here's why:
My Code Right Now
ViewModel
internal protocol DetailViewModelInput {
func viewDidLoad(with name: String)
}
internal protocol DetailViewModelOutput {
var gnomeObject: Observable<Gnome?> { get }
}
struct DetailViewModel: DetailViewModelType, DetailViewModelInput, DetailViewModelOutput {
let disposeBag = DisposeBag()
let gnomeObject: Observable<Gnome?>
init() {
gnomeObject = viewDidLoadProperty
.asObservable()
.filter { !$0.isEmpty }
.map { guard let gnome = Gnome
.fetch(uniqueValue: $0, forKey: "name")! as? Gnome else { return nil }
return gnome
}
}
let viewDidLoadProperty = Variable<String>("")
func viewDidLoad(with name: String) {
viewDidLoadProperty.value = name
}
}
ViewController
I make the binding as follows:
func bindViewModel() {
viewModel.outputs.gnomeObject
.subscribe { observable in self.populate(with: observable.element != nil ? observable.element! : nil) }
.addDisposableTo(viewModel.disposeBag)
}
And this is "fine". It works perfectly (at least as expected).
But, I while reading the following book: https://victorqi.gitbooks.io/rxswift/content/tips.html
In the tips section it says:
Always strive to model your systems or their parts as pure functions. Those pure functions can be tested easily and can be used to modify operator behaviors.
And after reading it I'm changed my ViewModel as follows:
ViewModel (Edited)
internal protocol DetailViewModelInput {
func viewDidLoad(with name: String)
}
internal protocol DetailViewModelOutput {
func gnomeObject() -> Observable<Gnome?>
}
protocol DetailViewModelType {
var disposeBag: DisposeBag { get }
var inputs: DetailViewModelInput { get }
var outputs: DetailViewModelOutput { get }
}
struct DetailViewModel: DetailViewModelType, DetailViewModelInput {
let disposeBag = DisposeBag()
let viewDidLoadProperty = Variable<String>("")
func viewDidLoad(with name: String) {
viewDidLoadProperty.value = name
}
}
// MARK: DetailViewModelOutput
extension DetailViewModel: DetailViewModelOutput {
func gnomeObject() -> Observable<Gnome?> {
return viewDidLoadProperty
.asObservable()
.filter { !$0.isEmpty }
.map { guard let gnome = Gnome
.fetch(uniqueValue: $0, forKey: "name")! as? Gnome else { return nil }
return gnome
}
}
}
The difference in the ViewModels is the GnomeObject declaration, in one it is a var and in the "edited" is a func.
My concern is, that every time gnomeObject() gets called from the ViewController, it will create a new instance of the observable.
What should be the best practice in this case?
Hmm, in the first version, gnomeObject is a let, not a var. Once it is set, it is never changed to a different object.
In the second version gnomeObject() returns a different object every time it's called. So this actually breaks the "pure function" paradigm. (Note: if the Observable was a struct instead of a class then this wouldn't be the case because structs don't have identity.)
Your first example follows the pure function concept while your second version breaks it.
If you're looking to eliminate the need to instantiate gnomeObject in the initializer, you could modify the first example to use a lazy var like so:
lazy var gnomeObject: Observable<Gnome?> = self.viewDidLoadProperty
.asObservable()
.filter { !$0.isEmpty }
.map { guard let gnome = Gnome
.fetch(uniqueValue: $0, forKey: "name")! as? Gnome else { return nil }
return gnome
}
When they say you should use pure functions they mean that functions (when possible) should have the same output for the same set of inputs, meaning, if a function is called twice with the same set of inputs it should return the same thing twice.
That means you don't have any hidden mutable state that the caller of the functions is not aware of (a property in the class that owns the method, for example). Everything should be as explicit as possible.
So, it's something you should be aware of when it comes to functions. But it's completely ok to use properties, as you were doing in the first code, they don't apply to this.
Related
I am new to RxSwift and RxCocoa
I need to any advice for learning
After result of Checking Id Validation, expect no word in label
But it is updating label and no entering in break point at bind function
What’s problem my code…?
var disposeBag: DisposeBag = DisposeBag()
let viewModel = ViewModel()
override func viewDidLoad() {
super.viewDidLoad()
let input: Signal<String> = userIDTextField.rx.text.orEmpty
.asSignal(onErrorSignalWith: .empty())
let output: Driver<String> = viewModel.bind(input)
disposeBag.insert(
output.drive(userIDLabel.rx.text)
)
}
struct ViewModel {
func checkUserIDFromDB(id: String) -> Signal<Bool> {
return .just(false).asSignal()
}
func bind(_ input: Signal<String>) -> Driver<String> {
let validState = input
.map { _ in self.checkUserIDFromDB(id:)}
.withLatestFrom(input)
return validState.asDriver(onErrorDriveWith: .empty())
}
}
This line: .map { _ in self.checkUserIDFromDB(id:)} produces a Signal<(String) -> Signal<Bool>> which is likely not what you wanted.
I'm going to assume that the goal here is to pass the entered string to the network request and wait for it to emit. If it emits true then emit the string to the label, otherwise do nothing...
Further, let's simplify things by using the Observable type instead of Signals and Drivers:
final class ViewController: UIViewController {
let userIDTextField = UITextField()
let userIDLabel = UILabel()
let disposeBag = DisposeBag() // this should be a `let` not a `var`
let viewModel = ViewModel()
override func viewDidLoad() {
super.viewDidLoad()
let input = userIDTextField.rx.text.orEmpty
let output = viewModel.bind(input.asObservable())
disposeBag.insert(
output.bind(to: userIDLabel.rx.text)
)
}
}
struct ViewModel {
func checkUserIDFromDB(id: String) -> Observable<Bool> { .just(false) }
func bind(_ input: Observable<String>) -> Observable<String> {
input.flatMapLatest { id in // Note this should be `flatMapLatest` not `map`
Observable.zip( // zip up the text with its response
Observable.just(id),
self.checkUserIDFromDB(id: id) // you weren't actually making the network call. This makes it.
.catchAndReturn(false) // if the call fails, emit `false`.
)
}
.compactMap { $0.1 ? $0.0 : nil } // if the response is true, emit the text, else nothing
}
}
The biggest concern I have with this code is what happens if the user continues to type. This will fire after every character the user enters which could be a lot of network requests, the flatMapLatest will cancel ongoing requests that are no longer needed, but still... Consider putting a debounce in the stream to reduce the number of requests.
Learn more about the various versions of flatMap from this article.
Edit
In response to your comment. In my opinion, a ViewModel should not be dependent on RxCocoa, only RxSwift. However, if you feel you must use Driver, then something like this would be appropriate:
func bind(_ input: ControlProperty<String>) -> Driver<String> {
input.asDriver()
.flatMapLatest { id in
Driver.zip(
Driver.just(id),
self.checkUserIDFromDB(id: id)
.asDriver(onErrorJustReturn: false)
)
}
.compactMap { $0.1 ? $0.0 : nil }
}
Using Signal doesn't make much sense in this context.
As I understand, it is best to only test public methods of a class.
Let's have a look at this example. I have a view model for the view controller.
protocol MyViewModelProtocol {
var items: [SomeItem] { get }
var onInsertItemsAtIndexPaths: (([IndexPath]) -> Void)? { get set }
func viewLoaded()
}
class MyViewModel: MyViewModelProtocol {
func viewLoaded() {
let items = createDetailsCellModels()
updateCellModels(with: items)
requestDetails()
}
}
I want to test class viewLoaded(). This class calls two other methods - updateItems() and requestDetails()
One of the methods sets up the items and the other one call API to retrieve data and update those items. Items array us updated two times and onInsertItemsAtIndexPaths are called two times - when setting up those items and when updating with new data.
I can test whether after calling viewLoaded() expected items are set up and that onInsertItemsAtIndexPaths is called.
However, the test method will become rather complex.
What is your view, should I test those two methods separately or just write this one huge test?
By testing only viewLoaded(), my idea is that the implementation can change and I only care that results are what I expect.
I think the same thing, only public functions should be tested, since public ones use private ones, and your view on MVVM is correct. You can improve it by adding a DataSource and a Mapper that allows you to improve testing.
However, yes, the test seems huge to me, the tests should test simple units and ensure that small parts of the code work well, with the example you show is difficult, you need to divide by layers (clean code).
In the example you load the data into the viewModel and make it difficult to mockup the data. But if you have a Domain layer you can pass the UseCase mock to the viewModel and control the result. If you run a test on your example, the result will also depend on what the endpoint returns. (404, 200, empty array, data with error ...). So it is important, for testing purposes, to have a good separation by layers. (Presentation, Domain and Data) to be able to test each one separately.
I give you an example of how I would test a view mode, sure there are better and cooler examples, but it's an approach.
Here you can see a viewModel
protocol BeersListViewModel: BeersListViewModelInput, BeersListViewModelOutput {}
protocol BeersListViewModelInput {
func viewDidLoad()
func updateView()
func image(url: String?, index: Int) -> Cancellable?
}
protocol BeersListViewModelOutput {
var items: Box<BeersListModel?> { get }
var loadingStatus: Box<LoadingStatus?> { get }
var error: Box<Error?> { get }
}
final class DefaultBeersListViewModel {
private let beersListUseCase: BeersListUseCase
private var beersLoadTask: Cancellable? { willSet { beersLoadTask?.cancel() }}
var items: Box<BeersListModel?> = Box(nil)
var loadingStatus: Box<LoadingStatus?> = Box(.stop)
var error: Box<Error?> = Box(nil)
#discardableResult
init(beersListUseCase: BeersListUseCase) {
self.beersListUseCase = beersListUseCase
}
func viewDidLoad() {
updateView()
}
}
// MARK: Update View
extension DefaultBeersListViewModel: BeersListViewModel {
func updateView() {
self.loadingStatus.value = .start
beersLoadTask = beersListUseCase.execute(completion: { (result) in
switch result {
case .success(let beers):
let beers = beers.map { DefaultBeerModel(beer: $0) }
self.items.value = DefaultBeersListModel(beers: beers)
case .failure(let error):
self.error.value = error
}
self.loadingStatus.value = .stop
})
}
}
// MARK: - Images
extension DefaultBeersListViewModel {
func image(url: String?, index: Int) -> Cancellable? {
guard let url = url else { return nil }
return beersListUseCase.image(with: url, completion: { (result) in
switch result {
case .success(let imageData):
self.items.value?.items?[index].image.value = imageData
case .failure(let error ):
print("image error: \(error)")
}
})
}
}
Here you can see the viewModel test using mocks for the data and view.
class BeerListViewModelTest: XCTestCase {
private enum ErrorMock: Error {
case error
}
class BeersListUseCaseMock: BeersListUseCase {
var error: Error?
var expt: XCTestExpectation?
func execute(completion: #escaping (Result<[BeerEntity], Error>) -> Void) -> Cancellable? {
let beersMock = BeersMock.makeBeerListEntityMock()
if let error = error {
completion(.failure(error))
} else {
completion(.success(beersMock))
}
expt?.fulfill()
return nil
}
func image(with imageUrl: String, completion: #escaping (Result<Data, Error>) -> Void) -> Cancellable? {
return nil
}
}
func testWhenAPIReturnAllData() {
let beersListUseCaseMock = BeersListUseCaseMock()
beersListUseCaseMock.expt = self.expectation(description: "All OK")
beersListUseCaseMock.error = nil
let viewModel = DefaultBeersListViewModel(beersListUseCase: beersListUseCaseMock)
viewModel.items.bind { (_) in}
viewModel.updateView()
waitForExpectations(timeout: 10, handler: nil)
XCTAssertNotNil(viewModel.items.value)
XCTAssertNil(viewModel.error.value)
XCTAssert(viewModel.loadingStatus.value == .stop)
}
func testWhenDataReturnsError() {
let beersListUseCaseMock = BeersListUseCaseMock()
beersListUseCaseMock.expt = self.expectation(description: "Error")
beersListUseCaseMock.error = ErrorMock.error
let viewModel = DefaultBeersListViewModel(beersListUseCase: beersListUseCaseMock)
viewModel.updateView()
waitForExpectations(timeout: 10, handler: nil)
XCTAssertNil(viewModel.items.value)
XCTAssertNotNil(viewModel.error.value)
XCTAssert(viewModel.loadingStatus.value == .stop)
}
}
in this way you can test the view, the business logic and the data separately, in addition to being a code that is very reusable.
Hope this helps you, I have it posted on github in case you need it.
https://github.com/cardona/MVVM
I'm very new to RxSwift and RxCocoa and I've recently made heavy use of Variable because of how convenient it is to just push mutations into the Variable through its value. Now that it is deprecated I'm trying to understand how best to use BehaviorRelay instead. There's an Rx-y way of doing what I want to do, but I'm having a hard time landing on it.
What I want is to put an instance of struct-based model behind a ViewModel and observe changes to it and bind UI elements in such a way that I can mutate that model through the BehaviorRelay.
The model is simple:
struct Pizza {
var name: String
var price: Float
}
So is the View Model:
final class PizzaViewModel {
let pizzaRelay = BehaviorRelay<Pizza>(value: Pizza(name: "Sausage", price: 5.00))
init(pizza: Pizza) {
pizzaRelay.accept(pizza)
// I feel like I'm missing something Rx-like here...
}
}
Then somewhere you would maybe bind a UITextField to the BehaviorRelay like so:
viewModel
.pizzaRelay
.asObservable()
.map { $0.name }
.bind(to: nameTextField.rx.text)
.disposed(by: disposeBag)
The question becomes: if you need to push values from the text field back into the BehaviorRelay how should that work?
nameTextField
.rx
.controlEvent([.editingChanged])
.asObservable()
.subscribe(onNext: { [weak self] in
guard let self = self else { return }
if let text = self.nameTextField.text {
self.viewModel.pizzaRelay.value.name = text // does not compile because value is a let
}
}).disposed(by: disposeBag)
I'm probably not using the correct types here or I'm not thinking in the correct Rx-fashion in-terms of streams of inputs/outputs, but I'm curious how others might approach this problem?
Other things I've considered:
Just reconstructing a new Pizza in the .subscribe using current value in the BehaviorRelay, mutating the name and then .accept-ing that back into the relay. That doesn't feel exactly right, though.
Creating individual BehaviorRelay's for each property I want to mutate on my Pizza, then .accept-ing values for each property and then using combineLatest on all those relays and returning a Observable<Pizza>. But that feels clunky also.
How should this work in an ideal world? Am I thinking about this incorrectly? Help! My head hurts.
In an ideal world, you wouldn't use Relays or even Subjects for such code. Instead of starting with a struct, you should start with a flow. How should data move through your system?
As an example, here is a view controller with view model that can convert Fahrenheit to Celsius and back:
struct TempInOut {
let fahrenheit: Observable<String>
let celsius: Observable<String>
}
func tempViewModel(input: TempInOut) -> TempInOut {
let celsius = input.fahrenheit
.compactMap { Double($0) }
.map { ($0 - 32) * 5.0/9.0 }
.map { "\($0)" }
let fahrenheit = input.celsius
.compactMap { Double($0) }
.map { $0 * 5.0/9.0 + 32 }
.map { "\($0)" }
return TempInOut(fahrenheit: fahrenheit, celsius: celsius)
}
The main thing to understand is how the data flows from input.fahrenheit to output.celsius, and how it flows from input.celsius to output.fahrenheit.
It's a different way of thinking about your program... I recently heard about the notion of "temporal design" and I think that's a good term of art for it.
Here is the view controller that would use the above view model.
class ViewController: UIViewController {
#IBOutlet weak var fahrenheitField: UITextField!
#IBOutlet weak var celsiusField: UITextField!
let disposeBag = DisposeBag()
override func viewDidLoad() {
super.viewDidLoad()
let input = TempInOut(
fahrenheit: fahrenheitField.rx.text.orEmpty.asObservable(),
celsius: celsiusField.rx.text.orEmpty.asObservable()
)
let output = tempViewModel(input: input)
disposeBag.insert(
output.fahrenheit.bind(to: fahrenheitField.rx.text),
output.celsius.bind(to: celsiusField.rx.text)
)
}
}
I am trying to get a list of classes that have adopted a certain Protocol Migration: Preparation, and then to append those classes into an array. Here is the function in question:
struct Migrations {
static func getMigrations() -> [Preparation.Type] {
var migrationsList = [Preparation.Type]()
var count = UInt32(0)
let classList = objc_copyClassList(&count)!
for i in 0..<Int(count) {
let classInfo = ClassInfo(classList[i])!
if let cls = classInfo.classObject as? Migration.Type {
migrationsList.append(cls)
print(cls.description)
}
}
return migrationsList
}
}
In principle all that should work, but when debugging I note that the classInfo variable is referring to each class in the iteration, but when assigning and casting in the if let as line, the constant cls is always blank - neither a value/class nor nil, just completely blank.
Any idea what I got wrong with that code?
I am also open to suggestions for any better way to get a list of all classes that have adopted a particular protocol...
EDIT: I forgot to provide the code for ClassInfo
import Foundation
struct ClassInfo: CustomStringConvertible, Equatable {
let classObject: AnyClass
let className: String
init?(_ classObject: AnyClass?) {
guard classObject != nil else { return nil }
self.classObject = classObject!
let cName = class_getName(classObject)!
self.className = String(cString: cName)
}
var superclassInfo: ClassInfo? {
let superclassObject: AnyClass? = class_getSuperclass(self.classObject)
return ClassInfo(superclassObject)
}
var description: String {
return self.className
}
static func ==(lhs: ClassInfo, rhs: ClassInfo) -> Bool {
return lhs.className == rhs.className
}
}
I can't explain why cls is always blank, like I said in my comment it's something I run into every time I'm dealing with meta types. As for making the code work as intended, I found this q&a and updated it with Swift 3 to get this code which should cover your situation. It's important to stress that this will only work if you correctly expose Swift to the Objective-C runtime.
Drop this code anywhere and call print(Migrations.getMigrations()) from a convenient entry point.
struct Migrations {
static func getMigrations() -> [Preparation.Type] {
return getClassesImplementingProtocol(p: Preparation.self) as! [Preparation.Type]
}
static func getClassesImplementingProtocol(p: Protocol) -> [AnyClass] {
let classes = objc_getClassList()
var ret = [AnyClass]()
for cls in classes {
if class_conformsToProtocol(cls, p) {
ret.append(cls)
}
}
return ret
}
static func objc_getClassList() -> [AnyClass] {
let expectedClassCount = ObjectiveC.objc_getClassList(nil, 0)
let allClasses = UnsafeMutablePointer<AnyClass?>.allocate(capacity: Int(expectedClassCount))
let autoreleasingAllClasses = AutoreleasingUnsafeMutablePointer<AnyClass?>(allClasses)
let actualClassCount:Int32 = ObjectiveC.objc_getClassList(autoreleasingAllClasses, expectedClassCount)
var classes = [AnyClass]()
for i in 0 ..< actualClassCount {
if let currentClass: AnyClass = allClasses[Int(i)] {
classes.append(currentClass)
}
}
allClasses.deallocate(capacity: Int(expectedClassCount))
return classes
}
}
class Migration: Preparation {
}
#objc
protocol Preparation {
}
I'm trying to save a struct in background but I get this error :
closure cannot implicitly capture a mutating self parameter
This is my code :
//MARK: Parse self methods
fileprivate mutating func ParseSave(_ completionBlock: #escaping SuccessCompletionBlock) {
let message: PFObject = PFObject(className: "Message")
if let id = self.id {
//this object exit just update it
message.objectId = id
}
// set attributes
if let text = self.text {
message["text"] = text
}
message["sender"] = PFUser(withoutDataWithObjectId: self.sender.id)
message["conversation"] = PFObject(withoutDataWithClassName: "Conversation", objectId: conversationId)
message["viewed"] = self.viewed
message.saveInBackground { (success, error) in
if success {
// the next 3 lines cause the error : (when I try to update the struct - self )
self.id = message.objectId
self.createdAt = message.createdAt ?? self.createdAt
self.updatedAt = message.updatedAt ?? self.updatedAt
}
completionBlock(success, error)
}
}
I've checked those question: 1 - 2 I've added the #escaping
but didn't work.
I think it will help if we minimally elicit the error message you're getting. (For delay, see dispatch_after - GCD in swift?.)
struct S {
var name = ""
mutating func test() {
delay(1) {
self.name = "Matt" // Error: Closure cannot ...
// ... implicitly capture a mutating self parameter
}
}
}
The reason lies in the peculiar nature of struct (and enum) mutation: namely, it doesn't really exist. When you set a property of a struct, what you're really doing is copying the struct instance and replacing it with another. That is why only a var-referenced struct instance can be mutated: the reference must be replaceable in order for the instance to be mutable.
Now we can see what's wrong with our code. Obviously it is legal for a mutating method to mutate self; that is what mutating means. But in this case we are offering to go away for a while and then suddenly reappear on the scene (after 1 second, in this case) and now mutate self. So we are going to maintain a copy of self until some disconnected moment in the future, when self will suddenly be somehow replaced. That is incoherent, not least because who knows how the original self may have been mutated in the meantime, rendering our copy imperfect; and the compiler prevents it.
The same issue does not arise with a nonescaping closure:
func f(_ f:()->()) {}
struct S {
var name = ""
mutating func test() {
f {
self.name = "Matt" // fine
}
}
}
That's because the closure is nonescaping; it is executed now, so the incoherency about what will happen in the future is absent. This is an important difference between escaping and nonescaping closures, and is one of the reasons why they are differentiated.
Also, the same issue does not arise with a class:
class C {
var name = ""
func test() {
delay(1) {
self.name = "Matt" // fine
}
}
}
That's because the class instance is captured by reference in the closure, and a class instance is mutable in place.
(See also my little essay here: https://stackoverflow.com/a/27366050/341994.)