How can you mimic the description output?

2019-03-05 19:24发布

问题:

Imagine a class Fruit:

class Fruit: NSObject {
    override var description:String {
        return super.description
    }
}

var apple = Fruit()
var banana = Fruit()

print(apple)  // Output: <MyProject.Fruit: 0x7fa719627e00>
print(banana) // Output: <MyProject.Fruit: 0x7fa71962dab0>

Question: How can you mimic this ouput?

I currently have the following:

class Fruit: NSObject {
    override var description:String {
        print(super.description)
        return "<\(NSStringFromClass(self.dynamicType)): 0x\(String(self.hash, radix:16))>"
    }
}

Which now outputs the following:

<MyProject.Fruit: 0x7fb958c289a0>
<MyProject.Fruit: 0x7fb958c289a0>
<MyProject.Fruit: 0x7fb958c22df0>
<MyProject.Fruit: 0x7fb958c22df0>

As you can see the output is the same which is what I wanted. Now I am wondering if this is the proper way to mimic it's output or that I am overlooking something as mentioned in the comments below.

Credits: Matt, Martin R and Vacawama

回答1:

Any subclass of NSObject inherits the description method of NSObject (which is defined in the NSObjectProtocol):

class Foo1 : NSObject { }
print(Foo1())
// <MyProject.Foo1: 0x100612fd0>

This "default implementation" prints the class name and the memory address of the object, see for example Friday Q&A 2013-01-25: Let's Build NSObject, where it is shown how the Objective-C implementation could look like:

- (NSString *)description
{
    return [NSString stringWithFormat: @"<%@: %p>", [self class], self];
}

The %p format prints the value of a pointer as a hexadecimal number, preceded by 0x.

To mimic that in Swift, we can use

  • String(reflecting: self.dynamicType) which returns the fully-qualified class name as a string, and
  • unsafeAddressOf(self) which returns a pointer to the storage of the object.

Example (using square brackets [] to demonstrate that the overridden method is used):

class Foo2 : NSObject {
    override var description : String {
        let className = String(reflecting: self.dynamicType)
        let address = unsafeAddressOf(self)
        return String(format: "[%@: %p]", className, address)
    }
}
print(Foo2())
// [MyProject.Foo2: 0x100613310]

class Foo3 : Foo2 { }
print(Foo3())
// [MyProject.Foo3: 0x102000540]

This works for "pure Swift classes" as well, because no Foundation methods are used:

class Bar : CustomStringConvertible {
    var description : String {
        let className = String(reflecting: self.dynamicType)
        let address = unsafeAddressOf(self)
        return String(format: "[%@: %p]", className, address)
    }
}
print(Bar())
// [MyProject.Bar: 0x102001200]

Note that (as already mentioned in above comments), the hash value of an object is not necessarily identical to the memory address. A simple example is NSArray() whose hash value is just the number of elements:

let array = NSArray(objects: 1, 2, 3)
print(unsafeAddressOf(array)) // 0x00000001020011a0
print(array.hashValue) // 3

Update for Swift 3:

class Bar : CustomStringConvertible {
    var description : String {
        let className = String(reflecting: type(of: self))
        let address = Unmanaged.passUnretained(self).toOpaque()
        return "[\(className): \(address)]"
    }
}


回答2:

Use String(self.hash, radix:16). You might need to prefix the 0x yourself.