I have a class(A) that has a struct variable (S). In one function of this class I call a mutating function on struct variable,this function takes a closure. Body of this closure checks the struct variable's name property.
Struct's mutating function in turns calls a function of some class(B). This class's function again takes a closure. In this closure's body mutate the struct, i.e. change the name property, and call the closure that was supplied by the first class.
When the first class (A) closure is called where we are inspecting the struct's name property, it is never changed.
But in step 2 if I use a struct (C) instead of class B, I see that inside class A's closure struct is actually changed. Below is the code:
class NetworkingClass {
func fetchDataOverNetwork(completion:()->()) {
// Fetch Data from netwrok and finally call the closure
completion()
}
}
struct NetworkingStruct {
func fetchDataOverNetwork(completion:()->()) {
// Fetch Data from netwrok and finally call the closure
completion()
}
}
struct ViewModelStruct {
/// Initial value
var data: String = "A"
/// Mutate itself in a closure called from a struct
mutating func changeFromStruct(completion:()->()) {
let networkingStruct = NetworkingStruct()
networkingStruct.fetchDataOverNetwork {
self.data = "B"
completion()
}
}
/// Mutate itself in a closure called from a class
mutating func changeFromClass(completion:()->()) {
let networkingClass = NetworkingClass()
networkingClass.fetchDataOverNetwork {
self.data = "C"
completion()
}
}
}
class ViewController {
var viewModel: ViewModelStruct = ViewModelStruct()
func changeViewModelStruct() {
print(viewModel.data)
/// This never changes self.viewModel inside closure, Why Not?
viewModel.changeFromClass {
print(self.viewModel.data)
}
/// This changes self.viewModel inside/outside closure, Why?
viewModel.changeFromStruct {
print(self.viewModel.data)
}
}
}
var c = ViewController()
c.changeViewModelStruct()
Why this different behaviour. I thought the differentiating factor should be whether I use a struct for viewModel or a class. But here it is depending upon whether Networking is a class or structure, that is independent of any ViewController or ViewModel. Can anyone help me understand this?
How about this?
In your solution 2, 3, it need to assign new View Model in ViewController. So I wanna make it automatically by using Protocol Extension. didSet observer works well! But this need to remove force casting in delegate method.
I think I have an idea about the behaviour we are getting in the original question. My understanding is derived from the behaviour of inout parameters inside closures.
Short answer:
It is related to whether closure that captures value types are escaping or nonescaping. To make this code work do this.
Long answer:
Let me give some context first.
inout parameters are used to change values outside of the function scope, like in the below code:
Here x is passed as inout parameter to a function. This function changes value of x in a closure, so it is changed outside of it's scope. Now x's value is 23. We all know this behaviour when we use reference types. But for value types inout parameters are pass by value. So here x is pass by value in the function, and marked as inout. Before passing x into this function, a copy of x is created and passed. So inside the changeOutsideValue this copy is modified, not the original x. Now when this function returns, this modified copy of x copied back to the original x. So we see x is modified outside only when the function returns. Actually it sees that if after changing the inout parameter if the function returns or not i.e. the closure that is capturing x is escaping kind or nonescaping kind.
When the closure is of escaping type, i.e. it just capture the copied value, but before the function returns it is not called. Look at the below code:
Here function is capturing the copy of x in a escaping closure for future usages and returns that closure. So when function returns it writes the unchanged copy of x back to x (value is 22). If you print x, it is still 22. If you call the returned closure, it changes the local copy inside closure and it is never copied to outside x, so outside x is still 22.
So it all depends whether the closure where you are changing the inout parameter is of escaping or non escaping type. If it is nonescaping, changes are seen outside, if it is escaping they are not.
So coming back to our original example. This is the flow:
var c = ViewController()
, So it same as c.In ViewModel's mutating
we create a Networking class instance and pass a closure to fetchDataOverNetwork function. Notice here that for changeFromClass function the closure that fetchDataOverNetwork takes is of escaping type, because changeFromClass makes no assumption that the closure passed in fetchDataOverNetwork will be called or not before changeFromClass returns.
The viewModel self that is captured inside the fetchDataOverNetwork's closure is actually a copy of viewModel self. So self.data = "C" is actually changing the copy of viewModel, not the same instance that is hold by the viewController.
You can verify this if you put all the code inside a swift file and emit SIL (Swift Intermediate Language). Steps for this are at the end of this answer. It becomes clear that capturing viewModel self in fetchDataOverNetwork closure prevents viewModel self from being optimized to stack. This means that instead of using alloc_stack, the viewModel self variable is allocated using alloc_box:
When we print self.viewModel.data in changeFromClass closure it is printing the viewModel's data that is hold by the viewController, not the copy that is being changed by the fetchDataOverNetwork closure. And since the fetchDataOverNetwork closure is of escaping type and viewModel's data is used (printed) before changeFromClass function could return, changed viewModel is not copied to original viewModel (viewController's).
Now as soon as changeFromClass method returns changed viewModel is copied back to the original viewModel, so if you do "print(self.viewModel.data)" just after changeFromClass call, you see the value is changed. (this is because though fetchDataOverNetwork is assumed to be of escaping type, at runtime it is actually turn out to be of nonescaping type)
Now as @san pointed out in comments that "If you add this line self.data = "D" after let networkingClass = NetworkingClass() and remove 'self.data = "C" ' then it prints 'D'". This also make sense because self outside the closure is exact self that is hold by the viewController, since you removed self.data = "C" inside closure, there is no capturing of viewModel self. On the other hand if you don't remove self.data = "C" then it captures a copy of self. In this case print statement prints C. Check that.
This explains the behaviour of changeFromClass, but what about changeFromStruct that is working properly? In theory same logic should be applied to changeFromStruct and things should not work. But as it turns out (by emitting SIL for changeFromStruct function) the viewModel self value captured in networkingStruct.fetchDataOverNetwork function is same self as outside of the closure, so everywhere the same viewModel self is modified:
This is confusing and I have no explanation to this. But this is what I found. At least it clears the air about changefromClass behaviour.
Demo code Solution:
For this demo code the solution to make changeFromClass work as we expect is to make fetchDataOverNetwork function's closure nonescaping like so:
This tells changeFromClass function that before it returns passed closure (that is capturing viewModel self) will be called for sure so there is no need to do alloc_box and make a separate copy.
Real scenario Solutions:
In reality fetchDataOverNetwork will make a web service request and return. When response comes then the completion will be called. So it will always be of escaping type. This will create the same issue. Some ugly solutions for this could be:
Make ViewModel a struct. From mutating function return a new mutated self, either as return value or inside completion depending on your use case:
In this case caller have to always make sure it assigns the returned value to it's original instance, like so:
Make ViewModel a struct. Declare a closure variable in struct and call it with self from every mutating function. Caller will provide the body of this closure.
Hope I am clear in my explaination. I know it is confusing, so you will have to read and try this multiple times.
Some of the resources I referred are here, here and here.
Last one is a accepted swift proposal in 3.0 about removing this confusion. I am not sure if this is it is implemented in swift 3.0 or not.
Steps to emit SIL:
Put all your code in a swift file.
Go to terminal and do this:
Look at output.txt, search for methods you want to see.
This is not a solution but with this code we can see that the
ViewController's
,viewModel.data
is properly set for both class and struct cases. What is different is that theviewModel.changeFromClass
closure captures a staleself.viewModel.data
. Notice in particular that only the '3 self' print for class is wrong. Not the '2 self' and '4 self' prints wrapping it.