iOS architecture: Object with many optionals - ios

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).

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

Is there any way to optimize struct copy in Swift?

I'm developing an iOS app and have the following data model:
struct Student {
var name: String?
var age: UInt?
var hobbies: String?
...
}
This model is used as the data source in one view controller, where each property value will be filled in an UITextfield instance so that the user can edit a student's information. Every time a user finishes typing an item, e.g. the name, the new value will override the old model's corresponding property.
The problem is, since struct is a value type instead of a reference type, a new model instance is generated every time I assign a new property value to it. There may be above 20 properties in my model and I think so many copies are quite a waste. For some reasons I'm not allowed to use class. Is there any way to optimize this? Will these copies cause any performance issues?
you can create a func with mutating keyword like below
struct Point {
var x = 0.0
mutating func add(_ t: Double){
x += t
}
}
find more here

How to find value difference of two struct instances in Swift

I have two instances from the same struct in Swift. I need to find out key-values that have the same keys but different values.
For example:
struct StructDemo {
let shopId: Int
let template: String?
}
let a = StructDemo(shopId: 3, template: "a")
let a = StructDemo(shopId: 3, template: "different a")
// My expectation is to return the change pairs
let result = [template: "different a"]
My approach is as show below but comes errors.
static func difference(left: StructDemo, right: StructDemo) -> [String: Any]{
var result:[String: Any] = [:]
for leftItem in Mirror(reflecting: left).children {
guard let key = leftItem.label else { continue }
let value = leftItem.value
if value != right[key] { // This is the problem. Errror message: Protocol 'Any' as a type cannot conform to 'RawRepresentable'
result[key] = right[key]
}
}
}
Appreciate for any suggestion and solutions.
Thank you
The problem that you are seeing is that you referred to
right[key]
but right is a StructDemo and is not subscriptable. You can't look up fields given a runtime name. Well, you can with Mirror which you correctly used for left, but you did not mirror right.
Using Mirror will lead to other issues, as you will have expressions with static type Any where Equatable will be required in order to compare values.
IMHO, your best bet is to avoid a generic, reflective approach, and just embrace static typing and write a custom difference functions that iterates all the known fields of your type. Hard coding is not so bad here, if there is only one struct type that you are trying to diff.
If you have a handful of struct types each needing diffs then that might be a different story, but I don't know a good way to get around the need for Equatable. But if you have a ton of diffable types, maybe you want dictionaries to begin with?

How NSMapTable works

I'm trying to figure out how NSMapTable works
So I'm trying in playground the following code:
class Person {
var name: String
init(name: String ) {
self.name = name
print("\(name) is being initialized")
}
deinit {
print("\(name) is being deinitialized")
}
}
var hobyePerson : NSMapTable? = NSMapTable<Person, NSMutableString>
(keyOptions: .weakMemory, valueOptions: .weakMemory)
var rob : Person? = Person(name: "Rob Appleseed") // print : Rob Appleseed is being initialized
hobyePerson?.setObject("golf", forKey: rob)
hobyePerson?.count // return : 1
rob = nil // print : Rob Appleseed is being deinitialized
hobyePerson?.count // return : 1 (WHY ???!!!!)
as written in the documentation: "Keys and/or values are optionally held “weakly” such that entries are removed when one of the objects is reclaimed."
why even though I initialized the object so that it has a weak reference to the key-value pair when rob is deallocated, I still have an element in hobyePerson?
NSMapTable's weak behavior options work best when you don't care when keys/values are released, but rather, you do care that the keys/values aren't strongly retained and will be released at some point after the object of interest becomes nil.
Why so?
As a Foundation class, the authors of NSMapTable had to balance both features and performance.
Consequently, as an "optimization" for performance, they chose that weakly referenced objects that become nil are NOT immediately removed from the map table...! Rather, this happens "later" when it can be efficiently done -- such as when the map table internally gets resized, etc.
As #Luke also mentions in his answer, see this excellent writeup about an experiment done on NSMapTable's behavior for more details:
http://cocoamine.net/blog/2013/12/13/nsmaptable-and-zeroing-weak-references/
Yes, this is a strange and unfortunate behavior. This article goes into it in some depth. Although it doesn't explore weak-to-weak specifically, the behavior described is the same. As that author notes, hobyePerson.keyEnumerator().allObjects.count and hobyePerson.objectEnumerator().allObjects.count will contain 0 as expected at the end of all this. He also points out that Apple has sort of documented this behavior in the Mountain Lion release notes.
However, weak-to-strong NSMapTables are not currently recommended, as
the strong values for weak keys which get zero’d out do not get
cleared away (and released) until/unless the map table resizes itself.
Sorry I don't have a better explanation for you.
It didn't work for me so I implemented simple weak map like this.. Will improve it overtime but for now works:
import Foundation
private struct WeakValue<Value:AnyObject> {
weak var value: Value?
}
public class CSWeakValueDictionary<Key:AnyObject, Value:AnyObject> {
private let dictionary = NSMutableDictionary()
public subscript(source: Key) -> Value? {
get {
let value = (dictionary["\(source)"] as? WeakValue<Value>)?.value
if value == nil { dictionary.removeObject(forKey: "\(source)") }
return value
}
set { dictionary["\(source)"] = WeakValue(value: newValue) }
}
}

Why does String implicitly become String! in Swift

I received a compilation error from Xcode yesterday, saying
Binary operator '&&' cannot be applied to two Bool operands [1]
on
if text != nil && presentingViewController != nil {
...
}
text is defined earlier as
var text: String = "" {
didSet {
...
}
}
and presentingViewController is from UIViewController, which is the super class.
A friend of mine told me that it was caused by text, which was of String!, and the issue was solved by changing
var text: String
to
var text: String?
However, it still puzzles me why my explicitly defined String becomes String!. Can anyone provide any detail on this implicit conversion?
[1] This compilation error does not make sense, and this question is not a duplicate of another question of the same compilation error.
Your problem is related to what is called "optionals" in swift.
In Objective-C you could have a reference to NSString and it could either be pointing to an NSString instance - or to nil. In swift this is different!
A "regular" String in swift can never be nil. Only an "optional" String can be nil. This is how it would look in swift code:
var myRegularString : String // this can never be nil, can't compare to nil
var myOptionalString : String? // this can be nil (note the ?)
You have a "regular" String. So the compiler complains when you try to compare it to nil.
Optionals are explained here: Swift Programming Language/Basics/Optionals
Your explicitly defined String is not String! is ... String. Simple like that ;-)
String! is an implicitly unwrapped Optional<String>
String? is an Optional<String>
Optional<T> means that it can be a value of type T or Nothing (and nothing can be compared to nil).
Thus when you declare:
var text = ""
you declare an actual String and it cannot be nil. That's why the compiler is complaining: a String cannot be Nothing. Think of it as comparing a String with a CGFloat ... the compiler will complain.
Since String! is an Optional<String> than it can be compared against nil.
The ! simply is you declaring that you are absolutely sure it is not Nothing such that you can use text without unwrapping its actual value.
BTW, do not confuse and empty String (i.e.: "") with Nothing.
var text = "" is an actual String just empty and it is not comparable with Nothing
Hope this helps.
Your friend most certainly didn't solve your problem, he just glossed over it.
You declared an instance variable of type String. text will always be a String. It can never, ever be nil. Asking "text != nil" is pointless because it cannot be nil. It doesn't even compile, because a String, always being nil, cannot even checked for being nil.
Your friend suggested to use String? or String!. That's in your situation total nonsense. You have text which is guaranteed to be always a String. Instead you change it to text which may be a String or nil. This doesn't help you in any way. Yes, now you can ask it whether it is nil. Before, you didn't even have to ask.

Swift - casting a nil core data string as an optional value

I have a field stored on a core data object called "metadata" which is of type String (no optional, because Apple docs say not to mess with optionals in CD). Sometimes, the metadata field is nil. In checking whether this value is nil, I do the following check:
if object.metadata as String? != nil {
...
}
However, my code continuously crashes on this line as an EXC_BAD_ACCESS. I have also tried:
if let metadata = object.metadata as String? {
...
}
Which doesn't work either. I cast objects successfully to optionals in other parts of my code, so I don't understand why this particular case isn't working. How do you check whether a core data property is a nil string?
It looks like what you really want is this:
if object.metadata != nil {
...
}
or this:
if let metadata = object.metadata as? String {
// You can now freely access metadata as a non-optional
...
}
--EDIT--
My mistake, I didn't read the first part of your question thoroughly enough. It looks like the duplicate answer has a solution for this. Essentially, the generated managed object subclass is a bug and you should modify the properties to be either optional or implicitly unwrapped. You can check both of those using the first method for implicitly unwrapped and second for optionals.
There are several questions which discuss the issue of the generated subclasses not producing optional properties. I wouldn't be too concerned about editing the subclasses; there's nothing special about them except that Apple is making it easier to create them.
Check if property is set in Core Data?
Swift + CoreData: Cannot Automatically Set Optional Attribute On Generated NSManagedObject Subclass
--Edit2--
If you really don't want to touch the subclass you can access the property using valueForKey() and could add that as an extension if you wanted something a bit cleaner.
if let metadata = object.valueForKey("metadata") as String? {
...
}
In an extension:
extension ObjectClass {
var realMetadata: String? {
set {
self.setValue(newValue, forKey: "metadata")
}
get {
return self.valueForKey("metadata") as String?
}
}
}

Resources