swift How to implement the copy method more simply? - ios

I have created multiple classes, all of which need to implement the NSCopying protocol, but there are a lot of properties in my classes, is there an easier way?
Below is my current way:
class TestA: NSObject, NSCopying {
var a: CGFloat = 0
var b: CGFloat = 0
required override init() {
}
func copy(with zone: NSZone? = nil) -> Any {
let item = type(of: self).init()
item.a = a
item.b = b
return item
}
}
class TestB: TestA {
var c: CGFloat = 0
var d: CGFloat = 0
override func copy(with zone: NSZone? = nil) -> Any {
let item = super.copy(with: zone) as! TestB
item.c = c
item.b = b
return item
}
}
My thought is, can we take all the properties of the class, automatically create a new object, assign values to the new object?

Use the initializer.
class TestA: NSObject, NSCopying {
var a: CGFloat = 0
var b: CGFloat = 0
required override init() {}
convenience init(a: CGFloat, b: CGFloat) {
self.init()
self.a = a
self.b = b
}
func copy(with zone: NSZone? = nil) -> Any {
let item = TestA(a: a, b: b)
return item
}
}
Doing it this way doesn't really save code since you still need an initializer that takes values for all properties, but you do get a simplified copy method and another initializer that might be useful in other situations too.

You can look at KeyValueCoding package with KeyValueCoding protocol which implements enumeration of all properties of an object and setting values by key paths for pure swift classes and structs.
Based on it you can implement Copying protocol:
protocol Copying: KeyValueCoding {
init()
}
extension Copying {
func makeCopy() -> Self {
var item = Self()
var _self = self
metadata.properties.forEach {
item[$0.name] = _self[$0.name]
}
return item
}
}
How it works:
class TestA: Copying {
var a: CGFloat = 1
var b: Int = 2
required init() {}
}
class TestB: TestA {
let c: String = "Hello Copy!"
let d: Date = Date(timeIntervalSince1970: 123456789)
}
let objectA = TestA()
objectA.a = 100
objectA.b = 200
let copiedA = objectA.makeCopy()
print(copiedA.a) // "100.0"
print(copiedA.b) // "200"
let objectB = TestB()
objectB.a = 100
objectB.b = 200
let copiedB = objectB.makeCopy()
print(copiedB.a) // "100.0"
print(copiedB.b) // "200"
print(copiedB.c) // "Hello Copy!"
print(copiedB.d.timeIntervalSince1970) // "123456789.0"
So as you can see this approach works with inherited properties as well.
Moreover it works with structs:
struct MyStruct: Copying {
let a = 1.0
let b = 2
let c = "c"
}
let myStruct = MyStruct()
let copied = myStruct.makeCopy()
print(copied) // MyStruct(a: 1.0, b: 2, c: "c")

I think I've found a solution, but I'm not sure if this will have any ill effects. Can someone tell me what's wrong with this。Thanks!
#objcMembers class TestA: NSObject, NSCopying {
var a: CGFloat = 0
var b: CGFloat = 0
var c: CGFloat = 0
required override init() {
}
func copy(with zone: NSZone? = nil) -> Any {
let item = type(of: self).init()
for property in getAllPropertys() {
let value = self.value(forKey: property)
item.setValue(value, forKey: property)
}
return item
}
func getAllPropertys()->[String]{
var result = [String]()
var count:UInt32 = 0
let proList = class_copyPropertyList(object_getClass(self),&count)
for i in 0..<numericCast(count) {
let property = property_getName((proList?[i])!);
let proper = String.init(cString: property)
result.append(proper)
}
return result
}
}

Related

Builder pattern set method in swift

I am just shifted from Android base to ios, looking for builder pattern get and set method in swift unable to find anything like it. Found following only
var ptype : String? {
get{
return self.ptype
}set (ptype) {
self.ptype = ptype
}
}
After using so many libraries written in Swift, I have rarely seen people use the builder pattern in Swift.
I think the Builder Pattern's main advantages can already be achieved with Swift's other language features. You can totally create a constructor where all the parameters are optional, and you almost just recreated the builder pattern in Swift:
class Foo {
let a: Int
let b: String
let c: Bool
init(a: Int = 0, b: String = "", c: Bool = false) {
self.a = a
self.b = b
self.c = c
}
}
You can create a Foo like this:
// You can omit any of the arguments, just like the builder pattern
Foo(
a: 123
b: "Hello World"
c: true
)
I would argue that's an even cleaner version of something like this in Java:
new FooBuilder()
.setA(123)
.setB("Hello World")
.setC(true)
.build()
But if you insist, here is some really verbose Swift that implements the Builder pattern:
class Foo {
private(set) var a: Int = 0
private(set) var b: String = ""
private(set) var c: Bool = false
init(a: Int = 0, b: String = "", c: Bool = false) {
self.a = a
self.b = b
self.c = c
}
class Builder {
private var innerFoo = Foo()
#discardableResult
func withA(_ a: Int) -> Builder {
innerFoo.a = a
return self
}
#discardableResult
func withB(_ b: String) -> Builder {
innerFoo.b = b
return self
}
#discardableResult
func withC(_ c: Bool) -> Builder {
innerFoo.c = c
return self
}
func build() -> Foo {
return innerFoo
}
}
}
The following tip looks like what you wanted https://github.com/vincent-pradeilles/swift-tips#implementing-the-builder-pattern-with-keypaths. I'll copy code here for quick looking.
protocol With {}
extension With where Self: AnyObject {
#discardableResult
func with<T>(_ property: ReferenceWritableKeyPath<Self, T>, setTo value: T) -> Self {
self[keyPath: property] = value
return self
}
}
extension UIView: With {}
let view = UIView()
let label = UILabel()
.with(\.textColor, setTo: .red)
.with(\.text, setTo: "Foo")
.with(\.textAlignment, setTo: .right)
.with(\.layer.cornerRadius, setTo: 5)
view.addSubview(label)

Hashable == method does not detect difference between two objects swift

I implemented the class below:
class Table : Hashable {
var uid : Int
var timeRemaining : Int?
var currentPrice : Double?
var hashValue: Int {
return uid.hashValue
}
static func ==(lhs: Table, rhs: Table) -> Bool {
return lhs.uid == rhs.uid && lhs.timeRemaining == rhs.timeRemaining && lhs.currentPrice == rhs.currentPrice
}
init (uid: Int, timeRemaining: Int?, currentPrice: Double?) {
self.uid = uid
self.timeRemaining = timeRemaining
self.currentPrice = currentPrice
}
}
I've also defined an array of objects of this class:
private var tables = [Table]()
Next, I have the following method which runs every second:
func updateAuctions() {
let oldItems = tables
let newItems = oldItems
for table in newItems {
let oldPrice = table.currentPrice!
let timeRemaining = table.timeRemaining!
table.currentPrice = oldPrice + 0.50
table.timeRemaining = timeRemaining - 1
}
let changes = diff(old: oldItems, new: newItems)
collectionView.reload(changes: changes, section: 0) { (complete) in
if (complete) {
self.tables = newItems
}
}
}
This uses the DeepDiff framework described here: https://github.com/onmyway133/DeepDiff
My goal is to refresh the UICollectionView with the changes made to the tables array, however no changes are detected by the framework, even though my == method checks that the timeRemaining and currentPrice match.
let newItems = oldItems
Since both array contain object instances, wouldn't they just point to the same objects? So when you iterate through newItems and changing values, you are essentially changing values of oldItems too. You can verify this by printing the values of both array after the for loop.
Maybe you can try something similar to the following?
func updateAuctions() {
let oldItems = tables
let newItems = [Table]()
for item in oldItems {
newItems.append(Table(uid: item.uid, timeRemaining: item.timeRemaining! - 1, currentPrice: item.currentPrice! + 0.50))
}
let changes = diff(old: oldItems, new: newItems)
collectionView.reload(changes: changes, section: 0) { (complete) in
if (complete) {
self.tables = newItems
}
}
}

Observation is not getting fired in Swift 4.2

Any ideas why Swift is not smart enough to infer the parameters passed to the observeWrapper function.
Code:
let implementation = QuestionJSONStrategy(name: questionGroup.course.rawValue)
_ = observeWrapper(implementation)
showQuestion()
}
func observeWrapper<T: NSObject & QuestionStrategy>(_ object: T) -> NSKeyValueObservation {
return object.observe(\.questionIndex, options: .new) { _, change in
guard let newValue = change.newValue else { return }
print(newValue)
}
}
QuestionStrategy Protocol:
#objc protocol QuestionStrategy :AnyObject {
var questions :[Question] { get set}
var questionIndex :Int { get set }
init(name :String)
func nextQuestion() -> Question
}
QuestionJSONStrategy Class:
#objc public class QuestionJSONStrategy :NSObject, QuestionStrategy {
var questions: [Question] = [Question]()
#objc dynamic var questionIndex: Int = 0

Swift 2 Correct way to get level of any object game, protocol? struct? extension?

This is my first game, and I'm new on swift and sprite kit.
I must have a level for each class that needs get level. Like car lev1 car lev 2 etc. I have read about protocol extension etc, witch is the best way to approach level management?
I have tried to use LevelTraker as extension of this protocol:
protocol LevelTracker {
typealias TypeUnit: TypeGame
var nameClass: String! {get set}
var currentLevel : Int {get set}
mutating func levelIncreases()
}
but with extension, i must write 3 var each class that needs level.
i try the same extension LevelTraker with struct LevelTraker:
func getClassName (theClass:AnyObject) -> String {
let name = _stdlib_getDemangledTypeName(theClass); return name}
protocol TypeGame {}
enum transportType : TypeGame {
case ground, sea, air
}
struct LevelTracker {
var sender: AnyObject
var TypeUnit: TypeGame
private func getSaveFileWhitName() -> String {
let saveWithName = getClassName(sender) + "." + String(TypeUnit)
return saveWithName
}
var currentLevel : Int {
get {
let stringName = getSaveFileWhitName()
let returnValue : Int = dataBase.read(stringName) as? Int ?? 1 //Check for first run of app, if = nil, set = 1
return returnValue
}
set (newValue) {
let stringName = getSaveFileWhitName()
let level : Int = self.currentLevel
let val = newValue
if (newValue > level) {dataBase.write(val, key: stringName)}
}
}
mutating func levelIncreases() {self.currentLevel++}
///SERVE SOLO PER SVILUPPO
mutating func RESETLEVEL() {dataBase.write(1, key: getSaveFileWhitName())}
}
To use: (thanks #Krzak)
class car {
init () {
let level = LevelTracker(sender: self, TypeUnit: transportType.ground).currentLevel
}
}
But I don't want modify all init object that use level, and the super super class in common, some class don't have propriety level.
The reason why you have compiler error is in your last line. You're missing the .ground
I'm not sure how you're thinking though that this will work, shouldn't it be var?
var level = LevelTracker(sender: self, TypeUnit: transportType.ground).currentLevel
What I am reading it sounds like you are doing this:
class Level : AnyObject
{
private func getSaveFileWhitName() -> String {
let saveWithName = getClassName(sender) + "." + String(TypeUnit)
return saveWithName
}
var currentLevel : Int {
get {
let stringName = getSaveFileWhitName()
let returnValue : Int = dataBase.read(stringName) as? Int ?? 1 //Check for first run of app, if = nil, set = 1
return returnValue
}
set (newValue) {
let stringName = getSaveFileWhitName()
let level : Int = self.currentLevel
let val = newValue
if (newValue > level) {dataBase.write(val, key: stringName)}
}
}
mutating func levelIncreases() {self.currentLevel++}
///SERVE SOLO PER SVILUPPO
mutating func RESETLEVEL() {dataBase.write(1, key: getSaveFileWhitName())}
}
class car : Level
{
init () {
let level = self.currentLevel
}
}
I found a solution, I'm happy to have some comment.
protocol TypeGame {}
enum transportType : TypeGame {
case car, bus, trak
}
protocol LevelTracker {
var nameClass: String! {get}
var currentLevel : Int {get set}
mutating func levelIncreases()
}
extension LevelTracker {
var currentLevel : Int {
get {/*set to DB*/ return 1}
set (newValue) {/*set to DB*/}
}
mutating func levelIncreases() {self.currentLevel++}}
A protocol only for transport object:
protocol Transport : LevelTracker {}
Ok, now my (simplified) class are:
class AllNode {//SKSpriteNode
init(){}
}
class TransportGame:AllNode, Transport {
var nameClass : String! = "Transport"
override init() {
super.init()
self.nameClass = nameClass + "." + getClassName(self)}
}
class Car : TransportGame {}
class miniCar : Car {}
class Bus: TransportGame {}
class Tree: AllNode {}
var carOne = Car()
let levelCar = carOne.currentLevel
var busOne = Bus()
let levelBue = busOne.currentLevel
var treeOne = Tree()
tree.currentLevel //ERROR YUPPI!!!! :)
Now the tree class can't access to level!
What do you think about this solution?

Swift Protocol Conformance

Here is an example of a set of value relationships that I am toying around with.
protocol Configurable {
func configure(data: Any?) -> Void
}
class RelatedObject {
var x: String = ""
var y: String = ""
}
class Example {
var a: String = ""
var b: String = ""
var c: String = ""
}
extension Example: Configurable {
func configure(data: Any?) //I want the parameter to be of type RelatedObject?, not Any?
{
if let d = data as? RelatedObject { //I don't want to have to do this every time i implement Configurable on an object.
//do stuff
a = d.x
b = d.y
c = d.x + d.y
}
}
}
Is there a way for my classes that implement the Configurable protocol to be able to restrict the specific type of object they accept within the function signature?
I feel like Swift would/could/should have a way avoid a situation where I have to check class types for what gets passed into my object that I want configured.
You are looking for typealias in your protocol definition.
protocol Configurable {
typealias InputData
func configure(data: InputData) -> Void
}
In anything that implements your protocol you set the typealias to the type you would like.
class RelatedObject {
var x: String = ""
var y: String = ""
}
class Example {
var a: String = ""
var b: String = ""
var c: String = ""
}
extension Example: Configurable {
typealias InputData = RelatedObject
func configure(data: InputData) {
a = data.x
b = data.y
c = data.x + data.y
}
}

Resources