Reputation: 3223
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
.
merge
are both NSObjects?
Upvotes: 1
Views: 2175
Reputation: 20257
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!)
}
}
}
}
Upvotes: 1
Reputation: 59536
Lets define a HasValue
protocol (available only for classes) like this
protocol HasValue: class {
typealias T
var val: T? { get set }
}
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
HasValue
(therefore are classes)val
types for both params are equalsclass 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)
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)
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")
Upvotes: 1
Reputation: 763
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
}
}
Upvotes: 1