Memory safety of “+=” operator in Swift
I know you've got it, but a clarification for future readers; in your comments, you said:
... it is, in the end, a problem with any one-line code changing self by reading self first.
No, that alone is not sufficient. As the Memory Safety chapter says, this problem manifests itself only when:
- At least one is a write access.
- They access the same location in memory.
- Their durations overlap.
Consider:
var foo = 41
foo = foo + 1
The foo = foo + 1
is not a problem (nor would foo += 1
; nor would foo += foo
) because constitutes a series of "instantaneous" accesses. So although we have (to use your phrase) "code changing self by reading self first", it is not a problem because their durations do not overlap.
The problem only manifests itself when you're dealing with "long-term" accesses. As that guide goes on to say:
A function has long-term write access to all of its in-out parameters. The write access for an in-out parameter starts after all of the non-in-out parameters have been evaluated and lasts for the entire duration of that function call. If there are multiple in-out parameters, the write accesses start in the same order as the parameters appear.
One consequence of this long-term write access is that you can’t access the original variable that was passed as in-out, even if scoping rules and access control would otherwise permit it—any access to the original creates a conflict.
So, consider your second example:
var stepSize = 1
func incrementInPlace(_ number: inout Int) {
number += stepSize
}
incrementInPlace(&stepSize)
In this case, you have a long-term access to whatever number
references. When you invoke it with &stepSize
, that means you have a long-term access to the memory associated with stepSize
and thus number += stepSize
means that you're trying to access stepSize
while you already have a long-term access to it.
When to use inout parameters?
inout
means that modifying the local variable will also modify the passed-in parameters. Without it, the passed-in parameters will remain the same value. Trying to think of reference type when you are using inout
and value type without using it.
For example:
import UIKit
var num1: Int = 1
var char1: Character = "a"
func changeNumber(var num: Int) {
num = 2
print(num) // 2
print(num1) // 1
}
changeNumber(num1)
func changeChar(inout char: Character) {
char = "b"
print(char) // b
print(char1) // b
}
changeChar(&char1)
A good use case will be swap
function that it will modify the passed-in parameters.
Swift 3+ Note: Starting in Swift 3, the inout
keyword must come after the colon and before the type. For example, Swift 3+ now requires func changeChar(char: inout Character)
.
Swift: When i pass a parameter to a function does the function copies the parameter or uses the reference to that parameter
When you pass a reference type into a swift function you are passing along a reference to that object, so any changes that you make to the object in that method will effect that object outside the function. You can achieve a similar effect for value types if you label them as inout parameters.
Swift memory conflict where it should not happen
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 { ... }
The use of IN OUT in Ada
An in
parameter can be read but not written by the subprogram. in
is the default. Prior to Ada 2012, functions were only allowed to have in
parameters. The actual parameter is an expression.
An out
parameter implies that the previous value is of no interest. The subprogram is expected to write to the parameter. After writing to the parameter, the subprogram can read back what it has written. On exit the actual parameter receives the value written to it (there are complications in this area!). The actual parameter must be a variable.
An in out
parameter is like an out
parameter except that the previous value is of interest and can be read by the subprogram before assignment. For example,
procedure Add (V : Integer; To : in out Integer; Limited_To : Integer)
is
begin
-- Check that the result wont be too large. This involves reading
-- the initial value of the 'in out' parameter To, which would be
-- wrong if To was a mere 'out' parameter (it would be
-- uninitialized).
if To + V > Limited_To then
To := Limited_To;
else
To := To + V;
end if;
end Add;
Related Topics
Add a File Generated by Run Script into The Test Target Compilation List in Xcode
Why Do I Get Rgb Values of (0,0,0) for an Nsimage with a Transparent Background
How to Specify The Name of The Output Executable
Why Does a Function Have Long-Term Write Access to All of Its In-Out Parameters
Update UIapplicationshortcutitem from Extension
Swift Method Parameters in Documentation
Swift: Check Which Value in Nsarray Is Closest to Another Given Value
Error Validating Cms Signature
How to Save Re-Ordered Tableview Cells to Core Data
Writing an Enum Case Check to a Bool Variable Without Equatable Conformance
No Trailing Closures Support for Methods with Default Parameter Values
Using Enumerated with Foreach in Swiftui
Cropping Cgrect from Avcapturephotooutput (Resizeaspectfill)
Uiview.Animatewithduration Not Animating
Swift: Object Instance by Name