在我的 react-native 应用程序中的 Swift 中编译器生成的崩溃

Posted

技术标签:

【中文标题】在我的 react-native 应用程序中的 Swift 中编译器生成的崩溃【英文标题】:compiler-generated crash in Swift in my react-native app 【发布时间】:2020-03-21 13:24:56 【问题描述】:

我的 healthkit swift 代码在生产环境中不断在后台崩溃,我这辈子都想不通。我对 Swift 还很陌生,所以也许我在实现过程中犯了一个根本性错误。

崩溃的特点:

这似乎只发生在生产中。 (我们的内部测试程序仅包含 10 台设备(所以可能巧合的是它没有在那里被拾取)。 发生在 ios 版本 - 10、11、12、13 仅针对极少数用户(活跃受众的 1.5%)发生,但对于这些相同的用户却非常频繁。

请在下面从我的 Crashlytics 帐户中找到崩溃日志。

Crashed: com.facebook.react.HKManagerQueue
0  stepapp                      0x100f4f324 specialized HKManager.getTotal(_:typeStr:unit:options:completion:) + 4372984612 (<compiler-generated>:4372984612)
1  stepapp                      0x100f52e04 HKManager._getTotals(_:completion:) + 397 (HKManager.swift:397)
2  stepapp                      0x100f532ac @objc HKManager.getTotals(_:resolver:rejecter:) + 4373000876 (<compiler-generated>:4373000876)
3  CoreFoundation                 0x1af698c20 __invoking___ + 144
4  CoreFoundation                 0x1af568d30 -[NSInvocation invoke] + 300
5  CoreFoundation                 0x1af569908 -[NSInvocation invokeWithTarget:] + 76
6  stepapp                      0x101184e6c -[RCTModuleMethod invokeWithBridge:module:arguments:] + 241556
7  stepapp                      0x101187248 facebook::react::invokeInner(RCTBridge*, RCTModuleData*, unsigned int, folly::dynamic const&) + 250736
8  stepapp                      0x101186fac invocation function for block in facebook::react::RCTNativeModule::invoke(unsigned int, folly::dynamic&&, int) + 250068
9  libdispatch.dylib              0x1af35e610 _dispatch_call_block_and_release + 24
10 libdispatch.dylib              0x1af35f184 _dispatch_client_callout + 16
11 libdispatch.dylib              0x1af30b404 _dispatch_lane_serial_drain$VARIANT$mp + 608
12 libdispatch.dylib              0x1af30bdf8 _dispatch_lane_invoke$VARIANT$mp + 420
13 libdispatch.dylib              0x1af315314 _dispatch_workloop_worker_thread + 588
14 libsystem_pthread.dylib        0x1af3aeb88 _pthread_wqthread + 276
15 libsystem_pthread.dylib        0x1af3b1760 start_wqthread + 8
com.apple.main-thread
0  libsystem_kernel.dylib         0x19c960634 mach_msg_trap + 8
1  libsystem_kernel.dylib         0x19c95faa0 mach_msg + 72
2  CoreFoundation                 0x19cb08288 __CFRunLoopServiceMachPort + 216
3  CoreFoundation                 0x19cb033a8 __CFRunLoopRun + 1444
4  CoreFoundation                 0x19cb02adc CFRunLoopRunSpecific + 464
5  GraphicsServices               0x1a6aa3328 GSEventRunModal + 104
6  UIKitCore                      0x1a0c1063c UIApplicationMain + 1936
7  stepapp                        0x100edf330 main + 14 (main.m:14)
8  libdyld.dylib                  0x19c98c360 start + 4
com.apple.uikit.eventfetch-thread
0  libsystem_kernel.dylib         0x19c960634 mach_msg_trap + 8
1  libsystem_kernel.dylib         0x19c95faa0 mach_msg + 72
2  CoreFoundation                 0x19cb08288 __CFRunLoopServiceMachPort + 216
3  CoreFoundation                 0x19cb033a8 __CFRunLoopRun + 1444
4  CoreFoundation                 0x19cb02adc CFRunLoopRunSpecific + 464
5  Foundation                     0x19ce42784 -[NSRunLoop(NSRunLoop) runMode:beforeDate:] + 228
6  Foundation                     0x19ce42664 -[NSRunLoop(NSRunLoop) runUntilDate:] + 88
7  UIKitCore                      0x1a0ca8e80 -[UIEventFetcher threadMain] + 152
8  Foundation                     0x19cf7309c __NSThread__start__ + 848
9  libsystem_pthread.dylib        0x19c8a5d8c _pthread_start + 156
10 libsystem_pthread.dylib        0x19c8a976c thread_start + 8

我在下面附上了我的实现,其中包含崩溃日志中提到的行

func _getTotals(_ options: Dictionary<String, Any>, completion: @escaping (Dictionary<String, Double>?) -> Void) 
    var stepsDone = false;
    var distanceDone = false;
    var caloriesDone = false;

    let steps = HKQuantityType.quantityType(forIdentifier: .stepCount);
    let distance = HKQuantityType.quantityType(forIdentifier: .distanceWalkingRunning);
    let calories = HKQuantityType.quantityType(forIdentifier: .activeEnergyBurned);

    var results = Dictionary<String, Double>();

    // ???? THIS IS LINE 397 which is indicated in the crash report above
    self.getTotal(steps!, typeStr: HKManager.STEP_TYPE_STR, unit: HKUnit.count(), options: options)  (totalSteps, error) in
      stepsDone = true;
      if (totalSteps != nil) 
        results["steps"] = totalSteps;
      

      if (stepsDone == true && distanceDone == true && caloriesDone == true) 
        return completion(results);
      
    

    self.getTotal(distance!, typeStr: HKManager.DISTANCE_TYPE_STR, unit: HKUnit.meter(), options: options)  (totalDistance, error) in
      distanceDone = true;
      if (totalDistance != nil) 
        results["distance"] = totalDistance;
      
      if (stepsDone == true && distanceDone == true && caloriesDone == true) 
        return completion(results);
      
    

    self.getTotal(calories!, typeStr: HKManager.CALORIES_TYPE_STR, unit: HKUnit.kilocalorie(), options: options)  (totalCalories, error) in
      caloriesDone = true;
      if (totalCalories != nil) 
        results["calories"] = totalCalories;
      
      if (stepsDone == true && distanceDone == true && caloriesDone == true) 
        return completion(results);
      
    
  

我还附上了上面代码中使用的 self.getTotal(...) 函数的实现。在这个函数中需要注意的是,我切换到在后台 qos 中执行我的 HealthKit 查询,以确保这些查询不会在主线程上运行。我认为这可能是导致崩溃的原因。


  func getTotal(_ type: HKQuantityType, typeStr: String, unit: HKUnit, options: Dictionary<String, Any>, completion: @escaping (Double?, Error?) -> Void) 
    guard (self.healthStore != nil) else 
      let error = NSError(domain: "Healthkit not initialized", code: 50, userInfo: [:]);
      return completion(nil, error);
    

    var start: Date;

    if (options["startDate"] != nil) 
      start = self.strToDate(dateStr: options["startDate"] as! String);
     else 
      let date = Date()
      let cal = Calendar(identifier: .gregorian)
      let midnight = cal.startOfDay(for: date);

      start = midnight;
    

    var ignoreMin = false;
    if (options["ignoreMin"] != nil) 
      ignoreMin = options["ignoreMin"] as! Bool;
    

    if (ignoreMin != true && start < self.minStartDate && self.minStartDate != nil) 
      start = self.minStartDate;
    

    var end: Date = Date();
    if (options["endDate"] != nil) 
      end = self.strToDate(dateStr: options["endDate"] as! String);
    

    var sources = options["sources"] as? [String];
    if (sources == nil || (sources?.capacity)! < 1) 
      sources = ["com.apple.health."];
    

    DispatchQueue.global(qos: .background).async  [weak self] in

      // fetch sources
      self?.getSources(sampleTypeStr: typeStr, sampleType: type, sources: sources!)  (s, error) in

        if (s == nil || ((s?.capacity) ?? 0) < 1) 
          return completion(0.0, nil);
        

        let sourcePredicate = HKQuery.predicateForObjects(from: s!);

        // todo: enter date patterns
        let datePredicate = HKQuery.predicateForSamples(withStart: start, end: end, options: []);

        //      predicate = [NSPredicate predicateWithFormat:@"metadata.%K != YES", HKMetadataKeyWasUserEntered];
        let manualPredicate = HKQuery.predicateForObjects(withMetadataKey: HKMetadataKeyWasUserEntered, operatorType: .notEqualTo, value: "YES");

        let compound = NSCompoundPredicate(andPredicateWithSubpredicates: [
          sourcePredicate,
          datePredicate,
          manualPredicate
        ]);


        let statOptions = HKStatisticsOptions.cumulativeSum;


        let query = HKStatisticsQuery.init(quantityType:type , quantitySamplePredicate: compound, options: statOptions, completionHandler:  (query, results, error) in

          if (error != nil) 
            return completion(nil, error);
          

          var total = 0.0;

          // handle if results came back as nil, or sum came back as nil
          guard (results != nil && results?.sumQuantity() != nil) else 
            return completion(total, nil);
          

          total = results?.sumQuantity()?.doubleValue(for: unit) ?? 0.0;

          return completion(total, nil);
        );

        // execute stats query for step counts by source
        self?.healthStore?.execute(query);
      
   
  

我非常感谢任何形式的帮助或指点。提前致谢。

【问题讨论】:

您能否分享完整的错误消息。基于上述,它看起来不完整。也许截图会有所帮助。 嗨,我已经添加了更多的堆栈跟踪,但老实说,它非常长 【参考方案1】:

明显的问题是从多个线程并行写入字典对象。在伪代码中:

results = [:]
getTotal() // start thread 1
getTotal() // start thread 2
getTotal() // start thread 3

thread 1: write results
thread 2: write results
thread 3: write results

Swift Dictionary 不是线程安全的,必须同步并行写入。

在您的代码中,简单的更改是将DispatchQueue async 向上移动到_getTotals,并从getTotal 中删除DispatchQueue

func _getTotals(_ options: Dictionary<String, Any>, completion: @escaping (Dictionary<String, Double>?) -> Void) 
  DispatchQueue.global(qos: .background).async 
    var results = Dictionary<String, Double>()
    self.getTotal(...)
    self.getTotal(...)
    self.getTotal(...)
  

以这种方式,一切都在单个后台线程中运行; getTotal 调用一个接一个地被串行调用。

但是如果你需要并行运行getTotal,你必须同步访问results变量。这通常通过使用预定义的共享串行队列再次调用 DispatchQueue async 来完成。

【讨论】:

以上是关于在我的 react-native 应用程序中的 Swift 中编译器生成的崩溃的主要内容,如果未能解决你的问题,请参考以下文章

react-native 状态中的奇怪问题

react-native 应用程序中的 canOverrideExistingModule 问题

无法使用react-native app中的redux-persist检查索引/主文件上是否已加载持久状态

React-Native:导航到堆栈导航器中的抽屉

react-native 中的图像预览

React-Native with Redux:API 对象出现在我的 console.log 中,但不在我的视图中