8

I'm working on converting this In-App Purchase tutorial into Swift and have come across an issue trying to save the completion handler. We need to store this completion handler so that it can be called later. I cannot figure out how to store a copy of the closure from the arguments. Specifically, I believe I need to copy it but Xcode states this object doesn't have a copy function.

I've defined it like so:

typealias RequestProductsCompletionHandler = (success: Bool, products: NSArray) -> Void

Here I declare a property for it:

var completionHandler: RequestProductsCompletionHandler?

Then this is where I need to store the passed in completion handler into my property:

func requestProductsWithCompletionHandler(completionBlock: RequestProductsCompletionHandler) -> Void {
    //self.completionHandler = completionBlock.copy() //problem: RequestProductsCompletionHandler does not have a member named copy
}

This is how it was done in the Obj-C tutorial:

typedef void (^RequestProductsCompletionHandler)(BOOL success, NSArray * products);

RequestProductsCompletionHandler _completionHandler;

- (void)requestProductsWithCompletionHandler:(RequestProductsCompletionHandler)completionHandler {
    _completionHandler = [completionHandler copy];
}

And later it is used like so:

_completionHandler(YES, skProducts);
_completionHandler = nil;

EDIT: I've removed the .copy() to prevent the error (also had to make the NSArray optional so that I can set it to nil later). My question is, will it work as expected without explicitly copying it? I don't believe it will because Apple stated closures are reference types. This will store a reference to the original closure which I don't want to do. How does one enforce a copy?

Jordan H
  • 52,571
  • 37
  • 201
  • 351

1 Answers1

4

Yes. It will work as expected.

Whenever you call requestProductsWithCompletionHandler you create a closure and pass it to that function. And as you mentioned, when you set completionHandler you actually set it to be a reference to the given closure.

In Objective C, to store the block as ivar you had to copy a block. That's because a block first residing on the stack memory where it was defined. And copy moved it to the heap memory so it can be used

tsafrir
  • 1,711
  • 12
  • 13
  • I had the exact problem, so thanks. While researching the answer, I came across this: https://developer.apple.com/library/ios/documentation/Swift/Conceptual/Swift_Programming_Language/AutomaticReferenceCounting.html#//apple_ref/doc/uid/TP40014097-CH20-ID56 in the Swift documentation. I tried adding the `lazy` variable, but it gave me an error (there is no initializer for the variable). Will this cause a reference cycle? – coopersita Aug 25 '15 at 17:02
  • @coopersita As described in the document you mentioned, you should define 'capture list' for the closure and specify that 'self' is unowned. `{ [unowned self] in ...}` – tsafrir Aug 25 '15 at 21:29
  • Thanks, @tsafrir, I also found this http://stackoverflow.com/questions/24320347/shall-we-always-use-unowned-self-inside-closure-in-swift – coopersita Aug 26 '15 at 20:57