In Swift Collections are pass by value by default and we can user inout to make it pass by reference in function arguments but how can we do it in closure capture variables?
var list = [1, 2, 3]
func edit(inout list: [Int]) {
list.append(4)
dispatch_async(dispatch_get_main_queue()) {
list.append(5)
}
}
edit(&list)
...// after dispatch_async was executed
NSLog("\(list)")
Result will be [1, 2, 3, 4]
How can I modify the original variable () inside closure?
UPDATE:
Actually I have a workaround to handle this case by putting the array into an object so I can pass this object to the function by reference and we can modify the same array instance inside the function. but I want see any clever way to archive that
For getting a variable escape from a closure you need #escaping, check this out. A workaround is to put a completion function as an argument rather than an inout variable.
class MyClass {
static func edit(_ list: [Int], _ completion: #escaping ([Int]) -> ()) {
var list = list
list.append(4)
DispatchQueue.main.async() {
list.append(5)
completion(list)
}
}
}
var myList = [1, 2, 3]
MyClass.edit(myList) { (list) in
myList = list
print("My list after editing: \(myList)")
}
print("My list without editing: \(myList)")
Note: The above example is for Swift 3 where inout parameters are not allowed to be captured in closures. Based on your post, you may be using a lower version of Swift where you might be able to use inout rather than setting a mutable copy of the list: var list = list. However, the logic is very similar.
For more information, check the Limiting inout capture to #noescape contexts in Swift evolution:
Swift's behavior when closures capture inout parameters and escape
their enclosing context is a common source of confusion. We should
disallow implicit capture of inout parameters except in #noescape
closures.
Related
I need to manipulate data inside a byte array using swift. The compiler will not permit the following:
var a:[UInt8] = [1,2,3,4,5]
func tt(a:[UInt8]) {
a[2]=10
}
tt(a:a)
The compiler complains:
Cannot assign through subscript: 'a' is a 'let' constant
What is the correct way to create functions that can modify large byte arrays. (They are very large)
I am aware there are Unsafe pointer options, but I am trying various types of unsafe pointer parameters and none of them seem to work or they report even more obscure compiler errors, so I thought I would ask here. i.e.
func tt(a:UnsafePointer<[UInt8]>) {
a[2]=a[3]
}
func tt(a:UnsafeMutablePointer<[UInt8]>) {
a[2]=a[3]
}
func tt(a:[UInt8]) {
a[2]=10
}
In above function, the function parameter a is a let constant, you can't change it inside the function.
You need to use inout parameter to be able to modify this.
var a: [UInt8] = [1,2,3,4,5]
func tt(a: inout [UInt8]) {
a[2] = 10
}
tt(a: &a)
Each value passed inside a function parameter is a constant, to be able to mutate it you need to sign it as inout
func tt(a: inout [UInt8]){
a[2]=10
}
I was trying to modify a parameter of function from within a escaping closure as below:
var completion = [()->Void]()
func addCompletion(closure: #escaping ()->Void) {
completion.append(closure)
}
func testAdd(v: inout Int) {
addCompletion{
v = 1 // Compiler tells 'Escaping closure captures 'inout' parameter 'v'
print("hello1 \(v)")
}
}
var x = 0
testAdd(v:&x)
for comp in completion {
comp()
}
I was wondering if there is another way to let escaping closure change surrounding variable (as function's inout parameter), besides making it into reference type as a class object.
inout is specified to have a "copy-in copy-out" behaviour. The value of x is copied when you call the function. In the function body, the copy of x can be modified, or whatever. Then, when the function returns, the copy is copied again, and assigned to the original x. "Call-by-reference" could happen as an optimisation.
This explains why you can't modify an inout parameter in an escaping closure. The swift compiler can't possibly know when every escaping closure returns, to copy the modified value back.
You can use an actual pointer:
func testAdd(v: UnsafeMutablePointer<Int>) {
addCompletion{
v.pointee = 1 // you just need to change all references to "v" to "v.pointee"
print("hello1 \(v.pointee)")
}
}
Alternatively, if you don't like pointers, you can also do this trick (adapted from my other answer):
func testAdd(modifyV: #escaping ((inout Int) -> Void) -> Void) {
addCompletion{
modifyV { v in // here you can change v however you like!
v = 1
print("hello1 \(v)")
}
}
}
You just need to change the caller to:
testAdd { $0(&x) }
I have a theoretical question, I did not find related topics.
At some point, I decided that it would be nice to have a small extension for an array:
var array = [Int]()
array += 1
The code is quite simple:
extension Array {
mutating static func +=(lhs: Array, rhs: Element) {
lhs.append(rhs)
}
}
To achieve this we align with two factors that make perfect sense to me:
Array is a struct and this operation requires a mutation
Infix operator reload requires a static function
Unfortunately, it is impossible due Swift does not allow mutating functions to be static. And this is the part I don't quite understand.
Your += mutates the first argument, not the Array type.
Therefore it must not be declared mutating (which makes no
sense for a static method because you cannot mutate the type), but the first parameter must be inout:
extension Array {
static func +=(lhs: inout Array, rhs: Element) {
lhs.append(rhs)
}
}
var array = [Int]()
array += 1
print(array) // [1]
Because mutating doesn't mean "mutates anything", but rather, "mutates self". Your function attempts to mutate lhs, not self.
Your current code won't work because lhs is being passed by value. The lhs parameter is a local copy of whatever argument the caller supplied to it, thus any changes your function makes will be local to the function and won't persist. You'll need to instead have lhs be passed by reference, by delcaring it as a inout Array.
By using static keyword before method name means, we call method by struct/class name (Not by an object) so we don't have any object here.
By using mutating keyword, we are mutating 'self' object.
So while using static we don't have any object to mutate.
I have an extension Array in the form of:
extension Array
{
private func someFunction(someClosure: (() -> Int)?)
{
// Do Something
}
func someOtherFunction(someOtherClosure: () -> Int)
{
someFunction(someClosure: someOtherClosure)
}
}
But I'm getting the error: Passing non-escaping parameter 'someOtherClosure' to function expecting an #escaping closure.
Both closures are indeed non-escaping (by default), and explicitly adding #noescape to someFunction yields a warning indicating that this is the default in Swift 3.1.
Any idea why I'm getting this error?
-- UPDATE --
Screenshot attached:
Optional closures are always escaping.
Why is that? That's because the optional (which is an enum) wraps the closure and internally saves it.
There is an excellent article about the quirks of #escaping here.
As already said, Optional closures are escaping. An addition though:
Swift 3.1 has a withoutActuallyEscaping helper function that can be useful here. It marks a closure escaping only for its use inside a passed closure, so that you don't have to expose the escaping attribute to the function signature.
Can be used like this:
extension Array {
private func someFunction(someClosure: (() -> Int)?) {
someClosure?()
}
func someOtherFunction(someOtherClosure: () -> Int) {
withoutActuallyEscaping(someOtherClosure) {
someFunction(someClosure: $0)
}
}
}
let x = [1, 2, 3]
x.someOtherFunction(someOtherClosure: { return 1 })
Hope this is helpful!
The problem is that optionals (in this case (()-> Int)?) are an Enum which capture their value. If that value is a function, it must be used with #escaping because it is indeed captured by the optional.
In your case it gets tricky because the closure captured by the optional automatically captures another closure. So someOtherClosure has to be marked #escaping as well.
You can test the following code in a playground to confirm this:
extension Array
{
private func someFunction(someClosure: () -> Int)
{
// Do Something
}
func someOtherFunction(someOtherClosure: () -> Int)
{
someFunction(someClosure: someOtherClosure)
}
}
let f: ()->Int = { return 42 }
[].someOtherFunction(someOtherClosure: f)
The Swift Playground has this function:
func repeatItem<Item>(item: Item, numberOfTimes: Int) -> [Item] {
var result = [Item]()
for _ in 0..<numberOfTimes {
result.append(item)
}
return result
}
let strArray: [String] = repeatItem("knock", numberOfTimes:4) //!!!!
Why is there a numberOfTimes: in the function call and why does removing it give me the error "missing argument label"? More confusingly, why does adding an argument label to "knock" give me "extraneous argument label"?
EDIT:
Also this piece of code has not arguments labels in the call:
func anyCommonElements <T: SequenceType, U: SequenceType where T.Generator.Element: Equatable, T.Generator.Element == U.Generator.Element> (lhs: T, _ rhs: U) -> Bool {
for lhsItem in lhs {
for rhsItem in rhs {
if lhsItem == rhsItem {
return true
}
}
}
return false
}
anyCommonElements([1, 2, 3], [3])
Question 1
This is by construction of Swift. From Swift language guide for functions - Function Parameter Names:
By default, the first parameter omits its external name, and the
second and subsequent parameters use their local name as their
external name. All parameters must have unique local names. Although
it’s possible for multiple parameters to have the same external name,
unique external names help make your code more readable.
...
If you do not want to use an external name for the second or
subsequent parameters of a function, write an underscore (_) instead
of an explicit external name for that parameter.
Note from above that you can supersede this demand by placing an underscore _ in front of 2nd (and onward) parameter name. In your case:
func repeatItem<Item>(item: Item, _ numberOfTimes: Int) -> [Item] { ...
Finally note that this has nothing to do with generics, but with Swift functions in general.
Question 2
Try replacing your line
let strArray: [String] = repeatItem("knock", numberOfTimes:4) //!!!!
with
let strArray = [String](count: 4, repeatedValue: "knock")
This uses the initialiser for array objects with repeated entries.
func repeatItem<Item>(item: Item, numberOfTimes: Int) -> [Item]
is a method that takes two parameters, the first is item and the second is named numberOfTimes.
In Swift, when you call a method or a function you have to write the name of the parameters followed by ":" and the its value.
In Swift the name of the first parameter can be omitted.