Trouble with non-escaping closures in Swift 3
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!
Closure use of non-escaping parameter - Swift 3 issue
updateCompleted
is of type (_ completed: @escaping UpdateInProgressCompletion) -> ()
, which as it's a function parameter itself, means that it is non-escaping by default (note that 'non-escaping by default' behaviour is only applicable to function closure arguments, see this Q&A, as well as its dupe target on the topic).
Therefore in order to allow updateCompleted
to escape, you need to mark (_ completed: @escaping UpdateInProgressCompletion) -> ()
as @escaping
in your typealias
:
typealias UpdateInProgressHandler = (@escaping (_ completed: @escaping UpdateInProgressCompletion) -> ()) -> ()
Why do closures require an explicit `self` when they're all non-escaping by default in Swift 3?
In Swift 3, all closures are non-escaping by default
No, in Swift 3, only closure function arguments (i.e function inputs that are functions themselves) are non-escaping by default (as per SE-0103). For example:
class A {
let n = 5
var bar : () -> Void = {}
func foo(_ closure: () -> Void) {
bar = closure // As closure is non-escaping, it is illegal to store it.
}
func baz() {
foo {
// no explict 'self.' required in order to capture n,
// as foo's closure argument is non-escaping,
// therefore n is guaranteed to only be captured for the lifetime of foo(_:)
print(n)
}
}
}
As closure
in the above example is non-escaping, it is prohibited from being stored or captured, thus limiting its lifetime to the lifetime of the function foo(_:)
. This therefore means that any values it captures are guaranteed to not remain captured after the function exits – meaning that you don’t need to worry about problems that can occur with capturing, such as retain cycles.
However, a closure stored property (such as bar
in the above example) is by definition escaping (it would be nonsensical to mark it with @noescape
) as its lifetime not limited to a given function – it (and therefore all its captured variables) will remain in memory as long as the given instance remains in memory. This can therefore easily lead to problems such as retain cycles, which is why you need to use an explicit self.
in order to make the capturing semantics explicit.
In fact, case in point, your example code will create a retain cycle upon viewDidLoad()
being called, as someClosure
strongly captures self
, and self
strongly references someClosure
, as it's a stored property.
It's worth noting that as an extension of the "stored function properties are always escaping" rule, functions stored in aggregates (i.e structures and enumerations with associated values) are also always escaping, as there's no restrictions on what you do with such aggregates. As pointed out by pandaren codemaster, this currently includes Optional
– meaning that Optional<() -> Void>
(aka. (() -> Void)?
) is always escaping. The compiler might eventually make this a special case for function parameters though, given that optional is already built on a lot of compiler magic.
Of course, one place where you would expect to be able to use the @noescape
attribute is on a closure that’s a local variable in a function. Such a closure would have a predictable lifetime, as long as it isn’t stored outside of the function, or captured. For example:
class A {
let n = 5
func foo() {
let f : @noescape () -> Void = {
print(n)
}
f()
}
}
Unfortunately, as @noescape
is being removed in Swift 3, this won't be possible (What's interesting is that in Xcode 8 GM, it is possible, but yields a deprecation warning). As Jon Shier says, we’ll have to wait for it to be re-added to the language, which may or may not happen.
Swift: Escaping closure captures non-escaping parameter 'onCompletion'
You have to mark both completion handlers with @escaping
. Usually the compiler offers a fix
class RestApiManager: NSObject {
static let sharedInstance = RestApiManager()
let baseURL = "http://api.randomuser.me/"
func getRandomUser(onCompletion : @escaping (JSON) -> Void) {
makeHTTPGetRequest(path: baseURL, onCompletion: { json, err -> Void in
onCompletion(json)
})
}
func makeHTTPGetRequest(path: String, onCompletion: @escaping ServiceResponse) {
let request = NSMutableURLRequest(url : URL(string: path)! as URL)
let session = URLSession.shared
let task = session.dataTask(with: request as URLRequest, completionHandler: { data, response, error in
let json:JSON = JSON(data as Any)
onCompletion(json, error as NSError?)
})
task.resume()
}
}
Swift-3: closure with escaping and non-escaping behaviour together
The rule for when you need @escaping
is simple – if a closure function argument can escape the lifetime of the function call, it needs to be marked as @escaping
(the compiler simply won't let you compile it otherwise).
In your example code, completionHandler
is not marked as @escaping
in f2
– therefore it cannot escape the lifetime of f2
. Therefore it cannot possibly escape the lifetime of f1
either, thus you don't need to mark f1
's completionHandler
as @escaping
.
If however, f2
's completionHandler
could escape the lifetime of f2
, then you would have to mark both f2
and f1
's parameters as @escaping
as it could escape the lifetime of both calls.
Closure use of non-escaping parameter may allow it to escape
This is due to a change in the default behaviour for parameters of function type. Prior to Swift 3 (specifically the build that ships with Xcode 8 beta 6), they would default to being escaping – you would have to mark them @noescape
in order to prevent them from being stored or captured, which guarantees they won't outlive the duration of the function call.
However, now @noescape
is the default for function-typed parameters. If you want to store or capture such functions, you now need to mark them @escaping
:
protocol DataServiceType {
func fetchData(location: String, completion: @escaping (DataFetchResult) -> Void)
func cachedData(location: String) -> Data?
}
func fetchData(location: String, completion: @escaping (DataFetchResult) -> Void) {
// ...
}
See the Swift Evolution proposal for more info about this change.
How to call non-escaping closure inside a local closure?
Non-parameter closures are @escaping
, by default
"If
localClosure
is, by default, non-escaping, then why ..."
Based on the discussion in the comments below (thanks @Hamish), we can state the following facts regarding non-parameter closures† in Swift 3.0:
- They are, contrary to what one might believe,
@escaping
, by default. As@noescape
is deprecated in Swift 3 (see e.g. Xcode 8 release notes or Swift evolution proposal SE-0103), this means that non-parameter closures cannot be made non-escaping without making use of deprecated methods. - As described in the following evolution thread, the lack of
@noescape
attribute for non-parameter closures is a missing feature (somewhat of a regression as this was not a limitation in Swift 2.2), but one that is not necessarily to be implemented in the future (if I'm to understand the answer by Apple dev. Jordan Rose in the linked evolution thread). We may however (still) apply the deprecated
@noescape
attribute to a non-parameter closure to make it non-escaping, but will then notably be prompted with an incorrect warning (as below, emphasis mine), which has now been reported as a bug by @Hamish, see bug report SR-2969."
@noescape
is default and is deprecated"
To summarize, localClosure
is @escaping
, which naturally means it cannot be allowed to wrap the non-escaping closure parameter closure
of test(...)
.
[†] By non-parameter closures, I refer to all closures that are not parameters to a function, i.e., closures that are not supplied to a function as an argument.
As a side-note, which you possibly already knows given your question: we may naturally mark closure
as @escaping
if we'd wish to process/wrap it as in your example.
func test(closure: @escaping () -> ()) -> () -> () {
let escapingLocalClosure = { closure() }
return escapingLocalClosure
}
Related Topics
Operation Not Permitted When Executing 'Killall' with Swift
How to Open to a Specific View Using Home Quick Actions
How to Await X Seconds with Async Await Swift 5.5
Cannot Convert Value of Type 'Binding<String>' to Expected Argument Type 'Binding<String>'
Nscollectionviewitem Never Instantiate
Can You Enforce a Typealias in Swift
Error with Parse Query Findobjectsinbackgroundwithblock
Why Can't I Use Subscripting on a Ckrecord Object in Swift
Swift Error Using Initialized Properties in Expressions Before Super.Init
Passing a Variable Through a Segue? Xcode 8 Swift 3
How to Save a Custom Class as an Attribute of a Coredata Entity in Swift 3
Swift Throws Python Errors from Terminal
Exc_Bad_Instruction Happens When Using Dispatch_Get_Global_Queue on iOS 7(Swift)
Change a Dictionary's Key in Swift
Explicitly Unwrapping Optional Nil Does Not Cause Crash
Does the Initializer of an 'Open' Class Need to Be Open as Well
Swift 1.2 Not Working with Same Function Name and Different Parameter