I just encountered one weird problem related to protocol checking. Here is the swift code using Xcode playground.
//-------------------------------------------------------------------------------
protocol IGenericCallback {
@discardableResult
func Callback(_ objects: Any...) -> Int
}
//-------------------------------------------------------------------------------
class CGenericCallback: IGenericCallback {
private let Closure: (_ objects: Any...) -> Int
init(closure: @escaping (_ objects: Any...) -> Int) {
Closure = closure
}
@discardableResult
func Callback(_ objects: Any...) -> Int {
// Array splatting is not supported yet. That's fine now.
Closure(objects)
return 0
}
}
//-------------------------------------------------------------------------------
class PackCallback {
var objectList: [Any]
init(_ objects: Any...) {
objectList = objects;
}
}
//-------------------------------------------------------------------------------
func test(cb: IGenericCallback?, param: Any) {
let pack = PackCallback(cb as Any, param)
if cb is IGenericCallback {
print("***** cb is IGenericCallback")
}
print("pack.objectList[0] = \(pack.objectList[0])")
if pack.objectList[0] is IGenericCallback {
print("***** pack.objectList[0] is IGenericCallback")
}
else {
print("***** pack.objectList[0] is NOT IGenericCallback!! WHY?")
}
}
//-------------------------------------------------------------------------------
test(cb: CGenericCallback.init(closure: {(objects: Any...) -> Int in
print("pack callback done. count=\(objects.count)")
return 0
}), param: 0)
I got the result:
***** cb is IGenericCallback
pack.objectList[0] = Optional(__lldb_expr_9.CGenericCallback)
***** pack.objectList[0] is NOT IGenericCallback!! WHY?
If I change "func test(cb: IGenericCallback?, param: Any)" to "func test(cb: IGenericCallback, param: Any)", the result is correct. But I don't know why? Can anyone help me? Thanks.
I think this is because Optional does not conform to your protocol (or P in my example below). Optional<CGenericCallback> does not imply that Optional<CGenericCallback>: CGenericCallback.
protocol P {}
class C: P {}
let c_1 = C()
let c_2: C? = c_1
let p_1: P = c_1
let p_2: P? = c_1
func test(_ any: Any, name: String) {
any is C
? print("is C - \(name)")
: print("is not C - \(name)")
any is P
? print("is P - \(name)")
: print("is not P - \(name)")
print()
}
test(c_1, name: "c_1")
test(c_2 as Any, name: "c_2")
test(p_1, name: "p_1")
test(p_2 as Any, name: "p_2")
This prints:
is C - c_1
is P - c_1
is C - c_2
is not P - c_2
is C - p_1
is P - p_1
is C - p_2
is not P - p_2
If we now add this conditional conformance:
extension Optional: P where Wrapped: P {}
the result will look like this:
is C - c_1
is P - c_1
is C - c_2
is P - c_2
is C - p_1
is P - p_1
is C - p_2
is not P - p_2
However I'm not sure why it prints is not P - p_2, cc @Joe_Groff is this intended or a bug?
I think this is due the fact that protocols do not conform to themselves. Please correct me if I'm wrong.
Update:
If you swap the above extension with:
extension Optional: P where Wrapped == P {}
You'll get as the result:
is C - c_1
is P - c_1
is C - c_2
is not P - c_2
is C - p_1
is P - p_1
is C - p_2
is P - p_2
This is the exact opposite of the previous issue. That in fact means that extension Optional: P where Wrapped: P {} would fix both issues if P would conform to itself like Error protocol conforms to itself in Swift 5.
Yeah, it's a known bug that dynamic casting sometimes fails to unwrap Optionals when trying protocol casts. Relatedly, it also fails to bridge sometimes when bridging would enable a protocol cast to succeed.
Thanks, @DevAndArtist and @Joe_Groff ! It looks like that it should be a bug. I would expect the 4 cases in your example should be C and P. I will remove protocol and change it to class as a workaround. "class" seems performing correctly now. Hope the fix is coming soon.