Pass an enum to notification userInfo in would lead to crash in iOS 14.6

My app always crash when I trying to assign an enum to a value of[String: Any] only on iOS14.6, tested on iOS15, iOS16, iOS17, never saw this issue.

The function would pass an ConnectState enum and a serial number string to a userInfo of notification, received class may perform some UI updates when connect state changed.

func post(state: ConnectState, serial: Int) {
    var userInfo: [String: Any] = [:]
    userInfo["state"] = state
    userInfo["serial"] = serial
    print(userInfo)
    NotificationCenter.default.post(name: NSNotification.Name("PostName"), object: nil, userInfo: userInfo)
}
@objc enum ConnectState: UInt8 {
    case connected = 0x00
    case disConnected = 0x01
    var description: String {
        switch self {
        case .connected:
            return "connected"
        case .disConnected:
            return "disConnected"
        }
    }
}

Classes received this notification, may perform some task based on connect state changed.

class AClass {
    init() {
        NotificationCenter.default.addObserver(self, selector: #selector(handleNotification(_:)), name: NSNotification.Name("PostName"), object: nil)
    }
    @objc func handleNotification(_ notification :Notification) {
        let state = notification.userInfo?["state"] as! ConnectState
        print("AClass handleNotification: \(state.description)")
    }
}

let a = AClass()
post(state: .disConnected, serial: 123456)
post(state: .connected, serial: 123456)

//["serial": 123456, "state": __lldb_expr_3.ConnectState]
//AClass handleNotification: disConnected
//["serial": 123456, "state": __lldb_expr_3.ConnectState]
//AClass handleNotification: connected

The crashes always happened in the line that assigning the ConnectState enum to dictionary [String: Any] value, both connect state and serialNumber are not nil or optional.

I'm not sure why would this related to a allocate issue, is my code wrong?

AppName(645,0x16f61f000) malloc: can't allocate region
:*** mach_vm_map(size=1152921504606863360, flags: 100) failed (error code=3)
AppName(645,0x16f61f000) malloc: *** set a breakpoint in malloc_error_break to debug
```Since we only have one iOS14.6 device, it will be a challenge to test on other same iOS version device, nor test on simulator.

Accepted Reply

So I dusted off (literally) an iOS 14 test device and tried this out. Specifically, using Xcode 15.3 I added code to post and receive a notification like so:

final class MainViewController: UIViewController {

    @objc enum ConnectState: UInt8 {
        case connected = 0x00
        case disConnected = 0x01
    }

    let testNoteName = Notification.Name("com.example.Test750235.test")

    override func viewDidLoad() {
        super.viewDidLoad()
        NotificationCenter.default.addObserver(
            self,
            selector: #selector(didReceiveTest(note:)),
            name: testNoteName,
            object: nil
        )
    }

    @objc func didReceiveTest(note: Notification) {
        print("did receive test, note: \(note)")
    }

    func post() {
        let state = ConnectState.connected
        var userInfo: [String: Any] = [:]
        userInfo["state"] = state
        NotificationCenter.default.post(name: testNoteName, object: nil, userInfo: userInfo)
    }

    … rest of the view controller …
}

This didn’t crash. When I tap a button that calls post(), the didReceiveTest(note:) method runs and prints:

did receive test, note: name = com.example.Test750235.test, object = nil, userInfo = Optional([AnyHashable("state"): Test750235.MainViewController.ConnectState])

This is on iOS 14.8.

Please create a new test project, add this code, and try it on your iOS 14 test device. That’ll tell us whether my code is replicating the problem properly, or whether this is tied to your environment somehow. For example, I doubt that the difference between iOS 14.6 and 14.8 matters here, but it’s always possible.

Share and Enjoy

Quinn “The Eskimo!” @ Developer Technical Support @ Apple
let myEmail = "eskimo" + "1" + "@" + "apple.com"

  • Thanks for your detailed test, I'll find time to repeat your test on our device!

Add a Comment

Replies

I'm manage to pass parameters to userInfo with one of following methods:

// one of following methods can prevent crash issue on iPhone8 with iOS 14.6
//     1.
        let userInfo: [String : Any] = [ConstValue.connectState:connectState,ConstValue.serialNumber:serialNumber]
        NotificationCenter.default.post(name: Notification.Name(Global.HomeDeviceStateChange), object: nil, userInfo: userInfo)

//     2.
        NotificationCenter.default.post(name: Notification.Name(Global.HomeDeviceStateChange), object: nil, userInfo: [ConstValue.connectState:connectState, ConstValue.serialNumber:serialNumber])

However it's still unclear why the original method would cause crash on iPhone8 with iOS14.6?

//This method would cause crash on iPhone8 with iOS 14.6
        var userInfo: [String: Any] = [:]
        userInfo[ConstValue.connectState] = connectState
        userInfo[ConstValue.serialNumber] = serialNumber
        NotificationCenter.default.post(name: Notification.Name(Global.HomeDeviceStateChange), object: nil, userInfo: userInfo)

So I dusted off (literally) an iOS 14 test device and tried this out. Specifically, using Xcode 15.3 I added code to post and receive a notification like so:

final class MainViewController: UIViewController {

    @objc enum ConnectState: UInt8 {
        case connected = 0x00
        case disConnected = 0x01
    }

    let testNoteName = Notification.Name("com.example.Test750235.test")

    override func viewDidLoad() {
        super.viewDidLoad()
        NotificationCenter.default.addObserver(
            self,
            selector: #selector(didReceiveTest(note:)),
            name: testNoteName,
            object: nil
        )
    }

    @objc func didReceiveTest(note: Notification) {
        print("did receive test, note: \(note)")
    }

    func post() {
        let state = ConnectState.connected
        var userInfo: [String: Any] = [:]
        userInfo["state"] = state
        NotificationCenter.default.post(name: testNoteName, object: nil, userInfo: userInfo)
    }

    … rest of the view controller …
}

This didn’t crash. When I tap a button that calls post(), the didReceiveTest(note:) method runs and prints:

did receive test, note: name = com.example.Test750235.test, object = nil, userInfo = Optional([AnyHashable("state"): Test750235.MainViewController.ConnectState])

This is on iOS 14.8.

Please create a new test project, add this code, and try it on your iOS 14 test device. That’ll tell us whether my code is replicating the problem properly, or whether this is tied to your environment somehow. For example, I doubt that the difference between iOS 14.6 and 14.8 matters here, but it’s always possible.

Share and Enjoy

Quinn “The Eskimo!” @ Developer Technical Support @ Apple
let myEmail = "eskimo" + "1" + "@" + "apple.com"

  • Thanks for your detailed test, I'll find time to repeat your test on our device!

Add a Comment