在Swift中,是否可以在运行时向对象添加变量?

6
具体来说,我想在UIView实例中添加一个枚举类型的变量,而不需要子类化或创建扩展。

谢谢。


2
可能可以使用ObjC运行时,Swift也共享它。然而,如果有人在考虑这个问题,最好认真思考一下,“为什么我需要这个?我真的需要这个吗?”这不是标准的做事方式,可能会有许多意想不到的陷阱。实现您的最终目标可能有更好的方法。 - BergQuester
如果我问了,那是因为我认为那是最好的方法。我知道标准的方式(子类化/扩展-还有更多吗?),但在这种情况下,它们都没有太多意义。无论如何,谢谢。 - Víctor Albertos
你介意分享一下背景吗?我总是对这样的问题很好奇。 - Nate Cook
我必须实现一个UI小部件(比如画廊),因为现有的任何一个都不符合我们的需求。为了达到一定程度的抽象,我实现了一组类。其中一个类通过协议公开了一个名为getView()的方法,该方法返回在客户端创建的视图。由于每次“画廊”需要下一个项目时,此视图都是由客户端提供的,因此它不能是UIView的子类。即便如此,该类仍然需要跟踪某些值,我认为最好将此信息作为动态属性在运行时添加到视图中。 - Víctor Albertos
如果你尝试使用抽象化的方式,却陷入了这样一个境地,即运行时是最有吸引力的选项,那么也许你应该重新考虑一下。听起来你正在试图将太多的状态混合到你的视图层中。也许使用 ModelView 可以整理好事情。 - CodaFi
4个回答

11

以下是一个简单但完整的示例,源自于jckarter的回答

它展示了如何向现有类添加新属性。这是通过在扩展块中定义计算属性来实现的。计算属性存储为关联对象:

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))
        }
    }
}

5

关于objc_setAssociatedObject()的先前答案是正确的方法,但是我认为苹果对此的API尚未经过审查,因为我在使用它们时遇到了困难,我认为它们应该被使用。 (我不应该去弄不安全的指针之类的东西。)这是我现在正在使用的解决方案。

首先,您需要一些Objective-C粘合剂(按照苹果的说明在同一个项目中混合使用Objective-C和Swift:

// 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));
}

下面是你在程序的其余部分中要调用的Swift方法:
// 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)
}

最后,这是一个使用标签将特定视图控制器的视图标记为“调试”的示例。
// 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)")
    }
}

这种方法允许你在setter中将value设置为nil以清除键的值,并从getter中返回一个可选类型。注意,key参数在两种情况下必须完全相同(k1 === k2),而不仅仅是等效的(k1 == k2)。

还要注意,这只能标记NSObject或其子类的实例 - 它对Swift原生类无效。 value必须也是NSObject子类, 但字符串和数字字面量都自动桥接到Objective-C,因此您不需要进行任何显式转换。


1
您可以使用Objective-C运行时中的objc_setAssociatedObject()函数将一个对象(您可以将该枚举包装成一个对象)附加到另一个对象上,并使用objc_getAssociatedObject()检索它。

0

objc_AssociationPolicy.OBJC_ASSOCIATION_COPY_NONATOMIC


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接