简体   繁体   中英

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 . Now imagine I want to merge B into A through a statement like

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

  • How can I make sure that the arguments to merge are both 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

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)
  2. val types for both params are equals

Scenario 1: params have the same type

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

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.

So we could also merge different instances of different classes having val of the same type like

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

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

An example of using reflection to do this in 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). You could also make this a static method that could merge any 2 objects of any NSObject type:

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

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

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