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

后端 未结 3 2251
温柔的废话
温柔的废话 2020-12-30 16:09

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

Thanks.

3条回答
  •  伪装坚强ぢ
    2020-12-30 16:35

    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 
    
    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.

提交回复
热议问题