How to override NSDate description? ..Method Swizzling?

764 Views Asked by At

How can I call

print(NSDate()) 

and instead of receiving the usual response, obtain the one I have in a function named getString() that is part of an extension of NSDate.

Here's my extension:

extension NSDate {
    
   //NSDate to String
    public func getString() -> String {
        let dateFormatter = NSDateFormatter()
        dateFormatter.dateFormat = "yyyy-MM-dd HH:mm:ss ZZZ"
        dateFormatter.locale = NSLocale.currentLocale()
        
        return dateFormatter.stringFromDate(self)
    }
}

Please, note that I don't want to just use:

NSDate().getString()

I want to override original description of this class.

UPDATE:

So, everything looks like the only option is Method Swizzling, if even possible.

Anyone interested in the bounty?

HEADS UP

I'm doing this just for personal growth and to get to understand the concept, not planning on shipping an App using it in this scenario, not even sure right now in what scenarios I could be using it.

5

There are 5 best solutions below

3
On BEST ANSWER
import Foundation

extension NSDate: Streamable {
    public func writeTo<Target : OutputStreamType>(inout target: Target) {
        let dateFormatter = NSDateFormatter()
        dateFormatter.dateFormat = "yyyy-MM-dd HH:mm:ss ZZZ"
        dateFormatter.locale = NSLocale.currentLocale()

        print("without swizzling", dateFormatter.stringFromDate(self), toStream: &target)
    }
}

let date = NSDate()

print(date) // without swizzling 2016-03-05 00:09:34 +0100

to print 'default' / original behavior / use

print(date.description)

if you are worry about using print in your extension just replace it with

//print("without swizzling", dateFormatter.stringFromDate(self), toStream: &target)
let str = dateFormatter.stringFromDate(self)
str.writeTo(&target)
1
On

By definition Extensions can add functionalities but not override them. Souce: The Swift Programming Language - Extensions

What you can do is implement the protocol CustomStringConvertible and replace that behaviour:

extension NSDate : CustomStringConvertible {
   var description: String {
     let dateFormatter = NSDateFormatter()
        dateFormatter.dateFormat = "yyyy-MM-dd HH:mm:ss ZZZ"
        dateFormatter.locale = NSLocale.currentLocale()

        return dateFormatter.stringFromDate(self)
  }
}
3
On

You could define an override of NSDate in your module with the same class name. You have to follow the guidelines for overriding NSDate, which is a bit tedious (but it works in the playground at least)

The compiler should use your override instead of the foundation one when in scope:

 public class NSDate:Foundation.NSDate
 {
     override public var description:String { return getString() }

     private var dateValue:NSTimeInterval = Foundation.NSDate().timeIntervalSinceReferenceDate

     override public var timeIntervalSinceReferenceDate:NSTimeInterval { return dateValue }

     override public init()
     { super.init() }

     override public init(timeIntervalSinceReferenceDate ti: NSTimeInterval)
     { 
       super.init()
       dateValue = ti 
     }

     required public init?(coder aDecoder: NSCoder)
     { super.init(coder:aDecoder) }

     //NSDate to String
     public func getString() -> String 
     {
         let dateFormatter = NSDateFormatter()
         dateFormatter.dateFormat = "yyyy-MM-dd HH:mm:ss ZZZ"
         dateFormatter.locale = NSLocale.currentLocale()
         return dateFormatter.stringFromDate(self) + " This is the Overriden One"
     }
 }

 print("\(NSDate())")

prints: 2016-03-04 18:16:22 -0500 This is the Overriden One

note that this actually overrides the description variable and not merely trick the print function.

3
On

I'm pretty sure this is a terrible, bad, no-good, horrible idea. But here you go:

extension NSDate {
    private static let dateFormatter = NSDateFormatter()
    private static var once = dispatch_once_t()
    static func swizzleDescription() {
        dispatch_once(&once) {
            dateFormatter.dateFormat = "yyyy-MM-dd HH:mm:ss ZZZ"
            dateFormatter.locale = NSLocale.currentLocale()

            let originalMethod = class_getInstanceMethod(self, "description")
            let replacementMethod = class_getInstanceMethod(self, "description_terribleIdea")
            method_exchangeImplementations(originalMethod, replacementMethod)
        }
    }

    func description_terribleIdea() -> String {
        return NSDate.dateFormatter.stringFromDate(self)
    }
}

let date = NSDate()
print(date)
NSDate.swizzleDescription()
print(date)

Output:

2016-03-04 22:17:20 +0000
2016-03-04 16:17:20 -0600
2
On

could define your own version of print:

func print(date: NSDate) {
    print(date.getString())
}

extension NSDate {

    //NSDate to String
    public func getString() -> String {
        let dateFormatter = NSDateFormatter()
        dateFormatter.dateFormat = "yyyy-MM-dd HH:mm:ss ZZZ"
        dateFormatter.locale = NSLocale.currentLocale()

        return dateFormatter.stringFromDate(self)
    }
}

both of these calls will now print the same thing:

let date = NSDate()
print(date.getString())
print(date)