Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Cannot reference a local function with capture from another local function (swift)

Tags:

ios

swift

 var first_name = ""

    func problemFunc() {

        FBRequestConnection.startForMeWithCompletionHandler { (connection: FBRequestConnection!, result: AnyObject!, error: NSError!) -> Void in
            if let fbGraphUserDict = result as? Dictionary<String, AnyObject>{
               first_name = fbGraphUserDict["first_name"] as NSString
                println(first_name)
            }
        }
    }


    PFFacebookUtils.logInWithPermissions(permissions, {
        (user: PFUser!, error: NSError!) -> Void in
        if user == nil {
            NSLog("Uh oh. The user cancelled the Facebook login.")
        } else if user.isNew {
            NSLog("User signed up and logged in through Facebook!")
        } else {
            NSLog("User logged in through Facebook!")
            problemFunc() // error is here

        }
    })

This code is inside an @Ibaction button. I cannot build because the call to problemFunc() triggers the error message in the title of this post. If I move the first_name var definition inside the problemFunc it will work ok. But I need it out, because another function will need to access its value. I'm really not sure at what causes this problem, if you have a clue, please help.

like image 449
Robert Brax Avatar asked Oct 07 '14 13:10

Robert Brax


3 Answers

Use a closure instead of a function:

var first_name = ""

let problemFunc = { () -> () in

    FBRequestConnection.startForMeWithCompletionHandler { (connection: FBRequestConnection!, result: AnyObject!, error: NSError!) -> Void in
        if let fbGraphUserDict = result as? Dictionary<String, AnyObject>{
           first_name = fbGraphUserDict["first_name"] as NSString
            println(first_name)
        }
    }
}


PFFacebookUtils.logInWithPermissions(permissions, {
    (user: PFUser!, error: NSError!) -> Void in
    if user == nil {
        NSLog("Uh oh. The user cancelled the Facebook login.")
    } else if user.isNew {
        NSLog("User signed up and logged in through Facebook!")
    } else {
        NSLog("User logged in through Facebook!")
        problemFunc() // error is here

    }
})
like image 167
fluidsonic Avatar answered Nov 10 '22 17:11

fluidsonic


Here are the basic principles in play: (from Apple's docs: https://developer.apple.com/library/ios/documentation/Swift/Conceptual/Swift_Programming_Language/Closures.html#//apple_ref/doc/uid/TP40014097-CH11-ID103)

"Global and nested functions, as introduced in Functions, are actually special cases of closures. Closures take one of three forms:

  • Global functions are closures that have a name and do not capture any values.
  • Nested functions are closures that have a name and can capture values from their enclosing function.
  • Closure expressions are unnamed closures written in a lightweight syntax that can capture values from their surrounding context."

ie this is ok

func someFunc() {
  func nestFunc()  {}
}

but this is not

func someFunc() {
  func nestFunc()  {
     func nestedFunc2() { }
  }
}

If you look at this in Xcode the third function (func nestedFunc2) will give you the error "Cannot reference a local function with capture from another local function"

The top function (func someFunc) is a global scope function and those work like regular functions/methods.

The second function (func nestFunc) is a nested function which is a named closure one level deep that can capture the scope of its parent global function.

Nested functions, can capture the scope of a global function but not the scope of another nested function.

That's why we need a closure i.e.

func someFunc() {
   func nestFunc()  {
       let strictClosure = { () -> () in
        //this is where you write the code
        }
   }
}
like image 36
erparker Avatar answered Nov 10 '22 18:11

erparker


@fluidsonic answer should solve the problem. However note that you're doing some spaghetti code, because you are modifying a variable captured by a closure, and executed in the context of another function. That's hard to track if you need to debug, and more generally hard to follow when and how that variable is modified.

A more linear and better readable flow is to define problemFunc as a function taking a function as parameter, and calling that function rather than directly setting the value in the first_name variable:

let problemFunc = { (callback: (String -> Void) -> ()) in

    FBRequestConnection.startForMeWithCompletionHandler { (connection: FBRequestConnection!, result: AnyObject!, error: NSError!) -> Void in
        if let fbGraphUserDict = result as? Dictionary<String, AnyObject>{
            let first_name = fbGraphUserDict["first_name"] as NSString
            callback(first_name) // << here you call the callback passing the `first_name` local variable
            println(first_name)
        }
    }
}

and do the actual assignment to first_name in a closure you define when calling problemFunc:

PFFacebookUtils.logInWithPermissions(permissions, {
    (user: PFUser!, error: NSError!) -> Void in
    if user == nil {
        NSLog("Uh oh. The user cancelled the Facebook login.")
    } else if user.isNew {
        NSLog("User signed up and logged in through Facebook!")
    } else {
        NSLog("User logged in through Facebook!")
        problemFunc { (name: String) -> Void in
            first_name = name
        }
    }
})
like image 2
Antonio Avatar answered Nov 10 '22 17:11

Antonio