Can we test if objects conforming to the same protocol are identical in swift without casting?
The identity operator ===
can only be applied to references, i.e. instances of classes.
If all types conforming to the FooBar
protocol are classes then
you can declare it as a “class-constrained protocol”
protocol FooBar : AnyObject { }
(AnyObject
is the protocol to which all classes implicitly conform.)
Then
if foo1! === foo2! { ... }
compiles and works as expected, because the compiler "knows" that
both operands are references to a class instance.
Swift Check if Two Objects Conforming to a Protocol Are Referentially The Same
You should try to upcast the delegate
and then check for equality:
func something() {
if item.delegate as? Container !== self {
print("hi")
}
}
Full working code example
protocol ContainerDelegate {}
protocol FileProtocol {
var delegate: ContainerDelegate? { get set }
}
class StaticFile: NSObject, FileProtocol {
var delegate: ContainerDelegate?
}
class Container: NSObject, ContainerDelegate {
var item: FileProtocol
func something() {
if item.delegate as? Container !== self {
print("hi")
}
}
override init() {
item = StaticFile()
}
}
let c = Container()
let c2 = Container()
c.item.delegate = c2
c.something() // hi gets printed
c.item.delegate = c
c.something() // hi does **not** get printed
How do you test the identity of Strings in Swift?
Strings in Swift are value types not reference types. That is, they only have value, not identity. In this sense, it does not make sense to check if two strings have the same “identity”. Unless they are the same variable, they are different. Instead, you should just check if they have the same value using ==
.
Under the hood there may be interning. In fact, there definitely is:
struct StringBits {
let underlyingPtr: UnsafeMutablePointer<Void>
let padding1: UnsafeMutablePointer<Void>
let padding2: UnsafeMutablePointer<Void>
}
let s1 = "abcd"
let s2 = "abcd"
let bits1 = unsafeBitCast(s1, StringBits.self)
let bits2 = unsafeBitCast(s2, StringBits.self)
println(bits1.underlyingPtr) // 0x0000000117654000
println(bits2.underlyingPtr) // also 0x0000000117654000
Similarly, if you initialize one string from another, they will share the same storage until one of them is mutated (i.e. strings are copy-on-write).
But these details are hidden from you as implementation details that you aren’t supposed to need to know. As far as the semantics are concerned, s1
and s2
are entirely unrelated.
If you’re concerned about performance, the underlying String type will probably use the pointers to its storage to efficiently check for equality in cases where they are sharing the same storage. Arrays (which are implemented similarly) do, as can be seen here:
struct NeverEqual: Equatable { }
// NOT a correct implementation of ==, since == must be
// reflexive, and this isn’t:
func ==(lhs: NeverEqual, rhs: NeverEqual)->Bool { return false }
let x=[NeverEqual()]
let y=x
// this returns true – because it doesn’t bother comparing the
// elements, because it knows x and y share the same storage
x==y
Testing type for class conformance in Swift
As Martin notes in a comment, any value can be cast to AnyObject
in Swift, because Swift will wrap value types in an opaque _SwiftValue
class, and the cast will always succeed. There is a way around this, though.
The way to check whether a value is a reference type without this implicit casting is to check whether its type is AnyObject.Type
, like so:
func printIsObject(_ value: Any) {
if type(of: value) is AnyObject.Type {
print("Object")
} else {
print("Other")
}
}
class Foo {}
struct Bar {}
enum Quux { case q }
printIsObject(Foo()) // => Object
printIsObject(Bar()) // => Other
printIsObject(Quux.q) // => Other
Note that it's crucial that you check whether the type is AnyObject.Type
not is AnyObject
. T.self
, the object representing the type of the value, is itself an object, so is AnyObject
will always succeed. Instead, is AnyObject.Type
asks "does this inherit from the metatype of all objects", i.e., "does this object which represents a type inherit from an object that represents all object types?"
Edit: Evidently, I'd forgotten that Swift includes AnyClass
as a synonym for AnyObject.Type
, so the check can be simplified to be is AnyClass
. However, leaving the above as a marginally-expanded explanation for how this works.
If you want this method to also be able to handle Optional
values, you're going to have to do a bit of special-casing to add support. Specifically, because Optional<T>
is an enum
regardless of the type of T
, you're going to need to reach in to figure out what T
is.
There are a few ways to do this, but because Optional
is a generic type, and it's not possible to ask "is this value an Optional<T>
?" without knowing what T
is up-front, one of the easier and more robust ways to do this is to introduce a protocol which Optional
adopts that erases the type of the underlying value while still giving you access to it:
protocol OptionalProto {
var wrappedValue: Any? { get }
}
extension Optional: OptionalProto {
var wrappedValue: Any? {
switch self {
case .none: return nil
case let .some(value):
// Recursively reach in to grab nested optionals as needed.
if let innerOptional = value as? OptionalProto {
return innerOptional.wrappedValue
} else {
return value
}
}
}
}
We can then use this protocol to our advantage in cache
:
func cache(id: Int, instance: Any) {
if let opt = instance as? OptionalProto {
if let wrappedValue = opt.wrappedValue {
cache(id: id, instance: wrappedValue)
}
return
}
// In production:
// cache[id] = WeakBox(boxed: instance as AnyObject)
if type(of: instance) is AnyClass {
print("\(type(of: instance)) is AnyClass")
} else {
print("\(type(of: instance)) is something else")
}
}
This approach handles all of the previous cases, but also infinitely-deeply-nested Optional
s, and protocol types inside of Optional
s:
class Foo {}
struct Bar {}
enum Quux { case q }
cache(id: 1, instance: Foo()) // => Foo is AnyClass
cache(id: 2, instance: Bar()) // => Bar is something else
cache(id: 3, instance: Quux.q) // => Quux is something else
let f: Optional<Foo> = Foo()
cache(id: 4, instance: f) // => Foo is AnyClass
protocol SomeProto {}
extension Foo: SomeProto {}
let p: Optional<SomeProto> = Foo()
cache(id: 5, instance: p) // => Foo is AnyClass
How to compare Any value types
The only way to do this is with a function other than ==
that takes a type parameter, and then compares the values if they are both of that type:
func isEqual<T: Equatable>(type: T.Type, a: Any, b: Any) -> Bool {
guard let a = a as? T, let b = b as? T else { return false }
return a == b
}
Now, using your variables above, you can compare them like this:
var any1: Any = 1
var any2: Any = 1
var any3: Any = "test"
var any4: Any = "test"
isEqual(type: Int.self, a: any1, b: any2) // true
isEqual(type: Int.self, a: any2, b: any3) // false
isEqual(type: String.self, a: any3, b: any4) // true
Related Topics
How to Capture Depth Data from Camera in iOS 11 and Swift 4
Viewcontroller Slide Animation
How to Convert PDF to Png Efficiently
Arkit - Viewport Size VS Real Screen Resolution
How to Make Physics Bodies Stick to Nodes Anchor Points
How to Create an Array of Functions
Uiscrollview with Embedded Uiimageview; How to Get the Image to Fill the Screen
How to Make Keyboard Dismiss When I Press Out of Searchbar on Swift
What Is the Advantage of a Lazy Var in Swift
Checking If Textfields Are Empty Swift
Add a Line as a Selection Indicator to a Uitabbaritem in Swift
Reading Currently Playing Track in MACos Using Scriptingbridge Not Working
Swiftui - Wait Until Firestore Getdocuments() Is Finished Before Moving On
How to Write a Function That Will Unwrap a Generic Property in Swift Assuming It Is an Optional Type
How to Create Several Cached Uicolor