【问题标题】:Crash when running collectionGroup query in Cloud Firestore (Swift)在 Cloud Firestore (Swift) 中运行 collectionGroup 查询时崩溃
【发布时间】:2021-02-04 02:22:39
【问题描述】:

我使用 Swift、SwiftUI 和 Cloud Firestore 运行一个移动应用项目,我需要根据用户的不同设置/偏好来找到他们。我已经通过使用 collectionGroup 查询解决了这个问题。但有时(可能 10 次中的 1 次)查询崩溃而没有任何(对我而言)可理解的错误消息。复合索引是使用 XCode 提供的 http 链接创建的。

这是我使用的功能:

func getUsersFromActivityPrefs(genders:[String], activities:[Int],skillScore_min:Int, skillScore_max:Int,completion:@escaping ([String]) -> ()) {
   
var matchUsers = [String]()
var count = 0
let db = Firestore.firestore()

for gender in genders {
    for activity in activities {
        let dbRef = db.collectionGroup("activity_preferences")
            .whereField("gender", isEqualTo: gender)
            .whereField("activityid", isEqualTo: activity)
            .whereField("status", isEqualTo: true)
            .whereField("skill_score", isGreaterThanOrEqualTo: skillScore_min)
            .whereField("skill_score", isLessThanOrEqualTo: skillScore_max)
            .limit(to: 100)
        dbRef.getDocuments {( snap, err) in
            count+=1
            if err != nil {
                print(err!.localizedDescription)
            }
            for i in snap!.documentChanges{
                let uid = i.document.get("uid") as? String ?? ""
                if uid != "" && !matchUsers.contains(uid) {
                    matchUsers.append(uid)
                    if matchUsers.count == 100 {
                        count = genders.count * activities.count
                        completion(matchUsers) //escaping completion handler
                        return
                    }
                }
            }
            if count == genders.count * activities.count {
                completion(matchUsers)
                return
            }
        }
    }
}

}

我附上了来自 XCode 的跟踪日志和崩溃消息。我使用最新版本的 Firebase SDK,部署目标是 iOS14。 这是我得到的跟踪日志:

线程 #1,队列 = 'com.apple.main-thread',停止原因 = EXC_BAD_ACCESS(代码=1,地址=0x4f) 帧#0:0x00007fff4b80dd66 AttributeGraphAG::Graph::input_value_ref_slow(AG::data::ptr<AG::Node>, AG::AttributeID, unsigned int, AGSwiftMetadata const*, bool*, long) + 322 frame #1: 0x00007fff4b81f1a5 AttributeGraphAGGraphGetValue + 203 帧 #2: 0x00007fff55e7ffab SwiftUISwiftUI.DynamicBody.phase.getter : SwiftUI._GraphInputs.Phase + 27 frame #3: 0x00007fff55e80176 SwiftUISwiftUI.DynamicBody.updateValue() -> () + 294 帧 #4: 0x00007fff55b9583a SwiftUIpartial apply forwarder for implicit closure #2 (Swift.UnsafeMutableRawPointer, __C.AGAttribute) -> () in implicit closure #1 (A1.Type) -> (Swift.UnsafeMutableRawPointer, __C.AGAttribute) -> () in closure #1 () -> (Swift.UnsafeMutableRawPointer, __C.AGAttribute) -> () in closure #1 (Swift.UnsafePointer<A1>) -> AttributeGraph.Attribute<A> in AttributeGraph.Attribute.init<A where A == A1.Value, A1: AttributeGraph.StatefulRule>(A1) -> AttributeGraph.Attribute<A> + 26 frame #5: 0x00007fff4b808d03 AttributeGraphAG::Graph::UpdateStack::update() + 505 帧 #6: 0x00007fff4b809199 AttributeGraphAG::Graph::update_attribute(AG::data::ptr<AG::Node>, bool) + 335 frame #7: 0x00007fff4b80d8e8 AttributeGraphAG::Graph::value_ref(AG::AttributeID, AGSwiftMetadata const*, bool*) + 130 第 8 帧:0x00007fff4b81f1f3 AttributeGraphAGGraphGetValue + 281 frame #9: 0x00007fff561aeeb7 SwiftUISwiftUI.GraphHost.updatePreferences() -> Swift.Bool + 39 帧 #10: 0x00007fff55c9a8cf SwiftUISwiftUI.ViewGraph.updateOutputs(at: SwiftUI.Time) -> () + 95 frame #11: 0x00007fff5611310c SwiftUIclosure #1 () -> () in (SwiftUI 中的扩展):SwiftUI.ViewRendererHost.render(interval: Swift.Double, updateDisplayList: Swift.Bool) -> () + 1308 框架 #12: 0x00007fff56112327 SwiftUI(extension in SwiftUI):SwiftUI.ViewRendererHost.render(interval: Swift.Double, updateDisplayList: Swift.Bool) -> () + 343 frame #13: 0x00007fff55ba07de SwiftUIclosure #1 () -> () in SwiftUI._UIHostingView.requestImmediateUpdate() -> () + 62 第 14 帧:0x00007fff562739ae SwiftUIreabstraction thunk helper from @escaping @callee_guaranteed () -> () to @escaping @callee_unowned @convention(block) () -> () + 14 frame #15: 0x0000000112ebd8ac libdispatch.dylib_dispatch_call_block_and_release + 12 帧#16:0x0000000112ebea88 libdispatch.dylib_dispatch_client_callout + 8 frame #17: 0x0000000112eccf23 libdispatch.dylib_dispatch_main_queue_callback_4CF + 1152 第 18 帧:0x00007fff203a8276 CoreFoundation__CFRUNLOOP_IS_SERVICING_THE_MAIN_DISPATCH_QUEUE__ + 9 frame #19: 0x00007fff203a2b06 CoreFoundation__CFRunLoopRun + 2685 帧 #20:0x00007fff203a1b9e CoreFoundationCFRunLoopRunSpecific + 567 frame #21: 0x00007fff2b773db3 GraphicsServicesGSEventRunModal + 139 帧 #22: 0x00007fff24660af3 UIKitCore-[UIApplication _run] + 912 frame #23: 0x00007fff24665a04 UIKitCoreUIApplicationMain + 101 第 24 帧:0x000000010db84a5b 陪练main at AppDelegate.swift:14:7 frame #25: 0x00007fff20257415 libdyld.dylibstart + 1

附件: XCode 崩溃1 综合指数2

【问题讨论】:

  • 看来您使用的是 SwiftUI?这似乎与错误有关,而不是问题中的代码。另外,一般来说,Firebase 不喜欢在紧密的循环中运行——你有两个,所以那里可能有一些东西。您可能会考虑进一步隔离问题,因为它是间歇性的 - 添加一些打印语句和/或中断并单步执行代码,直到它崩溃以尝试缩小问题。
  • 谢谢杰。我已将崩溃隔离到 getDocuments 行。 IE。它永远不会进入返回闭包。有没有办法避免这两个循环?当它们只是组合时(如 OR),在 Firebase 中 whereField 语句的使用非常有限。
  • @Jay 你能指出一些避免网络操作循环的东西吗?
  • @bsod 可能不是因为这不是所说的:-) Firebase 闭包中的 UI 调用在主线程上,这似乎是 SwiftUI。我们不知道它是从哪里调用的,也不知道它如何影响 UI,但它似乎确实与 SwiftUI 有关。看看this answeranswer。所有猜测atm。
  • @Jay 关注 RTDB。 Firestore 是一种不同的动物,我没有看到任何使用它循环网络操作的问题,尤其是 OP 正在做的少量问题。错误的访问错误表明闭包正在访问一些被过早释放或闭包本身丢失的东西。我无法重现该错误,所以我认为问题出在此代码之外是对的。

标签: ios swift firebase google-cloud-firestore


【解决方案1】:
func getUsersFromActivityPrefs(genders: [String], activities: [Int], skillScore_min: Int, skillScore_max: Int, completion: @escaping ([String]) -> Void) {
    var matchUsers = [String]()
    var count = 0
    let db = Firestore.firestore()
    let dispatch = DispatchGroup() // instantiate dispatch group outside loop
    
    for gender in genders {
        for activity in activities {
            dispatch.enter() // enter group on each iteration
            
            let dbRef = db.collectionGroup("activity_preferences")
                .whereField("gender", isEqualTo: gender)
                .whereField("activityid", isEqualTo: activity)
                .whereField("status", isEqualTo: true)
                .whereField("skill_score", isGreaterThanOrEqualTo: skillScore_min)
                .whereField("skill_score", isLessThanOrEqualTo: skillScore_max)
                .limit(to: 100)
            
            dbRef.getDocuments {( snap, err) in
                if let snap = snap {
                    count += 1
                    
                    for doc in snap.documents {
                        if let uid = doc.get("uid") as? String,
                           !matchUsers.contains(uid) {
                            matchUsers.append(uid)
                        }
                    }
                } else if let err = err {
                    print(err)
                }
                
                dispatch.leave() // always leave no matter what the db returned
            }
        }
    }
    
    /*
     this is the group's completion handler and it's only
     called once after all groups have entered and left
     */
    dispatch.notify(queue: .main) {
        completion(matchUsers)
    }
}

【讨论】:

  • 该代码似乎工作正常 :) :那么问题与我有两个 completion() 返回有关吗?
  • @Nikodym 当你运行它时会发生什么?
  • 我得到“成功”打印
  • 不幸的是,我在修改代码时遇到了同样的崩溃错误。
  • @Nikodym 那么你的问题是你如何处理完成,我怀疑这是竞争电话。修复很简单,但我无法从您的代码中看出您到底想要发生什么。您将查询限制为 100 个结果,但只处理文档更改(为什么?)并检查数组计数是否为 100(为什么?)。这次文件抓取的计划是什么?
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2023-03-27
  • 1970-01-01
  • 1970-01-01
  • 2021-10-17
相关资源
最近更新 更多