简体   繁体   English

如何通过swift中的泛型实现对象合并?

[英]How to achieve object merging through generics in swift?

Imagine I have a class Number: 想象一下,我有一个班级编号:

class Number {
    var val: Double?
}

and have two instances of that class, A and B . 并且有两个类AB实例。 Now imagine I want to merge B into A through a statement like 现在假设我想通过类似的语句将B合并到A

merge(B, into: A)

Now of course I could write the function like this: 现在我当然可以写这样的函数:

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

But that isn't reusable at all. 但这根本不可重复使用。 Is there a way I could write a generic merge class? 有没有办法可以编写泛型合并类?

UPDATE : Although some of the answers offer good and viable solutions, none of them are "generic" enough (generic here is meant in a non-technical way).So looking at the answers, I got some inspiration, and here is the solution I am now considering: make Number a NSObject subclass and declare all the properties that can be merged as dynamic. 更新 :虽然一些答案提供了良好和可行的解决方案,但它们都不够“通用”(这里的通用意味着非技术方式)。所以我看到了答案,我得到了一些灵​​感,这就是解决方案我现在正在考虑:使Number成为一个NSObject子类,并声明所有可以合并为动态的属性。 For example: 例如:

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

Then declaring a protocol that mergeable classes must respect 然后声明可合并类必须遵守的协议

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

And then declaring a global function that performs a merge: 然后声明执行合并的全局函数:

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

And I know that this will not work because the arguments to merge are not necessarily NSObjects . 我知道这不会起作用,因为合并的参数不一定是NSObjects

  • How can I make sure that the arguments to merge are both NSObjects? 如何确保merge的参数都是NSObjects?
  • Can avoid having to specify the names of all my mergeable values by simply obtaining a list of my object's dynamic values? 通过简单地获取对象的动态值列表,可以避免必须指定所有可合并值的名称?

Im not sure what you are expecting but there is generic solution: 我不确定你的期望,但有通用的解决方案:

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

Protocol 协议

Lets define a HasValue protocol (available only for classes) like this 让我们像这样定义一个HasValue协议(仅适用于类)

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

Merge 合并

Now we can define a generic function 现在我们可以定义一个泛型函数

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

The constraints in the function signature do guarantee that 函数签名中的约束可以保证这一点

  1. Both params do conform to HasValue (therefore are classes) 两个参数都符合HasValue (因此是类)
  2. val types for both params are equals 两个参数的val类型是等于的

Scenario 1: params have the same type 场景1: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)

Scenario 2: params have different types but their values have the same type 场景2:params具有不同的类型,但它们的值具有相同的类型

I did not constrain the 2 params of Merge to having the same type, I am only checking that the val properties of the 2 params must have the same type. 我并没有将Merge的2个参数限制为具有相同的类型,我只是检查2个参数的val属性必须具有相同的类型。

So we could also merge different instances of different classes having val of the same type like 所以我们也可以合并具有相同类型的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)

Scenario 3: params have generic types 场景3:params具有泛型类型

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

It sounds like what you want is a generic function that uses reflection to merge properties. 听起来你想要的是一个使用反射来合并属性的泛型函数。 Reflection is limited in Swift, but it is doable using the MirrorType . Swift中的反射是有限的,但使用MirrorType是可行的。 I have used this method before to build a generic json parser in swift - you could do something similar but instead of parsing a json dictionary to properties map your two object's properties. 我之前使用过这个方法在swift中构建一个通用的json解析器 - 你可以做类似的事情但不是解析json字典到属性映射你的两个对象的属性。

An example of using reflection to do this in swift: 使用反射在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!)
            }
        }
    }
}

This assumes the object defining the merge method is a subclass of NSObject (so it can take advantage of NSKeyValueCoding). 这假设定义merge方法的对象是NSObject的子类(因此它可以利用NSKeyValueCoding)。 You could also make this a static method that could merge any 2 objects of any NSObject type: 你也可以使这个静态方法可以合并任何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!)
            }
        }
    }
}

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM