Springboard crashing when adding a lot of triggers to UNUserNotificationCenter

烂漫一生 提交于 2019-11-30 09:59:12

I've found a workaround (bug already logged with Apple bug ID 29286162)

Essentially, it seems the SpringBoard crashes if the UNUserNotificationCenter is busy adding notifications and it hasn't finished doing so, while the user presses the home button to return to the SpringBoard. Clearly, somehow the thread is dying (well, possibly since I've got to let go of the background task identifier, hoping the UNUserNotificationCenter will happily consume the passed requests in its own system provided background thread), leaving the on-going asynchronous operation in limbo, causing Springboard to eventually crash. I now simply spawn a thread in a background task (UIBackgroundTaskIdentifier) and hold on to it till it's finished adding all the notifications. This results in zero crashes - confirmed also by all the users previously complaining the same.

  // arrTasksToSchedule -> holds UNNotificationRequests to add

  __block NSInteger volatile processedAlerts = 0;
  NSInteger totalToProcess = [arrTasksToSchedule count];
  UNUserNotificationCenter *center = [UNUserNotificationCenter currentNotificationCenter];
  for (UNNotificationRequest* notifRequest in arrTasksToSchedule) {
    [center addNotificationRequest:notifRequest withCompletionHandler:^(NSError * _Nullable error) {
      processedAlerts++;
    }];
  }

  // Let the runloop carry on processing events till we're done
  while (processedAlerts != totalToProcess) {
    [[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode beforeDate:[NSDate distantFuture]];
  }

  // Okay we're done, and can now let go of our held UIBackgroundTaskIdentifier  

The documentation says nothing about background tasks, perils of using UNUserNotificationCenter in a background task or anything to do with multiple threads etc. I can only assume they didn't realize most apps do their notification registrations when their apps are sent to the background (or during a background content-refresh push notification).

Since the Springboard was crashing - crash reports were not being generated for the app itself. Users kept complaining and this was one of those terribly difficult situations where you've got no clue. I hope this saves someone a lot of headache.

UPDATE: Although the code above works just fine now, I've made a slightly modification as the while loop's condition may NOT be met in case the user closes the app and presses the power button to switch the screen off. In this case, the runloop obviously has nothing to process and will block. Instead, the following does the trick:

// Wait in a run loop
while (processedAlerts != totalToProcess) {
  [NSThread sleepForTimeInterval:0.15];

  // Don't use the following, as it will stop firing in case
  // the runloop has no events (such as touch events) firing
  // [[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode beforeDate:[NSDate distantFuture]];
}

Recently the guys at Todoist released an update (11.2.7) indicating that they've worked around the Springboard crash.

Shortly after, Apple released iOS 10.2. Since then, I've started receiving reports from users of my app that the bug has been resolved. However, curious how the guys at Todoist worked around the problem, I reached out to them and this is what their lead iOS developer shared with me:

Hi Junjie,

Yes, you’re right. The springboard crash in iOS 10 was actually related to scheduling local user notifications. That issue was actually fixed on iOS 10.2, but we also fixed it for iOS 10.1 by just doing the needed changes to user notifications. Before we was adding all requests, regardless if they was already added, and we started to compare them with the already scheduled and just add those that are different:

let requestsToAdd = requestsToSchedule.filter{!pendingRequests.contains($0)}

Hope this helps.

Nuno

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