We've implemented Push Notifications through SalesForce Marketing Cloud and after some beta testing with internal testers it seems the SFMCSDK is causing our app to Crash intermittently due to "_dispatch_once". Not sure if this is an issue with the way we've implemented the SDK or simply a bug from the SDK itself, when we open the crash in project we only get the following information:
TestFlight metrics include # crashes
crash details from Xcode Organizer
Screenshot of crash in project
We've followed the documentation to setup the SDK / Push Notifications and standard push notifications are working as expected. We have an issue receiving/reading openDirect urls from a terminated app state (works in foreground/background) but we removed those urls in the hopes that they were causing the crash, it seems they were not.
I also found an article that said setting 'Other Linker Flags' in Xcode Build Settings can cause the "_dispatch_once" crash but we checked and we don't define any.
We implement everything through AppDelegate in SwiftUI:
import SFMCSDK
import MarketingCloudSDK
import Foundation
import SwiftUI
import UIKit
class AppDelegate: NSObject, UIApplicationDelegate, ObservableObject, URLHandlingDelegate {
let appId = "correct appId String"
let accessToken = "correct accessToken String"
let appEndpoint = URL(string:"correct marketingCloud appEndpoint URL")!
let mid = "correct marketing cloud ID"
func configureSDK() {
#if DEBUG
SFMCSdk.setLogger(logLevel: .debug)
#endif
let mobilePushConfiguration = PushConfigBuilder(appId: appId)
.setAccessToken(accessToken)
.setMarketingCloudServerUrl(appEndpoint)
.setDelayRegistrationUntilContactKeyIsSet(true)
.setMid(mid)
.setInboxEnabled(false)
.setLocationEnabled(false)
.setAnalyticsEnabled(true)
.build()
let completionHandler: (OperationResult) -> () = { result in
if result == .success {
// module is fully configured and ready for use
SFMCSdk.mp.setURLHandlingDelegate(self)
} else if result == .error {
// module failed to initialize, check logs for more details
} else if result == .cancelled {
// module initialization was cancelled
} else if result == .timeout {
// module failed to initialize due to timeout, check logs for more details
}
}
SFMCSdk.initializeSdk(ConfigBuilder().setPush(config: mobilePushConfiguration,
onCompletion: completionHandler).build())
}
func registerPush(contactID:String?) {
#if !targetEnvironment(simulator)
#if DEBUG
SFMCSdk.identity.setProfileId("developer@developer.com")
setupMobilePush()
#else
if let contactKey = contactID {
SFMCSdk.identity.setProfileId(contactKey)
setupMobilePush()
}
#endif
#endif
}
func sfmc_handleURL(_ url: URL, type: String) {
print(url.description)
}
}
extension AppDelegate: UNUserNotificationCenterDelegate {
func setupMobilePush() {
DispatchQueue.main.async {
UNUserNotificationCenter.current().delegate = self
// Request authorization from the user for push notification alerts.
UNUserNotificationCenter.current().requestAuthorization(
options: [.alert, .sound, .badge], completionHandler: {
(_ granted: Bool, _ error: Error?) -> Void in
if error == nil {
if granted == true {
// Logic if authorized
}
} else {
print(error!.localizedDescription)
}
})
UIApplication.shared.registerForRemoteNotifications()
}
}
// SDK: REQUIRED IMPLEMENTATION
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
self.configureSDK()
return true
}
// MobilePush SDK: REQUIRED IMPLEMENTATION
func application(_ application: UIApplication,
didRegisterForRemoteNotificationsWithDeviceToken deviceToken: Data) {
SFMCSdk.mp.setDeviceToken(deviceToken)
}
// MobilePush SDK: REQUIRED IMPLEMENTATION
func application(_ application: UIApplication,
didFailToRegisterForRemoteNotificationsWithError error: Error) {
print(error)
}
// MobilePush SDK: REQUIRED IMPLEMENTATION
func application(_ application: UIApplication, didReceiveRemoteNotification userInfo:
[AnyHashable : Any], fetchCompletionHandler completionHandler: @escaping
(UIBackgroundFetchResult) -> Void) {
SFMCSdk.mp.setNotificationUserInfo(userInfo)
completionHandler(.newData)
}
// MobilePush SDK: REQUIRED IMPLEMENTATION
@available(iOS 10.0, *)
func userNotificationCenter(_ center: UNUserNotificationCenter, didReceive response:
UNNotificationResponse, withCompletionHandler completionHandler: @escaping () ->
Void) {
SFMCSdk.mp.setNotificationRequest(response.notification.request)
completionHandler()
}
// MobilePush SDK: REQUIRED IMPLEMENTATION
@available(iOS 10.0, *)
func userNotificationCenter(_ center: UNUserNotificationCenter, willPresent
notification: UNNotification, withCompletionHandler completionHandler: @escaping
(UNNotificationPresentationOptions) -> Void) {
completionHandler(.alert)
}
}
Any insight would be most appreciated!
This is because of accessing keychain from the background when data protection is enabled. Add the below code before SDK init and we should not be seeing the crash anymore. SFMCSdk.setKeychainAccessErrorsAreFatal(errorsAreFatal: false)
Please take a look our Learning app: https://github.com/salesforce-marketingcloud/MarketingCloudSDK-iOS/tree/spm/examples/LearningApp to see how this implemented in detail.
Thanks Prakashini