Values, Protocols and Codables
Florent Vilmart
•
•
•
•
What we’ll cover
Disclaimer
I have no idea what I’m doin’
•
•
•
•
•
Parse SDK
not your average SDK
An API, to build API’s
Parse SDK iOS/OSX Parse Swift
Runtime Dynamism Compile-time Dynamism
Object Oriented PoP
NSObject struct
async (Bolts) sync
iOS/macOS/tvOS/watchOS Just Swift
Objective-C vs Swift
The past, the present…
o
An REST API is a contract
… but …
Object
Data
Data
Object
Serialize ⚠️💣
Send
Responds
💣 ⚠️ Deserialize
💥
💥
Any API Client…
Till today
T:
Encodable
Data
Data
U:
Decodable
JSONEncoder
Send
Responds
JSONDecoderFor Free!
For Free!
URLSession
REST with Swift 4
T: Encodable
URLSession
U: Decodable
Great spot to fake / mock your server!
Or datastore
Or anything really…
REST abstract
Encodable / Decodable
Strings, Numbers, enum, Arrays, Dictionaries (of Codables) …
Objects / Structs with only Codable’s
Custom *(through protocol conformance)
Implement CodingKeys for encoding subset
Composability
Codable’s
•
•
•
•
Encoders / Decoders
•
•
•
•
•
JSONEncoder / ParseEncoder
JSON and friends
Protocols
protocol Saving: Encodable {
associatedtype SavingType
func save(options: Options) throws -> SavingType
func save() throws -> SavingType
}
protocol Fetching: Decodable {
associatedtype FetchingType
func fetch(options: Options) throws -> FetchingType
func fetch() throws -> FetchingType
}
ObjectType Protocol
public protocol ObjectType: Fetching, Saving {
static var className: String { get }
var objectId: String? { get set }
var createdAt: Date? { get set }
var updatedAt: Date? { get set }
var ACL: ACL? { get set }
}
public extension ObjectType {
public func save(options: Options = []) throws -> Self {
return try Command<Self, SaveResponse>(method: .POST,
path: object.endpoint,
params: nil,
body: object)
.execute(options: options)
.map { $0.apply(self) }
}
}
Extensible
extension Saving {
typealias Callback = (Self.SavingType?, Error?) -> Void
func save(options: API.Options = [],
callback: @escaping Callback) {
runAsync(options: options,
function: self.save,
callback: callback)
}
}
runAsync
let queue = DispatchQueue(label: "org.parse-community.ParseSwift.async")
private func runAsync<T>(options: API.Options,
function: @escaping (API.Options) throws -> T?,
callback: @escaping (T?, Error?) -> Void) {
queue.async {
do {
callback(try function(options), nil)
} catch let e {
callback(nil, e)
}
}
}
As a SDK user
struct GameScore: ParseSwift.ObjectType {
//: Those are required for ObjectType
var objectId: String?
var createdAt: Date?
var updatedAt: Date?
var ACL: ACL?
//: Your own properties
var score: Int
//: a custom initializer
init(score: Int) {
self.score = score
}
}
let score = GameScore(score: 10)
guard let savedScore = try? score.save() else { fatalError() }
public struct Command<T, U>: Encodable where T: Encodable, U: Decodable {
let method: Method
let path: Endpoint
let params: [String: String?]?
let body: T?
public func data() throws -> Data? {
return try JSONEncoder().encode(body)
}
public func execute(options: Options) throws -> Response<U> {
return try options.executor.execute(command: self, options: options)
}
internal func decode(responseData: Data) throws -> Response<U> {
do {
return Response(object: try Decoder.json.decode(U.self, from: responseData))
} catch _ {
throw try Decoder.json.decode(ParseError.self, from: responseData)
}
}
}
Command
Commands
extension Command where T: ObjectType {
// MARK: custom encoding
func data() throws -> Data? {
return try API.Encoder.parse.encode(body)
}
}
struct Response<T> where T: Decodable {
let object: T
func map<U>(_ mapper: (T) throws -> U) rethrows -> U {
return try mapper(object)
}
}
Executor
extension API.Command {
internal func getURLRequest(options: API.Options) throws -> URLRequest {
/* build a URLRequest from the command */
}
}
extension URLSession: APIExecutor {
public func execute<T, U>(command: API.Command<T, U>,
options: API.Options) throws -> API.Response<U> {
let urlRequest = try command.getURLRequest(options: options)
let responseData = try self.syncDataTask(with: urlRequest)
return try command.decode(responseData: responseData)
}
}
•
•
•
•
•
About Async
A Quick Word on sync code
no async is the best async
extension URLSession {
internal func syncDataTask(with request: URLRequest) throws -> Data {
let semaphore = DispatchSemaphore(value: 0)
var data: Data?
var error: Error?
var response: URLResponse?
dataTask(with: request) { (responseData, urlResponse, responseError) in
data = responseData; error = responseError; response = urlResponse;
semaphore.signal()
}.resume()
semaphore.wait()
guard let responseData = data else {
guard let error = error else { // no err no res?
throw NSError(domain: "unknown", code: -1, userInfo: ["response": response!])
}
throw error
}
return responseData
}
}
🤦♀️
•
•
•
•
•
What’s next
Because all is not green!
Values, Protocols and Codables
Florent Vilmart

Value protocols and codables

  • 1.
    Values, Protocols andCodables Florent Vilmart
  • 2.
  • 3.
    Disclaimer I have noidea what I’m doin’
  • 4.
  • 5.
    An API, tobuild API’s
  • 6.
    Parse SDK iOS/OSXParse Swift Runtime Dynamism Compile-time Dynamism Object Oriented PoP NSObject struct async (Bolts) sync iOS/macOS/tvOS/watchOS Just Swift Objective-C vs Swift The past, the present…
  • 7.
  • 9.
    An REST APIis a contract … but …
  • 10.
    Object Data Data Object Serialize ⚠️💣 Send Responds 💣 ⚠️Deserialize 💥 💥 Any API Client… Till today
  • 11.
  • 12.
    T: Encodable URLSession U: Decodable Greatspot to fake / mock your server! Or datastore Or anything really… REST abstract
  • 13.
    Encodable / Decodable Strings,Numbers, enum, Arrays, Dictionaries (of Codables) … Objects / Structs with only Codable’s Custom *(through protocol conformance) Implement CodingKeys for encoding subset Composability Codable’s
  • 14.
  • 15.
  • 17.
    Protocols protocol Saving: Encodable{ associatedtype SavingType func save(options: Options) throws -> SavingType func save() throws -> SavingType } protocol Fetching: Decodable { associatedtype FetchingType func fetch(options: Options) throws -> FetchingType func fetch() throws -> FetchingType }
  • 18.
    ObjectType Protocol public protocolObjectType: Fetching, Saving { static var className: String { get } var objectId: String? { get set } var createdAt: Date? { get set } var updatedAt: Date? { get set } var ACL: ACL? { get set } } public extension ObjectType { public func save(options: Options = []) throws -> Self { return try Command<Self, SaveResponse>(method: .POST, path: object.endpoint, params: nil, body: object) .execute(options: options) .map { $0.apply(self) } } }
  • 19.
    Extensible extension Saving { typealiasCallback = (Self.SavingType?, Error?) -> Void func save(options: API.Options = [], callback: @escaping Callback) { runAsync(options: options, function: self.save, callback: callback) } }
  • 20.
    runAsync let queue =DispatchQueue(label: "org.parse-community.ParseSwift.async") private func runAsync<T>(options: API.Options, function: @escaping (API.Options) throws -> T?, callback: @escaping (T?, Error?) -> Void) { queue.async { do { callback(try function(options), nil) } catch let e { callback(nil, e) } } }
  • 21.
    As a SDKuser struct GameScore: ParseSwift.ObjectType { //: Those are required for ObjectType var objectId: String? var createdAt: Date? var updatedAt: Date? var ACL: ACL? //: Your own properties var score: Int //: a custom initializer init(score: Int) { self.score = score } } let score = GameScore(score: 10) guard let savedScore = try? score.save() else { fatalError() }
  • 22.
    public struct Command<T,U>: Encodable where T: Encodable, U: Decodable { let method: Method let path: Endpoint let params: [String: String?]? let body: T? public func data() throws -> Data? { return try JSONEncoder().encode(body) } public func execute(options: Options) throws -> Response<U> { return try options.executor.execute(command: self, options: options) } internal func decode(responseData: Data) throws -> Response<U> { do { return Response(object: try Decoder.json.decode(U.self, from: responseData)) } catch _ { throw try Decoder.json.decode(ParseError.self, from: responseData) } } } Command
  • 23.
    Commands extension Command whereT: ObjectType { // MARK: custom encoding func data() throws -> Data? { return try API.Encoder.parse.encode(body) } } struct Response<T> where T: Decodable { let object: T func map<U>(_ mapper: (T) throws -> U) rethrows -> U { return try mapper(object) } }
  • 24.
    Executor extension API.Command { internalfunc getURLRequest(options: API.Options) throws -> URLRequest { /* build a URLRequest from the command */ } } extension URLSession: APIExecutor { public func execute<T, U>(command: API.Command<T, U>, options: API.Options) throws -> API.Response<U> { let urlRequest = try command.getURLRequest(options: options) let responseData = try self.syncDataTask(with: urlRequest) return try command.decode(responseData: responseData) } }
  • 25.
  • 26.
    A Quick Wordon sync code no async is the best async extension URLSession { internal func syncDataTask(with request: URLRequest) throws -> Data { let semaphore = DispatchSemaphore(value: 0) var data: Data? var error: Error? var response: URLResponse? dataTask(with: request) { (responseData, urlResponse, responseError) in data = responseData; error = responseError; response = urlResponse; semaphore.signal() }.resume() semaphore.wait() guard let responseData = data else { guard let error = error else { // no err no res? throw NSError(domain: "unknown", code: -1, userInfo: ["response": response!]) } throw error } return responseData } } 🤦♀️
  • 27.
  • 28.
    Values, Protocols andCodables Florent Vilmart

Editor's Notes

  • #6 - Many of you may have already used, it - Schema less
  • #7 Unlike other SDK’s like FB/Github, schemas are not defined by us We provide the facility to create custom schemas Which makes the whole shebang quite more difficult. But, we know response types, error codes, bare minimal object (objectId, createdAt… , errors, responses)
  • #19 start with those 2 fundamental protocols Becaust that’s the base (for files and object and Pointers etc…) Provides the abstraction necessary. Could add Destroying but … Easily extensible
  • #24 - Encodable, because batch API 🤓 - params is [String: String?]? 😪 (QueryStringEncoder?) - tied to HTTP semantics 🤷🏻‍♀️ - executor in options func data() throws -> Data? Response (simple wrapper) I cannot subclass, butt……
  • #27 not a big fan of deps of deps of deps taking a side, is usually hindering adoption Who’S better? Rx, Reactive, Bolts, PromiseKit… Don’t care much