新的 Firebase 数据导致 TableView 单元格闪烁 (Firebase/iOS/Swift)

Posted

技术标签:

【中文标题】新的 Firebase 数据导致 TableView 单元格闪烁 (Firebase/iOS/Swift)【英文标题】:New Firebase Data Causes Flicker of TableView Cells (Firebase/iOS/Swift) 【发布时间】:2017-01-13 17:18:32 【问题描述】:

我的主要问题是如何消除闪烁,但我也只想知道我是否正确且最有效地处理非规范化 Firebase 数据。我的方法是否接近正确?

因此,我正在努力尝试正确显示来自 Firebase 数据库的数据,其中数据已被非规范化。我有帖子,然后是与每个帖子相关联的 cmets。每次有人打开帖子的 cmets 部分时,通过从视图控制器切换到新的视图控制器,它会获取帖子的唯一键 (postKey),然后扫描与 postCommentGroup 中包含的 postKey 关联的 cmets 组。 cmets 组,即 postCommentGroup 中每个 postKey 的子项,仅以 commentKey 为键,“true”为值,表示哪些 cmets 与哪些帖子相关联。 cmets 位于一个完全不同的分支中,因为我认为 Firebase 文档建议应该这样做。

我基本上有 3 层嵌套的观察者。

为了清楚起见,我在 tableview 中使用 dequeuereusablecells 回收单元格,并且我还有一个基本的延迟加载/图像缓存机制,可能也会干扰事物,但我在其他不太复杂的情况下具有相同的机制表格视图,所以我认为这不是问题。

由于我缺乏知识,除了经历这个循环之外,我不知道如何显示数据。我认为这个循环可能会导致闪烁,但我不知道如何让它加载数据。我已经尝试过其他各种方法,例如使用查询,但我一直无法让它工作。

附带说明一下,我已经尝试加快了解如何查询数据的速度(我认为这可能对我有所帮助),但是 Swift 的语法和 Firebase 都进行了更新,使得前面的例子有点难以理解。

此外,在 Firebase 网站或 Github 上的任何 Firebase 文档中,我都找不到以某种复杂的方式正确使用非规范化数据的良好、最近示例。有没有人知道关于使用 Swift 3.0 和 Firebase(最新版本 - 不是旧版本)处理非规范化数据的好的参考资料,无论是 GitHub 上的项目,还是博客,或者只是最*** 上有用的帖子?

这是 firebase 数据结构:

   
"comments" : 
        "-KaEl8IRyIxRbYlGqyXC" : 
          "description" : "1",
          "likes" : 1,
          "postID" : "-KaEfosaXYQzvPX5WggB",
          "profileImageUrl" : "https://firebasestorage.googleapis.com",
          "timePosted" : 1484175742269,
          "userID" : "9yhij9cBhJTmRTexsRfKRrnmDRQ2",
          "username" : "HouseOfPaine"
        
      ,
     
      "postCommentGroup" : 
        "-KaEfosaXYQzvPX5WggB" : 
          "-KaEl8IRyIxRbYlGqyXC" : true,
          "-KaEl9HiPCmInE0aJH_f" : true,
          "-KaF817rRpAd2zSCeQ-M" : true
        ,
        "-KaF9ZxAekTEBtFgdB_5" : 
          "-KaFEcXsSJyJwvlW1w2u" : true

        ,
        "-KaJyENJFkYxCffctymL" : 
          "-KaQYa0d08D7ZBirz5B4" : true
        
      ,
      "posts" : 
        "-KaEfosaXYQzvPX5WggB" : 
          "caption" : "Test",
          "comments" : 11,
          "imageUrl" : "https://firebasestorage.googleapis.com/",
          "likes" : 0,
          "profileImageUrl" : "https://firebasestorage.googleapis.com/",
          "timePosted" : 1484174347995,
          "title" : "test",
          "user" : "17lIDKNx6LgzQmaeQ2ING582zi43",
          "username" : "Freedom"
        
      ,

这是我的代码:

func commentGroupObserver() 

    DataService.ds.REF_POST_COMMENT_GROUP.observeSingleEvent(of: .value, with:  (snapshot) in

        if snapshot.value != nil 

            if let snapshots = snapshot.children.allObjects as? [FIRDataSnapshot] , snapshots.count > 0 

                self.comments = []

                for snap in snapshots 

                    if let tempVarPostKeyForCommentGroup = snap.key as String? 

                        if tempVarPostKeyForCommentGroup == self.post.postKey 

                            self.postKeyForCommentGroup = tempVarPostKeyForCommentGroup

                            self.commentObservers()
                         else 

                        
                     else 

                    
                

            

         else 
            print("error")
        

    )





func commentObservers() 

    if postKeyForCommentGroup != nil 

        constantHandle = DataService.ds.REF_POST_COMMENT_GROUP.child(postKeyForCommentGroup).observe(.value, with:  (snapshot) in

            if snapshot.value != nil 

                if let snapshots = snapshot.children.allObjects as? [FIRDataSnapshot], snapshots.count > 0

                

                    self.comments = []

                    for snap in snapshots 

                        if let theCommentIDForEachComment = snap.key as String? 
                             DataService.ds.REF_COMMENTS.child(theCommentIDForEachComment).queryOrdered(byChild: "timePosted").observeSingleEvent(of: .value, with:  (snapshots) in

                                if let commentDict = snapshots.value as? Dictionary<String, AnyObject> 

                                    let key = snapshots.key
                                    let comment = Comment(commentKey: key, dictionary: commentDict)
                                    self.comments.insert(comment, at: 0)     
                                
                                self.tableView.reloadData()
                            )
                        
                     
                

             else 

            
        )

     else 

    


更新:

我了解了如何使用上一篇 *** 帖子中概述的查询和委托模式:

getting data out of a closure that retrieves data from firebase

但我不知道我是否正确使用了委托模式。

代码已通过使用查询进行了简化,但仍然闪烁。也许我没有正确使用委托模式?

    func commentGroupObserver() 
    DataService.ds.REF_POST_COMMENT_GROUP.queryOrderedByKey().queryStarting(atValue: post.postKey).queryEnding(atValue: post.postKey).observeSingleEvent(of: .value, with:  (snapshot) in
        self.postKeyForCommentGroup = self.post.postKey
        self.commentObservers()
    )



func commentObservers() 
    if postKeyForCommentGroup != nil 
        constantHandle = DataService.ds.REF_POST_COMMENT_GROUP.child(postKeyForCommentGroup).observe(.value, with:  (snapshot) in
            if snapshot.value != nil 
                if let snapshots = snapshot.children.allObjects as? [FIRDataSnapshot]
                
                    self.comments = []
                    for snap in snapshots 
                        if let theCommentIDForEachComment = snap.key as String? 
                            DataService.ds.REF_COMMENTS.child(theCommentIDForEachComment).queryOrdered(byChild: "timePosted").observe(.value, with:  (snapshots) in

                                if let commentDict = snapshots.value as? Dictionary<String, AnyObject> 

                                    let key = snapshots.key
                                    let comment = Comment(commentKey: key, dictionary: commentDict)
                                    self.comments.insert(comment, at: 0)

                                

                                self.didFetchData(comments: self.comments)

                            )



                        

                    

                

             else 

            
        )

     else 

    



func didFetchData(comments data:[Comment])
    self.tableView.reloadData()

还有协议

 protocol MyDelegate
func didFetchData(comments:[Comment]) 

解决问题的代码:

根据 Jay 的建议,我删除了不必要的 postCommentGroup,只在评论下查询了评论所属帖子的 UID:

    func commentObservers() 

    let queryRef = DataService.ds.REF_COMMENTS.queryOrdered(byChild: "postID").queryEqual(toValue: self.post.postKey)

    queryRef.observe(.value, with:  snapshot in

        if let snapshots = snapshot.children.allObjects as? [FIRDataSnapshot] 

            for snap in snapshots 

                if let commentDict = snap.value as? Dictionary<String, AnyObject> 
                    let key = snap.key
                    let comment = Comment(commentKey: key, dictionary: commentDict)
                    self.comments.insert(comment, at: 0)
                
            
        

        self.tableView.reloadData()
    )

【问题讨论】:

请删除您的数据结构的图片并用文本数据替换它。这样它就可以复制和粘贴,并且可以被搜索。要导出 JSON 结构,请转到 Firebase 控制台和数据库,然后从窗口右上角的“三个垂直点”中选择导出 JSON。 当您使用 .value 读取一次数据并使用 .childAdded 再次读取所有相同数据时,您也可能会双重加载数据(这会导致闪烁)。但是,情况并非如此 - 问题中没有足够的代码知道。 我删除了图像并上传了我的数据的编辑版本(JSON 结构)。我删除了与 childAdded 的第二个观察,因为它让可能看到这个问题的人感到困惑。我之前添加了它来测试加载数据的不同方式。使用 childAdded 删除第二个观察对闪烁没有影响,并且在我添加它之前它一直在闪烁。 我一直在尝试实现各种查询数据的方法,但我认为因为我的数据结构依赖于许多父值的自动 ID 来对我没有成功的事物进行排序。我一直在玩 DispatchGroups。他们看起来很有希望,但到目前为止我还没有成功。谢谢你的建议。祝你有美好的一天。 self.tableView.reloadData() 移出 for 循环。在循环完成填充数组后调用它,否则如果你有 50 个项目,tableView 将被刷新 50 次,而它实际上只需要一次。 【参考方案1】:

您的方法可能需要通过简化来调整。我想提供所有的螺母和螺栓,所以它有点冗长,而且本身可以简化。

虽然非规范化是正常的,但这不是必需的,在某些情况下可能会增加额外的复杂性。您的结构 postCommentGroup 中的“层”似乎不需要。

看起来您有一个包含帖子的视图控制器,以及当用户在第一个控制器上点击帖子时显示 cmets 的第二个视图控制器。

你真的只需要一个posts节点和一个cmets节点

posts
   post_id_0
     title: "my post title"
     caption: "some caption"
     uid: "uid_0"
   post_id_1
     title: "another post title
     caption: "another caption
     uid: "uid_0"

和一个引用帖子的 cmets 节点

comments
   comment_0
     post_id: "post_id_0"
     uid: "uid_1"
     likes: "10"
   comment_1
     post_id: "post_id_0"
     uid: "uid_1"
     likes: "7"
   comment_2
     post_id: "post_id_1"
     uid: "uid_1"
     likes: "2"

设置:

class CommentClass 
    var commentKey = ""
    var comment = ""
    var likes = ""


var postsArray = [ [String: [String:AnyObject] ] ]()
var commentsArray = [CommentClass]()

加载所有帖子的代码:

    let postsRef = ref.child("posts")

    postsRef.observeSingleEvent(of: .value, with:  snapshot in

        for snap in snapshot.children 
            let postSnap = snap as! FIRDataSnapshot
            let postKey = postSnap.key //the key of each post
            let postDict = postSnap.value as! [String:AnyObject] //post child data

            let d = [postKey: postDict]
            self.postsArray.append(d)
        
        //postsTableView.reloadData
        print(self.postsArray) //just to show they are loaded
    )

然后,当用户点击帖子时,加载并显示 cmets。

    self.commentsArray = [] //start with a fresh array since we tapped a post
    //placeholder, this will be the post id of the tapped post
    let postKey = "post_id_0" 
    let commentsRef = ref.child("comments")
    let queryRef = commentsRef.queryOrdered(byChild: "post_id")
                              .queryEqual(toValue: postKey)

    //get all of the comments tied to this post
    queryRef.observeSingleEvent(of: .value, with:  snapshot in

        for snap in snapshot.children 
            let commentSnap = snap as! FIRDataSnapshot
            let commentKey = commentSnap.key //the key of each comment
            //the child data in each comment
            let commentDict = commentSnap.value as! [String:AnyObject] 
            let comment = commentDict["comment"] as! String
            let likes = commentDict["likes"] as! String
            let c = CommentClass()
            c.commentKey = commentKey
            c.comment = comment
            c.likes = likes

            self.commentsArray.append(c)
        

        //commentsTableView.reload data

        //just some code to show the posts are loaded
        print("post:  \(postKey)")
        for aComment in self.commentsArray 
            let comment = aComment.comment
            print("  comment: \(comment)")
        
    )

结果输出

post:  post_id_0
  comment: I like post_id_0
  comment: post_id_0 is the best evah

以上代码经过测试,没有闪烁。显然,由于您要加载一些图像等,因此需要针对您的用例进行调整,但以上内容应该可以解决问题并且更易于维护。

【讨论】:

非常感谢。感谢您的时间。我认为这是最好的方法。这实际上是我的数据在项目的一次迭代中的结构方式,因此它是对解决问题的观察者代码的简单修复。 一个快速的问题,我不知道它在 SO 的 q/a 格式中是否合适,但是这样做是否会产生任何成本?查询是不是很糟糕?我在某处读到,这样做代价高昂,这就是我试图避免它的原因。 @jasonhdev 如果回答有帮助,请采纳!不涉及额外费用,而且查询从来都不是“坏事”。但是,查询的开销比观察的要多,因此如果您可以观察而不是查询,则应用程序的效率会更高。它实际上归结为:您想要 all 节点中的数据还是节点中的 selection 数据。选择是更少的数据,因此更有效和更少的字节下载。 Firebase 计划的费用取决于您每月的存储量和下载量。 Free Spark 计划是每月 10G 和 1G 存储;有足够的发展空间。 好的。很抱歉延迟接受答案。我不知道我应该接受答案。为了记录,我之前曾尝试点击向上箭头图标,但它没有让我。 @jasonhdev 我很高兴它有帮助!当您第一次布置项目时可能会非常艰巨,因此请继续运输,是的,您做对了!【参考方案2】:

我仅在图片中经历过这种闪烁。每次在集合视图中发生某些事情时,该应用都会下载图片。

我在网上找到的解决方案。 NSCache。 您可以缓存图片,如果图片链接没有改变,图片将从缓存中加载。在滚动 Instagram 或 Facebook 等收藏视图时也很有帮助。 我在 Swift 5 中没有找到太多的解决方案。所以让我与你分享。

待办事项: 创建一个新的 swift 文件。 复制此代码,它会创建一个自定义 ImageView 类。 在情节提要上为这个自定义类设置图像视图,按 ctrl+拖动。 或者在您的 swift 文件中以编程方式执行相同的操作。

let imageCache = NSCache<NSString, UIImage>()
class CustomImageView: UIImageView 
var imageUrlString: String?

func loadImageUsingUrlString(urlString: String) 
    
    imageUrlString = urlString
    
    guard let url = URL(string: urlString) else  return 
    
    image = nil
    
    if let imageFromCache = imageCache.object(forKey: urlString as NSString) 
        self.image = imageFromCache
        print("local")
        return
    
    
    URLSession.shared.dataTask(with: url, completionHandler:  (data, respones, error) in
        
        if error != nil 
            print(error ?? "")
            return
        
        
        DispatchQueue.main.async 
            guard let imageToCache = UIImage(data: data!) else  return 
            
            if self.imageUrlString == urlString 
                self.image = imageToCache
                print("most mentem a kepet")
            
            
            imageCache.setObject(imageToCache, forKey: urlString as NSString)
        
        
    ).resume()


【讨论】:

以上是关于新的 Firebase 数据导致 TableView 单元格闪烁 (Firebase/iOS/Swift)的主要内容,如果未能解决你的问题,请参考以下文章

Firebase 实时数据库获取函数返回未定义(较新的 firebase 版本,2021) - 使用 React Native

如何在新的 Firebase 控制台中检查数据库和存储使用情况

Android Firebase valueEventListener 导致应用程序崩溃

Firebase 云函数 - createCustomToken

协助区分 URL 和 Tableview 数据

新的分页库也可以与 Firebase 一起使用?