【问题标题】:Async Closure Issue Solve in Swift not working?在 Swift 中解决异步关闭问题不起作用?
【发布时间】:2018-01-12 21:12:45
【问题描述】:

我已经把问题归结为这个

这个闭包:

  override func viewDidLoad() {
    super.viewDidLoad()
    let data = homeDataSource()
    getPrivatePosts { (posts) in
        print("postsCOUNT" , posts!.count)
        data.posts = posts!
    }
    self.datasource = data
    collectionView?.reloadData()

}

打印出“postCOUNT 1 postCOUNT 3”

然后当我打印 data.posts 的计数时,我得到 0...这是怎么回事?这是完整的代码

这是一个自定义的 UICollectionView:

import LBTAComponents
import Firebase
class homeView: DatasourceController {

override func viewDidLoad() {
    super.viewDidLoad()
    let data = homeDataSource()
    getPrivatePosts { (posts) in
        print("postsCOUNT" , posts!.count)
        data.posts = posts!
    }
    self.datasource = data
    collectionView?.reloadData()

}

override func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {
    return CGSize(width: view.frame.width , height: 150)
}

func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, referenceSizeForHeaderInSection section: Int) -> CGSize {
    return CGSize(width: view.frame.width, height: 0   )
}

func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, referenceSizeForFooterInSection section: Int) -> CGSize {
    return CGSize(width: view.frame.width, height: 0)
}
// just to test
override func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
    performSegue(withIdentifier: "goToNewPost", sender: self)
}


func getPrivatePosts(completion : @escaping (_ privatePosts : [Post]?) ->()){
    // fill posts array with posts from all buddys "privataPosts only"


    var ret = [Post]()
    staticValuesForData.instance.dataBaseUserref.child((Auth.auth().currentUser?.uid)!).child("contacts").observe( .value , with: { (snapshot) in

        let dict = snapshot.children.allObjects as! [DataSnapshot]
        for d in dict{
            if let contactUid = d.childSnapshot(forPath: "uid").value as? String{


                staticValuesForData.instance.dataBaseUserref.child(contactUid).child("privatePosts").observe( .value, with: { (snapshot) in

                    let posts = snapshot.children.allObjects as! [DataSnapshot]
                    print("postval" , posts)

                    for post in posts{

                        if let dict = post.value as? [String : AnyObject]{
                            let fullname = dict["fullname"] as! String
                            let picUrl = dict["picUrl"] as! String
                            let postContent = dict["postContent"] as! String
                            let time = dict["time"] as! Int
                            let uid = dict["uid"] as! String
                            let username = dict["username"] as! String

                            print("first name of person who did the post" , fullname)

                            let reposts = dict["reposts"] as! [String]

                            let downs = dict["downs"] as! [String]
                            // possible issue
                            var comments = [Comment]()

                            let commentArr = snapshot.childSnapshot(forPath: "comments").children.allObjects as! [DataSnapshot]

                            for c in commentArr{
                                if let dict = c.value as? [String : AnyObject]{

                                    let cuid = dict["uid"] as! String
                                    let ccommentText = dict["commentText"] as! String
                                    let cpicUrl = dict["picUrl"] as! String
                                    let cusername = dict["username"] as! String
                                    let ctime = dict["time"] as! Int

                                    let com = Comment(uid: cuid, commentText: ccommentText, time: ctime, picUrl: cpicUrl, username: cusername)

                                    comments.append(com)

                                }

                            }

                            print("HERE : post content\(postContent) username : \(username) commentArr \(comments)")

                            let postToAdd = Post(postContent: postContent, picUrl: picUrl, userName: username, fullName: fullname, postID: uid, postTime: time, downs: downs, reposts: reposts, comments: comments)

                            print("LOOK AT MEE   \(postToAdd.userName) is the username of the post object \(postToAdd.postContent) is the contetn")

                            ret.append(postToAdd)
                            print("RET" , ret)
                        }
                    }
                    completion(ret) // this is where the completion block should be called
                })

            }
        }
    })
}

}

这是一个数据源对象:

import LBTAComponents
class homeDataSource: Datasource {

var posts = [Post]()



override func numberOfItems(_ section: Int) -> Int {
    print("COUNT " , posts.count)
    return posts.count

}

override func headerClasses() -> [DatasourceCell.Type]? {
    return [userHeader.self]
}

override func footerClasses() -> [DatasourceCell.Type]? {
    return [userFooter.self]
}

override func cellClasses() -> [DatasourceCell.Type] {
    return [userCell.self]
}

override func item(_ indexPath: IndexPath) -> Any? {
    return posts[indexPath.item]
}

}

框架可以在这里使用:

pod 'LBTAComponents'

【问题讨论】:

  • 您能否发布 Firebase 数据结构的屏幕截图?
  • 绝对不可能在包含异步任务的闭包中声明一个具有默认值的变量。无论如何,从包含异步任务的函数中返回某些东西是不可能的。请学习了解异步数据处理的工作原理。
  • @rMickeyD 问题与数据库无关。我知道我的“ret”帖子数组中有正确的对象问题在于为什么“帖子”数组没有被填充,谢谢你的帮助
  • 你能给我解释一下吗? @vadian
  • 您必须在异步代码块内调用完成,否则它会在异步完成之前调用完成行并且永远不会更新。

标签: ios swift asynchronous


【解决方案1】:

你有两次相同的基本误解。

在第二个代码部分中,您创建的 ret 变量最初为空,然后触发一些异步任务。但是,您在异步任务之外调用完成(ret),因此它将在异步任务完成之前立即触发,因此返回您的初始空值。

第一个代码也会遇到同样的问题,因为您创建的 postArray 最初是空的,然后调用 getPrivatePosts 函数来提供完成处理程序,但该完成处理程序将在异步任务中调用,因此可能会有延迟,但您使用value 立即返回,因此将返回空的初始值。

【讨论】:

  • 我能做些什么来解决这个问题??
  • 这将取决于您要使用 posts 属性的目的。例如。这是用来填充 UITableView 的吗?
  • @zackkkkkkk 查看您链接的问题的答案。查看他们在哪里调用completion 并将其与您调用它的位置进行比较。
  • 它用于填充自定义 UIcollection 视图 @UpholderOfTruth 感谢您的帮助:)
  • @rmaddy 他们在方法本身内部调用它,我也尝试过感谢。他们称完成了我刚刚尝试过的主要问题,但并没有改变任何东西......可能是什么问题?
【解决方案2】:

您不应该以这种方式创建您的帖子数组。您应该创建一个可变数组:

var posts = [Post]()

然后在您的视图控制器的 viewDidLoad 中,您应该从您的服务 (Firebase) 填充该数组。

override func viewDidLoad() {
    super.viewDidLoad()

    getPrivatePosts() { posts in
        self.posts = posts ?? []
    }
}

您发布函数也永远不会从服务返回您想要的数据,因为您在服务请求范围之外调用完成块。将完成块移动到 getPrivatePosts 函数的 staticValuesForData.instance.dataBaseUserref.child 部分中的 for 循环底部,如下所示:

class func getPrivatePosts(completion : (_ privatePosts : [Post]?) ->.   ()){
    // fill posts array with posts from all buddys "privataPosts only"


    var ret = [Post]()
    staticValuesForData.instance.dataBaseUserref.child((Auth.auth().currentUser?.uid)!).child("contacts").observe( .value , with: { (snapshot) in

        let dict = snapshot.children.allObjects as! [DataSnapshot]
        for d in dict{
            if let contactUid = d.childSnapshot(forPath: "uid").value as? String{


                staticValuesForData.instance.dataBaseUserref.child(contactUid).child("privatePosts").observe( .value, with: { (snapshot) in

                    let posts = snapshot.children.allObjects as! [DataSnapshot]
                    print("postval" , posts)

                    for post in posts{

                        if let dict = post.value as? [String : AnyObject]{
                            let fullname = dict["fullname"] as! String
                            let picUrl = dict["picUrl"] as! String
                            let postContent = dict["postContent"] as! String
                            let time = dict["time"] as! Int
                            let uid = dict["uid"] as! String
                            let username = dict["username"] as! String

                            print("first name of person who did the post" , fullname)

                            let reposts = dict["reposts"] as! [String]

                            let downs = dict["downs"] as! [String]
                            // possible issue
                            var comments = [Comment]()

                            let commentArr = snapshot.childSnapshot(forPath: "comments").children.allObjects as! [DataSnapshot]

                            for c in commentArr{
                                if let dict = c.value as? [String : AnyObject]{

                                    let cuid = dict["uid"] as! String
                                    let ccommentText = dict["commentText"] as! String
                                    let cpicUrl = dict["picUrl"] as! String
                                    let cusername = dict["username"] as! String
                                    let ctime = dict["time"] as! Int

                                    let com = Comment(uid: cuid, commentText: ccommentText, time: ctime, picUrl: cpicUrl, username: cusername)

                                    comments.append(com)

                                }

                            }

                            print("HERE : post content\(postContent) username : \(username) commentArr \(comments)")

                            let postToAdd = Post(postContent: postContent, picUrl: picUrl, userName: username, fullName: fullname, postID: uid, postTime: time, downs: downs, reposts: reposts, comments: comments)

                            print("LOOK AT MEE   \(postToAdd.userName) is the username of the post object \(postToAdd.postContent) is the contetn")

                            ret.append(postToAdd)
                            print("RET" , ret)
                        }
                    }
                    completion(ret) // this is where the completion block should be called
                })

            }
        }

    })
}

我希望这会有所帮助。

【讨论】:

  • 您能详细说明最后一部分吗?对不起,我也不明白你指的是什么部分
  • @zackkkkkkk 我将完成块调用移到您在问题中发布的函数中,如果有帮助,请告诉我。
  • 这个答案正确地解决了 OP 问题。此外,由于 OP 正在使用观察者,他还应该在 viewController 生命周期的某个时间点移除观察者,例如 viewWillDisappear。出于这个原因,如果他要将另一个 viewController 推送到这个 viewController 之上,则最好填充 viewWillAppear 而不是 viewDidLoad。
  • 非常感谢您与我一起解决这个问题 :( 不幸的是,这不起作用,请参阅上面我更新的源代码可能是另一个问题?(更新的源代码不包括完成的移动,但别担心我移动了它)@naturaln0va
  • 你如何建议我这样做会以任何方式影响帖子的刷新吗? @rMickeyD
猜你喜欢
  • 2021-03-27
  • 2019-03-27
  • 2019-02-13
  • 2021-12-04
  • 2022-10-14
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2020-01-18
相关资源
最近更新 更多