Modifying function-level variables from within a closure?

10 views
Skip to first unread message

Alex Hall

unread,
Sep 13, 2015, 11:39:24 PM9/13/15
to swift-l...@googlegroups.com
Hi all,
I've tried to look this up online, but can't find anything. I have a function that calls a framework function, and passes in two closures as part of the necessary arguments. The framework function wants both closures to return nothing. What I want to do is take the results passed into the first closure and set them equal to a variable in my function, so my function can return that variable. In the second closure (called if the framework fails), I want to set my variable to nil. Here's a representation of what I'm talking about:

func myFunction() -> [String]? {
var resultsToReturn:[String]?
let successHandler= {
(results:[String]?) in
resultsToReturn=results
}

let errorHandler = {
(error:NSError) in
print("\(error.localizedDescription)")
resultsToReturn=nil
}

myFrameworkFunction(success:successHandler, failure:failureHandler)
return resultsToReturn
}

This way, I can store the results of success and return them, or return nil to indicate failure (yes, throwing would be better, but for now I'm mostly interested in saving those results). Right now, it's looking like resultsToReturn isn't being modified at all outside the closures, so I'm not sure how to save what the success closure is offering. I'm using Xcode 7 GM on 10.11 GM.

--
Have a great day,
Alex Hall
meh...@icloud.com

Brent Royal-Gordon

unread,
Sep 13, 2015, 11:56:30 PM9/13/15
to Alex Hall, swift-l...@googlegroups.com
Closures natively support modifying variables captured from the scope they're in; there's nothing special you need to do to make that work. However, many APIs that take a completion closure do it because they perform an operation asynchronously—that is, the operation isn't finished by the time myFrameworkFunction returns and the next line of code executes. Is that true of your function? If so, there's just no (easy) way to fill in resultsToReturn by the time that return statement executes, and you'll have to restructure your code so that the completion blocks continue the operation.

--
Brent Royal-Gordon
Sent from my iPhone
> --
> You received this message because you are subscribed to the Google Groups "Swift Language" group.
> To unsubscribe from this group and stop receiving emails from it, send an email to swift-languag...@googlegroups.com.
> To post to this group, send email to swift-l...@googlegroups.com.
> To view this discussion on the web visit https://groups.google.com/d/msgid/swift-language/D192B1EA-53B0-464F-B267-7B4EE2B569C9%40icloud.com.
> For more options, visit https://groups.google.com/d/optout.

Alex Hall

unread,
Sep 14, 2015, 12:15:11 AM9/14/15
to Brent Royal-Gordon, swift-l...@googlegroups.com

> On Sep 13, 2015, at 23:56, Brent Royal-Gordon <br...@architechies.com> wrote:
>
> Closures natively support modifying variables captured from the scope they're in; there's nothing special you need to do to make that work.
I hoped it was that easy. This seemed an odd thing for Swift to not handle.

> However, many APIs that take a completion closure do it because they perform an operation asynchronously—that is, the operation isn't finished by the time myFrameworkFunction returns and the next line of code executes. Is that true of your function?
Yes, that's exactly it. The framework is pulling data from the internet, and does so asynchronously.

> If so, there's just no (easy) way to fill in resultsToReturn by the time that return statement executes, and you'll have to restructure your code so that the completion blocks continue the operation.
Sounds like fun. Thanks for the answer, at least my original question had a simple solution. Were the function not asynchronous, then, my example would work as expected.

Brent Royal-Gordon

unread,
Sep 14, 2015, 12:27:02 AM9/14/15
to Alex Hall, swift-l...@googlegroups.com
> Were the function not asynchronous, then, my example would work as expected.

Yup.

--
Brent Royal-Gordon
Architechies

Jens Alfke

unread,
Sep 14, 2015, 11:53:46 AM9/14/15
to Alex Hall, Brent Royal-Gordon, swift-l...@googlegroups.com

On Sep 13, 2015, at 9:15 PM, Alex Hall <meh...@icloud.com> wrote:

Sounds like fun. Thanks for the answer, at least my original question had a simple solution. Were the function not asynchronous, then, my example would work as expected.

A little more detail: If the closure (or Obj-C block, same thing) outlives the function it was called from, it makes copies of the local variables from the enclosing scopes. So it can continue to use those variables, and even change them, but of course the changes no longer affect the scope it was called from (which doesn’t exist anymore.)

Asynchronous callbacks definitely make it harder to think about the flow of control. You have to imagine it skipping past the block and continuing out of the function back to its caller … and then at some later point returning to the block. I sometimes put a comment at the start of an asynchronous block to remind the reader about that, like “// This gets called later on after the grommet is finished detuning.”

—Jens

Alex Hall

unread,
Sep 14, 2015, 1:33:17 PM9/14/15
to Jens Alfke, Brent Royal-Gordon, swift-l...@googlegroups.com
Good idea, because the way I use the framework, it's not made obvious that the work happens asynchronously.

What's the best way to do this, then, in your opinion? I essentially need to get the results (as an array) back from the block, so I can give that array back to my app to have it processed. I've thought of NSNotifications, but I'm not sure how I'd set things up, and it seems like a bit of a cheat besides. I don't want my whole app to block while waiting for this to happen, but the specific part that gets and processes the array from the framework can block if it can run in the background. That is, I've considered running the whole update process--in m app--asynchronously and letting my Cocoa bindings take care of updating my table as my array gets added to. The core problem still remains, though: even in an async chunk of my own code, the framework is still running asynchronously as well. I'm reading about GCD and related topics, but any thoughts anyone has would be great.

—Jens

Jens Alfke

unread,
Sep 14, 2015, 2:38:03 PM9/14/15
to Alex Hall, Brent Royal-Gordon, swift-l...@googlegroups.com

On Sep 14, 2015, at 10:33 AM, Alex Hall <meh...@icloud.com> wrote:

What's the best way to do this, then, in your opinion? I essentially need to get the results (as an array) back from the block, so I can give that array back to my app to have it processed. I've thought of NSNotifications, but I'm not sure how I'd set things up, and it seems like a bit of a cheat besides.

Structure your API for asynchrony in the same way as the frameworks you’re using: Give your function a block parameter that it'll call later when it has the results. Then when the main thread of the app calls your function, it won’t block, and it can provide a callback that will do whatever it needs to update the UI.

—Jens

Alex Hall

unread,
Sep 14, 2015, 5:03:18 PM9/14/15
to Jens Alfke, Brent Royal-Gordon, swift-l...@googlegroups.com
I'd considered that, but I'm still stuck on knowing when the async call is done. In other words, how would my function know when to execute the completion block I gave it, instead of running it right away?
.

—Jens

Jens Alfke

unread,
Sep 14, 2015, 7:46:25 PM9/14/15
to Alex Hall, Brent Royal-Gordon, swift-l...@googlegroups.com

On Sep 14, 2015, at 2:03 PM, Alex Hall <meh...@icloud.com> wrote:

I'd considered that, but I'm still stuck on knowing when the async call is done. In other words, how would my function know when to execute the completion block I gave it, instead of running it right away?

Your function just calls it from its own closure that it passed to the async system routine.

—Jens

Brent Royal-Gordon

unread,
Sep 14, 2015, 8:15:21 PM9/14/15
to Alex Hall, Jens Alfke, swift-l...@googlegroups.com
> how would my function know when to execute the completion block I gave it, instead of running it right away?

You know it’s done when the async call you’re making calls *your* completion closures.

func frobSpludgerWithCompletion(completion: [String]? -> Void) {
myFrameworkFunction(success: { strings in completion(strings) }, failure: { completion(nil) })
}

If the signatures happen to be compatible, you could even pass the completion block the user provided to you in directly:

func frobSpludgerWithCompletion(completion: [String]? -> Void) {
myFrameworkFunction(success: completion, failure: { completion(nil) })
}

--
Brent Royal-Gordon
Architechies

Alex Hall

unread,
Sep 14, 2015, 11:48:03 PM9/14/15
to Brent Royal-Gordon, Jens Alfke, swift-l...@googlegroups.com
Thanks guys, it works perfectly!
Reply all
Reply to author
Forward
0 new messages