Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to use unsafeDowncast in Swift 3

Tags:

ios

swift

I'm currently upgrading a forked Framework to Swift 3.

public convenience init<Object: AnyObject>(object: Object,
                        keyPath: String,
                        options: NSKeyValueObservingOptions = [],
                        block: @escaping (_ object: Object, _ change: Change, _ kvo: KVObserver) -> Void) {

    self.init(__object: object, keyPath: keyPath, options: options, block: { (object, change, kvo) in

        block(unsafeDowncast(object),
              Change(rawDict: change), kvo)
    })
}

At block(unsafeDowncast(object) the compiler gives missing argument to:.

According to Swift Evolution unsafeDowncast got changed to:

enter image description here

The passed argument object is x:, but how do I need to define the to: value?

This is, what I have come up with:

public convenience init<Object: AnyObject>(object: Object,
                        keyPath: String,
                        options: NSKeyValueObservingOptions = [],
                        block: @escaping (_ object: Object, _ change: Change, _ kvo: KVObserver) -> Void) {

    self.init(__object: object, keyPath: keyPath, options: options, block: { (object, change, kvo) in

        block(unsafeDowncast(object as AnyObject, to: Object.self),
              Change(rawDict: change as [String : AnyObject]?), kvo)
    })
} 

The idea was, that to: expects the .Type and the block says, that object: Object. That's why to: Object.self.

change as [String : AnyObject]?) was a compiler suggestion.

The compiler is satisfied. But I'm not sure if my code is correct. I'm not even 100% sure what the unsafeDowncast does in the first place. What does to: T.Type means in that context and how do I need to define it correctly?

Help is very appreciated.

like image 960
David Seek Avatar asked Sep 06 '25 03:09

David Seek


1 Answers

 unsafeDowncast(x, to: Object.self)

assumes that x is an instance of Object and returns x as Object. So it is identical to

 x as! Object

but without any runtime checks. If x is not an instance of Object then the behaviour is undefined and anything can happen, from crashes to unexpected results.

In Swift 2, the "destination type" was inferred from the context, in Swift 3 you have to provide it as an argument.

In your case, when the notification center calls the closure

 { (object, change, kvo) in
   // ...
 }

and you forward the call to the

 block: (_ object: Object, _ change: Change, _ kvo: KVObserver) -> Void

closure given in the init method, you know that object is an instance of Object, because that is what you passed in to the registration.

But the compiler does not know that. Therefore object must be downcasted to Object, and that is what

unsafeDowncast(object as AnyObject, to: Object.self)

does. The additional cast as AnyObject is necessary because in the notification callback, object has type Any, and not AnyObject as in Swift 2. So your code

block(unsafeDowncast(object as AnyObject, to: Object.self), ...)

is correct, but the more straightforward way would be

block(object as! Object, ...)

The forced cast is acceptable here because you know that it must succeed, otherwise you would have a programming error.

like image 195
Martin R Avatar answered Sep 07 '25 21:09

Martin R