8
\$\begingroup\$

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)
\$\endgroup\$
4
  • 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
    Commented Aug 31, 2015 at 12:31
  • \$\begingroup\$ Don't make answer invalidating edits. \$\endgroup\$
    – nhgrif
    Commented 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
    Commented 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
    Commented Sep 2, 2015 at 4:47

3 Answers 3

11
\$\begingroup\$
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)
}
\$\endgroup\$
3
  • \$\begingroup\$ I need to give endBackgroundTask as a closure, because this wrapper is written to help the functions that are asynchronous. The approach of calling endBackgroundTask(identifier) just after f() only works for methods that are synchronous. Calling newF multiple times would not create any problem because identifier is an instance variable on BackgroundExecutable and not a static variable. identifier would not be overwritten. Finally, Your point on () -> Void is correct, it's madness. Updating the question to use typealias to solve that. \$\endgroup\$
    – Ayush Goel
    Commented Sep 1, 2015 at 13:14
  • \$\begingroup\$ Yes, identifier is an instance variable. And what happens if you call execute twice on the same instance? That instance variable is overwritten. \$\endgroup\$
    – nhgrif
    Commented 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 use backgroundExecutable and not the class itself. \$\endgroup\$
    – Ayush Goel
    Commented Sep 2, 2015 at 6:52
7
\$\begingroup\$

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
}
\$\endgroup\$
0
\$\begingroup\$

A slightly simpler approach would be to add an extension onto UIApplication itself, for executing a function in the background.

Pros:

  1. No extra classes are needed.
  2. The callback syntax is simpler.
  3. The identifier is always handled correctly.

Cons:

  1. 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()
}
\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.