Swift - copy only non-nil properties from one Struct to another - swift

I am trying to copy the values JSON decoded Firebase RemoteConfig's new values struct to another local instance same type. Obviously it replaces all old values from new object, but I want to replace only properties that have non-nil, so the local property will preserve their default values.
eg:
struct Config {
let port: Int
let details: String?
}
var localConfig = Config(port: 80, details: "Default configuration")
var remoteConfig = Config(port: 100, details: nil)
/// Here I want to copy only those properties are not nil
/// otherwise leave its default value
localConfig = remoteConfig
print (localConfig)
print (remoteConfig)
Obviously I can check each property for nil and assign individually but its not easy if the structure if huge and complex. I just wanted to know if there any standard option in Swift to achieve this.

How about:
struct Config {
var port: Int
var details: String?
mutating func patch(with remote: Config){
self.port = remote.port
self.details = remote.details ?? self.details
}
}
Usage:
var local = Config(port: 2, details: "myDetail")
var remote = Config(port: 3, details: nil)
local.patch(with: remote)

Related

PropertyListDecoder handle key not found

I have a struct that is saved in user defaults as a property list. If the struct changes (perhaps after a app version update), the PropertyListDecoder().decode generates an error for key not found. For example, I've added the key "passwordProtect" to my struct. When the app gets the stored struct from user defaults and tries to decode it, it gives the error Swift.DecodingError.keyNotFound(CodingKeys(stringValue: "passwordProtect", intValue: nil)
The behavior I want in this case is that since passwordProtect is not set, I'd like to decode to my struct but with a default value for passwordProtect. I have already declared the variables default value in my struct. How can I get this behavior?
My struct:
struct Settings: Codable {
var showTimeOutMessage: Bool = false
var browserLimit: Bool = false
var browserLimitSeconds: Int = 300
var passwordProtect: Bool = false
var metaTime: TimeInterval?
init(fromDict : [String : Any?]?){...}
}
How I save it:
var settingsStruct = Settings(fromDict: formatedDict)
settingsStruct.metaTime = Date().timeIntervalSince1970
defaults.set(try? PropertyListEncoder().encode(settingsStruct), forKey:"settings")
How I retrieve it:
if let settingsData = defaults.value(forKey:"settings") as? Data {
let settingsStruct = try! PropertyListDecoder().decode(Settings.self, from: settingsData)
dump(settingsStruct)
}
Thanks!

How to create a pointer in Swift?

I'm working with Swift 3.
I would like to have this C syntax :
int myVar;
int *pointer = &myVar;
So modifying pointer or myVar does the same exact same thing.
Also I don't know if it makes any difference, but in my case myVar is an array containing elements of a class and pointer is a pointer to one element of this array.
The & also exists in Swift but can only be used as part of a parameter list (e.g. init, func, closure).
var i = 5
let ptr = UnsafeMutablePointer(&i)
print(ptr.pointee) // 5
// or
let ptr = UnsafeMutablePointer<Int>.allocate(capacity: 1)
ptr.initialize(to: 5)
// or with a closure
let ptr: UnsafePointer = { $0 }(&i)
(Assuming I understand what you're asking for....)
Try the following code in a playground. It should print "99" three times.
class Row {
var rowNumber = 0
}
var rows = [Row]()
let testRow = Row()
testRow.rowNumber = 1
rows.append(testRow)
let selectedRow = rows[0]
selectedRow.rowNumber = 99
print(testRow.rowNumber)
print(selectedRow.rowNumber)
print(rows[0].rowNumber)
By default, there's no copying of objects as part of an assignment statement. If it were a struct, that would be different.
Adding a bit for completeness:
If you want a similar effect with scalar values instead of objects, Swift supplies various types of wrappers.
let intPointer = UnsafeMutablePointer<Int>.allocate(capacity: 8) // Should be 1, not 8 according to comment re: docs
let other = intPointer
other.pointee = 34
print(intPointer.pointee)
(Warning: I haven't used these wrappers for anything except experimenting in a playground. Don't trust it without doing some research.)
Same example as #Phillip. But I used struct. In this example rows[0] won't change:
struct Row {
var rowNumber = 0
}
var rows = [Row]()
var testRow = Row()
testRow.rowNumber = 1
rows.append(testRow)
var selectedRow = rows[0]
selectedRow.rowNumber = 99
print(testRow.rowNumber) // prints 1
print(selectedRow.rowNumber) // prints 99
print(rows[0].rowNumber) // prints 1
There are no C style pointers (Unsafe Pointer) as the question asks however objects are shared by reference and structures are by value:
Swift assign, pass and return a value by reference for reference type and by copy for Value Type
structures are always copied when they are passed around in your code, but classes are passed by reference.
For example
How to have pointers/ references to objects
class Song {
init(title: String, image: String, file: String, volume: Float, queuePlayer: AVQueuePlayer, playerLooper: AVPlayerLooper?) {
self.title = title
self.image = image
...
}
var title: String
var image: String
...
}
var aSong = Song(title: "", image: "", ...)
var arrOfSongReferences: [Song] = [Song]()
arrOfSongReferences.append(aSong)
var ptrToASong: Song = aSong
aSong = nil
// Due to Swift garbage collection ARC (Automatic Reference Counting), we still have references to the original aSong object so it won't be deleted
If data is struct you cannot do this
struct Song {
var title: String
var image: String
...
}
var aSong: Song = Song(title: "", image: "", ...)
var copyOfASong: Song = aSong
Method
You can also pass by reference into a function
// this would be inside a class, perhaps Player. It doesn't have to be a static btw
static func playSound(_ sound: inout Song, volume: Float = 0.0) {
if (sound.playerLooper == nil) {
...
}
}
// usage
Player.playSound(sound: &aSong)

Swift 4, keyPath get error: cannot assign to immutable expression of type 'Any'

public struct Person {
var fid: Int
var name: String
}
public struct Contact {
var fid: Int
var name: String
}
var pks = [\Person.fid, \Person.name]
var cks = [\Contact.fid, \Contact.name]
var p = Person(fid: 10, name: "hello")
var c = Contact(fid: 11, name: "test")
c[keyPath: cks[0]] = p[keyPath: pks[0]]
I want copy Contact's values to Person use swift 4 keyPath. Get an
error: cannot assign to immutable expression of type 'Any'
I don't understand why?
c[keyPath: cks[0] as! WritableKeyPath<Contact, Int>] = p[keyPath: pks[0]] as! Int
will work. bug how can I do like this:
pks.indices.forEach { index in
let pk = pks[index]
let ck = cks[index]
c[keyPath: ck] = p[keyPath: pk]
}
I'm hitting the same issue but the problem seems to be that it cannot infer writeable types when you mix them:
var mixed = [\Person.fid, \Person.name] // [PartialKeyPath<Person>]
var ids = [\Person.fid, \Person.sid] // [ReferenceWriteableKeyPath<Person, Int]
var mixedIds = [\Person.fid, \Contact.fid] // [AnyKeyPath]
var strings = [\Person.firstName, \Person.surname] // [ReferenceWriteableKeyPath<Person, String>]
In theory this would work:
let person = Person()
person[keyPath:strings[0]] = strings[1]
There seems to be no way to use Swift 4 KeyPath functionality on dynamic types. The compiler needs to be able to infer the types at compile time, not run time. The error you are receiving is telling you just that - compiler can't infer value type and so can't guarantee the value can be changed.

Get and Set Array in Swift Class

I was build an IOS application, and somehow I have to do an aggregation in swift class, but every time I want to get the data, it always return an error. it seems like I the data always return a nil result.
I want to push the DoctorList member object (schedule) in the view controller class
I have create the object, and I also called the init() function. but, however, when I push the (or call) the init function for the DoctorList class and pass the array of ScheduleList, the content of the schedule member in doctorlist class will always be empty.
so when I try to get the schedule, it will return a nil result.
can every one tell me what I did wrong, because I already change the code but it still give a nil result.
I have two class like this
class DoctorList: NSObject {
var DoctorID: String?
var DoctorName: String?
var SpecialtyID: String?
var SpecialtyName: String?
var ImageURL: String?
var Schedule: [ScheduleList]?
init(_ DoctorID:String, _ DoctorName:String, _ SpecialtyID:String, _ SpecialtyName:String, _ ImageUrl:String, _ Schedule:[ScheduleList] ){
self.DoctorID = DoctorID
self.DoctorName = DoctorName
self.SpecialtyID = SpecialtyID
self.SpecialtyName = SpecialtyName
self.ImageURL = ImageUrl
for sc in Schedule {
self.Schedule?.append(ScheduleList(sc.DoctorID!, sc.DayName!, sc.FirstHour!, sc.LastHour!))
}
}
var getSchedule: [ScheduleList] {
get {
return self.Schedule!
}
}
and this one
class ScheduleList: NSObject {
var DoctorID: String?
var DayName: String?
var FirstHour: String?
var LastHour: String?
init(_ DoctorID:String, _ DayName:String, _ FirstHour:String, _ LastHour:String ){
self.DoctorID = DoctorID
self.DayName = DayName
self.FirstHour = FirstHour
self.LastHour = LastHour
}
the return value for the schedule was always empty
I'm sorry, could anyone give a suggestion how to make a global variable in swift?
You haven't initialized the Schedule array.
The append statement in the loop just never execute:
for sc in Schedule {
// self.Schedule is nil so anything come after the question mark does not run
self.Schedule?.append(ScheduleList(sc.DoctorID!, sc.DayName!, sc.FirstHour!, sc.LastHour!))
}
To fix it initialize your array before use:
self.Schedule = [ScheduleList]()
for sc in Schedule {
self.Schedule?.append(ScheduleList(sc.DoctorID!, sc.DayName!, sc.FirstHour!, sc.LastHour!))
}
Also, your code is a pain to read:
Optionals everywhere! You should decide what properties can and cannot be nil and get rid of the unnecessary ? and !
The convention in Swift is lowerCamelCase for variable names, and CamelCase for class names
No need to inherit from NSObject unless you want something from the ObjC world, whether working with ObjC or use KVO
Just noticed, recommendations from #CodeDifferent should be appropriate and helpful.
It is because you haven't initialised the Schedule in DoctorList
init(_ DoctorID:String, _ DoctorName:String, _ SpecialtyID:String, _ SpecialtyName:String, _ ImageUrl:String, _ Schedule:[ScheduleList] ){
self.DoctorID = DoctorID
self.DoctorName = DoctorName
self.SpecialtyID = SpecialtyID
self.SpecialtyName = SpecialtyName
self.ImageURL = ImageUrl
// Use this for init an empty array and append the content
self.Schedule = [ScheduleList]()
self.Schedule?.append(contentsOf: Schedule)
}
An example of the result:
let schedule = ScheduleList("scheduleId", "name", "1st", "last")
let doctorList = DoctorList("docId", "docName", "specId", "scpecName", "imgURL", [schedule])
if let list = doctorList.Schedule {
print("\(list[0].DoctorID)")
}

How to get unique value from Realm database in swift

I do news application in swift using Realm database. In my database have same news categories. How to get unique value from Realm database?
I use primary key
class News: Object {
dynamic var newsID: String = ""
dynamic var newsTitle: String = ""
dynamic var newsFullText: String = ""
dynamic var newsImage: String = ""
dynamic var newsAutor: String = ""
dynamic var newsCommentCount: String = ""
dynamic var newsSeenCount: String = ""
dynamic var newsDate: String = ""
dynamic var newsCategory: String = ""
override static func primaryKey() -> String? {
return "newsID"
}
}
I'm try to get
let realm = try! Realm()
let menuName = realm.objects(News)
for i in menuName.filter("newsCategory") {
nameLabel.text = i.newsCategory
}
But it is not work.
Starting from Realm 3.10 it's now possible to
Add Results.distinct(by:) / -[RLMResults
distinctResultsUsingKeyPaths:], which return a Results containing only
objects with unique values at the given key paths.
Old response - before Realm 3.10
It is not possible yet to obtain a "distinct"-like functonality from a Realm query (track the open issue here)
However, there are some workarounds suggested in the thread I mentioned above (please read it to get the full context), by user apocolipse :
// Query all users
let allUsers = Realm().objects(User)
// Map out the user types
let allTypes = map(allUsers) { $0.type }
// Fun part: start with empty array [], add in element to reduced array if its not already in, else add empty array
let distinctTypes = reduce(allTypes, []) { $0 + (!contains($0, $1) ? [$1] : [] )
Or better yet, a different approach using Sets (by user jpsim):
let distinctTypes = Set(Realm().objects(User).valueForKey("type") as! [String])
Obviously the workarounds aren't as efficient as a direct DB query, so use with care (and testing under realistic load).