【问题标题】:How to achieve object merging through generics in swift?如何在swift中通过泛型实现对象合并?
【发布时间】:2016-02-12 21:06:57
【问题描述】:

假设我有一个班级编号:

class Number {
    var val: Double?
}

并且有该类的两个实例,AB。 现在想象一下我想通过这样的语句将B合并到A

merge(B, into: A)

现在我当然可以这样写函数:

func merge(from: Number, into: Number){
    into.val = from.val
}

但这根本不能重复使用。有没有办法可以编写一个通用的合并类?

更新:尽管一些答案提供了良好且可行的解决方案,但它们都不够“通用”(这里的通用是指非技术性的) .所以看答案,我得到了一些启发,这是我现在正在考虑的解决方案:将 Number 设为 NSObject 子类,并将所有可以合并的属性声明为动态。例如:

class Number: NSObject {
    //Put the required init and initWithCoder: here
    dynamic var val: Double?
}

然后声明可合并类必须遵守的协议

protocol Mergeable: class {
    var mergeablePropertyKeys:[String] {get}
}

然后声明一个执行合并的全局函数:

func merge<U: Mergeable, Mergeable where U.Type == V.Type>(from: U, into:V){
    for property in U.mergeablePropertyKeys {
        V.setValue(U.valueForKey(property), property)
    }
}

而且我知道这行不通,因为要合并的参数不一定是NSObjects

  • 如何确保merge 的参数都是NSObjects?
  • 是否可以通过简单地获取对象的动态值列表来避免指定所有可合并值的名称?

【问题讨论】:

  • 泛型合并类是什么意思?全局通用合并功能?定义通用合并函数的协议?
  • 要么,要么,它可以通过我认为的协议来实现,我不太确定如何。
  • 你说的是映射吗?
  • 在 Swift 中使用 NSObject 确实意味着走错了方向。
  • @appzYourLife 我也有这种感觉,但我们看得更清楚吗?如果我们有良好的 ol' c 指针,这将非常容易。

标签: ios swift oop generics


【解决方案1】:

听起来你想要的是一个使用反射来合并属性的通用函数。反射在 Swift 中是有限的,但使用 MirrorType 是可行的。我之前使用过这种方法来构建一个通用的json parser in swift - 你可以做一些类似的事情,但不是解析一个 json 字典来映射你的两个对象的属性。

在 swift 中使用反射来做这件事的一个例子:

func merge<T>(itemToMerge:T) {
    let mirrorSelf = Mirror(reflecting: self)
    let mirrorItemToMerge = Mirror(reflecting: itemToMerge)
    for mirrorSelfItem in mirrorSelf.children {
        // Loop through items in mirrorItemToMerge.
        for mirrorImageItem in mirrorItemToMerge.children {
            // If you have a parameter who's name is a match, map the value
            // OR You could add any custom mapping logic you need for your specific use case
            if mirrorSelfItem.label == mirrorImageItem.label {
                // To set values, use self.setValue(valueToSet, forKey: propertyName)
                self.setValue(mirrorImageItem.value as? AnyObject, forKey: mirrorImageItem.label!)
            }
        }
    }
}

这假定定义合并方法的对象是 NSObject 的子类(因此它可以利用 NSKeyValueCoding)。你也可以把它变成一个静态方法,可以合并任意 NSObject 类型的任意 2 个对象:

static func merge<T1: NSObject, T2: NSObject>(itemChanging:T1, itemToMerge:T2) {
    let mirrorSelf = Mirror(reflecting: itemChanging)
    let mirrorItemToMerge = Mirror(reflecting: itemToMerge)
    for mirrorSelfItem in mirrorSelf.children {
        // Loop through items in mirrorItemToMerge.
        for mirrorImageItem in mirrorItemToMerge.children {
            // If you have a parameter who's name is a match, map the value
            // OR You could add any custom mapping logic you need for your specific use case
            if mirrorSelfItem.label == mirrorImageItem.label {
                // To set values, use self.setValue(valueToSet, forKey: propertyName)
                self.setValue(mirrorImageItem.value as? AnyObject, forKey: mirrorImageItem.label!)
            }
        }
    }
}

【讨论】:

  • 遗憾的是它只完成了一半的工作。这是获取对象的所有属性 + 值的绝妙方法,但是如何设置它们呢?
  • 检查 SwiftSerializable git repo 中的反序列化方法。它也处理这个问题。在您的情况下,虽然您不会创建新对象 - 它只会使用已经存在的对象。
  • setValue就是你所需要的——类似于Objective-C的键值编码,就是deserialize方法使用的。
  • 所以我的类需要是一个 NSObject 子类并且有'动态变量'是的?
  • 它只需要是一个 NSObject 子类。使用键值编码不需要动态。除非有特殊需要,否则我不会使用它们。
【解决方案2】:

我不确定您的期望,但有通用解决方案:

class Number<T> {
    var val: T?
}

protocol Merge {
    func merge(from: Self, into: Self)
}

extension Number: Merge {
    func merge(from: Number, into: Number) {
        into.val = from.val
    }
}

【讨论】:

  • 我有一个合并协议的想法,但为了避免这样的样板代码,我希望在其中定义可以合并的属性。
  • 而不是 into.val = from.val,只提到 val 将是最佳的
【解决方案3】:

协议

让我们像这样定义一个HasValue 协议(仅适用于类)

protocol HasValue: class {
    typealias T
    var val: T? { get set }
}

合并

现在我们可以定义一个泛型函数

func merge<U: HasValue, V:HasValue where U.T == V.T>(from: U, into:V) {
    into.val = from.val
}

函数签名中的约束确实保证了

  1. 两个参数都符合HasValue(因此是类)
  2. val 两个参数的类型相等

场景一:params 类型相同

class Number: HasValue {
    var val: Double?
}

let one = Number()
one.val = 1

let two = Number()
two.val = 2

merge(one, into: two)
print(two.val) // Optional(1.0)

场景2:参数类型不同,但值类型相同

我没有将Merge 的2 个参数限制为具有相同的类型,我只是检查2 个参数的val 属性必须具有相同的类型。

所以我们还可以合并具有相同类型的 val 的不同类的不同实例,例如

class Phone: HasValue {
    var val: Int?
}

class Computer: HasValue {
    var val: Int?
}

let iPhone = Phone()
iPhone.val = 10

let iMac = Computer()
iMac.val = 9

merge(iPhone, into: iMac)
print(iMac.val) // Optional(10)

场景 3:参数有泛型

class Box<S>: HasValue {
    var val: S?
}

let boxOfString = Box<String>()
boxOfString.val = "hello world"

let boxOfInt = Box<Int>()
boxOfInt.val = 12

merge(boxOfString, into: boxOfInt) // << compile error

let boxOfWords = Box<String>()
boxOfWords.val = "What a wonderful world"

merge(boxOfString, into: boxOfWords)
print(boxOfWords.val) // Optional("hello world")

【讨论】:

  • 但是当我想合并超过val 时会发生什么?如果我有一个需要合并的具有 1000 个属性的对象怎么办?
  • 如果我有一个需要合并的具有 1000 个属性的对象怎么办?你应该先分解那个对象。它太大而无用。
  • @vikingosegundo 你明白我的意思。我不想为要合并的每个属性都制定一个协议。
猜你喜欢
  • 1970-01-01
  • 2011-10-18
  • 1970-01-01
  • 2014-10-09
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2020-05-14
相关资源
最近更新 更多