Swift function works in app but not within override func viewDidLoad()

岁酱吖の 提交于 2020-01-11 11:33:07

问题


In my iOS app, I have two Firebase-related functions that I want to call within viewDidLoad(). The first picks a random child with .queryOrderedByKey() and outputs the child's key as a string. The second uses that key and observeEventType to retrieve child values and store it in a dict. When I trigger these functions with a button in my UI, they work as expected.

However, when I put both functions inside viewDidLoad(), I get this error:

Terminating app due to uncaught exception 'InvalidPathValidation', reason: '(child:) Must be a non-empty string and not contain '.' '#' '$' '[' or ']''

The offending line of code is in my AppDelegate.swift, highlighted in red:

class AppDelegate: UIResponder, UIApplicationDelegate, UITextFieldDelegate

When I comment out the second function and leave the first inside viewDidLoad, the app loads fine, and subsequent calls of both functions (triggered by the button action) work as expected.

I added a line at the end of the first function to print out the URL string, and it doesn't have any offending characters: https://mydomain.firebaseio.com/myStuff/-KO_iaQNa-bIZpqe5xlg

I also added a line between the functions in viewDidLoad to hard-code the string, and I ran into the same InvalidPathException issue.

Here is my viewDidLoad() func:

override func viewDidLoad() {
    super.viewDidLoad()
    let tap: UITapGestureRecognizer = UITapGestureRecognizer(target: self, action: #selector(ViewController.dismissKeyboard))
    view.addGestureRecognizer(tap)
    pickRandomChild()
    getChildValues()
}

Here is the first function:

func pickRandomChild () -> String {
    var movieCount = 0
    movieRef.queryOrderedByKey().observeEventType(.Value, withBlock: { (snapshot) in
        for movie in snapshot.children {
            let movies = movie as! FIRDataSnapshot
            movieCount = Int(movies.childrenCount)
            movieIDArray.append(movies.key)
        }
        repeat {
            randomIndex = Int(arc4random_uniform(UInt32(movieCount)))
        } while excludeIndex.contains(randomIndex)
        movieToGuess = movieIDArray[randomIndex]
        excludeIndex.append(randomIndex)
        if excludeIndex.count == movieIDArray.count {
            excludeIndex = [Int]()
        }
        let arrayLength = movieIDArray.count

    })
    return movieToGuess
}

Here is the second function:

func getChildValues() -> [String : AnyObject] {
    let movieToGuessRef = movieRef.ref.child(movieToGuess)
    movieToGuessRef.observeEventType(.Value, withBlock: { (snapshot) in
        movieDict = snapshot.value as! [String : AnyObject]
        var plot = movieDict["plot"] as! String
        self.moviePlot.text = plot
        movieValue = movieDict["points"] as! Int
        })
    return movieDict
)

And for good measure, here's the relevant portion of my AppDelegate.swift:

import UIKit
import Firebase

@UIApplicationMain
class AppDelegate: UIResponder, UIApplicationDelegate, UITextFieldDelegate {
    var window: UIWindow?

func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [NSObject: AnyObject]?) -> Bool {
    FIRApp.configure()
    return true
}

I'm guessing Swift is executing the code not in the order I expect. Does Swift not automatically wait for the first function to finish before running the second? If that's the case, why does this pairing work elsewhere in the app but not in viewDidLoad?


回答1:


Edit: The issue is that closures are not called in order.

I'm not sure what your pickRandomChild() and getChildValues() methods are, so please post them as well, but the way I fixed this type issue was by sending the data through a closure that can be called in your ViewController.

For example when I wanted to grab data for a Full Name and Industry I used this. This method takes a Firebase User, and contains a closure that will be called upon completion. This was defined in a class specifically for pulling data.

func grabDataDict(fromUser user: FIRUser, completion: (data: [String: String]) -> ()) {

    var myData = [String: String]()

    let uid = user.uid
    let ref = Constants.References.users.child(uid)

    ref.observeEventType(.Value) { (snapshot, error) in
        if error != nil {
            ErrorHandling.defaultErrorHandler(NSError.init(coder: NSCoder())!)
            return
        }

        let fullName = snapshot.value!["fullName"] as! String
        let industry = snapshot.value!["industry"] as! String

        myData["fullName"] = fullName
        myData["industry"] = industry

        completion(data: myData)
    }
}

Then I defined an empty array of strings in the Viewcontroller and called the method, setting the variable to my data inside the closure.

messages.grabRecentSenderIds(fromUser: currentUser!) { (userIds) in
        self.userIds = userIds
        print(self.userIds)
}

If you post your methods, however I can help you with those specifically.

Edit: Fixed Methods

1.

func pickRandomChild (completion: (movieToGuess: String) -> ()) {
    var movieCount = 0
    movieRef.queryOrderedByKey().observeEventType(.Value, withBlock: { (snapshot) in
        for movie in snapshot.children {
            let movies = movie as! FIRDataSnapshot
            movieCount = Int(movies.childrenCount)
            movieIDArray.append(movies.key)
        }
        repeat {
            randomIndex = Int(arc4random_uniform(UInt32(movieCount)))
        } while excludeIndex.contains(randomIndex)
        movieToGuess = movieIDArray[randomIndex]
        excludeIndex.append(randomIndex)
        if excludeIndex.count == movieIDArray.count {
            excludeIndex = [Int]()
        }
        let arrayLength = movieIDArray.count

        // Put whatever you want to return here.
        completion(movieToGuess)
    })
}

2.

func getChildValues(completion: (movieDict: [String: AnyObject]) -> ()) {
    let movieToGuessRef = movieRef.ref.child(movieToGuess)
    movieToGuessRef.observeEventType(.Value, withBlock: { (snapshot) in
        movieDict = snapshot.value as! [String : AnyObject]
        var plot = movieDict["plot"] as! String
        self.moviePlot.text = plot
        movieValue = movieDict["points"] as! Int

        // Put whatever you want to return here.
        completion(movieDict)
    })
}

Define these methods in some model class, and when you call them in your viewcontroller, you should be able to set your View Controller variables to movieDict and movieToGuess inside each closure. I made these in playground, so let me know if you get any errors.




回答2:


Your functions pickRandomChild() and getChildValues() are asynchronous, therefore they only get executed at a later stage, so if getChildValues() needs the result of pickRandomChild(), it should be called in pickRandomChild()'s completion handler / delegate callback instead, because when one of those are called it is guaranteed that the function has finished.

It works when you comment out the second function and only trigger it with a button press because there has been enough time between the app loading and you pushing the button for the asynchronous pickRandomChild() to perform it action entirely, allowing getChildValues() to use its returned value for its request.



来源:https://stackoverflow.com/questions/38820491/swift-function-works-in-app-but-not-within-override-func-viewdidload

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!