Unowned variables and async functions
Seems like an x-y problem. If a Student's school
can go out of existence while this Student still exists, that is an incorrect use of unowned
. The precondition for saying unowned
is that that must be absolutely impossible.
Pass variable to dataTask async function
For this purpose DispatchGroup
is the recommended API for example
var neededVariable = 1
let group = DispatchGroup()
let session = URLSession.shared
for _ in 0...10 {
group.enter()
let urlS = "https://sub2pewdiepie.com/subscribe.php?param1=123"
let url = URL(string: urlS.addingPercentEncoding(withAllowedCharacters: CharacterSet.urlQueryAllowed)!)!
let task = session.dataTask(with: url, completionHandler: { data, response, error in
defer(group.leave())
if let error = error { print(error); return }
do {
neededVariable += 1
print(neededVariable)
} catch let error { print(error.localizedDescription) }
})
task.resume()
}
group.notify(queue: DispatchQueue.main) {
print("notify", neededVariable)
}
How closure captures values in Swift?
From Swift Programming Guide - Closures
A closure can capture constants and variables from the surrounding context in which it’s defined. The closure can then refer to and modify the values of those constants and variables from within its body, even if the original scope that defined the constants and variables no longer exists.
A closure captures variables, not the contents of variables. When we are talking about local variables in a function (which are normally allocated on stack), it makes sure they are accessible even when the function exits and other local variables are deallocated, therefore we can do things like this:
func myFunc() {
var array: [Int] = []
DispatchQueue.main.async {
// executed when myFunc has already returned!
array.append(10)
}
}
Your example is similar. The closure captures the variable. This is a variable on module level, therefore its scope always exists. When you reassign its value, it will affect the value read inside the closure.
Or, in other words, the closure will be equivalent to:
var closure = {
print(CurrentModule.element?.name ?? "default value")
}
where CurrentModule
is the name of your main module (which is usually the name of your project).
To prevent this behavior and capture the value of the variable instead, we can use closure capture list. Unfortunately, the official documentation does not properly explain what exactly is a capture list. Basically, using a capture list you declare variables local to the closure using values that are available when the closure is created.
For example:
var closure = { [capturedElement = element] in
print(capturedElement?.name ?? "default value")
}
This will create a new variable capturedElement
inside the closure, with the current value of variable element
.
Of course, usually we just write:
var closure = { [element] in
print(element?.name ?? "default value")
}
which is a shorthand for [element = element]
.
How do I access variables that are inside closures in Swift?
var dict: NSDictionary! // Declared in the main thread
The closure is then completed asynchronously so the main thread doesn't wait for it, so
println(dict)
is called before the closure has actually finished. If you want to complete another function using dict then you will need to call that function from within the closure, you can move it into the main thread if you like though, you would do this if you are going to be affecting UI.
var dict: NSDictionary!
NSURLConnection.sendAsynchronousRequest(request, queue: NSOperationQueue.mainQueue(), completionHandler: {(response, data, error) in
var jsonError: NSError?
let json = NSJSONSerialization.JSONObjectWithData(data!, options: NSJSONReadingOptions.MutableContainers, error: &jsonError) as? NSDictionary
dict = json
//dispatch_async(dispatch_get_main_queue()) { //uncomment for main thread
self.myFunction(dict!)
//} //uncomment for main thread
})
func myFunction(dictionary: NSDictionary) {
println(dictionary)
}
Passing a local mutable struct into an async let binding
As SE-0317 - async let bindings says:
...
async let
is similar to alet
, in that it defines a local constant that is initialized by the expression on the right-hand side of the=
. However, it differs in that the initializer expression is evaluated in a separate, concurrently-executing child task.The child task begins running as soon as the
async let
is encountered....
A
async let
creates a child-task, which inherits its parent task's priority as well as task-local values. Semantically, this is equivalent to creating a one-offTaskGroup
which spawns a single task and returns its result ...Similarly to the [
group.addTask
] function, the closure is@Sendable
andnonisolated
, meaning that it cannot access non-sendable state of the enclosing context. For example, it will result in a compile-time error, preventing a potential race condition, for aasync let
initializer to attempt mutating a closed-over variable:var localText: [String] = ...
async let w = localText.removeLast() // error: mutation of captured var 'localText' in concurrently-executing codeThe
async let
initializer may refer to any sendable state, same as any non-isolated sendable closure.
So, it is not the case that the parameter to data(for:delegate:)
is copied and then the asynchronous task is created, but rather the other way around.
Usually, if you were using a closure, you would just add request
to the closure’s capture list, but that’s not possible in this case. E.g., you could create a Task
yourself with a capture list, achieving something akin to async let
, but with greater control:
func test() async throws {
var request = URLRequest(url: URL(string:"https://httpbin.org/get")!)
request.httpMethod = "GET" // just for example
let task = Task { [request] in
try await URLSession.shared.data(for: request).0
}
// do some more stuff in parallel
print("Response body: \(String(data: try await task.value, encoding: .utf8) ?? "Not string")")
}
Obviously, you can simply await
the data(for:delegate:)
, rather than async let
, and the problem goes away:
func test() async throws {
var request = URLRequest(url: URL(string:"https://httpbin.org/get")!)
request.httpMethod = "GET" // just for example
let data = try await URLSession.shared.data(for: request).0
print("Response body: \(String(data: data, encoding: .utf8) ?? "Not string")")
}
How exactly are structs captured in escaping closures?
While it might make sense for a struct to be copied, as your code demonstrates, it is not. That's a powerful tool. For example:
func makeCounter() -> () -> Int {
var n = 0
return {
n += 1 // This `n` is the same `n` from the outer scope
return n
}
// At this point, the scope is gone, but the `n` lives on in the closure.
}
let counter1 = makeCounter()
let counter2 = makeCounter()
print("Counter1: ", counter1(), counter1()) // Counter1: 1 2
print("Counter2: ", counter2(), counter2()) // Counter2: 1 2
print("Counter1: ", counter1(), counter1()) // Counter1: 3 4
If n
were copied into the closure, this couldn't work. The whole point is the closure captures and can modify state outside itself. This is what separates a closure (which "closes over" the scope where it was created) and an anonymous function (which does not).
(The history of the term "close over" is kind of obscure. It refers to the idea that the lambda expression's free variables have been "closed," but IMO "bound" would be a much more obvious term, and is how we describe this everywhere else. But the term "closure" has been used for decades, so here we are.)
Note that it is possible to get copy semantics. You just have to ask for it:
func foo(){
var wtf = Wtf()
DispatchQueue.global().async { [wtf] in // Make a local `let` copy
var wtf = wtf // To modify it, we need to make a `var` copy
wtf.x = 5
}
Thread.sleep(forTimeInterval: 2)
// Prints 1 as you expected
print("x = \(wtf.x)")
}
In C++, lambdas have to be explicit about how to capture values, by binding or by copying. But in Swift, they chose to make binding the default.
As to why you're allowed to access wtf
after it's been captured by the closure, that's just a lack of move semantics in Swift. There's no way in Swift today to express "this variable has been passed to something else and may no longer be accessed in this scope." That's a known limitation of the language, and a lot of work is going into fix it. See The Ownership Manifesto for more.
Swift 3: capture strong self in @escaping closure without asynchronous work
In SecondClass
, there is no need to create a strongSelf
variable. Where would you put it? The point is that self
is guaranteed not to be nil
anyway because you are running within the scope of one of its methods.
The same is true of your additional question, but for a different reason. suggestions
is now static
, so prefixing with self
is a matter of syntax, (I am presuming you meant to also prefix the suggest
method with static
).
However, in FirstClass
, there is a subtle difference between capturing strongSelf
and not capturing it.
Because you are using [weak self]
, self
could be nil
when you enter that block so you need to check against that anyway. One way is to repeatedly use optional chaining, i.e.:
self?.doSomething()
self?.doSomethingElse()
This is saying:
If I have a reference to self, do something. If I still have a
reference to self, do something else.
By adding a strongSelf
variable:
guard let strongSelf = self else {
return
}
strongSelf.doSomething()
strongSelf.doSomethingElse()
...you are saying:
do something and do something else if you have a reference to
self
,
otherwise do nothing.
So, you guarantee that if the first thing happens, so does the second. The approach you take is going to depend on your application.
Related Topics
How to Convert a Float Value to Byte Array in Swift
How to Pass an Error Pointer in the Swift Language
Trying to Use Keychainitemwrapper by Apple "Translated" to Swift
Swift: Get an Element from a Tuple
Swift: How to Disable User Interaction While Touch Action Is Being Carried Out
Can/How to Replace My Kvo Stuff with Rc3
Check If Variable Is a Block/Function/Callable in Swift
How to Get Motion Events with the Apple Tv Remote
How to Use Swift Package Manager in Xcode 9 Playground
Using Cocoa Nssavepanel in Sandbox Causes Assertion Failure
In Swift, When Using "[Weak Self] In", Should I Double Up on It When Nested Inside Another Closure
Get a Unique String for a Given Anyobject
Swift Uifont Ibinspectable - Is It Possible
In Swiftui How to Set the Environment Variable of Editmode in an Xcodepreview
Dispatchqueue:Cannot Be Called with Ascopy = No on Non-Main Thread
Xcode 11 Doesn't Recognize Core Data Entity