See the simple code snippet below. For my app I need to include a call to CTCellularPlanProvisioningRequest, part of CoreTelephony. This class is available for iOS 12+. I still have a big user base on iOS 10 and 11 to support, so I enclosed my call in 'if #available`, like so:
if #available(iOS 12, *) {
let cpProvioningRequest = CTCellularPlanProvisioningRequest()
print("cpProvioningRequest: \(cpProvioningRequest)")
} else {
print("No iOS 12+ available")
}
It is ok for me that the call is not executed on iOS<12. However, when I run this code on a simulator device running iOS 11.4 the app crashes at the loading stage. On a simulator running iOS 12 or 13, the call works fine. And when I comment out the two lines with CTCellularPlanProvisioningRequest, the app runs on fine iOS 11.
The error I get on 11.4 is:
dyld: Symbol not found: _OBJC_CLASS_$_CTCellularPlanProvisioningRequest
Referenced from: /Users/frans/Library/Developer/CoreSimulator/Devices/25E0C601-5D38-4B41-A807-3575BC23AAB9/data/Containers/Bundle/Application/4DC30320-77F6-4107-A83A-EF0F5C5B464D/TestNewAPIOldiOS.app/TestNewAPIOldiOS
Expected in: /Library/Developer/CoreSimulator/Profiles/Runtimes/iOS 11.4.simruntime/Contents/Resources/RuntimeRoot/System/Library/Frameworks/CoreTelephony.framework/CoreTelephony
in /Users/frans/Library/Developer/CoreSimulator/Devices/25E0C601-5D38-4B41-A807-3575BC23AAB9/data/Containers/Bundle/Application/4DC30320-77F6-4107-A83A-EF0F5C5B464D/TestNewAPIOldiOS.app/TestNewAPIOldiOS
And my thread view shows:
dyld`__abort_with_payload:
0x10d2500d4 <+0>: movl $0x2000209, %eax ; imm = 0x2000209
0x10d2500d9 <+5>: movq %rcx, %r10
0x10d2500dc <+8>: syscall
-> 0x10d2500de <+10>: jae 0x10d2500e8 ; <+20>
0x10d2500e0 <+12>: movq %rax, %rdi
0x10d2500e3 <+15>: jmp 0x10d24e601 ; cerror_nocancel
0x10d2500e8 <+20>: retq
0x10d2500e9 <+21>: nop
0x10d2500ea <+22>: nop
0x10d2500eb <+23>: nop
My question is: How can I ensure that this call to CTCellularPlanProvisioningRequest works normally on iOS 12+ while not causing a crash or other negative effect on iOS<12?
For completeness, below is the complete class. Deployment target is set to iOS 10. I am aware of the prerequisites for CTCellularPlanProvisioningRequest and in my complete app I have those filled in. I tested on Xcode 11.2.1, as well as Xcode 10. I tried several different device simulators, running various pre-iOS12 versions. I have NOT been able to run this on a physical device running iOS 10 or 11, because I don't have access to one.
// TestNewAPIOldiOS
//
// Created by Frans Glorie on 15/11/2019.
// Copyright © 2019 Frans Glorie. All rights reserved.
//
import UIKit
import CoreTelephony
class ViewController: UIViewController {
// Functions
override func viewDidLoad() {
super.viewDidLoad()
let ctSubscriber = CTSubscriber()
print("Subscriber: \(ctSubscriber)")
if #available(iOS 12, *) {
let cpProvioningRequest = CTCellularPlanProvisioningRequest()
print("cpProvioningRequest: \(cpProvioningRequest)")
} else {
print("No iOS 12+ available")
}
}
}
Thanks!
Frans
I finally received an answer from Apple and what they suggest seems to work: "After reviewing your feedback, we have some additional information for you.
There is a known issue on our side of things, but you can work around this by explicitly weak linking against the framework. To do that, go to build phases, 'link binary with libraries' section, add CoreTelephony there and set the status field from required to optional. That fixes the crash for now. We've filed a separate bug for the issue you mentioned and will fix it in a future release. Thanks! "
I tried this and it seems to work fine. Further testing is needed, but this seems to solve the problem. Hope this helps anyone else.
The workarounds posted here earlier unfortunately all solve only a part of the problem.
Since CTCellularPlanProvisioningRequest
is NSObject subclass as a workaround you can instantiate it through obj-c dynamic runtime with failsafe for iOS11 and lower
if let classAvailable = NSClassFromString("CTCellularPlanProvisioningRequest") {
let cpProvioningRequest = (classAvailable as! NSObject).init()
print("cpProvioningRequest: \(cpProvioningRequest)")
}
By doing so you will not create dependency in your binary on the _OBJC_CLASS_$_CTCellularPlanProvisioningRequest
symbol.
UPDATE
Following OP comments in my answer here's code to invoke addPlan
dynamically.
if let requestClassAvailable = NSClassFromString("CTCellularPlanProvisioningRequest"),
let provisioningClassAvailable = NSClassFromString("CTCellularPlanProvisioning") {
if let cpProvisioningRequestType = requestClassAvailable as? NSObject.Type,
let cpProvisioningType = provisioningClassAvailable as? NSObject.Type {
let cpProvisioningRequest = cpProvisioningRequestType.init()
let cpProvisinioning = cpProvisioningType.init()
let addPlanSelector = NSSelectorFromString("addPlanWith:completionHandler:")
if let methodIMP = cpProvisinioning.method(for: addPlanSelector) {
unsafeBitCast(methodIMP,to:(@convention(c)(Any?,Selector,Any?,((UInt) -> Void)?)->Void).self)(cpProvisinioning, addPlanSelector, cpProvisioningRequest, nil)
}
}
}
It's far from perfect, but maybe it will suffice until you get an answer from Apple.
Btw I covered in details details of dynamic invoking in Swift in my answer here
The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.