Archiving Custom Object in Swift - Extended - ios

I am working on an iOS App on Swift 4.0. The app uses an 3rd party SDK where there is a model lets say,
class Customer: NSCopying, NSObject {
var name: String!
var age: Int!
var address: Address!
}
At that point I have no control to modify any properties and signature for the model as its inside SDK. But I need to store the object in disk/user defaults and load when needed.
Is it possible? If it is then how can I do that?

One way is to use SwiftyJSON to convert the model object to JSON data:
extension Customer {
func toJSON() -> JSON {
return [
"name": name
"age": age
"address": address.toJSON() // add a toJSON method the same way in an Address extension
]
}
static func fromJSON(_ json: JSON) -> Customer {
let customer = Customer()
customer.name = json["name"].string
customer.age = json["age"].int
customer.address = Address.fromJSON(json["address"]) // add a fromJSON method the same way
}
}
Now you can do something like saving to UserDefaults
UserDefaults.standard.set(try! Customer().toJSON().rawData(), forKey: "my key")
let customer = Customer.fromJSON(JSON(data: UserDefaults.standard.data(forKey: "my key")!))

Related

Protocols and Enums in Swift with Apollo

I am using Apollo for Swift in an iOS app. I have multiple types that all represent the same object. These types are auto-generated from a schema file and look something like this.
struct CurrentUser {
var id: String
...
}
struct MyUser {
var id: String
...
}
Basically Apollo generates multiple Swift types (one for each query) for the same underlying data type.
I want to create a new struct that unifies these types.
I would like to do something like this:
protocol UserProtocol {
var id: String { get }
}
struct User {
var id: String
...
init(_ data: UserProtocol) {
self.id = data.id
...
}
}
This approach however gives me an error when I try to construct a user object, telling me that "Type MyUser does not conform to UserProtocol". If I try to coerce the type with data as! UserProtocol I get a crash.
The only solution I've found is the following:
enum UserType {
case .currentUser(CurrentUser)
case .myUser(MyUser)
}
struct User {
var id: String
...
init(_ data: UserType) {
switch data {
case .myUser(let user):
self.id = data.id
...
case .currentUser(let user):
self.id = data.id
...
}
}
}
This approach works, but it leads to a lot of duplicated code in the init function. Is there a better way to do this in Swift?
I suspect the problem is that you need to explicitly conform the Apollo generated types to your protocol:
extension CurrentUser: UserProtocol { }
extension MyUser: UserProtocol { }
Remember that Swift is not duck-typed like some other languages, so a type with member var id: String is not UserProtocol until you declare it as such.
If for some reason you need to do some transformation of the Apollo types to fit the app models in the future, those extensions are a good place to do that, too.

How to replace/update dictionary in Array of Dictionary based on Type

I'm getting below JSON response from server, and displaying phone number on screen.
Now user can change/update any of phone number, so we have to update particular mobile number in same object and send it to server.
"phone_numbers": [
{
"type": "MOBILE",
"number": "8091212121"
},
{
"type": "HOME",
"number": "4161212943"
},
{
"type": "BUSINESS",
"number": "8091212344"
}
]
My model class is looks like this:
public struct Contact: Decodable {
public let phone_numbers: [Phone]?
}
public struct Phone: Decodable {
public let type: PhoneType?
public let number: String?
}
I'm struggling to update this JSON object for particular phone number.
For example, if I want to update BUSINESS number only in above array, What's best way to do it.
I'm using XCode 11 and Swift 5.
Because all your properties are defined as constants (let), nothing can be updated. You have to initialize and return a new Contact object with the updated phone numbers.
If you change the properties to var, then you can update:
public enum PhoneType: String, Decodable {
case mobile = "MOBILE"
case home = "HOME"
case business = "BUSINESS"
}
public struct Contact: Decodable {
public var phone_numbers: [Phone]?
mutating func update(phoneNumber: String, for type: PhoneType) {
guard let phone_numbers = self.phone_numbers else { return }
for (i, number) in phone_numbers.enumerated() {
if number.type == type {
self.phone_numbers![i].number = phoneNumber
}
}
}
}
public struct Phone: Decodable {
public var type: PhoneType?
public var number: String?
}
var contact = try! JSONDecoder().decode(Contact.self, from: jsonData)
contact.update(phoneNumber: "123456", for: .business)
I'm struggling to update this JSON object for particular phone number.
It shouldn't be a JSON object when you update it. Think of JSON as just a format for transferring data. Once transferred, you should parse it into something that you can work with, like an array of dictionaries or whatever. If you've done that, then more specific questions you might ask are:
How can I find a specific entry in an array?
How can I modify the fields of a struct?
How can I replace one entry in an array with another?
After looking at the definitions of your structures, I think the problem you're having probably has to do with how you've declared them:
public struct Phone: Decodable {
public let type: PhoneType?
public let number: String?
}
Because you used let to declare type and number, those fields cannot be changed after initialization. If you want the fields of a Phone struct to be modifiable, you need to declare them with var instead of let.
The same thing is true for your Contact struct:
public struct Contact: Decodable {
public let phone_numbers: [Phone]?
}
You've declared phone_numbers as an immutable array because you used let instead of var. If you want to be able to add, remove, or modify the array in phone_numbers, you need to use var instead.
The struct declarations you have right now work fine for reading the data from JSON because all the components of the JSON data are constructed using the values from the JSON. But again, you'll need to make those structs modifiable by switching to var declarations if you want to be able to make changes.
There are a couple of ways to approach this (I'm assuming PhoneType is an enum you have somewhere)
You can iterate over the array and guard for only business numbers, like so
for phone in phone_numbers{
guard phone.type == .MOBILE else { continue }
// Code that modifies phone
}
You can filter and iterate over the array, like so
phone_numbers.filter {$0.type == .BUSINESS }.forEach { phone in
// Modify phone here
}
You can then modify the right value in the array with it's index, like this
for (phoneIndex, phone) in phone_numbers.enumerated() {
guard phone.type == .BUSINESS else { continue }
phone_numbers[phoneIndex].type = ANOTHER_TYPE
}
Some can argue that the second is preferred over the first, because it is an higher order function, but in my day to day activities, I tend to use both and believe that this is a matter of taste

How would one create a List<AnyRealmObject> in Swift?

OK, first, I know that there is no such thing as AnyRealmObject.
But I have a need to have something the behaves just like a Realm List, with the exception that any kind of Realm Object can be added to the list -- they don't all have to be the same type.
Currently, I have something like this:
enter code here
class Family: Object {
var pets: List<Pet>
}
class Pet: Object {
var dog: Dog?
var cat: Cat?
var rabbit: Rabbit?
}
Currently, if I wanted to add in, say, Bird, I'd have to modify the Pet object. I don't want to keep modifying that class.
What I really want to do is this:
class Family: Object {
var pets: List<Object>
}
Or, maybe, define a Pet protocol, that must be an Object, and have var pets: List<Pet>
The point is, I want a databag that can contain any Realm Object that I pass into it. The only requirement for the databag is that the objects must be Realm Objects.
Now, since Realm doesn't allow for this, how could I do this, anyway? I was thinking of creating something like a Realm ObjectReference class:
class ObjectReference: Object {
var className: String
var primaryKeyValue: String
public init(with object: Object) {
className = ???
primaryKeyValue = ???
}
public func object() -> Object? {
guard let realm = realm else { return nil }
var type = ???
var primaryKey: AnyObject = ???
return realm.object(ofType: type, forPrimaryKey: primaryKey)(
}
}
The stuff with the ??? is what I'm asking about. If there's a better way of doing this I'm all ears. I think my approach is ok, I just don't know how to fill in the blanks, here.
(I'm assuming that you are writing an application, and that the context of the code samples and problem you provided is in terms of application code, not creating a library.)
Your approach seems to be a decent one given Realm's current limitations; I can't think of anything better off the top of my head. You can use NSClassFromString() to turn your className string into a Swift metaclass object you can use with the object(ofType:...) API:
public func object() -> Object? {
let applicationName = // (application name goes here)
guard let realm = realm else { return nil }
guard let type = NSClassFromString("\(applicationName).\(className)") as? Object.Type else {
print("Error: \(className) isn't the name of a Realm class.")
return nil
}
var primaryKey: String = primaryKeyValue
return realm.object(ofType: type, forPrimaryKey: primaryKey)(
}
My recommendation is that you keep things simple and use strings exclusively as primary keys. If you really need to be able to use arbitrary types as primary keys you can take a look at our dynamic API for ideas as to how to extract the primary key value for a given object. (Note that although this API is technically a public API we don't generally offer support for it nor do we encourage its use except when the typed APIs are inadequate.)
In the future, we hope to offer enhanced support for subclassing and polymorphism. Depending on how this feature is designed, it might allow us to introduce APIs to allow subclasses of a parent object type to be inserted into a list (although that poses its own problems).
This may not be a complete answer but could provide some direction. If I am reading the question correctly (with comments) the objective is to have a more generic object that can be the base class for other objects.
While that's not directly doable - i.e. An NSObject is the base for NSView, NSString etc, how about this...
Let's define some Realm objects
class BookClass: Object {
#objc dynamic var author = ""
}
class CardClass: Object {
#objc dynamic var team = ""
}
class MugClass: Object {
#objc dynamic var liters = ""
}
and then a base realm object called Inventory Item Class that will represent them
class InvItemClass: Object {
#objc dynamic var name = ""
#objc dynamic var image = ""
#objc dynamic var itemType = ""
#objc dynamic var book: BookClass?
#objc dynamic var mug: MugClass?
#objc dynamic var card: CardClass?
}
then assume we want to store some books along with our mugs and cards (from the comments)
let book2001 = BookClass()
book2001.author = "Clarke"
let bookIRobot = BookClass()
bookIRobot.author = "Asimov"
let item0 = InvItemClass()
item0.name = "2001: A Space Odyssey"
item0.image = "Pic of Hal"
item0.itemType = "Book"
item0.book = book2001
let item1 = InvItemClass()
item1.name = "I, Robot"
item1.image = "Robot image"
item1.itemType = "Book"
item1.book = bookIRobot
do {
let realm = try Realm()
try! realm.write {
realm.add(item0)
realm.add(item1)
}
} catch let error as NSError {
print(error.localizedDescription)
}
From here, we can load all of the Inventory Item Objects as one set of objects (per the question) and take action depending on their type; for example, if want to load all items and print out just the ones that are books.
do {
let realm = try Realm()
let items = realm.objects(InvItemClass.self)
for item in items {
switch item.itemType {
case "Book":
let book = item.book
print(book?.author as! String)
case "Mug":
return
default:
return
}
}
} catch let error as NSError {
print(error.localizedDescription)
}
As it stands there isn't a generic 'one realm object fits all' solution, but this answer provides some level of generic-ness where a lot of different object types could be accessed via one main base object.

How can I allow my function to accepts two different objects in swift?

I have a function which takes one argument. I wanted my function to accept two object types. How can I do it? Here is the example below:
func accept(user: Customer) {
...
}
It should accept Customer and Employee object reference.
accept(objRefCustomer)
accept(objRefEmployee)
Please help me in this case.
Alternative to super-classing: use protocols
You needn't necessarily use a superclass for this case (if Customer and Employee are struct value types; superclass option is not possible), but can rather use the more generic approach of protocols.
Define a protocol Users which blueprints properties and methods for your Customer and Employee instances (if we let Customer and Employee conform to Users, then we promise that instances of these two structures will have accessible the blueprinted properties and methods):
protocol Users {
var name: String { get }
func printTypeOfUser()
}
Define the Customer and Employee structures, and their conformance to the protocol Users:
struct Customer : Users {
let name: String
init(name: String) { self.name = name }
func printTypeOfUser() {
print("Is a Customer!")
}
}
struct Employee : Users {
let name: String
let id: Int
init(name: String, id: Int) { self.name = name; self.id = id }
func printTypeOfUser() {
print("Is an Employee!")
}
}
Now you can define a generic function where its generic, say T, is type constrained to types conforming to the protocol Users, which in this case is equivalent to the Customer or Employee types
func accept<T: Users>(user: T) {
print("Name of user: \(user.name) [\(user.dynamicType)]")
user.printTypeOfUser()
// do something additional employee-specific if user is an employee?
if let employee = user as? Employee {
print("User is an employee with id: \(employee.id)")
}
}
Example usage of this function for Employee as well as Customer instances:
let employee = Employee(name: "John", id: 1)
let customer = Customer(name: "Sarah")
accept(employee) /* Name of user: John [Employee]
Is an Employee!
User is an employee with id: 1 */
accept(customer) /* Name of user: Sarah [Customer]
Is a Customer! */
Instead of changing your Class structure and code base, you can use AnyObject. It will also be easier for you if, for example, in future you have to make this function accept parameters of class WaterMelon. Making all these classes inherit from a common parent class would be unnecessary overhead, not to mention hectic.
AnyObject is swift equivalent of objective c id. AnyObject is a protocol that can represent an instance of any class type.
It also has a more general counterpart, Any, which can represent any type at all (including structs and enums).
Following code will accept any class type parameter you pass:
func accept(sender : AnyObject) { //Or AnyObject? if you want to handle nil as well
...
}
To access properties of the classes you pass as AnyObject, you can use type casting.
For example below code will check sender type and typecast it for you:
if let customerRef = sender as? Customer {
// ...
// Sender is of customer class type. Use it with customerRef that we created
let customerName = customerRef.dynamicType.sampleNameProperty //Access a property of class Customer
customerRef.funcOfCustomerClass() //Call a method of class Customer
}
else{
//Sender is not of customer class type.
//Then it must be Employee??? Handle cases for employee here.
}
create a protocol, and use it as argument type. protocol can be also empty, it will work anyway. Works with struct and class as well;
ex:
protocol SomeFakeProtocol {}
class SomeClass: SomeFakeProtocol { //code here }
struct SomeStruct: SomeFakeProtocol { //code here }
func someFunction(arg: SomeFakeProtocol) { //code here }
Benefits - you can allow to use only types you want to. And, sure, you can do things like this:
extension String: SomeFakeProtocol {}
You can create a super class called People of Cutomer and Employee.
Then set user as type of People:
func accept(user: People) {
...
}
You don't need a super class, you can just pass an object of type AnyObject and in your function check the type of the object passed:
func accept(user: AnyObject) {
if let usr = user as? Person {
...
}
}
But if you have many types you want to pass you may want to make a protocol or a super class.

"foreign key" as primary key in realm

I'm getting started with realm and trying to figure out the best way to write my model layer, so after overthinking about it I decide to ask here to get some opinions. Here we go:
I'm writing a realm version of these tables:
Account
id - String (PK)
Name - String
Type - Int
Checking Account
id - String (FK PK)
In this case a Checking Account is an Account, but realm still not support inheritance (I think using Account as a superclass would be the better way to figure these tables out in the model layer)
This is what I actually did with realm:
Account class:
import Foundation
import RealmSwift
class Account: Object {
dynamic var id = NSUUID().UUIDString
dynamic var name = ""
dynamic var type = 3
override static func primaryKey() -> String {
return "id"
}
}
Checking Account class:
import Foundation
import RealmSwift
class CheckingAccount: Object {
dynamic lazy var id: String = {
return self.account.id
}()
dynamic var account = Account()
convenience init(name: String, type: AccountType) {
self.init()
self.account.name = name
self.account.type = type.rawValue
}
override static func primaryKey() -> String? {
return "id"
}
}
What about it? It's a "Don't do" way to figure out my SQL tables with realm?
Thank you!

Resources