Swift 5: saving complex objects to file storage - ios

I am working on a school assignment for which I have to develop an iOS application in Swift 5. The application needs to utilize a web service (a web-API) and either file storage or user defaults.
I had chosen to develop a "QR code manager", in which users can create QR codes for a URL by setting a few design parameters, which are then sent to a generator API. This API (upon an OK request) returns an image in a specified format (PNG in my case).
I have a class with the URL and all the design properties of the QR code, which will also contain the image itself. Please see below code snippet for the class.
public class QRCode {
var bsId : Int?
var url : String?
var name: String?
var frame: Frame?
var logo: QrCodeLogo?
var marker: Marker?
var color : String?
var bgColor : String?
var image : Data?
init(data: [String:String]) {
self.url = data["url"]
self.frame = Frame.allCases.first(where: { $0.description == data["frame"] })
self.logo = QrCodeLogo.allCases.first(where: { $0.description == data["logo"] })
self.marker = Marker.allCases.first(where: { $0.description == data["marker"] })
self.bgColor = data["backGroundColor"]
self.color = data["colorLight"]
}
init(json: String) {
// todo
}
}
extension QRCode {
func toDict() -> [String:Any] {
var dict = [String:Any]();
let otherSelf = Mirror(reflecting: self);
for child in otherSelf.children {
if let key = child.label {
dict[key] = child.value;
}
}
return dict;
}
}
All the properties are nullable for ease of development, the class will be further refactored once I have successfully implemented everything.
I have tried various methods I found all over the internet, one of which can be found in the class extension. The function toDict() translates the object properties and their values to a Dictionary object of type [String:Any]. However, I read that when the Any datatype is encoded and then decoded, Swift cannot determine which complex datatype the decoded data is supposed to be, effectively rendering the data either meaningless or unusable.
Another method I found was through extending the Codable-protocol in the class. As far as I am aware however, Codable only accepts primitive datatypes.
Please find below my currently written code for file storage handling. It is not complete yet, but I felt it was a good start and might help in this question.
class StorageManager {
fileprivate let filemanager: FileManager = FileManager.default;
fileprivate func filePath(forKey key: String) -> URL? {
guard let docURL = filemanager.urls(for: .documentDirectory, in: FileManager.SearchPathDomainMask.userDomainMask).first else {
return nil;
}
return docURL.appendingPathComponent(key);
}
func writeToStorage(identifier: String, data: QRCode) -> Void {
guard let path = filePath(forKey: identifier) else {
throw ApplicationErrors.runtimeError("Something went wrong writing the file to storage");
}
let dict = data.toDict();
// TODO:: Implement
}
func readFromStorage(identifier: String) -> Any {
// TODO:: Implement
return 0;
}
func readAllFromStorage() throws -> [URL] {
let docsURL = filemanager.urls(for: .documentDirectory, in: .userDomainMask)[0];
do {
let fileURLs = try filemanager.contentsOfDirectory(at: docsURL, includingPropertiesForKeys: nil);
return fileURLs;
} catch {
throw ApplicationErrors.runtimeError("Something went wrong retrieving the files from \(docsURL.path): \(error.localizedDescription)");
}
}
}
I am very new to Swift and I am running stuck on file storage. Is there any way I could store instances of this class in file storage in such a way that I could reïnstantiate this class when I retrieve the data?
Thanks in advance! Please do not hesitate to ask any questions if there are any.
Edit
Based on matt's comment, please find below the code snippets of the Marker, Frame, and QrCodeLogo enums.
The Frame enum:
public enum Frame: String, CaseIterable {
case noFrame
case bottomFrame
case bottomTooltip
case topHeader
static var count: Int { return 4 }
var description: String {
switch self {
case .noFrame:
return "no-frame"
case .bottomFrame:
return "bottom-frame"
case .bottomTooltip:
return "bottom-tooltip"
case .topHeader:
return "top-header"
}
}
}
The QrCodeLogo enum:
public enum QrCodeLogo: String, CaseIterable {
case noLogo
case scanMe
case scanMeSquare
static var count: Int { return 3 }
var description: String {
switch self {
case .noLogo:
return "no-logo"
case .scanMe:
return "scan-me"
case .scanMeSquare:
return "scan-me-square"
}
}
}
The Marker enum:
public enum Marker: String, CaseIterable {
case version1
case version2
case version3
case version4
case version5
case version6
case version7
case version8
case version9
case version10
case version11
case version12
case version13
case version15
case version16
static var count: Int { return 15 }
var description: String {
switch self {
case .version1:
return "version1"
case .version2:
return "version2"
case .version3:
return "version3"
case .version4:
return "version4"
case .version5:
return "version5"
case .version6:
return "version6"
case .version7:
return "version7"
case .version8:
return "version8"
case .version9:
return "version9"
case .version10:
return "version10"
case .version11:
return "version11"
case .version12:
return "version12"
case .version13:
return "version13"
case .version15:
return "version15"
case .version16:
return "version16"
}
}
}
All the above enums contain the valid design options for the API I use. They serve as an input restriction to prevent "invalid parameter"-errors from occurring.
Hopefully, this clears things up.
Thanks again!

A type can conform to Codable provided all its properties conform to Codable. All of your properties do conform to Codable except for the enums, and they will conform to Codable if you declare that they do. Thus, this simple sketch of your types compiles:
public enum Frame: String, Codable {
case noFrame
case bottomFrame
case bottomTooltip
case topHeader
}
public enum QrCodeLogo: String, Codable {
case noLogo
case scanMe
case scanMeSquare
}
public enum Marker: String, Codable {
case version1
case version2
case version3
case version4
case version5
case version6
case version7
case version8
case version9
case version10
case version11
case version12
case version13
case version15
case version16
}
public class QRCode : Codable {
var bsId : Int?
var url : String?
var name: String?
var frame: Frame?
var logo: QrCodeLogo?
var marker: Marker?
var color : String?
var bgColor : String?
var image : Data?
}
There are many, many other things about your code that could be improved.
You don't need CaseIterable or description for anything. Now that your type is Codable, you can use it to retrieve the values from JSON directly, automatically. If the names of your enum cases do not match the corresponding JSON keys, just make a CodingKey nested enum to act as a bridge.
In other words, being Codable makes your type both populatable directly from the JSON and serializable to disk.

Related

How to make swift Equatable enum available in Objective-C [duplicate]

I have this enum with String values, which will be used to tell an API method that logs to a server what kind of serverity a message has. I'm using Swift 1.2, so enums can be mapped to Objective-C
#objc enum LogSeverity : String {
case Debug = "DEBUG"
case Info = "INFO"
case Warn = "WARN"
case Error = "ERROR"
}
I get the error
#objc enum raw type String is not an integer type
I haven't managed to find anywhere which says that only integers can be translated to Objective-C from Swift. Is this the case? If so, does anyone have any best-practice suggestion on how to make something like this available in Objective-C?
One of the solutions is to use the RawRepresentable protocol.
It's not ideal to have to write the init and rawValue methods but that allows you to use this enum as usual in both Swift and Objective-C.
#objc public enum LogSeverity: Int, RawRepresentable {
case debug
case info
case warn
case error
public typealias RawValue = String
public var rawValue: RawValue {
switch self {
case .debug:
return "DEBUG"
case .info:
return "INFO"
case .warn:
return "WARN"
case .error:
return "ERROR"
}
}
public init?(rawValue: RawValue) {
switch rawValue {
case "DEBUG":
self = .debug
case "INFO":
self = .info
case "WARN":
self = .warn
case "ERROR":
self = .error
default:
return nil
}
}
}
From the Xcode 6.3 release notes (emphasis added):
Swift Language Enhancements
...
Swift enums can now be exported to Objective-C using the #objc
attribute. #objc enums must declare an integer raw type, and cannot be
generic or use associated values. Because Objective-C enums are not
namespaced, enum cases are imported into Objective-C as the
concatenation of the enum name and case name.
Here's a solution that works.
#objc public enum ConnectivityStatus: Int {
case Wifi
case Mobile
case Ethernet
case Off
func name() -> String {
switch self {
case .Wifi: return "wifi"
case .Mobile: return "mobile"
case .Ethernet: return "ethernet"
case .Off: return "off"
}
}
}
Here is work around if you really want to achieve the goal. However, you can access the enum values in objects that Objective C accepts, not as actual enum values.
enum LogSeverity : String {
case Debug = "DEBUG"
case Info = "INFO"
case Warn = "WARN"
case Error = "ERROR"
private func string() -> String {
return self.rawValue
}
}
#objc
class LogSeverityBridge: NSObject {
class func Debug() -> NSString {
return LogSeverity.Debug.string()
}
class func Info() -> NSString {
return LogSeverity.Info.string()
}
class func Warn() -> NSString {
return LogSeverity.Warn.string()
}
class func Error() -> NSString {
return LogSeverity.Error.string()
}
}
To call :
NSString *debugRawValue = [LogSeverityBridge Debug]
If you don't mind to define the values in (Objective) C, you can use the NS_TYPED_ENUM macro to import constants in Swift.
For example:
.h file
typedef NSString *const ProgrammingLanguage NS_TYPED_ENUM;
FOUNDATION_EXPORT ProgrammingLanguage ProgrammingLanguageSwift;
FOUNDATION_EXPORT ProgrammingLanguage ProgrammingLanguageObjectiveC;
.m file
ProgrammingLanguage ProgrammingLanguageSwift = #"Swift";
ProgrammingLanguage ProgrammingLanguageObjectiveC = #"ObjectiveC";
In Swift, this is imported as a struct as such:
struct ProgrammingLanguage: RawRepresentable, Equatable, Hashable {
typealias RawValue = String
init(rawValue: RawValue)
var rawValue: RawValue { get }
static var swift: ProgrammingLanguage { get }
static var objectiveC: ProgrammingLanguage { get }
}
Although the type is not bridged as an enum, it feels very similar to one when using it in Swift code.
You can read more about this technique in Grouping Related Objective-C Constants
Code for Xcode 8, using the fact that Int works but other methods aren't exposed to Objective-C. This is pretty horrible as it stands...
class EnumSupport : NSObject {
class func textFor(logSeverity severity: LogSeverity) -> String {
return severity.text()
}
}
#objc public enum LogSeverity: Int {
case Debug
case Info
case Warn
case Error
func text() -> String {
switch self {
case .Debug: return "debug"
case .Info: return "info"
case .Warn: return "warn"
case .Error: return "error"
}
}
}
This is my use case:
I avoid hard-coded Strings whenever I can, so that I get compile warnings when I change something
I have a fixed list of String values coming from a back end, which can also be nil
Here's my solution that involves no hard-coded Strings at all, supports missing values, and can be used elegantly in both Swift and Obj-C:
#objc enum InventoryItemType: Int {
private enum StringInventoryItemType: String {
case vial
case syringe
case crystalloid
case bloodProduct
case supplies
}
case vial
case syringe
case crystalloid
case bloodProduct
case supplies
case unknown
static func fromString(_ string: String?) -> InventoryItemType {
guard let string = string else {
return .unknown
}
guard let stringType = StringInventoryItemType(rawValue: string) else {
return .unknown
}
switch stringType {
case .vial:
return .vial
case .syringe:
return .syringe
case .crystalloid:
return .crystalloid
case .bloodProduct:
return .bloodProduct
case .supplies:
return .supplies
}
}
var stringValue: String? {
switch self {
case .vial:
return StringInventoryItemType.vial.rawValue
case .syringe:
return StringInventoryItemType.syringe.rawValue
case .crystalloid:
return StringInventoryItemType.crystalloid.rawValue
case .bloodProduct:
return StringInventoryItemType.bloodProduct.rawValue
case .supplies:
return StringInventoryItemType.supplies.rawValue
case .unknown:
return nil
}
}
}
Here's what I came up with. In my case, this enum was in the context providing info for a specific class, ServiceProvider.
class ServiceProvider {
#objc enum FieldName : Int {
case CITY
case LATITUDE
case LONGITUDE
case NAME
case GRADE
case POSTAL_CODE
case STATE
case REVIEW_COUNT
case COORDINATES
var string: String {
return ServiceProvider.FieldNameToString(self)
}
}
class func FieldNameToString(fieldName:FieldName) -> String {
switch fieldName {
case .CITY: return "city"
case .LATITUDE: return "latitude"
case .LONGITUDE: return "longitude"
case .NAME: return "name"
case .GRADE: return "overallGrade"
case .POSTAL_CODE: return "postalCode"
case .STATE: return "state"
case .REVIEW_COUNT: return "reviewCount"
case .COORDINATES: return "coordinates"
}
}
}
From Swift, you can use .string on an enum (similar to .rawValue).
From Objective-C, you can use [ServiceProvider FieldNameToString:enumValue];
You can create an private Inner enum. The implementation is a bit repeatable, but clear and easy. 1 line rawValue, 2 lines init, which always look the same. The Inner has a method returning the "outer" equivalent, and vice-versa.
Has the added benefit that you can directly map the enum case to a String, unlike other answers here.
Please feel welcome to build on this answer if you know how to solve the repeatability problem with templates, I don't have time to mingle with it right now.
#objc enum MyEnum: NSInteger, RawRepresentable, Equatable {
case
option1,
option2,
option3
// MARK: RawRepresentable
var rawValue: String {
return toInner().rawValue
}
init?(rawValue: String) {
guard let value = Inner(rawValue: rawValue)?.toOuter() else { return nil }
self = value
}
// MARK: Obj-C support
private func toInner() -> Inner {
switch self {
case .option1: return .option1
case .option3: return .option3
case .option2: return .option2
}
}
private enum Inner: String {
case
option1 = "option_1",
option2 = "option_2",
option3 = "option_3"
func toOuter() -> MyEnum {
switch self {
case .option1: return .option1
case .option3: return .option3
case .option2: return .option2
}
}
}
}
I think #Remi 's answer crashes in some situations as I had this:
My error's screesshot. so I post my edition for #Remi 's answer:
#objc public enum LogSeverity: Int, RawRepresentable {
case debug
case info
case warn
case error
public typealias RawValue = String
public var rawValue: RawValue {
switch self {
case .debug:
return "DEBUG"
case .info:
return "INFO"
case .warn:
return "WARN"
case .error:
return "ERROR"
}
}
public init?(rawValue: RawValue) {
switch rawValue {
case "DEBUG":
self = .debug
case "INFO":
self = .info
case "WARN":
self = .warn
case "ERROR":
self = .error
default:
return nil
}
}
}

Updating object properties with codable extension in Swift

let me start by saying that I have already implemented Decodable which decodes JSON into several objects with these two Integer values:
public class ARBufferData: DecoderUpdatable {
private var previousStation: Int
private var numberOfElements: Int
func update(from decoder: Decoder) throws {
//Still needs work
}
}
What I am now trying to achieve is making the created objects updatable so that when a value in the JSON changes (e.g. numberOfElements) only the value is changed in the corresponding object. I believe this guide can enable me to do it, but I am having trouble implementing it: Understanding and Extending Swift 4’s Codable
This is the extension of KeyedDecodingContainer:
extension KeyedDecodingContainer {
func update<T: DecoderUpdatable>(_ value: inout T, forKey key: Key, userInfo: Any) throws {
let nestedDecoder = NestedDecoder(from: self, key: key)
try value.update(from: nestedDecoder)
}
}
The reason this would be helpful is that I can then set a property observer on that value and trigger a redraw of the visualisation.
I would be very grateful, if anyone can help or point me in the right direction.
Thank you!
Cheers
There are two ways to update the class. One, you decode each int by itself and compare. Two, you implement DecoderUpdatable for Int and call container.update with them as argument.
public class ARBufferData: NSObject, Decodable, DecoderUpdatable {
init(previousStation: Int, numberOfElements: Int) {
self.previousStation = previousStation
self.numberOfElements = numberOfElements
}
#objc dynamic var previousStation: Int
#objc dynamic var numberOfElements: Int
private enum CodingKeys: String, CodingKey {
case previousStation, numberOfElements
}
public func update(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
try container.update(&previousStation, forKey: .previousStation)
try container.update(&numberOfElements, forKey: .numberOfElements)
}
}
extension Int: DecoderUpdatable {
public mutating func update(from decoder: Decoder) throws {
let container = try decoder.singleValueContainer()
let result = try container.decode(Int.self)
guard result != self else { return }
self = result
}
}
I do not know whether the blogpost-writer intended it this way though. If he did, then generating the DecoderUpdatable conformances for the basic types could be a use case for Sourcery, but that's off topic here.
In Swift4 there is an interesting way to observe which you may also be interested in:
let token = buffer.observe(\.numberOfElements, options: [.new, .old]) {
object, change in
if change.oldValue != change.newValue {
// Act on change of buffer.numberOfElements.
}
}
Source

iOS Networking Layer Architecture

Reason For Post
There are so many different solutions & examples on how to build a proper networking layer, but every app has different constraints, and design decisions are made based on trade-offs, leaving me uncertain about the quality of code I've written. If there are any Anti-Patterns, redundancies, or flat out bad solutions within my code that I have overlooked or simply lacked the knowledge to address, please do critique. This is a project I'd like to add to my portfolio, so I'm posting it here to get eyes on it, with some advice/tips.
Thanks for your time in advanced!
Some characteristics of my networking layer that I think could raise eyebrows:
Method contains a GETALL case, to indicate a list of data that must be fetched. I have not seen this in any of the open source code I've read. Is this a code smell?
enum Method {
case GET
/// Indicates how JSON response should be handled differently to abastract a list of entities
case GETALL
case PUT
case DELETE
}
I've made it, so each Swift Entity conforms to JSONable protocol, meaning it can be initialized with json and converted to json.
protocol JSONable {
init?(json: [String: AnyObject])
func toJSON() -> Data?
}
JSONable in practice with one of my entities:
struct User {
var id: String
var name: String
var location: String
var rating: Double
var keywords: NSArray
var profileImageUrl: String
}
extension User: JSONable {
init?(json: [String : AnyObject]) {
guard let id = json[Constant.id] as? String, let name = json[Constant.name] as? String, let location = json[Constant.location] as? String, let rating = json[Constant.rating] as? Double, let keywords = json[Constant.keywords] as? NSArray, let profileImageUrl = json[Constant.profileImageUrl] as? String else {
return nil
}
self.init(id: id, name: name, location: location, rating: rating, keywords: keywords, profileImageUrl: profileImageUrl)
}
func toJSON() -> Data? {
let data: [String: Any] = [Constant.id: id, Constant.name: name, Constant.location: location, Constant.rating: rating, Constant.keywords: keywords, Constant.profileImageUrl: profileImageUrl]
let jsonData = try? JSONSerialization.data(withJSONObject: data, options: [])
return jsonData
}
}
This allows me to use generics to initialize all my entities in my client- FirebaseAPI, after I retrieve JSON response. I also haven't seen this technique in the code I've read.
In the code below, notice how GETALL is implemented to flatten the list of JSON objects. Should I have to do this at all? Is there a better way to handle any type of Json structure response?
AND Entities are initialized generically, and returned as an Observable ( Using RxSwift ).
Do you sense any code smells?
/// Responsible for Making actual API requests & Handling response
/// Returns an observable object that conforms to JSONable protocol.
/// Entities that confrom to JSONable just means they can be initialized with json & transformed from swift to JSON.
func rx_fireRequest<Entity: JSONable>(_ endpoint: FirebaseEndpoint, ofType _: Entity.Type ) -> Observable<[Entity]> {
return Observable.create { [weak self] observer in
self?.session.dataTask(with: endpoint.request, completionHandler: { (data, response, error) in
/// Parse response from request.
let parsedResponse = Parser(data: data, response: response, error: error)
.parse()
switch parsedResponse {
case .error(let error):
observer.onError(error)
return
case .success(let data):
var entities = [Entity]()
switch endpoint.method {
/// Flatten JSON strucuture to retrieve a list of entities.
/// Denoted by 'GETALL' method.
case .GETALL:
/// Key (underscored) is unique identifier for each entity
/// value is k/v pairs of entity attributes.
for (_, value) in data {
if let value = value as? [String: AnyObject], let entity = Entity(json: value) {
entities.append(entity)
}
}
/// Force downcast for generic type inference.
observer.onNext(entities as! [Entity])
//observer.onCompleted()
/// All other methods return JSON that can be used to initialize JSONable entities
default:
if let entity = Entity(json: data) {
observer.onNext([entity] as! [Entity])
//observer.onCompleted()
} else {
observer.onError(NetworkError.initializationFailure)
}
}
}
}).resume()
return Disposables.create()
}
}
}
I manage different endpoints like so:
enum FirebaseEndpoint {
case saveUser(data: [String: AnyObject])
case fetchUser(id: String)
case removeUser(id: String)
case saveItem(data: [String: AnyObject])
case fetchItem(id: String)
case fetchItems
case removeItem(id: String)
case saveMessage(data: [String: AnyObject])
case fetchMessages(chatroomId: String)
case removeMessage(id: String)
}
extension FirebaseEndpoint: Endpoint {
var base: String {
// Add this as a constant to APP Secrts struct & dont include secrets file when pushed to github.
return "https://AppName.firebaseio.com"
}
var path: String {
switch self {
case .saveUser(let data): return "/\(Constant.users)/\(data[Constant.id])"
case .fetchUser(let id): return "/\(Constant.users)/\(id)"
case .removeUser(let id): return "/\(Constant.users)/\(id)"
case .saveItem(let data): return "/\(Constant.items)/\(data[Constant.id])"
case .fetchItem(let id): return "/\(Constant.items)/\(id)"
case .fetchItems: return "/\(Constant.items)"
case .removeItem(let id): return "/\(Constant.items)/\(id)"
case .saveMessage(let data): return "/\(Constant.messages)/\(data[Constant.id])"
case .fetchMessages(let chatroomId): return "\(Constant.messages)/\(chatroomId)"
case .removeMessage(let id): return "/\(Constant.messages)/\(id)"
}
}
var method: Method {
switch self {
case .fetchUser, .fetchItem: return .GET
case .fetchItems, .fetchMessages: return .GETALL
case .saveUser, .saveItem, .saveMessage: return .PUT
case .removeUser, .removeItem, .removeMessage: return .DELETE
}
}
var body: [String : AnyObject]? {
switch self {
case .saveItem(let data), .saveUser(let data), .saveMessage(let data): return data
default: return nil
}
}
}
Last thing, I'd like someone with professional eyes to look at is, how I use MVVM. I make all network requests from view model, which comes out looking something like this:
struct SearchViewModel {
// Outputs
var collectionItems: Observable<[Item]>
var error: Observable<Error>
init(controlValue: Observable<Int>, api: FirebaseAPI, user: User) {
let serverItems = controlValue
.map { ItemCategory(rawValue: $0) }
.filter { $0 != nil }.map { $0! }
.flatMap { api.rx_fetchItems(for: user, category: $0)
.materialize()
}
.filter { !$0.isCompleted }
.shareReplayLatestWhileConnected()
collectionItems = serverItems.filter { $0.element != nil }.dematerialize()
error = serverItems.filter { $0.error != nil }.map { $0.error! }
}
}
In order to call api requests in a more expressive, formalized way, I am able to call api.rx_fetchItems(for:) inside flatmap above, because I extend FirebaseAPI to conform to FetchItemsAPI. I will probably have to follow the same pattern for most other requests.
extension FirebaseAPI: FetchItemsAPI {
// MARK: Fetch Items Protocol
func rx_fetchItems(for user: User, category: ItemCategory) -> Observable<[Item]> {
// fetched items returns all items in database as Observable<[Item]>
let fetchedItems = rx_fireRequest(.fetchItems, ofType: Item.self)
switch category {
case .Local:
let localItems = fetchedItems
.flatMapLatest { (itemList) -> Observable<[Item]> in
return self.rx_localItems(user: user, items: itemList)
}
return localItems
case .RecentlyAdded:
// Compare current date to creation date of item. If its within 24 hours, It makes the cut.
let recentlyAddedItems = fetchedItems
.flatMapLatest { (itemList) -> Observable<[Item]> in
return self.rx_recentlyAddedItems(items: itemList)
}
return recentlyAddedItems
case .Trending:
let trendingItems = fetchedItems
.flatMapLatest { (itemList) -> Observable<[Item]> in
return self.rx_trendingItems(items: itemList)
}
return trendingItems
default:
let stubItem = Item(id: "DEFAULT", createdById: "createdBy", creationDate: 1.3, expirationDate: 2.4, title: "title", price: 2, info: "info", imageUrl: "url", bidCount: 4, location: "LA")
return Observable.just([stubItem])
}
}
// MARK: Helper Methods
private func rx_localItems(user: User, items: [Item]) -> Observable<[Item]> {
return Observable<[Item]>.create { observer in
observer.onNext(items.filter { $0.location == user.location }) // LA Matches stubs in db
return Disposables.create()
}
}
func rx_recentlyAddedItems(items: [Item]) -> Observable<[Item]> {
return Observable<[Item]>.create { observer in
let recentItems = items
.filter {
let now = Date(timeIntervalSinceReferenceDate: 0)
let creationDate = Date(timeIntervalSince1970: $0.creationDate)
if let hoursAgo = now.offset(from: creationDate, units: [.hour], maxUnits: 1) {
return Int(hoursAgo)! < 24
} else {
return false
}
}
observer.onNext(recentItems)
return Disposables.create()
}
}
func rx_trendingItems(items: [Item]) -> Observable<[Item]> {
return Observable<[Item]>.create { observer in
observer.onNext(items.filter { $0.bidCount > 8 })
return Disposables.create()
}
}
}
I'm attempting to follow SOLID principles, and level up with RxSWift + MVVM, so I'm still unsure about the best OOP design for clean, maintainable code.

Enum of structs in Swift 3.0

I am trying to create an enum of a struct that I would like to initialize:
struct CustomStruct {
var variable1: String
var variable2: AnyClass
var variable3: Int
init (variable1: String, variable2: AnyClass, variable3: Int) {
self.variable1 = variable1
self.variable2 = variable2
self.variable3 = variable3
}
}
enum AllStructs: CustomStruct {
case getData
case addNewData
func getAPI() -> CustomStruct {
switch self {
case getData:
return CustomStruct(variable1:"data1", variable2: SomeObject.class, variable3: POST)
case addNewData:
// Same to same
default:
return nil
}
}
}
I get the following errors:
Type AllStructs does not conform to protocol 'RawRepresentable'
I am assuming that enums cannot be used this way. We must use primitives.
It should be:
struct CustomStruct {
var apiUrl: String
var responseType: AnyObject
var httpType: Int
init (variable1: String, variable2: AnyObject, variable3: Int) {
self.apiUrl = variable1
self.responseType = variable2
self.httpType = variable3
}
}
enum MyEnum {
case getData
case addNewData
func getAPI() -> CustomStruct {
switch self {
case .getData:
return CustomStruct(variable1: "URL_TO_GET_DATA", variable2: 11 as AnyObject, variable3: 101)
case .addNewData:
return CustomStruct(variable1: "URL_TO_ADD_NEW_DATA", variable2: 12 as AnyObject, variable3: 102)
}
}
}
Usage:
let data = MyEnum.getData
let myObject = data.getAPI()
// this should logs: "URL_TO_GET_DATA 11 101"
print(myObject.apiUrl, myObject.responseType, myObject.httpType)
Note that upon Naming Conventions, struct should named as CustomStruct and enum named as MyEnum.
In fact, I'm not pretty sure of the need of letting CustomStruct to be the parent of MyEnum to achieve what are you trying to; As mentioned above in the snippets, you can return an instance of the struct based on what is the value of the referred enum.
I'm not commenting on the choice to use an enum here, but just explaining why you got that error and how to declare an enum that has a custom object as parent.
The error shows you the problem, CustomStruct must implement RawRepresentable to be used as base class of that enum.
Here is a simplified example that shows you what you need to do:
struct CustomStruct : ExpressibleByIntegerLiteral, Equatable {
var rawValue: Int = 0
init(integerLiteral value: Int){
self.rawValue = value
}
static func == (lhs: CustomStruct, rhs: CustomStruct) -> Bool {
return
lhs.rawValue == rhs.rawValue
}
}
enum AllStructs: CustomStruct {
case ONE = 1
case TWO = 2
}
A few important things that we can see in this snippet:
The cases like ONE and TWO must be representable with a Swift literal, check this Swift 2 post for a list of available literals (int,string,array,dictionary,etc...). But please note that in Swift 3, the LiteralConvertible protocols are now called ExpressibleByXLiteral after the Big Swift Rename.
The requirement to implement RawRepresentable is covered implementing one of the Expressible protocols (init?(rawValue:) will leverage the initializer we wrote to support literals).
Enums must also be Equatable , so you'll have to implement the equality operator for your CustomStruct base type.
Did you try conforming to RawRepresentable like the error is asking?
Using JSON representation should work for variable1 and variable3. Some extra work may be required for variable2.
struct CustomStruct: RawRepresentable {
var variable1: String
var variable2: AnyClass
var variable3: Int
init?(rawValue: String) {
guard let data = rawValue.data(using: .utf8) else {
return nil
}
guard let json = try? JSONSerialization.jsonObject(with: data, options: []) as? [String: Any] else {
return nil
}
self.variable1 = (json["variable1"] as? String) ?? ""
self.variable2 = (json["variable2"] as? AnyClass) ?? AnyClass()
self.variable3 = (json["variable3"] as? Int) ?? 0
}
var rawValue: String {
let json = ["variable1": self.variable1,
"variable2": self.variable2,
"variable3": self.variable3
]
guard let data = try? JSONSerialization.data(withJSONObject: json, options: []) else {
return ""
}
return String(data: data, encoding: .utf8) ?? ""
}
}
According to the documentation:
If a value (known as a “raw” value) is provided for each enumeration case, the value can be a string, a character, or a value of any integer or floating-point type.
So yes, you cannot set a struct type to be enum's raw value.
In your case I would suggest using string as the enum raw value and some dictionary mapping these strings to CUSTOM_STRUCT type.
A bit late on the party but maybe useful for someone else. I would consider to simply use computed variables instead of a struct.
enum MyEnum {
case getData
case addNewData
var variable1: String {
switch self {
case .getData: return "data1"
case .addNewData: return "data2"
}
}
var variable2: Int {
switch self {
case .getData: return 1
case .addNewData: return 2
}
}
// ....
}
Usage:
let data = MyEnum.getData
print (data.variable1) // "data1"

How to get a swift enum's associated value regardless of the enum case

I have an object FormField which has two properties: a String name, and a value which can accept any type--hence I've made it Any!. However, I've been told in a separate question to use an enum with associated values instead of Any!.
enum Value {
case Text(String!)
case CoreDataObject(NSManagedObject!)
}
class FormField {
var name: String
var value: Value?
// initializers...
}
This approach makes it awfully verbose to check for nullity however. If I wanted to display an alert view for all the missing fields in the form, I'll have to repeat a nil check for every case in a switch statement:
for field in self.fields {
if let value = field.value {
switch value {
case .Text(let text):
if text == nil {
missingFields.append(field.name)
}
case .CoreDataObject(let object):
if object == nil {
missingFields.append(field.name)
}
}
}
}
Is there a shorter way of accessing the enum's associated value, regardless of the type? If I make FormField.value an Any! the above code would be as easy as:
for field in self.fields {
if field.value == nil {
missingFields.append(field.name)
}
}
Define a method isMissing() inside the enum - write it once and only once. Then you get nearly exactly what you prefer:
for field in self.fields {
if field.value.isMissing() {
missingFields.append(field.name)
}
}
It would look something like this (from the Swift Interpreter):
1> class Foo {}
>
2> enum Value {
3. case One(Foo!)
4. case Two(Foo!)
5.
6. func isMissing () -> Bool {
7. switch self {
8. case let .One(foo): return foo == nil
9. case let .Two(foo): return foo == nil
10. }
11. }
12. }
13> let aVal = Value.One(nil)
aVal: Value = One {
One = nil
}
14> aVal.isMissing()
$R0: Bool = true
With Swift 2 it's possible to get the associated value using reflection.
To make that easier just add the code below to your project and extend your enum with the EVAssociated protocol.
public protocol EVAssociated {
}
public extension EVAssociated {
public var associated: (label:String, value: Any?) {
get {
let mirror = Mirror(reflecting: self)
if let associated = mirror.children.first {
return (associated.label!, associated.value)
}
print("WARNING: Enum option of \(self) does not have an associated value")
return ("\(self)", nil)
}
}
}
Then you can access the .asociated value with code like this:
class EVReflectionTests: XCTestCase {
func testEnumAssociatedValues() {
let parameters:[EVAssociated] = [usersParameters.number(19),
usersParameters.authors_only(false)]
let y = WordPressRequestConvertible.MeLikes("XX", Dictionary(associated: parameters))
// Now just extract the label and associated values from this enum
let label = y.associated.label
let (token, param) = y.associated.value as! (String, [String:Any]?)
XCTAssertEqual("MeLikes", label, "The label of the enum should be MeLikes")
XCTAssertEqual("XX", token, "The token associated value of the enum should be XX")
XCTAssertEqual(19, param?["number"] as? Int, "The number param associated value of the enum should be 19")
XCTAssertEqual(false, param?["authors_only"] as? Bool, "The authors_only param associated value of the enum should be false")
print("\(label) = {token = \(token), params = \(param)")
}
}
// See http://github.com/evermeer/EVWordPressAPI for a full functional usage of associated values
enum WordPressRequestConvertible: EVAssociated {
case Users(String, Dictionary<String, Any>?)
case Suggest(String, Dictionary<String, Any>?)
case Me(String, Dictionary<String, Any>?)
case MeLikes(String, Dictionary<String, Any>?)
case Shortcodes(String, Dictionary<String, Any>?)
}
public enum usersParameters: EVAssociated {
case context(String)
case http_envelope(Bool)
case pretty(Bool)
case meta(String)
case fields(String)
case callback(String)
case number(Int)
case offset(Int)
case order(String)
case order_by(String)
case authors_only(Bool)
case type(String)
}
The code above is now available as a cocoapod susbspec at
https://github.com/evermeer/Stuff#enum
It also has an other nice enum extension for enumerating all enum values.
If the associated values were of the same type for all enum cases the following approach could help.
enum Value {
case text(NSString!), two(NSString!), three(NSString!) // This could be any other type including AnyClass
}
// Emulating "fields" datastruct for demo purposes (as if we had struct with properties).
typealias Field = (en: Value, fieldName: String)
let fields: [Field] = [(.text(nil),"f1"), (.two(nil), "f2"), (.three("Hey"), "f3")] // this is analog of "fields"
let arrayOfFieldNamesWithEmptyEnums: [String] = fields.compactMap({
switch $0.en {
case let .text(foo), let .two(foo), let .three(foo): if foo == nil { return $0.fieldName } else { return nil }}
})
print("arrayOfFieldNamesWithEmptyEnums \(arrayOfFieldNamesWithEmptyEnums)")
Many other things can be obtained similarly.
let arrayOfEnumsWithoutValues: [Value] = fields.compactMap({
switch $0.en {
case let .text(foo), let .two(foo), let .three(foo): if foo == nil { return $0.en } else { return nil }}
})
print("arrayOfEnumsWithoutValues \(arrayOfEnumsWithoutValues)")
// just to check ourselves
if let index = arrayOfEnumsWithoutValues.index(where: { if case .two = $0 { return true }; return false }) {
print(".two found at index \(index)")
}

Resources