Some context:
I have the following code that is meant to look up property names for view controllers:
func getPropertyName(of target: UIResponder, in parent: UIResponder) -> String? {
for child in Mirror(reflecting: parent).children {
if let value = child.value as? UIResponder,
value === target,
let label = child.label {
return "\(type(of: parent)).\(label)"
}
}
return nil
}
This code is meant to be used as getPropertyName(of sender, in: someViewController)
and return something like "ItemViewController.addToCartBtn"`.
In the wild, a customer is seeing a crash with the following message and stack:
Found unexpected null pointer value while trying to cast value of type 'NSTimer' (0x...) to 'UIResponder' (0x...)
libswiftCore.dylib`getNonNullSrcObject(swift::OpaqueValue*, swift::TargetMetadata<swift::InProcess> const*, swift::TargetMetadata<swift::InProcess> const*) + 256
libswiftCore.dylib`tryCastToObjectiveCClass(swift::OpaqueValue*, swift::TargetMetadata<swift::InProcess> const*, swift::OpaqueValue*, swift::TargetMetadata<swift::InProcess> const*, swift::TargetMetadata<swift::InProcess> const*&, swift::TargetMetadata<swift::InProcess> const*&, bool, bool) + 88
libswiftCore.dylib`tryCast(swift::OpaqueValue*, swift::TargetMetadata<swift::InProcess> const*, swift::OpaqueValue*, swift::TargetMetadata<swift::InProcess> const*, swift::TargetMetadata<swift::InProcess> const*&, swift::TargetMetadata<swift::InProcess> const*&, bool, bool) + 992
libswiftCore.dylib`tryCast(swift::OpaqueValue*, swift::TargetMetadata<swift::InProcess> const*, swift::OpaqueValue*, swift::TargetMetadata<swift::InProcess> const*, swift::TargetMetadata<swift::InProcess> const*&, swift::TargetMetadata<swift::InProcess> const*&, bool, bool) + 1924
This suggested that children
returned a property dictionary and the cast to responder is failing due to a potential bug in Swift or edge condition.
But that's only half the story, since while trying to reproduce that, I was able to trivially cause a different crash by having it run on an object with a deallocated unowned
:
class ViewController: UIViewController {
unowned var myTimer: Timer
required init?(coder: NSCoder) {
let timer = Timer(timeInterval: 5, repeats: false, block: { _ in })
myTimer = timer
super.init(coder: coder)
}
}
Fatal error: Attempted to read an unowned reference but the object was already deallocated
swift::swift_abortRetainUnowned(void const*) ()
swift_unknownObjectUnownedLoadStrong ()
(anonymous namespace)::copyFieldContents(swift::OpaqueValue*, (anonymous namespace)::FieldType) ()
swift_reflectionMirror_subscript ()
So...
These crashes lead me to believe that Mirror
is not nearly as safe as it looks. Should I not use Mirror
in production code or are there additional safeguards I can put in place to avoid fatal errors?