Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Is it possible in Swift to add variables to an object at runtime?

Tags:

swift

Specifically, I would like to add a variable of type enum to an instance of UIView, without subclassing or create an extension.

Thanks.

like image 995
Víctor Albertos Avatar asked Aug 18 '14 17:08

Víctor Albertos


People also ask

Can we declare variables in extension in Swift?

With Obj-c Categories you can only add methods, not instance variables. In you example you have used @property as a shortcut to adding getter and setter method declarations. You still need to implement those methods. Similarly in Swift you can add use extensions to add instance methods, computed properties etc.

What is runtime in Swift?

The Swift runtime exports standard metadata objects for Builtin types as well as standard value witness tables that can be freely adopted by types with common layout attributes.

How do I create a variable instance in Swift?

Creating an Instance of a Class As you can see, to create an instance of a class, you declare a variable to hold a reference to the class, type an equal sign (the assignment operator) and then the name of the class followed by parentheses.


3 Answers

Here is a simple but complete example derived from jckarter's answer.

It shows how to add a new property to an existing class. It does it by defining a computed property in an extension block. The computed property is stored as an associated object:

import ObjectiveC

// Declare a global var to produce a unique address as the assoc object handle
var AssociatedObjectHandle: UInt8 = 0

extension MyClass {
    var stringProperty:String {
        get {
            return objc_getAssociatedObject(self, &AssociatedObjectHandle) as String
        }
        set {
            objc_setAssociatedObject(self, &AssociatedObjectHandle, newValue, objc_AssociationPolicy(OBJC_ASSOCIATION_RETAIN_NONATOMIC))
        }
    }
}
like image 115
Klaas Avatar answered Oct 18 '22 18:10

Klaas


The previous answer about objc_setAssociatedObject() is the right approach, but I think Apple's APIs for this have not yet been vetted, because I've had difficulty using them the way I think they ought to be used. (I shouldn't have to muck about with unsafe pointers and such.) Here's the solution I'm using now.

First, you need a bit of Objective-C glue (follow Apple's instructions for mixing Objective-C and Swift in the same project:

// RuntimeGlue.h
// Should be included from your bridging header.

@import Foundation;

void setAssociatedObject_glue(NSObject *object, const NSString *key, NSObject *value);
NSObject *getAssociatedObject_glue(NSObject *object, const NSString* key);


// RuntimeGlue.m

#import "RuntimeGlue.h"
#import <objc/runtime.h>

void setAssociatedObject_glue(NSObject *object, const NSString *key, NSObject *value) {
    objc_setAssociatedObject(object, (__bridge const void *)(key), value, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}

NSObject *getAssociatedObject_glue(NSObject *object, const NSString* key) {
    return objc_getAssociatedObject(object, (__bridge const void *)(key));
}

Next, the Swift methods you'll call from the rest of your program:

// Runtime.swift

import Foundation

public func setAssociatedObject(#object: NSObject, #key: NSString, #value: NSObject?) {
    setAssociatedObject_glue(object, key, value)
}

public func getAssociatedObject(#object: NSObject, #key: NSString) -> NSObject? {
    return getAssociatedObject_glue(object, key)
}

Finally, an example of use to tag a particular view controller's view as "debugging".

// MyViewController.swift

import UIKit

let debugKey: NSString = "DebugKey"

class MyViewController: UIViewController {
    override func viewDidLoad() {
        super.viewDidLoad()

        setAssociatedObject(object: self.view, key: debugKey, value: "debugging")
    }

    override func viewWillAppear(animated: Bool)  {
        super.viewWillAppear(animated)

        let val = getAssociatedObject(object: self.view, key: debugKey)
        println("val:\(val)")
    }
}

This approach lets you pass nil for value to the setter in order to clear the value for the key, and returns an optional from the getter. Note also that the key argument must be identical in both cases (k1 === k2) and not merely equivalent (k1 == k2).

Also note that this only lets you tag instances of NSObject or its subclasses-- it does not work for Swift native classes. The value must also be an NSObject subclass, but both strings and number literals automatically bridge to Objective-C, so you don't need to do any explicit casting.

like image 39
Wolf McNally Avatar answered Oct 18 '22 18:10

Wolf McNally


You can use the objc_setAssociatedObject() function from the Objective-C runtime to attach an object (you can wrap that enum into an object) onto another object, and objc_getAssociatedObject() to retrieve it.

like image 39
newacct Avatar answered Oct 18 '22 18:10

newacct