Last active
September 19, 2021 19:13
-
-
Save olejorgensen/71a063fbf77c2747f956ae8feb7a820b to your computer and use it in GitHub Desktop.
Swift Json Service Template
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import Foundation | |
import Combine | |
protocol JsonServiceProtocol { | |
var decoder: JSONDecoder { get } | |
var errorPublisher: PassthroughSubject<Error, Never> { get } | |
func publish(err: Error) | |
} | |
class BaseService: NSObject, JsonServiceProtocol { | |
// MARK:- Properties | |
let decoder: JSONDecoder | |
let errorPublisher = PassthroughSubject<Error, Never>() | |
//MARK:- Init, Deinit | |
override init() { | |
self.decoder = ServiceSettings.iso8601FullDecoder | |
super.init() | |
} | |
init(decoder: JSONDecoder) { | |
self.decoder = decoder | |
} | |
//MARK:- Methods | |
func publish(err: Error) { | |
publish(error: err as NSError) | |
} | |
func publish(error: NSError) { | |
RunLoop.main.perform { | |
Swift.print(error) | |
Swift.print(error.localizedDescription) | |
Swift.print(error.debugDescription) | |
self.errorPublisher.send(error) | |
} | |
} | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
extension DateFormatter { | |
static let iso8601Full: DateFormatter = { | |
let formatter = DateFormatter() | |
formatter.dateFormat = "yyyy-MM-dd'T'HH:mm:ss.SSS" | |
formatter.calendar = Calendar(identifier: .iso8601) | |
formatter.timeZone = TimeZone(secondsFromGMT: 0) | |
formatter.locale = Locale(identifier: "en_US_POSIX") | |
return formatter | |
}() | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import Foundation | |
import CoreLocation | |
import Combine | |
import MapKit | |
class GistService: BaseService { | |
// MARK:- Properties | |
let didRecieveYourObject = PassthroughSubject<YourObject, Never>() | |
private var isBusy = false | |
// MARK:- Metoder | |
func fetchYourObject(someQueryOrFilter: String) { | |
if isBusy { | |
return | |
} | |
guard let trimmedQuery = someQueryOrFilter.trimmedOrNil else { return } | |
guard let escapedQuery = trimmedQuery.toQueryValue else { return } | |
var path = "\(ServiceSettings.baseUrl)/yourobject?q=\(escapedQuery)" | |
guard let url = URL(string: path) else { | |
self.publish(err: ServiceErrors.makeInvalidQueryError(query: trimmedQuery)) | |
return | |
} | |
URLSession.shared.dataTask(with: url, completionHandler: { (data, response, error) in | |
self.isBusy = true | |
do { | |
if let err = error { | |
self.publish(err: err) | |
} else { | |
if let httpResponse = response as? HTTPURLResponse, let data = data { | |
if httpResponse.statusCode != 200 { | |
self.publish(err: ServiceErrors.makeHttpResponseError(urlString: path, statusCode: httpResponse.statusCode)) | |
} else { | |
var yourObject = try self.decoder.decode(YourObject.self, from: data) | |
RunLoop.main.perform { | |
self.didRecieveYourObject.send(yourObject) | |
} | |
} | |
} else { | |
self.publish(err: ServiceErrors.Network) | |
} | |
} | |
} catch let err { | |
self.publish(err: err) | |
} | |
self.isBusy = false | |
}).resume() | |
} | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import Foundation | |
class ServiceErrors { | |
static let ServiceErrorDomain = "ServiceErrors" | |
public static let Unknown = NSError( | |
domain: ServiceErrorDomain, | |
code: 20000, | |
userInfo: [ "Message" : "unknown error message" ] | |
) | |
public static let NoResult = NSError( | |
domain: ServiceErrorDomain, | |
code: 20001, | |
userInfo: [ "Message" : "no results message" ] | |
) | |
public static let Network = NSError( | |
domain: ServiceErrorDomain, | |
code: 20002, | |
userInfo: [ "Message" : "network error" ] | |
) | |
public static func makeHttpResponseError(urlString: String, statusCode: Int) -> Error { | |
return NSError( | |
domain: ServiceErrorDomain, | |
code: 20003, | |
userInfo: [ | |
"Message" : "friendly network error message", | |
"URL" : urlString, | |
"StatusCode" : statusCode | |
] | |
) | |
} | |
public static func makeInvalidRequestError(urlString: String) -> Error { | |
return NSError( | |
domain: ServiceErrorDomain, | |
code: 20004, | |
userInfo: [ | |
"Message" : "friendly invalid request error message", | |
"URL" : urlString | |
] | |
) | |
} | |
public static func makeInvalidQueryError(query: String) -> Error { | |
return NSError( | |
domain: ServiceErrorDomain, | |
code: 20005, | |
userInfo: [ | |
"Message" : "friendly invalid query error message", | |
"Query" : query | |
] | |
) | |
} | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
class ServiceSettings { | |
static let iso8601FullDecoder: JSONDecoder = { | |
var jsonDecoder = JSONDecoder() | |
jsonDecoder.dateDecodingStrategy = .formatted(DateFormatter.iso8601Full) | |
return jsonDecoder | |
}() | |
static let baseUrl: String = "https://some.service.org" | |
static let danishDateFormat: DateFormatter = { | |
let formatter = DateFormatter() | |
formatter.locale = Locale(identifier: "da_DK") | |
formatter.dateStyle = .medium | |
return formatter | |
}() | |
/// Most services only supports English floats | |
static let englishDecimalFormat: NumberFormatter = { | |
let formatter = NumberFormatter() | |
formatter.locale = Locale(identifier: "en_US") | |
formatter.numberStyle = .decimal | |
formatter.maximumFractionDigits = 6 | |
formatter.minimumFractionDigits = 6 | |
return formatter | |
}() | |
/// Present service floats in current locale | |
static let decimalFormatUI: NumberFormatter = { | |
let formatter = englishDecimalFormat.copy() as! NumberFormatter | |
formatter.locale = Locale.current | |
return formatter | |
}() | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import Foundation | |
public extension String { | |
/** | |
Trim using CharacterSet.whitespacesAndNewlines | |
*/ | |
var trimmedOrNil: String? { | |
get { | |
let trimmed = self.trimmingCharacters(in: CharacterSet.whitespacesAndNewlines) | |
return trimmed.isEmpty ? nil : trimmed | |
} | |
} | |
/** | |
Encode using CharacterSet.urlQueryAllowed | |
*/ | |
var toQueryValue: String? { | |
get { | |
return self.addingPercentEncoding(withAllowedCharacters: CharacterSet.urlQueryAllowed) | |
} | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment