I have the following code which has 3 objects which are then part of a 4th object. I'm getting errors trying to create the init method for the aggregate object (GTFS) because I'm passing (or trying to pass) the type of the 3 component objects (Stop, Route, Trip). I'm not sure why those have to be initialized before just their types being used.
protocol GTFSObject {
static var fileName: String { get }
init(csvRow: [String: String])
}
struct Stop: GTFSObject {
static let fileName = "stops.txt"
let stopID: String
let stopCode: String
let stopName: String
let stopDesc: String
let stopLat: Double
let stopLon: Double
let locationType: Int
let parentStation: String
init(csvRow: [String: String]) {
self.stopID = csvRow["stop_id"]!
self.stopCode = csvRow["stop_code"]!
self.stopName = csvRow["stop_name"]!
self.stopDesc = csvRow["stop_desc"]!
self.stopLat = Double(csvRow["stop_lat"]!)!
self.stopLon = Double(csvRow["stop_lon"]!)!
self.locationType = Int(csvRow["location_type"]!)!
self.parentStation = csvRow["parent_station"]!
}
}
struct Trip: GTFSObject {
static let fileName = "trips.txt"
let routeID: String
let serviceID: String
let tripID: String
init(csvRow: [String: String]) {
tripID = csvRow["trip_id"] ?? ""
routeID = csvRow["route_id"] ?? ""
serviceID = csvRow["service_id"] ?? ""
}
}
struct Route: GTFSObject {
static let fileName = "trips.txt"
let routeID: String
let agencyID: String
let routeShortName: String
let routeLongName: String
let routeDesc: String
let routeType: Int
let routeURL: String
let routeColor: String
let routeTextColor: String
init(csvRow: [String: String]) {
routeID = csvRow["route_id"] ?? ""
agencyID = csvRow["agency_id"] ?? ""
routeShortName = csvRow["route_short_name"] ?? ""
routeLongName = csvRow["route_long_name"] ?? ""
routeDesc = csvRow["route_desc"] ?? ""
routeType = Int(csvRow["route_type"] ?? "0") ?? 0
routeURL = csvRow["route_url"] ?? ""
routeColor = csvRow["route_color"] ?? ""
routeTextColor = csvRow["route_text_color"] ?? ""
}
}
class GTFS {
let routes: [Route]
let stops: [Stop]
let trips: [Trip]
init(gtfsFolderUrl: URL) {
self.stops = init_struct_from_url(gtfsFolderUrl: gtfsFolderUrl, type: Stop.self)
self.trips = init_struct_from_url(gtfsFolderUrl: gtfsFolderUrl, type: Trip.self)
self.routes = init_struct_from_url(gtfsFolderUrl: gtfsFolderUrl, type: Route.self)
}
private func init_struct_from_url<GTFSType>(gtfsFolderUrl: URL, type: GTFSType.Type) -> [GTFSType] where GTFSType : GTFSObject{
var returnList: [GTFSType] = []
let rows = try! NamedCSV(url: GTFS_FOLDER_URL.appendingPathComponent(type.fileName), delimiter: CSVDelimiter.comma, loadColumns: false).rows
for row in rows {
returnList.append(type.init(csvRow: row))
}
return returnList
}
}
The error I get is
'self' used in method call 'init_struct_from_url' before all stored properties are initialized
I don't know why I have to initialize the properties of the struct just to pass the type of the struct to this other function. What am I missing?
It's not about the three types that you are passing to init_struct_from_url. It's about the call init_struct_from_url itself.
You are actually calling:
self.init_struct_from_url(...)
^^^^^
The error is saying that that use of self is not allowed, because self is not initialised. If init_struct_from_url uses a property in self, it could see an uninitialised value.
Since init_struct_from_url doesn't actually use self at all and is just a helper function, you can make it an inner function of init:
init(gtfsFolderUrl: URL) {
func structFromUrl<GTFSType>(gtfsFolderUrl: URL, type: GTFSType.Type) -> [GTFSType] where GTFSType : GTFSObject{
var returnList: [GTFSType] = []
let rows = try! NamedCSV(url: GTFS_FOLDER_URL.appendingPathComponent(type.fileName), delimiter: CSVDelimiter.comma, loadColumns: false).rows
for row in rows {
returnList.append(type.init(csvRow: row))
}
return returnList
}
self.stops = structFromUrl(gtfsFolderUrl: gtfsFolderUrl, type: Stop.self)
self.trips = structFromUrl(gtfsFolderUrl: gtfsFolderUrl, type: Trip.self)
self.routes = structFromUrl(gtfsFolderUrl: gtfsFolderUrl, type: Route.self)
}
i want to convert Array from struct to List Realm .
static func mapGenreResponsetoGenreEntity( input genre: [GenreModel]) -> List {
var list = List<GenreEntity>()
return genre.map { result in
let newGenre = GenreEntity()
newGenre.gamesCount = result.gamesCount ?? 0
newGenre.id = result.id ?? 0
newGenre.imageBackground = result.imageBackground ?? "Unknown"
newGenre.name = result.name ?? "Unknown"
newGenre.slug = result.slug ?? "Unknown"
list.append(newGenre)
return list
}
}
and the genre is
struct GenreModel: Codable {
let gamesCount : Int?
let id : Int?
let imageBackground : String?
let name : String?
let slug : String?
}
How can i convert from array genre (Struct) to List realm which is GenreEntity ?
This should just be a matter of adding the new GenreEntity objects to an array and then return the entire array once done.
This should do it
func convertToList(genreArray: [GenreClass]) -> List<GenreEntityRealmModel> {
let genreEntityList = List<GenreEntityRealmModel>()
genreArray.forEach { model in
let genreEntity = GenreEntity()
genreEntity.gamesCount = model.gamesCount
genreEntityList.append(genreEntity)
}
return genreEntityList
}
I load data from Firebase into my custom "CommentModel" (newComment):
// Load the comment with id
func observeComment(commentId: String, completion: #escaping (CommentModel) -> Void) {
let db = Firestore.firestore()
db.collection("comments").document(commentId).getDocument { (snapshot, error) in
guard let dic = snapshot?.data() else { return }
let newComment = CommentModel(dictionary: dic)
completion(newComment)
}
}
My Model:
import UIKit
class CommentModel {
var postId: String?
var userUid: String?
var postText: String?
var postDate: Double?
init(dictionary: [String: Any]) {
postId = dictionary["postId"] as? String
userUid = dictionary["userUid"] as? String
postText = dictionary["postText"] as? String
postDate = dictionary["postDate"] as? Double
}
}
Which gives me the following result:
["user2" : 5], ["user1" : 4], ["user2" : 3], ["user1" : 2], ["user1" : 1]
What I am trying to achieve: Call a function in "observeComments" and append the data if the userID does not already exists and if the number is the lower than the existing number and load it into a new "CommentModel", no matter how much comments or user I have.
The result should look like:
["user1" : 2], ["user1" : 1]
Because I have two userIDs and returning the lowest number.
Dictionary cannot have duplicate keys and the dictionaries are not based on sorting, they are based on key value. This means you can't order a dictionary.
But, on the other hand, remove a value from a dictonary in swift is easy you only needs to do
dic.removeValue(forKey: "user2")
With conditional
var hues = ["Heliotrope": 296, "Coral": 16, "Aquamarine": 156]
if let value = hues.removeValue(forKey: "Coral") {
print("The value \(value) was removed.")
}
// Prints "The value 16 was removed."
it's this simple,
if v < comments["john"] { comments["john'] = v }
I was trying convert struct to Dictionary in Swift. This was my code:
extension Encodable {
var dictionary: [String: Any]? {
if let data = try? JSONEncoder().encode(self) {
if let dict = try? JSONSerialization.jsonObject(with: data) as? [String: Any] {
return dict
}
return nil
}
return nil
}
}
This works in most situation. But when I try to convert a nested structure which contains unicode characters such as Chinese, this happened:
struct PersonModel: Codable {
var job: String?
var contacts: [ContactSimpleModel]
var manager: ManagerSimpleModel?
}
struct ContactSimpleModel: Codable {
var relation: String
var name: String
}
struct ManagerSimpleModel: Codable {
var name: String
var age: Int
}
let contact1 = ContactSimpleModel(relation: "朋友", name: "宙斯")
let contact2 = ContactSimpleModel(relation: "同学", name: "奥丁")
let manager = ManagerSimpleModel(name: "拉斐尔", age: 31)
let job = "火枪手"
let person = PersonModel(job: job, contacts: [contact1, contact2], manager: manager)
if let dict = person.dictionary {
print(dict)
}
The result of this code is this:
["contacts": <__NSArrayI 0x600002471980>(
{
name = "\U5b99\U65af";
relation = "\U670b\U53cb";
},
{
name = "\U5965\U4e01";
relation = "\U540c\U5b66";
}
)
, "manager": {
age = 31;
name = "\U62c9\U6590\U5c14";
}, "job": 火枪手]
You can see the result. The Chinese characters in those nested structures were become a utf-8 encoding string. The top-level property "job": 火枪手 is right. But the values in those nested structures were not the original string.
Is this a bug of JSONSerialization? Or how to make it right?
More information. I used the result like this:
var sortedQuery = ""
if let dict = person.dictionary {
sortedQuery = dict.sorted(by: {$0.0 < $1.0})
.map({ "\($0)\($1)" })
.joined(separator: "")
}
It was used to check whether the query was legal. The result is not the same as Java or other platform.
The result is perfectly fine. That's the internal string representation – a pre-Unicode legacy – of an array or dictionary when you print it.
Assign the values to a label or text view and you will see the expected characters.
Say I have a struct Coin
struct Coin {
var value: Float?
var country: String?
var color: String?
}
I have two instances of a Coin; we'll call them coinA and coinB.
let coinA = Coin()
coinA.value = nil
coinA.country = "USA"
coinA.color = "silver"
let coinB = Coin()
coinB.value = 50.0
Now, I want to merge the values of coinB into coinA. So the result would be coinA whose values would result in:
country = "USA"
color = "silver"
value = 50.0
I am able to accomplish this with Dictionary objects using the merge() function. However, I am unsure how to accomplish this using custom Swift objects. Is there a way?
Update
Here's how I've gotten it to work with dictionaries:
var originalDict = ["A": 1, "B": 2]
var newDict = ["B": 69, "C": 3]
originalDict.merge(newDict) { (_, new) in new }
//originalDict = ["A": 1, "B": 69, "C": 3]
And I will further clarify, in this function if the newDict does not have keys that the originalDict, the originalDict maintains them.
Ultimately, the most efficient way in the fewest lines of code is probably exactly what you'd expect:
extension Coin {
func merge(with: Coin) -> Coin {
var new = Coin()
new.value = value ?? with.value
new.country = country ?? with.country
new.color = color ?? with.color
return new
}
}
let coinC = coinA.merge(with: coinB)
Note that in the above scenario, the resulting value will always be coinA's, and will only be coinB's if coinA's value for a given key is nil. Whenever you change, add, or delete a property on Coin, you'll have to update this method, too. However, if you care more about future-proofing against property changes and don't care as much about writing more code and juggling data around into different types, you could have some fun with Codable:
struct Coin: Codable {
var value: Float?
var country: String?
var color: String?
func merge(with: Coin, uniquingKeysWith conflictResolver: (Any, Any) throws -> Any) throws -> Coin {
let encoder = JSONEncoder()
let selfData = try encoder.encode(self)
let withData = try encoder.encode(with)
var selfDict = try JSONSerialization.jsonObject(with: selfData) as! [String: Any]
let withDict = try JSONSerialization.jsonObject(with: withData) as! [String: Any]
try selfDict.merge(withDict, uniquingKeysWith: conflictResolver)
let final = try JSONSerialization.data(withJSONObject: selfDict)
return try JSONDecoder().decode(Coin.self, from: final)
}
}
With that solution, you can call merge on your struct like you would any dictionary, though note that it returns a new instance of Coin instead of mutating the current one:
let coinC = try coinA.merge(with: coinB) { (_, b) in b }
I thought it would be interesting to show a solution based on Swift key paths. This allows us to loop somewhat agnostically through the properties — that is, we do not have to hard-code their names in a series of successive statements:
struct Coin {
var value: Float?
var country: String?
var color: String?
}
let c1 = Coin(value:20, country:nil, color:"red")
let c2 = Coin(value:nil, country:"Uganda", color:nil)
var c3 = Coin(value:nil, country:nil, color:nil)
// ok, here we go
let arr = [\Coin.value, \Coin.country, \Coin.color]
for k in arr {
if let kk = k as? WritableKeyPath<Coin, Optional<Float>> {
c3[keyPath:kk] = c1[keyPath:kk] ?? c2[keyPath:kk]
} else if let kk = k as? WritableKeyPath<Coin, Optional<String>> {
c3[keyPath:kk] = c1[keyPath:kk] ?? c2[keyPath:kk]
}
}
print(c3) // Coin(value: Optional(20.0), country: Optional("Uganda"), color: Optional("red"))
There are unfortunate features of key paths that require us to cast down from the array element explicitly to any possible real key path type, but it still has a certain elegance.
If you're willing to make the merge function specific to Coin, you can just use the coalesce operator like so:
struct Coin {
var value: Float?
var country: String?
var color: String?
func merge(_ other: Coin) -> Coin {
return Coin(value: other.value ?? self.value, country: other.country ?? self.country, color: other.color ?? self.color)
}
}
let coinC = coinA.merge(coinB)
This will return a new Coin using the values from coinB, and filling in any nils with those from coinA.
If your goal is to change coin A what you need is a mutating method. Note that structures are not like classes. If you would like to change its properties you need to declare your coin as variable. Note that none of your examples would compile if you declare your coins as constants:
struct Coin {
var value: Float?
var country: String?
var color: String?
mutating func merge(_ coin: Coin) {
value = value ?? coin.value
country = country ?? coin.country
color = color ?? coin.color
}
init(value: Float? = nil, country: String? = nil, color: String? = nil) {
self.value = value
self.country = country
self.color = color
}
}
Playground testing:
var coinA = Coin(country: "USA", color: "silver")
coinA.merge(Coin(value: 50))
print(coinA.country ?? "nil") // "USA"
print(coinA.color ?? "nil") // "silver"
print(coinA.value ?? "nil") // 50.0
This is not a high-level approach like the merge one you shared the link to but as long as you have a struct to implement the merge feature into, it will do the job.
func merge(other: Coin, keepTracksOfCurrentOnConflict: Bool) -> Coin {
var decidedValue = value
if decidedValue == nil && other.value != nil {
decidedValue = other.value
} else if other.value != nil {
//in this case, it's conflict.
if keepTracksOfCurrentOnConflict {
decidedValue = value
} else {
decidedValue = other.value
}
}
var resultCoin = Coin(value: decidedValue, country: nil, color: nil)
return resultCoin
}
}
You can do the same for other properties.
If you want to wrap it around protocol. The idea behind is the same:
you convert object's to dict
merge two dict's
convert merged dict back to your object
import Foundation
protocol Merge: Codable {
}
extension Dictionary where Key == String, Value == Any {
func mergeAndReplaceWith(object: [Key: Value]) -> [Key: Value] {
var origin = self
origin.merge(object) { (_, new) in
new
}
return origin
}
}
extension Merge {
func toJson() -> [String: Any] {
let jsonData = try! JSONEncoder().encode(self)
let json = try! JSONSerialization.jsonObject(with: jsonData, options: []) as! [String: Any]
return json
}
func merge(object: Merge) -> Merge {
let origin = self.toJson()
let objJson = object.toJson()
let decoder = JSONDecoder()
let merge = origin.mergeAndReplaceWith(object: objJson)
var jsonData = try! JSONSerialization.data(withJSONObject: merge, options: .prettyPrinted)
var mergedObject = try! decoder.decode(Self.self, from: jsonData)
return mergedObject
}
}
struct List: Merge {
let a: String
}
struct Detail: Merge {
struct C: Codable {
let c: String
}
let a: String
let c: C?
}
let list = List(a: "a_list")
let detail_without_c = Detail(a: "a_detail_without_c", c: nil)
let detail = Detail(a: "a_detail", c: Detail.C(c: "val_c_0"))
print(detail.merge(object: list))
print(detail_without_c.merge(object: detail))
Detail(a: "a_list", c: Optional(__lldb_expr_5.Detail.C(c: "val_c_0")))
Detail(a: "a_detail", c: Optional(__lldb_expr_5.Detail.C(c: "val_c_0")))
With this solution you can actually merge two representations of your endpoint, in my case it is List and Detail.