How to work with Firebase without allowing optional values

流过昼夜 提交于 2020-01-02 23:44:17

问题


I'm new to iOS development and I understand that allowing optional values when an object is initialized is not a 'good citizen' technique. That being said, I've read that it is good practice to always have values set, like this:

class Item{
    var name: String
    var color: String
    init(name: String, color: String) {
        self.name = name
        self.color = color
    }
}

This looks nice and tidy but how can I do something like that working with Firebase? Look what I've got so far:

private func loadPosts(){
    databaseHandle = ref.child("users/\(self.user.uid)/posts").observe(.value, with:{(snapshot) in
        var newPosts = [Post]()

        for itemSnapShot in snapshot.children {
            let post = Post(snapshot: itemSnapShot as! FIRDataSnapshot)
            newPosts.append(post!)
        }
        self.posts = newPosts
        self.tableView.reloadData()
    })
}

This guy is placed in my PostsViewController where I have my table view. This is my model:

class Post {
    var ref: FIRDatabaseReference?
    var title: String?
    var answer: String?
    var contentUrl: String?
    var photoUrl: String?
    var createdAt: String?
    var feeling: String?
    var kind: String?
    var text: String?
    var uid: String?
    var measurements: Dictionary<String, String>?

    //MARK: Initialization
    init?(snapshot: FIRDataSnapshot){
        ref = snapshot.ref

        let data = snapshot.value as! Dictionary<String, Any>

        title = data["title"]! as? String
        answer = data["answer"] as? String
        contentUrl = data["content_url"] as? String
        photoUrl = data["photo_url"] as? String
        createdAt = data["created_at"] as? String
        feeling = data["feeling"] as? String
        kind = data["kind"] as? String
        text = data["text"] as? String
        uid = data["uid"] as? String
        measurements = data["measurements"] as? Dictionary<String, String>
    }
}

I don't know exactly why but those question marks doesn't feel quite right and now and then I get some nil pointer error, which I think I should be able to avoid by using the 'good citizen' technique.

So, does anybody know how can I use Firebase following Swift best practices?


回答1:


Either you wish to allow the properties of your Post class to be nil or you don't.

If you do, that's fine. The code you posted allows any of them to be nil. You just need to safely access each property every time you need it.

If you don't, then don't make them optional. Then in your init you need to ensure none of the properties are set to nil by giving each a default if there is no value in the snapshot.

class Post {
    var ref: FIRDatabaseReference
    var title: String
    var answer: String
    var contentUrl: String
    var photoUrl: String
    var createdAt: String
    var feeling: String
    var kind: String
    var text: String
    var uid: String
    var measurements: [String : String]

    //MARK: Initialization
    init?(snapshot: FIRDataSnapshot) {
        if let data = snapshot.value as? [String : Any] {
            self.ref = snapshot.ref

            title = data["title"] as? String ?? ""
            answer = data["answer"] as? String ?? ""
            contentUrl = data["content_url"] as? String ?? ""
            photoUrl = data["photo_url"] as? String ?? ""
            createdAt = data["created_at"] as? String ?? ""
            feeling = data["feeling"] as? String ?? ""
            kind = data["kind"] as? String ?? ""
            text = data["text"] as? String ?? ""
            uid = data["uid"] as? String ?? ""
            measurements = data["measurements"] as? [String : String] ?? [:]
        } else {
            return nil
        }
    }
}

Note how this ensures there is a proper snapshot. Note how a default value is set to each property if there is no value in the snapshot. Obviously you can assign any default you wish. I use the empty string as an example.

Even if you want to allow the properties to be nil, you should at least update your code to check for a valid snapshot like in the code above.

Of course you can have a combination where some properties can't be nil and some can. That's up to your needs.




回答2:


First it is fine for you to have optionals in your data model, as long as you assign value to it later on in the future.

I would recommend to use ObserveSingleEvent() and you should make use of completion handler to make it easy. If you don't know completion handler: Link

I recommend:

• not to put database ref in your class model, and instead of using Dictionary<String, String>? just use [String: AnyObject]?

• make your post array public so that it can be accessed into the tableview.

Here's example:

class func getPosts(uid: String, _ completion: @escaping (_ posts: [Post]?, _ error: Error?) -> Void) {
    //update inside users node
    var posts = [Post]()
    Firebase.databaseRef.child("users").child(uid).child("posts").observeSingleEvent(of: FIRDataEventType.value, with: { (dataSnapshot) in
        guard let postsDictionary = dataSnapshot.value as? [String: AnyObject] else {
            completion(nil, nil)
            return
        }
        let n = postsDictionary.count
        for postDictionary in postsDictionary {
            let post = Post()

            post.userID = uid

            if let content = postDictionary.value["content"] as? String {
                post.content = content
            }
            if let imageURL = postDictionary.value["imageURL"] as? String {
                post.imageURL = imageURL
            }
            if let timeStamp = postDictionary.key as String! {
                if let date = timeStamp.convertToDate() {
                    post.timeStamp = date
                }
                post.postIdentifier = timeStamp
            }
            posts.append(post)

            if posts.count == n {
                // Sort the array by the newest post
                let sortedPosts = posts.sorted(by: { $0.timeStamp.compare($1.timeStamp) == .orderedDescending })
                completion(sortedPosts, nil)
            }
        }
    }) { (error) in
        completion(nil, error)
    }
}

Assigning to tableview be like:

getPosts(uid: Current.user.userID!) { (posts, error) in
        guard error == nil else {
            print(error.debugDescription)
            return
        }
        cell.label.text = posts[indexPath.item].content


来源:https://stackoverflow.com/questions/43266409/how-to-work-with-firebase-without-allowing-optional-values

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