RxSwft is very suitable for iOS MVVM.
Putting viewmodel everywhere, disobeys Law of Demeter ( The Least Knowledge Principle ).
What is the other drawbacks?
Will it leads to Memory Leakage?
Here is an example:
ViewController has a viewModel
ViewModel has some event signals, like the following back event
class ViewModel{
let backSubject = PublishSubject<String>()
}
ViewController has contentView and viewModel, and contentView init with viewModel
lazy var contentView: ContentView = {
let view = ContentView(viewModel)
view.backgroundColor = .clear
return view
}()
and ViewModel's various subject are subscribed in viewController to handle other part view
viewController is a Dispatch center.
ViewModel is Event Transfer station. ViewModel is in everywhere, in Controller, in View, to collect different event triggers.
the code is quite spaghetti
in ContentView, user tap rx event , binds to the viewModel in viewController
tapAction.bind(to: viewModel.backSubject).disposed(by: rx.disposeBag)
user events wires up easily.
But there is memory leakage actually.
So what's the other disadvantages?
ViewModel doesn't break the Law of Demeter but it does break the Single Responsibility Principle. The way you solve that is to use multiple view models, one for each feature, instead of a single view model for the entire screen. This will make view models more reusable and composable.
If you setup your view model as a single function that takes a number of Observables as input and returns a single Observable, you will also remove any possibility of a memory leak.
For example:
func textFieldsFilled(fields: [Observable<String?>]) -> Observable<Bool> {
Observable.combineLatest(fields)
.map { $0.allSatisfy { !($0 ?? "").isEmpty } }
}
You can attach the above to any scene where you want to enable a button based on whether all the text fields have been filled out.
You satisfy the SRP and since object allocation is handled automatically, there's no concern that the above will leak memory.
You are right, there are some drawbacks, if you want just a data-binding, I would suggest to use Combine instead, since no 3rd party libraries need, you have it already. RxSwift is a very powerful tool when you use it as a part of language, not just for data binding.
Some of suggestions from my experience working with RxSwift:
Try to make VMs as a structs, not classes.
Avoid having DisposeBag in your VM, rather make VC subscribe to everything(much better for avoiding memory leaks).
Make its own VMs for subview, cells, child VC, and not shared ones.
Since your VC is a dispatch centre, I would make a separate VM for your content view and make a communication between ContentView VM and ViewController VM through your controller.
Related
class HealthViewController: UIViewController {
var foods: [Food] = FoodUtils.getFoodList() // some expensive operations
var fruits: [Fruit];
override func viewDidLoad() {
super.viewDidLoad()
self.fruits = FoodUtils.getFruitList() // some expensive operations
}
}
I wonder for above class in iOS/Swift,
When FoodUtils.getFoodList() is prepared on runtime?
What is the good practice? preparing list inside viewDidLoad or in class scope? Which lifecycle of UIViewController will effect the memory on runtime for both cases?
In the code (object initialization):
var foods: [Food] = FoodUtils.getFoodList() // some expensive operations
the expensive operations are performed when the view controller instance is created.
in the code (inside the viewDidLoad):
self.fruits = FoodUtils.getFruitList() // some expensive operations
the expensive operations are performed once the interface elements (IB outlets) have been hooked with the viewcontroller, and the views have been loaded.
In practice it doesn't make a difference because viewDidLoad is performed after the class has been initialized WHEN USING SEGUES (for programmatically shown VCs read the note at the end).
If you are talking about an operation that can take several seconds, then the best practice would be, to perform the expensive operations BEFORE showing the view controller while a busy view (a view with an activity indicator) is shown.
Alternatively, you could do it in the viewDidAppear method, and start the View controller with an activity indicator shown, then when the expensive operations finishes, hide the activity indicator and load your data.
As a matter of fact, the second approach is used very commonly, specially when showing big lists of data. You must have seen it when using apps that start with a spinning indicator until the data is ready to be displayed.
Note:
You can separate the timing of the 2 functions if you are showing your view controller programmatically, since the first one is performed when you use the "load from nib" method. While the second one is performed once you actually try to access any views inside it.
Note 2:
Expensive network operations should always be performed on background threads so that the UI is not blocked. Which is why people often show activity indicators while info is being retrieved in the background.
In the iOS world, we're all used to the following pattern:
class UIViewController {
open var view: UIView!
}
A ViewController is obviously a controller controlling the view, which contains a lot of subviews.
Now, I have a lot of subviews that I want to reuse, and I want to enrich them with more functionalities. Think of a UISlider, a UILabel, or a UITableView that react to some events or some changes in the model. These subviews also need to be #IBDesignable with IBInspectable properties for customisation purposes. I also want to share those components through a library as well.
So in a way, I want small controllers controlling those subviews that will end up in the view of the ViewController.
I am thinking of doing this for the UIKit classes:
#IBDesignable
public class CustomSlider: UISlider {
}
That is a nice way to be able to provide the component with customisation options. The downside is that we're using inheritance here (would rather use composition), and I'm not sure if CustomSlider is really considered here a controller or not.
Can anyone tell me what are good practices around creating controllers for subviews that are customisable? Thanks in advance!
EDIT: Specific case for Views that have delegates and datasource:
#objc public class CustomTableView: UITableView, UITableViewDataSource, UITableViewDelegate {
#IBInspectable public var someCustomField: UInt = 0
public override init(frame: CGRect, style: UITableViewStyle) {
super.init(frame: frame, style: style)
dataSource = self
delegate = self
}
required public init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
dataSource = self
delegate = self
}
// Implement UITableViewDataSource and UITableViewDelegate
}
Is this bad pattern to have datasource = self and delegate = self, or is it ok?
It's a judgement call. I'd say if all you are doing is adding features to a single view class, it is probably better to just use a custom subclass like your CustomSlider example.
If, on the other hand, you're using small suites of objects, like a slider, a text field, a segmented control and a couple of labels, you might want to think about using container views, embed segues, and custom view controllers. Think of that as setting up tiles that manage sets of UI elements. You can put such a view controller anywhere you want, and size it as needed.
You could also create a custom controller object that manages one or more custom views, but there isn't really system "plumbing" for that, so the burden is on you to build mechanisms to support that approach. You have to teach your view controllers to talk to a controller object that isn't a view, but that HAS views inside it.
Apple does what you're talking about in a couple of instances: UITableViewController and UICollectionViewController, and I think they didn't do it right. A UITableViewController or UICollectionViewController can manage only a single view. You can't put a button at the bottom, or a label somewhere, or a segmented control. The content view of one of those specialized view controllers must be the corresponding view object, which limits their usefulness. You can, of course, get around the problem by using container views and embed segues, and that leads me to my suggestion.
EDIT:
As far as making a view object it's own data source, I would call that "a violation of the separation of powers". A table view is a view object, and the data source is a model object. By combining them, you're collapsing the view and the model into one. That's going to create a larger, very specialized object that is less likely to be reusable.
The same goes for making an object it's own delegate. The idea of the delegate pattern is to be able to leave certain decisions about an object's behavior up to somebody else, so that its more flexible. By making it its own delegate you are limiting the table view's range of behavior and making it less reusable.
Neither thing is going to "warp your mind, curve your spine, and make the enemy win the war" but they seem ill-advised.
I'm looking for a way to show a UIView "InventoryView" in 2 view controllers.
I'm working on an inventory system for my game that I trying to make but I need to be able to access it from my main view, where it will go to a InventoryViewController (in this ViewController is my InventoryView) but I also need to be able to access the InventoryView from my BattleViewController where it does not go to my InventoryViewController but where it print the InventoryView on my BattleViewController so I can access everything durning the battle.
Example:
(evrything is dragand drop, the UIView and the UIButtons)
InventoryViewController
class InventoryViewController: UIViewController {
class InventoryView: UIView {
//here are some UIButtons and labels
}
}
BattleViewController
class BattleViewController: UIViewController {
class InventoryView: UIView {
//it should print the Inventory Screen on my BattleViewController
//here are the same properties as it shows in the InventoryViewController
}
}
This is a great example to look at the way OOP programming works best.
Ask yourself the following questions:
What is the purpose of the view?
Are the interactions on the view homogenous across all the instances? (touch events, specific behavior, etc...)
What is the minimum amount of information you need to make the view look the way you want?
Once you have those answers, you can approach the concept of reusability of views safely.
The way to go about it is to subclass UIView, create the necessary elements of your view, setup your constraints (still in the view, either in a nib or programmatically), and implement any behavior that will be consistent across views (For example if the view is a segmented control, every time you click a segment all the others go grey and the one you clicked go blue. Since that's the primary purpose of the segmented control, the code for it should belong to the segmented control).
Chances are you will find the docs very useful: https://developer.apple.com/library/ios/documentation/UIKit/Reference/UIView_Class/
Lastly write a setup method that takes all the information you need and sets up all your graphical elements accordingly. Remember, views should never own their data (they should be templates, the controller will provide the data).
I have no idea what you view looks like but I assume the inventory is represented as an object. Then something like could be a good start:
class InventoryView: UIView {
var inventory: Inventory? {
didSet {
if let newInventory = inventory { //in case we set it to nil
setup(withInventory: newInventory)
}
}
}
private func setup(withInventory inventory: Inventory) {
//do your setup here
}
}
Then in your controller you can call:
let inventoryView = InventoryView()
inventoryView.inventory = myPlayer.inventory
You cannot use a view in two places, at least not within the UI. Every view can be added to only one super view at a time.
If you need the same contents to be displayed twice, create a UIViewController class which's view contains the common UI, create two of those and add them to your UI.
I have created a subclass of a UICollectionViewCell that shows some information. I have one property in with type Weather. When an instance of that is set I want to update the cell. Is the approach below bad? I am thinking of that I may trigger the view to be created to early if I access the UI components before it is loaded. Or is that non sense and only applies to UIViewController (with regard to using view property to early)?
If this is bad, what would be the correct way?
var weather: Weather? {
didSet {
if let weather = weather {
dayLabel.text = dayFormatter.stringFromDate(weather.fromDate)
// ... more code like this
}
}
}
You may want an else clause, though, clearing the text field if weather was nil. Likewise, if you might update this from a background thread, you might want to dispatch that UI update back to the main thread.
Be aware that this observer is not called when you set weather in the cell's init (nor would be the #IBOutlet be configure at that point, anyway). So make sure that you're not relying upon that.
Also, if Weather is mutable, recognize that if you change the fromDate of the existing Weather object, this won't capture that. (If Weather was mutable, you'd really want to capture its changing properties via KVO, a delegate-protocol pattern, or what have you.) But if you make Weather immutable, you should be fine.
So, technically, that's the answer to the question, but this raises a few design considerations:
One generally should strive to have different types loosely coupled, namely that one type should not be too reliant on the internal behavior of another. But here we have an observer within the cell which is dependent upon the mutability of Weather.
This use of a stored property to store a model object within view is inadvisable. Cells are reused as they scroll offscreen, but you probably want a separate model that captures the relevant model objects, the controller then handles the providing of the appropriate model object to the view object (the cell) as needed.
Bottom line, it's not advisable to use a stored property for "model" information inside a "view".
You can tackle both of these considerations by writing code which makes it clear that you're only using this weather parameter solely for the purpose of updating UI controls, but not for the purposes of storing anything. So rather that a stored property, I would just use a method:
func updateWithWeather(weather: Weather?) {
if let weather = weather {
dayLabel.text = dayFormatter.stringFromDate(weather.fromDate)
// ... more code like this
} else {
dayLabel.text = nil
// ... more code like this
}
}
And this would probably only be called from within collectionView:cellForItemAtIndexPath:.
But, this makes it clear that you're just updating controls based upon the weather parameter, but not trying to do anything beyond that. And, coincidentally, the mutability of the weather object is now irrelevant, as it should be. And if the model changes, call reloadItemsAtIndexPaths:, which will trigger your collectionView:cellForItemAtIndexPath: to be called.
There are times where a stored property with didSet observer is a useful pattern. But this should be done only when the property is truly a property of view. For example, consider a custom view that draws some shape. You might have stored properties that specify, for example, the width and the color of the stroke to be used when drawing the path. Then, having stored properties for lineWidth and strokeColor might make sense, and then you might have a didSet that calls setNeedsDisplay() (which triggers the redrawing of the view).
So, the pattern you suggest does have practical applications, it's just that it should be limited to those situations where the property is truly a property of the view object.
I would use a property observer if I planned up updating the value during the users session. If this is a value that only gets updated when the user first loads, I would just simply call a method when my view is initially loaded.
If you use a property observer, you can give it an initial value when you define it so the data is there when the user needs it. Also, if you're updating the user interface, make sure you do it on the main queue.
var weather: Weather = data {
didSet {
dispatch_async(dispatch_get_main_queue(),{
if let weather = weather {
dayLabel.text = dayFormatter.stringFromDate(weather.fromDate)
// ... more code like this
}
})
}
}
I am attempting to structure my application using the MVVM pattern. Therefor I have ViewModels that raise events when data changes, and the UI is expected to react to those events and update the visible UI controls.
I have a derived UITableViewCell that gets initialized with a certain ViewModel every time a new cell is created or dequeued (very similar to miguel's example here). One main difference being part of the initializing relies on subscribing to an event of the ViewModel. This creates a reference from the long lived ViewModel to this specific cell, holding it in memory for the lifetime of the ViewModel. When the cell is re-used, the old subscription is cleaned up and a new one created to the new ViewModel, that works fine.
However the problem is there doesn't seem to be any opportunity to clean up the last subscription once the cell is completely finished, which means it is held in memory for the lifetime of the ViewModel (much longer than I want it to be).
'Completely finished' depends on the VC hierarchy, but in this case the derived DialogViewController that contains the TableView with custom cells has been popped from the UINavigationController stack and has been disposed.
willMoveToSuperview is never called (I was hoping it would be with 'null' being passed in).
removeFromSuperview is never called.
Dispose on each cell is never called.
Disposing of the UITableViewController doesn't dispose each cell.
Disposing of the TableView within the controller doesn't even dispose each cell.
The only way I can manually dispose each cell (and hence clean up subscriptions) is by enumerating the cells manually myself in each of my derived UIViewControllers, something I want to avoid.
Has anyone has similar troubles like this? I cant be the first using the MVVM pattern with UITableViewCells.
Is this a bug with the Dispose pattern in the base MonoTouch UIKit wrappers?
EDIT:
Here is cut-down version of one of the custom UITableViewCells. Note I'm taking a pragmatic approach where I explicitly subscribe to events of properties I know may change, not a full MVVM bind every property to the UI. So my binding code consists of just standard event subscriptions:
public class MyCustomCell : UITableViewCell
{
private InvoiceViewModel currentViewModel;
private readonly UILabel label1;
private readonly UILabel label2;
public MyCustomCell(NSString reuseId)
: base(UITableViewCellStyle.Default, reuseId)
{
Accessory = UITableViewCellAccessory.DisclosureIndicator;
SelectedBackgroundView = new UIView()
{
BackgroundColor = UIColor.FromRGB(235,235,235),
};
label1 = new UILabel();
ContentView.Add(label1);
// The rest of the UI setup...
}
public void Update(MyViewModel viewModel)
{
if ( currentViewModel == viewModel )
return;
if ( currentViewModel != null )
{
// Cleanup old bindings.
currentViewModel.UnacknowledgedRemindersChanged -= HandleNotificationsChanged;
}
currentViewModel = viewModel;
if ( viewModel != null )
{
viewModel.UnacknowledgedRemindersChanged += HandleNotificationsChanged;
label1.Text = viewModel.SomeProperty;
// Update the rest of the UI with the current view model.
}
}
private void HandleNotificationsChanged()
{
// Event can fire on background thread.
BeginInvokeOnMainThread(() =>
{
// Relevant UI updates go here.
});
}
protected override void Dispose(bool disposing)
{
// Unsubscribes from ViewModel events.
Update(null);
base.Dispose(disposing);
}
}
And my derived MT.D element class has a 1:1 element:viewmodel, so the GetCell method looks like this:
public override UITableViewCell GetCell (UITableView tv)
{
var cell = (MyCustomCell) tv.DequeueReusableCell(key);
if (cell == null)
cell = new MyCustomCell(key);
cell.Update(viewModel);
return cell;
}
You're definitely not the first to do Mvvm table cells with MonoTouch.
I've blogged about it recently at http://slodge.blogspot.co.uk/2013/01/uitableviewcell-using-xib-editor.html
Before that there have been projects at NDC (search for "Flights of Norway") and there was a long running Mvvm project built on top of MonoTouch.Dialog.
In MvvmCross apps, we use tables bound to ObservableCollections and to other IList classes a lot.
Within these we generally don't hit many problems with left-living references, but that's because we generally don't encourage people to use long-living ViewModels - we try to create a new instance of ViewModel data to go with each View. However, I do understand that that may not be suitable for all appications.
When an Mvx user finds themselves with this type of problem, then some of the approaches we've tried are:
in iOS5 we did use the ViewDidUnload method to clean up bindings - but obviously that is now gone in iOS6.
depending on the UI presentation style (modal, splitview, navigationcontroller, popup, etc) we have tried manually detecting when views are 'popped' and using this to clear up bindings
again depending on the UI presentation style, we have tried using the ViewDidAppear, ViewDidDisappear events to add and tidy up the bindings
for all UIKit classes with data-binding we've always used Dispose as an extra place to try to clear up bindings
we've looked at using WeakReferences (especially from the ViewModel to the View) in order to get around the issues where the UIKit objects are owned by both iOS/ObjC and by MonoTouch/.Net - these problems are particularly hard to debug.
this weak reference code is likely to be one the key changes in the next release.
An example discussion about this (in Droid rather than in Touch) is on https://github.com/slodge/MvvmCross/issues/17
I'm sorry I can't offer you any specific advice at this moment. If you post some more example code about how you are creating and storing your bindings, I might be able to help more - but I can't really visualise what bindings you are creating right now.
I've got a few more links I'll add to this answer later - on mobile at present - too hard to add them here!
Update - for some more explanation on the WeakReference ideas, this is where we're heading now in v3 for MvvmCross - https://github.com/slodge/MvvmCross/tree/vNextDialog/Cirrious/Cirrious.MvvmCross.Binding/WeakSubscription - basically the idea is to use disposable weakreference event subscriptions - which won't keep the UIKit objects in RAM. It's not properly tested code yet. When it is, then I'll blog and talk about it more fully!