Reputation: 10317
I have 2 classes:
class Parent
{
func a() {
self.b()
}
func b() {
// I want to check here
if self is Parent // warning "'is' test is always true"
{
log("instance of parent")
}
}
}
class Child:Parent
{
}
I want to check like this
//
var child = Child()
child.a() // don't see log
var parent = Parent()
parent.a() // see log
I know that I can create a method like description
in superclass, and override it in subclass.
I wonder if Swift can check it without implement description
Thanks for your help
Upvotes: 9
Views: 9385
Reputation: 2768
The answer should be more simple, so it handles unexpected errors. ie. maybe you added a new subclass, but you forgot to update the switch statement, hence incorrectly printing Parent
.
The quick example I gave above, should either not print anything, or print Unexpected Class
or something like that so everything remains true, consistent, and future-proof. Seems like a no brainer to check the boolean child is Child
but you gotta do that carefully in a timely manner because the is
keyword always returns true
for subclasses (hence, the warning mentioned in the question 'is' test is always true
occurs when doing self is Parent
)
Here is my solution:
class Parent {
// MARK: Main method
func printClass() {
print(className)
}
// MARK: Helper methods
var isParent: Bool {
return type(of: self) === Parent.self
}
static var className: String {
return String(describing: self)
}
var className: String {
return type(of: self).className
}
// MARK: Implementation to recognize Parent, Child and any other Subclass
// Approach #1: Identify if "self" == "Parent"
func printClassOrSubclass() {
guard isParent else {
// Case #1: Class of "self" != "Parent", so "self" must be a subclass of "Parent"
print("\(className), subclass of Parent")
return
}
// Case #2: Class of "self" == "Parent", its not a subclass
printClass()
}
// Approach #2: Identify what class is "self" exactly. This approach is useful for calling a method or do something with "self" as a subclass instance (ie. "child == self as! Child")
func printClassOrSubclassWithDowncasting() {
guard !isParent else {
// Case #1: Class of "self" == "Parent", so "self" is not a subclass of "Parent"
printClass()
return
}
// Switch #1: Attempt to identify the exact class of "self", which is definitely a subclass of "Parent" by now.
switch self {
case let child as Child where className == Child.className:
// Case #2: Class of "self" == "Child"
child.printChildClass()
// In each case of Switch #1, returning is important so we dont continue to the Switch #2 by accident
return
default:
// Switch #1 failed to identify the exact class of self. Code will continue with Switch #2
break
}
// Switch #2: Attempt to identify if "self" is a subclass of a subclass of Parent (aka, subclass of "Child")
switch self {
case let subChild as Child:
// Case #3: Class of "self" != "Child" but is a subclass of "Child". (Maybe a different dev introduced "GrandChild" Class and forgot to tell you, classic)
print("\(className), unexpected subclass of Child")
subChild.printChildClass()
default:
// Case #4: Class of "self" could not be identified at all, but its still a subclass of "Parent". (Maybe marketing is testing an "Uncle" Class?)
print("\(className), unexpected subclass of Parent")
break
}
}
}
class Child: Parent {
func printChildClass() {
print("\(className), subclass of \(String(describing: class_getSuperclass(type(of: self))!))")
}
}
// Unexpected Subclasses
class GrandChild: Child {}
class Uncle: Parent {}
Test:
let parent = Parent()
parent.printClassOrSubclass()
parent.printClassOrSubclassWithDowncasting()
print("\n")
let child = Child()
child.printClassOrSubclass()
child.printClassOrSubclassWithDowncasting()
print("\n")
let grandChild = GrandChild()
grandChild.printClassOrSubclass()
grandChild.printClassOrSubclassWithDowncasting()
print("\n")
let uncle = Uncle()
uncle.printClassOrSubclass()
uncle.printClassOrSubclassWithDowncasting()
Result:
Parent
Parent
Child, subclass of Parent
Child, subclass of Parent
GrandChild, subclass of Parent
GrandChild, unexpected subclass of Child
GrandChild, subclass of Child
Uncle, subclass of Parent
Uncle, unexpected subclass of Parent
Notes:
I did say simple, but I wanted to make a thorough response addressing most cases. That said, you can just adjust/simplify the code for your specific use case. Also the 2 switch statements could be combined into 1, but you must have the switch cases within in perfect order (must always check for exact class, before checking subclass), otherwise you ll get wrong results. For my answer, I was having in mind that multiple devs could be handling and adding new classes and subclasses. So, imo the 2 switches should encourage having a better structure and less confusion for a longer term.
Upvotes: 1
Reputation: 93141
You get the warning because the the function is attached to the class it's defined within. As such, the compiler already knows ahead of time what type self
is. But your design is not a good one at all. For example, let's define a printMe
function on the parent:
class Parent
{
func printMe() {
if self is Parent {
print("Parent")
}
}
}
class Child: Parent {}
let child = Child()
child.printMe() // Output: Parent
Okay, the Child
class inherits printMe
from Parent
, so let's override it:
class Child: Parent {
func printMe() {
if self is Child {
print("Child")
}
}
}
And you have to redefine printMe
for every subclass. In each case, the compiler already know what class the function belongs to so why bother doing the is
test?
The proper design pattern is to use a protocol:
class Parent: MyProtocol {}
class Child: Parent {}
protocol MyProtocol {
func printMe()
}
extension MyProtocol {
func printMe() {
if let me = self as? Child {
print("Child")
} else if let me = self as? Parent {
print("Parent")
}
// Do something with `me` or you gonna get another warning
}
}
let parent = Parent()
let child = Child()
parent.printMe()
child.printMe()
Upvotes: 3
Reputation: 55665
This can be accomplished using the as
type cast operator:
var child = Child()
if let child = child as? Child {
//you know child is a Child
} else if let parent = child as? Parent {
//you know child is a Parent
}
There is also the is
keyword:
if child is Child {
//is a child
}
Note that in your code you're seeing a warning using is
- it will always be true, because comparing self
to Parent
from within class Parent
will always be true
. If you were comparing it against some other instance of a class instead of self
, or if you were comparing against some other type besides Parent
, this warning would disappear.
I would recommend reading more about this in the Swift Programming Language book, found on the iBooks Store - see the Type Casting chapter.
Upvotes: 7