19

In Objective-C, I created a very handy category (an extension, in Swift terms) of NSObject that added the ability to add arbitrary key/value pairs to any NSObject at runtime. It uses associated objects to attach a mutable dictionary to the object, and then provides get and set methods that get/set key/value pairs to/from that dictionary. It's only a few lines of code.

This makes it possible to attach arbitrary key/value pairs to ANY object at runtime, including objects created by the system. That's the key. There are cases where a system framework returns an object to you and you need to be able to attach a value to it.

This trick also makes it possible to create categories that have new instance variables. (Ok, they don't really, but for it does let you add new state variables to objects in a category.)

This isn't possible in Swift 1.2 because:

  • Swift doesn't have a base class for all objects like NSObject in Objective-C. It uses AnyObject, which is a protocol.
  • Swift 1.2 doesn't allow extensions to protocols.

I had to give up on this under Swift 1.2.

But Swift 2 allows extensions to protocols. I thought "Great, now I can add my extension that lets me add key/value pairs to AnyObject!"

No joy.

When I try to create my extension for AnyObject:

extension AnyObject: AssociatedObjectProtocol

I get the error message

'AnyObject' Protocol cannot be extended

Arghh! So close, but nope. It seems like the language explicitly forbids extending AnyObject. Why is this, and is there any way around it?

I don't use my category on NSObject that often, but when I do, it's a lifesaver. I'd like to add it to my bag of tricks in Swift.

I could add it to NSObject just like I do in Objective-C, but that means it only works for objects that inherit from NSObject - making it not work for native Swift classes.

Duncan C
  • 128,072
  • 22
  • 173
  • 272
  • 1
    Random note: I don't think you want your protocols to start with a lower-case letter. https://developer.apple.com/library/mac/documentation/Cocoa/Conceptual/CodingGuidelines/Articles/NamingBasics.html#//apple_ref/doc/uid/20001281-1002242-BBCIJGDB – Dan Rosenstark Nov 13 '15 at 18:38

3 Answers3

17

Unfortunately a workaround which does exactly the same as you want doesn't exist. So I would suggest to make a protocol which has an extension to add default implementations:

protocol A: AnyObject {}

// in Swift you would rather use this
protocol A: class {}

// add default implementations
extension A {
    func aMethod() {
        // code
    }
}

// Example
class B: A {}

// use the method
B().aMethod()

This approach does not make all classes automatically conform to this protocol (but only classes can conform to it). So you have to make them conform yourself. Since you don't use this as much this would be a reasonable solution.

Qbyte
  • 12,753
  • 4
  • 41
  • 57
  • I don't completely understand your suggestion. You're saying I would define the method signatures in a protocol, then provide the code as an extension to any class for which I need it? That means I have to duplicate the code in the extension for each target class, correct? – Duncan C Aug 13 '15 at 20:53
  • @DuncanC In this case you don't define any requirements in the protocol and you can use the method in the extension with any class that conforms to it without requiring additional code (added example in answer) – Qbyte Aug 13 '15 at 22:30
  • 1
    I can't make any sense out of the pseudocode in your answer. – Duncan C Aug 13 '15 at 23:26
  • 3
    @DuncanC What do you mean by pseudocode? This code is valid Swift 2 code. – Qbyte Aug 16 '15 at 13:37
  • Protocol extensions are odd... but this answer is amazing and helpful. I'm using it in my weird question-answer here: http://stackoverflow.com/a/33697088/8047 Thanks! – Dan Rosenstark Nov 13 '15 at 16:25
7

Along the lines of @Qbyte's answer, declaring that NSObject conforms to a protocol means that almost every class in the entire Cocoa/UIKit/Foundation universe will inherit that functionality:

protocol MyProtocol {
    func doSomething()
}

extension MyProtocol {
    func doSomething() {
        print("This is me, doing something.")
    }
}

extension NSObject: MyProtocol { }

You've just conformed 99% of Apple's framework classes to MyProtocol. You can make your own classes conform to MyProtocol by inheriting from NSObject or by simply conforming to it.

Aaron Rasmussen
  • 13,082
  • 3
  • 42
  • 43
  • 1
    This isn't really a solution. It's perfectly legal to use "extension NSObject" but it means for your own classes, you need to make all your own classes conform to NSObjectProtocol – original_username May 10 '16 at 07:57
  • This answer is quite ok, but using it it doesn't provides you some basic Swift compile time checks. For example, if you want to have a variable `var string: String { return self as! String }` and you call `arrayInstance.string` the compiler doesn't tell you anything, but the operation isn't possible as if you try `["element"] as! String` the compiler tells you that the operation always fails – Luca D'Alberti Oct 20 '16 at 13:38
  • if you're going to make NSObject conform to your protocol which is extended to include default behavior, then would it be different to extend NSObject directly instead of having a protocol? – Gobe Mar 16 '17 at 16:59
  • @Gobe, the advantage of doing it as a separate protocol, then making NSObject conform to that protocol is that you can also make *other* things conform to that protocol if you want to. If you only extend NSObject, you would have to subclass NSObject which may not always be preferable. – Mark A. Donohoe Oct 13 '18 at 02:55
-1

I feel like you had the answer in front of you. Extending NSObject works perfectly in Swift too. (Maybe not 6 years before tho )

extension NSObject { }

Oleg G.
  • 550
  • 5
  • 25
  • The poster was aware they can extend NSObject. They wanted a way to extend all native Swift classes. This is what they wrote: "I could add it to NSObject just like I do in Objective-C, but that means it only works for objects that inherit from NSObject - making it not work for native Swift classes. " – Patrick Nov 10 '21 at 10:00
  • A post already exist about it here: https://stackoverflow.com/a/30176106/5899647 the only way is to inherit from NSObject everywhere and extend it, or a custom protocol as the example given by @Aaron Rasmussen. This is why i said the answer is here already. – Oleg G. Nov 11 '21 at 21:39
  • Also, depending on what you exactly need, you might as well create global function and/or global variable. (could be even a singleton to make it "cleaner") – Oleg G. Nov 11 '21 at 21:41