iOS architecture: Object with many optionals

Viewed 144

This is a fairly specific situation, so I'll try to explain as many details as possible.

I'm making an app that should fetch a list of reservations, where it's possible to either add new, or tap an existing reservation and have a "detailed" view about the reservation where the reservation details are editable, and then have an option to save it.

REST APIs have been done in C#, and there's no documentation on what can and can't be null (nil, in Swift case). So I'm ending up with:

    struct Reservation: Codable {
var objectID: String?
var objectName: String?
var objectPrefix: String?
var reservationNumber: String?
var grownUPS: Int?
var teens: Int?
var children: Int?
var babies: Int?
var reservationDate: String?
var dateInserted: String?
var toDate:String?
var fromDate: String?
var price: Int?
var owner: String?
var note: String?
var agencyName: String?
var renterNote: String?
var reservationID: String?
    // 20 more properties

init(objectID: String? = nil,
         partnerID: String? = nil,
         objectName: String? = nil,
    // 20 more properties        
    )
    {

    self.objectID = objectID
    self.objectName = objectName
 // 20 more properties

}

So when I tap on an object, I pass a Reservation object, check every field, if not nil then set to TextField. On clicking save I update model from all TextFields, DatePickers, etc, and then do a network post or put request depending on whether it's a new reservation or editing existing.

If I tap on add, I pass an empty Reservation object, so all fields on "details" page are empty, and do a validation when clicking Save button.

It works so far, but all around it looks "Anti-Swift". Lot of optionals, lot of guards/unwrapping, tight coupling between "master" and "details" view, setting data retrieved from network in a closure (actual Alamofire call is hidden, but I'm not sure what will be nil, so I have to set each property to it's TextField with a nil-check/chaining).

Any arhitecture tips on how to improve this? All tutorials on this make a simple, local, non-optional approach that makes everything look shiny.

Keep in mind I've no documentation what is allowed to be null (data previously entered via web, or internal desktop app).

3 Answers

One thing that I can think of from the top of my head would be to remove the optionality of some properties by defining default values eg var babies: Int = 0 or if you're using Swift's decodable you can do something like this

babies = (try? container.decode(Int.self, forKey: .babies)) ?? 0

so you don't have to make your babies variable an optional

edit based on comment: the ?? aka coalescing nil operator will try to unwrap the optional value on the left and if it is nil, it will return the value on the right which in this case is 0

I don't think you should be bothered by optionals and optionals unwrapping.

One of the powers of optionals is, that anybody who works with your code, knows, that this thing may take nil as its value.

Unwrapping logic, either you use guards , nil coalescing or any other unwrapping technique describes your business logic. The fact, that you have "big" model is, IMO, just a fact that should be accepted. It's ok until your code stays reliable, readable, testable and understandable, doesn't cause unnecessary side effects and so on.

You might "fix" this problem by adding another level of abstraction over unwrapping or so. But, IMO, it should be done very carefully and only for the case of real benefits.

SwiftyJson solves exactly what you are facing. Its great in handling optional chaining and unwrapping of a great no of objects very efficiently and in a very Swifty way.

If some type conversion fails it doesn't break but gives an empty value so that your app works without you checking every single variable.

Here is basic conversion example provided. For details please go through their documentation.

// Getting a double from a JSON Array
let name = json[0].double

// Getting an array of string from a JSON Array
let arrayNames =  json["users"].arrayValue.map({$0["name"].stringValue})

// Getting a string from a JSON Dictionary
let name = json["name"].stringValue

// Getting a string using a path to the element
let path: [JSONSubscriptType] = [1,"list",2,"name"]
let name = json[path].string

// Just the same
let name = json[1]["list"][2]["name"].string

// Alternatively
let name = json[1,"list",2,"name"].string
Related