The following class BackgroundExecutable
is to be used to take a function f
and return a function f'
where f'
asks iOS for background execution. f
takes a completion closure which it uses to end the background execution.
class BackgroundExecutable {
var identifier: UIBackgroundTaskIdentifier
let f: (() -> Void) -> Void
init(f: (completion: () -> Void) -> Void) {
self.f = f
self.identifier = UIBackgroundTaskInvalid
}
func execute() {
let application = UIApplication.sharedApplication()
identifier = application.beginBackgroundTaskWithExpirationHandler {
application.endBackgroundTask(self.identifier)
}
f(endBackgroundTask)
}
func endBackgroundTask() {
UIApplication.sharedApplication().endBackgroundTask(identifier)
}
}
func backgroundExecutable(f: (() -> Void) -> Void) -> (() -> Void) {
let b = BackgroundExecutable(f: f)
return b.execute
}
Usage:
func F(completion: () -> Void) {
println("F Called")
completion()
}
let newF = backgroundExecutable(F)
-
1\$\begingroup\$ To be clear, "background" here refers to when the app itself is in the background, as opposed to a multithreading reference while the app is in the foreground. \$\endgroup\$nhgrif– nhgrif2015年08月31日 12:31:05 +00:00Commented Aug 31, 2015 at 12:31
-
\$\begingroup\$ Don't make answer invalidating edits. \$\endgroup\$nhgrif– nhgrif2015年09月01日 13:21:35 +00:00Commented Sep 1, 2015 at 13:21
-
4\$\begingroup\$ For more information, please see what you may and may not do after receiving answers . \$\endgroup\$Vogel612– Vogel6122015年09月01日 13:27:42 +00:00Commented Sep 1, 2015 at 13:27
-
\$\begingroup\$ Sorry, I was treating this as a gist. Thanks @Vogel612 for the reference. I will keep these points in mind going forward. \$\endgroup\$Ayush Goel– Ayush Goel2015年09月02日 04:47:19 +00:00Commented Sep 2, 2015 at 4:47
3 Answers 3
func backgroundExecutable(f: (() -> Void) -> Void) -> (() -> Void)
This is madness and it takes a number of mental gymnastics to figure out what is going on here.
This method takes, as its only (and poorly named) argument, a closure which returns void and takes, as its single argument, a closure which returns void from taking no arguments. Then this method returns a closure which takes no arguments and returns void.
Even writing it out in plain-English, it's hard to understand what's actually going on. And I'm left scratching my head trying to figure out why we even need a closure which takes a closure as its argument.
The major problem here is that we're using this closure's closure argument to pass the call to endBackgroundTask()
, a method which must be called, or we risk the OS killing our app.
Your class is attempting to provide a convenient wrapper to handling the background means for executing code while your application is in the background. But your class hasn't made things any more convenient then what they already are. It only makes them more nested and complicated.
So let's look at execute
for a second, shall we?
func execute() { let application = UIApplication.sharedApplication() identifier = application.beginBackgroundTaskWithExpirationHandler { application.endBackgroundTask(self.identifier) } f(endBackgroundTask) }
The crux of this is that endBackgroundTask
, another method of this class (which should perhaps be private
) is passed in as the closure the the user's background executing closure. If they don't call the closure that's passed in (and call it only as the last thing their closure does), there will be unintended problems going on here.
But why do that? We can instead do this:
f()
endBackgroundTask()
That's a step in the right direction. We're no longer relying on the caller to end the background task.
But there's another problem here. What if we call execute
multiple times and on multiple threads? The identifier
will be different each time we call execute
, and each time it overwrites the previous identifier. So all the previous background tasks can't be stopped, and so the OS will eventually kill our app for running indefinitely in the background.
Instead, we need our endBackgroundTask
method to take a string argument... the identifier of the task we're stopping. And we need to not rely on a string variable for our class.
So, it needs to look more like this:
f()
endBackgroundTask(identifier)
At this point, I'm not entirely certain the point of the class at all when a single method with just a handful of lines can manage it:
func backgroundExecute(worker: () -> Void) -> Void {
let application = UIApplication.sharedApplication()
identifier = application.beginBackgroundTaskWithExpirationHandler {
// you can't call endBackgroundTask here, and you don't need to
// here you should put code to handle the task running out of time before it is complete
}
worker()
application.endBackgroundTask(identifier)
}
-
\$\begingroup\$ I need to give
endBackgroundTask
as a closure, because this wrapper is written to help the functions that are asynchronous. The approach of callingendBackgroundTask(identifier)
just afterf()
only works for methods that are synchronous. CallingnewF
multiple times would not create any problem becauseidentifier
is an instance variable onBackgroundExecutable
and not a static variable.identifier
would not be overwritten. Finally, Your point on() -> Void
is correct, it's madness. Updating the question to usetypealias
to solve that. \$\endgroup\$Ayush Goel– Ayush Goel2015年09月01日 13:14:48 +00:00Commented Sep 1, 2015 at 13:14 -
\$\begingroup\$ Yes,
identifier
is an instance variable. And what happens if you callexecute
twice on the same instance? That instance variable is overwritten. \$\endgroup\$nhgrif– nhgrif2015年09月01日 13:22:16 +00:00Commented Sep 1, 2015 at 13:22 -
\$\begingroup\$ I made another mistake of not making the class
private
. What I intend is that you always only usebackgroundExecutable
and not the class itself. \$\endgroup\$Ayush Goel– Ayush Goel2015年09月02日 06:52:34 +00:00Commented Sep 2, 2015 at 6:52
A few things to point out:
Using single lettered variable names is bad practice, f
could be read as file
, function
or other things.
class BackgroundExecutable { var identifier: UIBackgroundTaskIdentifier let f: (() -> Void) -> Void init(f: (completion: () -> Void) -> Void) { self.f = f self.identifier = UIBackgroundTaskInvalid } func execute() { let application = UIApplication.sharedApplication() identifier = application.beginBackgroundTaskWithExpirationHandler { application.endBackgroundTask(self.identifier) } f(endBackgroundTask) } func endBackgroundTask() { UIApplication.sharedApplication().endBackgroundTask(identifier) } }
into:
class BackgroundExecutable {
var identifier: UIBackgroundTaskIdentifier
let function: (() -> Void) -> Void
init(function: (completion: () -> Void) -> Void) {
self.function = function
self.identifier = UIBackgroundTaskInvalid
}
func execute() {
let application = UIApplication.sharedApplication()
identifier = application.beginBackgroundTaskWithExpirationHandler {
application.endBackgroundTask(self.identifier)
}
function(endBackgroundTask)
}
func endBackgroundTask() {
UIApplication.sharedApplication().endBackgroundTask(identifier)
}
}
Is there a specific reason why you don't just return BackgroundExecutable(function: function)
as it is, instead of assigning it and then returning?
This would be better:
func backgroundExecutable(function: (() -> Void) -> Void) -> (() -> Void) {
return BackgroundExecutable(function: function).execute
}
A slightly simpler approach would be to add an extension onto UIApplication
itself, for executing a function in the background.
Pros:
- No extra classes are needed.
- The callback syntax is simpler.
- The identifier is always handled correctly.
Cons:
- The
UIApplication
class is "polluted" with external code.
Example:
Using a typealias
for the callback will also add some clarity.
extension UIApplication {
typealias BackgroundTaskCompletion = () -> Void
func executeBackgroundTask(f: (BackgroundTaskCompletion) -> Void) {
let identifier = beginBackgroundTask {
// nothing to do here
}
f() { [unowned self] in
self.endBackgroundTask(identifier)
}
}
}
Usage:
UIApplication.shared.executeBackgroundTask() { completion in
print("hello")
completion()
}