optional closure and check if it is nil

35,464

Solution 1

You need to wrap your closure signature in parentheses to make the closure itself optional. The way it's written now, the closure returns an optional Void (which doesn't really make sense).

var completionHandler: ((sucsess:Bool!, items:[AnyObject]!)->())?

Some style points and revisions to your example code:

 // Capitalize class names so it's clear what's a class 
class SomeClass {
    // "success" has two "c"s
    var completionHandler: ((success:Bool!, items:[AnyObject]!)->())?
    var hitpoints = 100
    var someset = ["oh no!","avenge me!"]

    init() { }

    func getHitFunc(impact:Int, passedCompletionsHandler:(success:Bool!, items:[AnyObject]!)->()){
        completionHandler = passedCompletionsHandler
        hitpoints = hitpoints - impact
    }

    // You were missing the argument list here:
    func checkIfDead() {
        if hitpoints <= 0 {

            // Rather than checking to see if the completion handler exists, you can
            // just call it using optional syntax like this:
            completionHandler?(success: true, items: someset)
        }
        completionHandler = nil
    }
}

Solution 2

First, in your declaration of the completion handler, you need to declare the whole thing as optional with the use of parentheses:

var completionHandler: ((_ success: Bool, _ items: [Any]?) -> ())?

Or, perhaps better, you can replace that final () with Void:

var completionHandler: ((_ success: Bool, _ items: [Any]?) -> Void)?

Also, note, I don't think you meant to make the Bool optional (because if the closure exists, you presumably always pass a success value of true or false). Clearly, the array of items might well be optional.

Anyway, when done, you'd just make sure to unwrap that optional:

func checkIfDead() {
    if hitpoints <= 0 {
        completionHandler?(true, items)
    }
    completionHandler = nil
}

This performs the closure if and only if it is not nil, avoiding the need to explicitly check if it was nil.


For what it's worth, this might be a case where your typealias might make this less confusing:

typealias CompletionHandlerClosureType = (_ success: Bool, _ items: [Any]?) -> Void

Then the property is simply:

var completionHandler: CompletionHandlerClosureType?

The function that takes this completionHandler as a optional parameter could do:

func startSomeProcess(passedCompletionHandler: CompletionHandlerClosureType?) {
    completionHandler = passedCompletionHandler
    // do whatever else you want
}

and then the final completion logic is unchanged:

func finishSomeProcess() {
    completionHandler?(true, items)
    completionHandler = nil
}

(Note, the above has been modified for Swift 3. Please see previous revision of this answer if you want to see Swift 2 renditions.)

Share:
35,464
Ágúst Rafnsson
Author by

Ágúst Rafnsson

Updated on June 14, 2020

Comments

  • Ágúst Rafnsson
    Ágúst Rafnsson about 4 years

    So what I want to have is a class that may get a closure passed to it in a function, it may also at some point want to disregard a that closure. How can I check if the closure variable is set and hwo can I delete it when I am done with it?

    Cannot invoke '!=' with an argument list of type '(@lvalue (sucsess: Bool!, products: [AnyObject]!) -> ()?, NilLiteralConvertible)' Type '(sucsess: Bool!, products: [AnyObject]!) -> ()?' does not conform to protocol 'NilLiteralConvertible'

    class someClass{
        //typealias completionHandlerClosureType = (sucsess:Bool!, items:[AnyObject]!)->()
        var completionHandler:(sucsess:Bool!, items:[AnyObject]!)->()?
        var hitpoints = 100
        var someset = ["oh no!","avenge me!"]
        init(){}
    
        func getHitFunc(impact:Int, passedCompletionsHandler:(sucsess:Bool!, items:[AnyObject]!)->()){
            completionHandler = passedCompletionsHandler
            hitpoints = hitpoints - impact
        }
    
        func checkIfDead{
            if hitpoints<=0 {               // The error received
                if completionHandler != nil{// Cannot invoke '!=' with an argument list of type 
                                            //'(@lvalue (sucsess: Bool!, products: [AnyObject]!) -> ()?, NilLiteralConvertible)' 
                    //run the handler if dead
                    completionHandler(sucsess: true, items: someset)
                    //do not run it again
                    completionHandler = nil     //Type '(sucsess: Bool!, products: [AnyObject]!) -> ()?' does not conform to protocol 'NilLiteralConvertible'
                }
            }
            else{
                completionHandler = nil      //Type '(sucsess: Bool!, products: [AnyObject]!) -> ()?' does not conform to protocol 'NilLiteralConvertible'
            }
        }
    }
    
  • Yitzchak
    Yitzchak over 7 years
    the type alias is great solution
  • truedat101
    truedat101 over 3 years
    This is fantastic in explanation. Still applicable to Swift 4. Completion blocks are much improved in swift over objective-C but still can take some hand wringing to get it right, especially as I am porting over the last few objective-C classes that someone thoughtfully left in neglect for years in this project.