Is it possible to conditionally specify a mocked object that is created after initialization? - swift

I am wondering if it's possible to create a repository as a mock version (in Swift, using Xcode 12.5) only for unit testing, when that repository is not set at initialization?
I am creating a viewModel that looks at two different types of objects (Foo and FooTwo), and depends on two different repositories (fooRepository (which has a factory type of fooStoreType) and FooTwoRepository (which has a factory type of fooStoreType)).
The fooTwoRepository is dependent on having a featuredFoo, too determine which 'FooTwo' objects should be returned. featuredFoo may not be set until the user takes some action (or at least until a downstream code flow completes), so I am setting the fooTwoRepository(foo: featuredFoo!) as part of a didSet whenever the featuredFoo gets set.
My issue is related to testing. I have a MockFooRepository and a MockFooTwoRepository for my unit tests. When I create the ViewModel in my test class, I can initialize my MockFooRepository, but since my MockFooTwoRepository doesn't get created until this didSet, I don't think I can create it this way.
Is there a good way to create this as the mock version only for unit testing in Xcode?
Or do I need to just create my FooTwoRepository on initialization, and have featuredFoo as an optional value so I don't have to set it right away, and then have it not do anything until featuredFoo gets set (and my didSet would just set featuredFoo in the repository for FooTwo)?
Here's my ViewModel:
class FooViewModel: ObservableObject {
#Published var fooTwoRepository: FooTwoStoreType?
#Published var fooRepository: FooStoreType
#Published var featuredFoo: Season? {
didSet {
guard featuredFoo != nil else {
print("Trying to set FooTwoRepository based on featured Foo. Featured Foo is nil.")
return
}
self.fooTwoRepository = MyFooTwoRepository(foo: featuredFoo!)
}
}
init(fooRepository: FooStoreType = FooRepository()) {
self.fooRepository = fooRepository
}
And for reference, my FooTwoRepository conforms to a factory method, FooTwoStoreType, which my MockFooTwoRepository also conforms to.
But it requires a featuredFoo to loadData, so I wait until the viewModel has gotten a featuredFoo to initialize this repository:
class FooTwoRepository: ObservableObject, FooTwoStoreType {
var featuredFoo: featuredFoo
init(featuredFoo: featuredFoo) {
self.featuredFoo = featuredFoo
loadData()
}

One solution that seems to be working is to initialize the FooTwoRepository (based on FooTwoStoreType) right away, and instead have my didSet under featuredFoo trigger that same property to be set in FooTwoRepository.
Then instead, in my FooTwoRepository, I make featuredFoo an optional variable, I don't set it upon initialization, and I load my data under a didSet whenever that property gets set, instead of right on initialization.
Here's my class (I changed the didSet under featuredFoo)
class FooViewModel: ObservableObject {
#Published var fooTwoRepository: FooTwoStoreType?
#Published var fooRepository: FooStoreType
#Published var featuredFoo: Season? {
didSet {
guard featuredFoo != nil else {
print("Trying to set FooTwoRepository based on featured Foo. Featured Foo is nil.")
return
}
self.fooTwoRepository.featuredFoo = featuredFoo
}
}
init(fooRepository: FooStoreType = FooRepository()) {
self.fooRepository = fooRepository
}
And here's my repository, where 'featuredFoo' is now optional, and the loadData() function is called under a didSet there, instead of at initialization:
class FooTwoRepository: ObservableObject, FooTwoStoreType {
var featuredFoo: Foo? {
didSet {
guard featuredFoo != nil else {
return
}
guard fooTwoList.count == 0 else {
print("Data already loaded. Skipping")
return
}
loadData()
}
}

Related

Access and modify a #EnvironmentObject on global functions

I have an ObservableObject declared on my main view (ContentView.swift).
final class DataModel: ObservableObject {
#AppStorage("stuff") public var notes: [NoteItem] = []
}
Then I declare it in the main entry of the app as (removed extra code not needed for this example):
#main struct The_NoteApp: App {
private let dataModel = DataModel()
var body: some Scene {
WindowGroup {
ContentView()
.environmentObject(self.dataModel)
}
}
In the ContentView.swift, I can use it on the different views I declared there:
struct NoteView: View {
#EnvironmentObject private var data: DataModel
// more code follows...
}
Now, I have a collection of global functions saved on FileFunctions.swift, which essentially are functions that interact with files on disk. One of them is to load those files and their content into my app.
Now, I'm trying to use #EnvironmentObject private var data: DataModel in those functions so at loading time, I can populate the data model with the actual data from the files. And when I declare that either as a global declaration in FileFunctions.swift or inside each function separately, I get two behaviors.
With the first one I get an error:
Global 'var' declaration requires an initializer expression or an explicitly stated getter`,
and
Property wrappers are not yet supported in top-level code
I tried to initialize it in any way, but it goes nowhere. With the second one, adding them to each function, Xcode craps on me with a segfault. Even if I remove the private and try to declare it in different ways, I get nowhere.
I tried the solution in Access environment variable inside global function - SwiftUI + CoreData, but the more I move things around the worse it gets.
So, how would I access this ObservableObject, and how would I be able to modify it within global functions?
Below is an example of a global function and how it's being called.
In FileFunctions.swift I have:
func loadFiles() {
var text: String = ""
var title: String = ""
var date: Date
do {
let directoryURL = try resolveURL(for: "savedDirectory")
if directoryURL.startAccessingSecurityScopedResource() {
let contents = try FileManager.default.contentsOfDirectory(at: directoryURL,
includingPropertiesForKeys: nil,
options: [.skipsHiddenFiles])
for file in contents {
text = readFile(filename: file.path)
date = getModifiedDate(filename: file.absoluteURL)
title = text.components(separatedBy: NSCharacterSet.newlines).first!
// I need to save this info to the DataModel here
}
directoryURL.stopAccessingSecurityScopedResource()
} else {
Alert(title: Text("Couldn't load notes"),
message: Text("Make sure the directory where the notes are stored is accessible."),
dismissButton: .default(Text("OK")))
}
} catch let error as ResolveError {
print("Resolve error:", error)
return
} catch {
print(error)
return
}
}
And I call this function from here:
#main struct The_NoteApp: App {
private let dataModel = DataModel()
var body: some Scene {
WindowGroup {
ContentView()
.environmentObject(self.dataModel)
.onAppear {
loadFiles()
}
}
}
You could change the signature of the global functions to allow receiving the model:
func loadFiles(dataModel: DataModel) { ... }
This way, you have access to the model instance within the function, what's left to do is to pass it at the call site:
var body: some Scene {
WindowGroup {
ContentView()
.environmentObject(self.dataModel)
.onAppear {
loadFiles(dataModel: self.dataModel)
}
You can do the same if the global functions calls originate from the views.
I would do something like this :
final class DataModel: ObservableObject {
public static let shared = DataModel()
#AppStorage("stuff") public var notes: [NoteItem] = []
}
#main struct The_NoteApp: App {
private let dataModel = DataModel.shared
var body: some Scene {
WindowGroup {
ContentView()
.environmentObject(self.dataModel)
}
}
now in your viewModel you can access it like this
class AnyClass {
init (){
print(DataModel.shared.notes)
}
// or
func printNotes(){
print(DataModel.shared.notes)
}
}
As discussed in the comments, here a basic approach which makes some changes to the structure by defining dedicated "components" which have a certain role and which are decoupled as far as necessary.
I usually define a namespace for a "feature" where I put every "component" which is related to it. This offers a couple of advantages which you might recognise soon later:
enum FilesInfo {}
Using a "DataModel" or a "ViewModel" to separate your "Data" from the View
makes sense. A ViewModel - as opposed to DataModel - just obeys the rules from the MVVM pattern. A ViewModel should expose a "binding". I call this "ViewState", which completely describes what the view should render:
extension FilesInfo {
enum ViewState {
struct FileInfo {
var date: Date
var title: String
}
case undefined
case idle([FileInfo])
init() { self = .undefined } // note that!
}
}
Why ViewState is an enum?
Because you might want to represent also a loading state when your load function is asynchronous (almost always the case!) and an error state later. As you can see, you start with a state that's "undefined". You can name it also "zero" or "start", or however you like. It just means: "no data loaded yet".
A view model basically looks like this:
extension FilesInfo {
final class ViewModel: ObservableObject {
#Published private(set) var viewState: ViewState = .init()
...
}
}
Note, that there is a default initialiser for ViewState.
It also may have public functions where you can send "events" to it, which may originate in the view, or elsewhere:
extension FilesInfo.ViewModel {
// gets the view model started.
func load() -> Void {
...
}
// func someAction(with parameter: Param) -> Void
}
Here, the View Model implements load() - possibly in a similar fashion you implemented your loadFiles.
Almost always, a ViewModel operates (like an Actor) on an internal "State", which is not always the same as the ViewState. But your ViewState is a function of the State:
extension FilesInfo.ViewModel {
private struct State {
...
}
private func view(_ state: State) -> ViewState {
//should be a pure function (only depend on state variable)
// Here, you likely just transform the FilesInfo to
// something which is more appropriate to get rendered.
// You call this function whenever the internal state
// changes, and assign the result to the published
// property.
}
}
Now you can define your FileInfosView:
extension FilesInfo {
struct ContentView: View {
let state: ViewState
let action: () -> Void // an "event" function
let requireData: () -> Void // a "require data" event
var body: some View {
...
.onAppear {
if case .undefined = state {
requireData()
}
}
}
}
}
When you look more closely on the ContentView, it has no knowledge from a ViewModel, neither from loadFiles. It only knows about the "ViewState" and it just renders this. It also has no knowledge when the view model is ready, or provides data. But it knows when it should render data but has none and then calls requireData().
Note, it does not take a ViewModel as parameter. Those kind of setups are better done in some dedicated parent view:
extension FilesInfo {
struct CoordinatorView: View {
#ObservedObject viewModel: ViewModel
var body: some View {
ContentView(
state: viewModel.viewState,
action: {},
requireData: viewModel.load
)
}
}
}
Your "coordinator view" deals with separating ViewModel from your specific content view. This is not strictly necessary, but it increases decoupling and you can reuse your ContentView elsewhere with a different ViewModel.
Your CoordinatorView may also be responsible for creating the ViewModel and creating target views for navigation. This depends on what convention you establish.
IMHO, it may make sense, to restrict the access to environment variables to views with a certain role, because this creates a dependency from the view to the environment. We should avoid such coupling.
Also, I would consider mutating environment variables from within Views a "smell". Environment variables should be kind of a configuration which you setup in a certain place in your app (also called "CompositionRoot"). You may end up with an uncontrollable net of variables if you allow that everyone can change any environment variable at any time. When you have "ViewModels" in your environment, these of course get not "mutated" when they change their state - these are classes - for a reason.
Basically, that's it for a very basic but functional MVVM pattern.

Swift memory conflict where it should not happen

I am working on a SwiftUI project, where I use the MVVM-architecture.
When changing a View-model object property from the SwiftUI view, it causes a memory conflict crash in the view-model object.
The error is of the type: Simultaneous accesses to 0x600003591b48, but modification requires exclusive access.
In steps, here is what happens:
View-model property is changed from view
View-model property changes model property
Model property notifies about changes
View-model receives change notification
View-model access model object
Crash occur due to memory conflict
Relevant code snippets are seen below. Xcode project is a standard SwiftUI project.
The error will happen, after first clicking the add button, and then the modify button.
If the "update" code is moved into the "receiveValue" closure, the error will not occur. Likewise, the error will not occur, if the View-model class is made non-generic.
To my best knowledge, the code is all-right, so I suspect it is a compiler problem. But I am not sure.
import Foundation
import SwiftUI
import Combine
struct ContentView: View {
#ObservedObject var item: ViewModel<Model> = ViewModel<Model>()
var body: some View {
VStack {
Button("Add", action: { item.add(model:Model()) })
Button("Modify", action: { item.selected.toggle() })
}
}
}
protocol ModelType {
var objectDidChange: ObservableObjectPublisher { get }
var selected: Bool { get set }
}
class Model: ModelType {
let objectDidChange = ObservableObjectPublisher()
var selected = false {
didSet {
objectDidChange.send()
}
}
}
class ViewModel<Model:ModelType>: ObservableObject {
var selected = false {
didSet {
model.selected = selected
}
}
func add(model: Model) {
self.model = model
cancellable = model.objectDidChange.sink(receiveValue: { _ in
self.update()
})
}
private var model: Model! = nil
private var cancellable: AnyCancellable? = nil
func update() {
// Crash log: Simultaneous accesses to 0x600003591b48, but modification requires exclusive access.
print("update \(model.selected)")
}
}
Short version: require AnyObject for ModelType.
Long version:
You're trying to read from self.model while you're in the middle of setting self.model. When you say "If the "update" code is moved into the "receiveValue" closure, the error will not occur," this isn't quite correct. I expect what you mean is you wrote this:
cancellable = model.objectDidChange.sink(receiveValue: { _ in
print("update \(model.selected)")
})
And that worked, but that's completely different code. model in this case is the local variable, not the property self.model. You'll get the same crash if you write it this way:
cancellable = model.objectDidChange.sink(receiveValue: { _ in
print("update \(self.model.selected)")
})
The path that gets you here is:
ViewModel.selected.didSet
WRITE to Model.selected <---
Model.selected.didSet
(observer-closure)
ViewModel.update
READ from ViewModel.model <---
This is a read and write to the same value, and that violates exclusive access. Note that the "value" in question is "the entire ViewModel value," not ViewModel.selected. You can show this by changing the update function to:
print("update \(model!)")
You'll get the same crash.
So why does this work when you take out the generic? Because this particularly strict version of exclusivity only applies to value types (like structs). It doesn't apply to classes. So when this is concrete, Swift knows viewModel is a class, and that's ok. (The why behind this difference a bit complex, so I suggest reading the proposal that explains it.)
When you make this generic, Swift has to be very cautious. It doesn't know that Model is a class, so it applies stricter rules. You can fix this by promising that it's a class:
protocol ModelType: AnyObject { ... }

Memory access conflict with structs and observer pattern

I'm implementing an Observer design pattern on a Struct model object. The idea is that I will pass my model down a chain of UIViewController and as each controller modifies it, previous controllers will also be updated with changes to the object.
I'm aware this problem could be solved by using a class instead of struct and modifying the object directly through reference, however I'm trying to learn more about using structs.
struct ModelObject {
var data: Int = 0 {
didSet {
self.notify()
}
}
private var observers = [ModelObserver]()
mutating func attachObserver(_ observer: ModelObserver){
self.observers.append(observer)
}
private func notify(){
for observer in observers {
observer.modelUpdated(self)
}
}
}
protocol ModelObserver {
var observerID: Int { get }
func modelUpdated(_ model: ModelObject)
}
class MyViewController : UIViewController, ModelObserver {
var observerID: Int = 1
var model = ModelObject()
override func viewDidLoad() {
self.model.attachObserver(self)
self.model.data = 777
}
func modelUpdated(_ model: ModelObject) {
print("received updated model")
self.model = model //<-- problem code
}
}
Simply put, my model object notifies any observer when data changes by calling notify().
My problem right now is memory access: when data gets set to 777, self.model becomes exclusively accessed, and when it calls notify which calls modelUpdated and eventually self.model = model, we get an error:
Simultaneous accesses to 0x7fd8ee401168, but modification requires exclusive access.
How can I solve this memory access issue?
If you're observing "a thing," then that "thing" has an identity. It's a particular thing that you're observing. You can't observe the number 4. It's a value; it has no identity. Every 4 is the same as every other 4. Structs are values. They have no identity. You should not try to observe them any more than you'd try to observe an Int (Int is in fact a struct in Swift).
Every time you pass a struct to a function, a copy of that struct is made. So when you say self.model = model, you're saying "make a copy of model, and assign it to this property." But you're still in an exclusive access block because every time you modify a struct, that also makes a copy.
If you mean to observe ModelObject, then ModelObject should be a reference type, a class. Then you can talk about "this particular ModelObject" rather than "a ModelObject that contains these values, and is indistinguishable from any other ModelObject which contains the same values."

Converting Older KVO to Swift 4

I'm trying to convert some old WWDC swift code to Swift 4. I think that I have everything done, except for this last bit that does some KVO. This has been pretty difficult to narrow it down to this last bit because everything appears to function like the example code - but these KVO methods do not get called in Swift 4. I found that out here: Open Radar Bug
What would be the Swift 4 way to represent the following?
// use the KVO mechanism to indicate that changes to "state" affect other properties as well
class func keyPathsForValuesAffectingIsReady() -> Set<NSObject> {
return ["state" as NSObject]
}
class func keyPathsForValuesAffectingIsExecuting() -> Set<NSObject> {
return ["state" as NSObject]
}
class func keyPathsForValuesAffectingIsFinished() -> Set<NSObject> {
return ["state" as NSObject]
}
And here are the variable definitions from the example:
override var isReady: Bool {
switch state {
case .initialized:
// If the operation has been cancelled, "isReady" should return true
return isCancelled
case .pending:
// If the operation has been cancelled, "isReady" should return true
guard !isCancelled else {
return true
}
// If super isReady, conditions can be evaluated
if super.isReady {
evaluateConditions()
}
// Until conditions have been evaluated, "isReady" returns false
return false
case .ready:
return super.isReady || isCancelled
default:
return false
}
}
override var isExecuting: Bool {
return state == .executing
}
override var isFinished: Bool {
return state == .finished
}
If more code is needed, please let me know.
If this is a duplicate question, please link to the duplicate here. I've been unable to find a solution.
The keyPathsForValuesAffecting… members can be properties instead of methods.
They must be declared #objc because the KVO system accesses the properties using the Objective-C runtime.
The properties should have type Set<String>.
If you use the #keyPath directive, the compiler can tell you when you've used an invalid key path (for example because of a spelling error or a change to the property name).
Thus:
#objc class var keyPathsForValuesAffectingIsReady: Set<String> {
return [#keyPath(state)]
}
#objc class var keyPathsForValuesAffectingIsExecuting: Set<String> {
return [#keyPath(state)]
}
#objc class var keyPathsForValuesAffectingIsFinished: Set<String> {
return [#keyPath(state)]
}
You also need to make sure your state property is declared #objc dynamic.
The main problem is that KVO is built using Objective-C, and it uses the Objective-C runtime to detect the existence of the keyPathsForValuesAffecting methods. In Swift 4, methods are no longer exposed to Objective-C by default if you don't include an #objc annotation on them. So, in a nutshell, adding the #objc annotation will probably fix your problem.
Another thing that I do—not strictly necessary, but it makes the code look a bit nicer—is to declare these as static constants. The #objc will cause these to get exposed to Objective-C as class methods, so it all works, and it's slightly cleaner. I like to put private on them, too, since these will never get called by Swift code, and there's no point cluttering your class's internal and/or public interface.
You also need to make sure that your state property is KVO-compliant, and sending the notifications when it is changed. You can either do this by making the property dynamic, which will cause the KVO system to automatically generate the notification calls for you, or you can manually call willChangeValue(for:) and didChangeValue(for:) (or the string-based versions, willChangeValue(forKey:) and didChangeValue(forKey:)) in your willSet and didSet handlers for the property.
Finally, don't use raw string key paths in Swift if you can avoid it. The #keyPath() mechanism is the preferred way to get string-based key paths (and for uses other than these legacy Objective-C methods that need to take strings, you should use the new KeyPath type which is better still). If your state property is not an Objective-C-compatible type, though, you're stuck with the old string key paths (in which case you'll fire the notifications in your willSet and didSet as described in the previous paragraph). Alternatively, you can create a dummy Any-typed object that mirrors your state property, purely for KVO purposes.
So, something like this:
#objc private static let keyPathsForValuesAffectingIsReady: Set<String> = [
#keyPath(state)
]
Now, the state property. If it's an Objective-C-compatible type, it's easy:
#objc dynamic var state: ...
Or, if it's not:
#objc var state: SomeNonObjCThing {
willSet { self.willChangeValue(forKey: "state") }
didSet { self.didChangeValue(forKey: "state") }
}
OR:
#objc private var _stateKVO: Any { return self.state }
var state: SomeNonObjCThing {
willSet { self.willChangeValue(for: \.stateKVO) }
didSet { self.didChangeValue(for: \.stateKVO) }
}
// you can now use #keyPath(_stateKVO) in keyPathsForValuesAffecting...
(NS)Operation relies heavily on NSObject-KVO
The closest Swift syntax is
#objc private class func keyPathsForValuesAffectingIsReady() -> Set<String> {
return [#keyPath(state)]
}
#objc private class func keyPathsForValuesAffectingIsExecuting() -> Set<String> {
return [#keyPath(state)]
}
#objc private class func keyPathsForValuesAffectingIsFinished() -> Set<String> {
return [#keyPath(state)]
}
Side note: You might need to make state thread-safe.

What is the purpose of willSet and didSet in Swift?

Swift has a property declaration syntax very similar to C#'s:
var foo: Int {
get { return getFoo() }
set { setFoo(newValue) }
}
However, it also has willSet and didSet actions. These are called before and after the setter is called, respectively. What is their purpose, considering that you could just have the same code inside the setter?
The point seems to be that sometimes, you need a property that has automatic storage and some behavior, for instance to notify other objects that the property just changed. When all you have is get/set, you need another field to hold the value. With willSet and didSet, you can take action when the value is modified without needing another field. For instance, in that example:
class Foo {
var myProperty: Int = 0 {
didSet {
print("The value of myProperty changed from \(oldValue) to \(myProperty)")
}
}
}
myProperty prints its old and new value every time it is modified. With just getters and setters, I would need this instead:
class Foo {
var myPropertyValue: Int = 0
var myProperty: Int {
get { return myPropertyValue }
set {
print("The value of myProperty changed from \(myPropertyValue) to \(newValue)")
myPropertyValue = newValue
}
}
}
So willSet and didSet represent an economy of a couple of lines, and less noise in the field list.
My understanding is that set and get are for computed properties (no backing from stored properties)
if you are coming from an Objective-C bare in mind that the naming conventions have changed. In Swift an iVar or instance variable is named stored property
Example 1 (read only property) - with warning:
var test : Int {
get {
return test
}
}
This will result in a warning because this results in a recursive function call (the getter calls itself).The warning in this case is "Attempting to modify 'test' within its own getter".
Example 2. Conditional read/write - with warning
var test : Int {
get {
return test
}
set (aNewValue) {
//I've contrived some condition on which this property can be set
//(prevents same value being set)
if (aNewValue != test) {
test = aNewValue
}
}
}
Similar problem - you cannot do this as it's recursively calling the setter.
Also, note this code will not complain about no initialisers as there is no stored property to initialise.
Example 3. read/write computed property - with backing store
Here is a pattern that allows conditional setting of an actual stored property
//True model data
var _test : Int = 0
var test : Int {
get {
return _test
}
set (aNewValue) {
//I've contrived some condition on which this property can be set
if (aNewValue != test) {
_test = aNewValue
}
}
}
Note The actual data is called _test (although it could be any data or combination of data)
Note also the need to provide an initial value (alternatively you need to use an init method) because _test is actually an instance variable
Example 4. Using will and did set
//True model data
var _test : Int = 0 {
//First this
willSet {
println("Old value is \(_test), new value is \(newValue)")
}
//value is set
//Finaly this
didSet {
println("Old value is \(oldValue), new value is \(_test)")
}
}
var test : Int {
get {
return _test
}
set (aNewValue) {
//I've contrived some condition on which this property can be set
if (aNewValue != test) {
_test = aNewValue
}
}
}
Here we see willSet and didSet intercepting a change in an actual stored property.
This is useful for sending notifications, synchronisation etc... (see example below)
Example 5. Concrete Example - ViewController Container
//Underlying instance variable (would ideally be private)
var _childVC : UIViewController? {
willSet {
//REMOVE OLD VC
println("Property will set")
if (_childVC != nil) {
_childVC!.willMoveToParentViewController(nil)
self.setOverrideTraitCollection(nil, forChildViewController: _childVC)
_childVC!.view.removeFromSuperview()
_childVC!.removeFromParentViewController()
}
if (newValue) {
self.addChildViewController(newValue)
}
}
//I can't see a way to 'stop' the value being set to the same controller - hence the computed property
didSet {
//ADD NEW VC
println("Property did set")
if (_childVC) {
// var views = NSDictionaryOfVariableBindings(self.view) .. NOT YET SUPPORTED (NSDictionary bridging not yet available)
//Add subviews + constraints
_childVC!.view.setTranslatesAutoresizingMaskIntoConstraints(false) //For now - until I add my own constraints
self.view.addSubview(_childVC!.view)
let views = ["view" : _childVC!.view] as NSMutableDictionary
let layoutOpts = NSLayoutFormatOptions(0)
let lc1 : AnyObject[] = NSLayoutConstraint.constraintsWithVisualFormat("|[view]|", options: layoutOpts, metrics: NSDictionary(), views: views)
let lc2 : AnyObject[] = NSLayoutConstraint.constraintsWithVisualFormat("V:|[view]|", options: layoutOpts, metrics: NSDictionary(), views: views)
self.view.addConstraints(lc1)
self.view.addConstraints(lc2)
//Forward messages to child
_childVC!.didMoveToParentViewController(self)
}
}
}
//Computed property - this is the property that must be used to prevent setting the same value twice
//unless there is another way of doing this?
var childVC : UIViewController? {
get {
return _childVC
}
set(suggestedVC) {
if (suggestedVC != _childVC) {
_childVC = suggestedVC
}
}
}
Note the use of BOTH computed and stored properties. I've used a computed property to prevent setting the same value twice (to avoid bad things happening!); I've used willSet and didSet to forward notifications to viewControllers (see UIViewController documentation and info on viewController containers)
If I've made a mistake anywhere, please edit to fix it!
You can also use the didSet to set the variable to a different value. This does not cause the observer to be called again as stated in Properties guide. For example, it is useful when you want to limit the value as below:
let minValue = 1
var value = 1 {
didSet {
if value < minValue {
value = minValue
}
}
}
value = -10 // value is minValue now.
These are called Property Observers:
Property observers observe and respond to changes in a property’s
value. Property observers are called every time a property’s value is
set, even if the new value is the same as the property’s current
value.
Excerpt From: Apple Inc. “The Swift Programming Language.” iBooks. https://itun.es/ca/jEUH0.l
I suspect it's to allow for things we would traditionally do with KVO such as data binding with UI elements, or triggering side effects of changing a property, triggering a sync process, background processing, etc, etc.
NOTE
willSet and didSet observers are not called when a property is set in an initializer before delegation takes place
The many well-written existing answers cover the question well, but I'll mention, in some detail, an addition that I believe is worth covering.
The willSet and didSet property observers can be used to call delegates, e.g., for class properties that are only ever updated by user interaction, but where you want to avoid calling the delegate at object initialization.
I'll cite Klaas up-voted comment to the accepted answer:
willSet and didSet observers are not called when a property is first
initialized. They are only called when the property’s value is set
outside of an initialization context.
This is a quite neat as it means e.g. the didSet property is a good choice of launch point for delegate callbacks & functions, for your own custom classes.
As an example, consider some custom user control object, with some key property value (e.g. position in rating control), implemented as a subclass of UIView:
// CustomUserControl.swift
protocol CustomUserControlDelegate {
func didChangeValue(value: Int)
// func didChangeValue(newValue: Int, oldValue: Int)
// func didChangeValue(customUserControl: CustomUserControl)
// ... other more sophisticated delegate functions
}
class CustomUserControl: UIView {
// Properties
// ...
private var value = 0 {
didSet {
// Possibly do something ...
// Call delegate.
delegate?.didChangeValue(value)
// delegate?.didChangeValue(value, oldValue: oldValue)
// delegate?.didChangeValue(self)
}
}
var delegate: CustomUserControlDelegate?
// Initialization
required init?(...) {
// Initialise something ...
// E.g. 'value = 1' would not call didSet at this point
}
// ... some methods/actions associated with your user control.
}
After which your delegate functions can be used in, say, some view controller to observe key changes in the model for CustomViewController, much like you'd use the inherent delegate functions of the UITextFieldDelegate for UITextField objects (e.g. textFieldDidEndEditing(...)).
For this simple example, use a delegate callback from the didSet of the class property value to tell a view controller that one of it's outlets have had associated model update:
// ViewController.swift
Import UIKit
// ...
class ViewController: UIViewController, CustomUserControlDelegate {
// Properties
// ...
#IBOutlet weak var customUserControl: CustomUserControl!
override func viewDidLoad() {
super.viewDidLoad()
// ...
// Custom user control, handle through delegate callbacks.
customUserControl = self
}
// ...
// CustomUserControlDelegate
func didChangeValue(value: Int) {
// do some stuff with 'value' ...
}
// func didChangeValue(newValue: Int, oldValue: Int) {
// do some stuff with new as well as old 'value' ...
// custom transitions? :)
//}
//func didChangeValue(customUserControl: CustomUserControl) {
// // Do more advanced stuff ...
//}
}
Here, the value property has been encapsulated, but generally: in situations like these, be careful not to update the value property of the customUserControl object in the scope of the associated delegate function (here: didChangeValue()) in the view controller, or you'll end up with infinite recursion.
The willSet and didSet observers for the properties whenever the property is assigned a new value. This is true even if the new value is the same as the current value.
And note that willSet needs a parameter name to work around, on the other hand, didSet does not.
The didSet observer is called after the value of property is updated. It compares against the old value. If the total number of steps has increased, a message is printed to indicate how many new steps have been taken. The didSet observer does not provide a custom parameter name for the old value, and the default name of oldValue is used instead.
Getter and setter are sometimes too heavy to implement just to observe proper value changes. Usually this needs extra temporary variable handling and extra checks, and you will want to avoid even those tiny labour if you write hundreds of getters and setters. These stuffs are for the situation.
In your own (base) class, willSet and didSet are quite reduntant , as you could instead define a calculated property (i.e get- and set- methods) that access a _propertyVariable and does the desired pre- and post- prosessing.
If, however, you override a class where the property is already defined, then the willSet and didSet are useful and not redundant!
One thing where didSet is really handy is when you use outlets to add additional configuration.
#IBOutlet weak var loginOrSignupButton: UIButton! {
didSet {
let title = NSLocalizedString("signup_required_button")
loginOrSignupButton.setTitle(title, for: .normal)
loginOrSignupButton.setTitle(title, for: .highlighted)
}
I do not know C#, but with a little guesswork I think I understand what
foo : int {
get { return getFoo(); }
set { setFoo(newValue); }
}
does. It looks very similar to what you have in Swift, but it's not the same: in Swift you do not have the getFoo and setFoo. That is not a little difference: it means you do not have any underlying storage for your value.
Swift has stored and computed properties.
A computed property has get and may have set (if it's writable). But the code in the getter and setter, if they need to actually store some data, must do it in other properties. There is no backing storage.
A stored property, on the other hand, does have backing storage. But it does not have get and set. Instead it has willSet and didSet which you can use to observe variable changes and, eventually, trigger side effects and/or modify the stored value. You do not have willSet and didSet for computed properties, and you do not need them because for computed properties you can use the code in set to control changes.