Skip to content

Commit c68aeb5

Browse files
committed
IOS-25: Implement result type for API classes
1 parent 1e8501a commit c68aeb5

File tree

17 files changed

+481
-388
lines changed

17 files changed

+481
-388
lines changed

Photobook.xcodeproj/xcshareddata/xcschemes/Photobook Test.xcscheme

Lines changed: 16 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,22 @@
2727
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
2828
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
2929
shouldUseLaunchSchemeArgsEnv = "NO">
30+
<MacroExpansion>
31+
<BuildableReference
32+
BuildableIdentifier = "primary"
33+
BlueprintIdentifier = "2E0B587B1FB30F2100F31A9E"
34+
BuildableName = "Photobook App.app"
35+
BlueprintName = "Photobook App"
36+
ReferencedContainer = "container:Photobook.xcodeproj">
37+
</BuildableReference>
38+
</MacroExpansion>
39+
<EnvironmentVariables>
40+
<EnvironmentVariable
41+
key = "TESTS_RUNNING"
42+
value = "1"
43+
isEnabled = "YES">
44+
</EnvironmentVariable>
45+
</EnvironmentVariables>
3046
<Testables>
3147
<TestableReference
3248
skipped = "NO">
@@ -49,24 +65,6 @@
4965
</BuildableReference>
5066
</TestableReference>
5167
</Testables>
52-
<MacroExpansion>
53-
<BuildableReference
54-
BuildableIdentifier = "primary"
55-
BlueprintIdentifier = "2E0B587B1FB30F2100F31A9E"
56-
BuildableName = "Photobook App.app"
57-
BlueprintName = "Photobook App"
58-
ReferencedContainer = "container:Photobook.xcodeproj">
59-
</BuildableReference>
60-
</MacroExpansion>
61-
<EnvironmentVariables>
62-
<EnvironmentVariable
63-
key = "TESTS_RUNNING"
64-
value = "1"
65-
isEnabled = "YES">
66-
</EnvironmentVariable>
67-
</EnvironmentVariables>
68-
<AdditionalOptions>
69-
</AdditionalOptions>
7068
</TestAction>
7169
<LaunchAction
7270
buildConfiguration = "Debug Test"
@@ -88,8 +86,6 @@
8886
ReferencedContainer = "container:Photobook.xcodeproj">
8987
</BuildableReference>
9088
</BuildableProductRunnable>
91-
<AdditionalOptions>
92-
</AdditionalOptions>
9389
</LaunchAction>
9490
<ProfileAction
9591
buildConfiguration = "Release Test"

Photobook/Controllers/OrderSummaryViewController.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -149,7 +149,7 @@ class OrderSummaryViewController: UIViewController {
149149
coverSnapshotPageView.backgroundColor = .clear
150150
coverSnapshotPageView.frame.size = CGSize(width: Constants.dimensionsForCoverPhotoSnapshot, height: Constants.dimensionsForCoverPhotoSnapshot / product.photobookTemplate.coverAspectRatio)
151151
coverSnapshotPageView.productLayout = product.productLayouts.first
152-
152+
coverSnapshotPageView.product = product
153153
coverSnapshotPageView.color = product.coverColor
154154
coverSnapshotPageView.setupTextBox(mode: .userTextOnly)
155155

Photobook/Model/Clients/APIClient.swift

Lines changed: 59 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -198,7 +198,7 @@ class APIClient: NSObject {
198198
/// Called when the app is launched by the system by pending tasks
199199
///
200200
/// - Parameter completionHandler: The completion handler provided by the system and that should be called when the event handling is done.
201-
func recreateBackgroundSession(_ completionHandler: (()->Void)? = nil) {
201+
func recreateBackgroundSession(_ completionHandler: (() -> Void)? = nil) {
202202
backgroundSessionCompletionHandler = completionHandler
203203

204204
// Trigger lazy initialisation
@@ -232,9 +232,8 @@ class APIClient: NSObject {
232232
}
233233

234234
// MARK: Generic dataTask handling
235-
236-
private func dataTask(context: APIContext, endpoint: String, parameters: [String : Any]?, headers: [String : String]?, method: HTTPMethod, encoding: APIParameterEncoding = .json, completion: @escaping (AnyObject?, APIClientError?) -> ()) {
237-
235+
private func dataTask(context: APIContext, endpoint: String, parameters: [String : Any]?, headers: [String : String]?, method: HTTPMethod, encoding: APIParameterEncoding = .json, parseJsonResponse: Bool = true, completion: @escaping (Result<AnyObject, APIClientError>) -> Void) {
236+
238237
var request = URLRequest(url: URL(string: baseURLString(for: context) + endpoint)!)
239238

240239
request.httpMethod = method.rawValue
@@ -289,61 +288,72 @@ class APIClient: NSObject {
289288
fatalError("API client: Unsupported HTTP method")
290289
}
291290

292-
urlSession.dataTask(with: request) { (data, response, error) in
291+
urlSession.dataTask(with: request) { [weak welf = self] (data, response, error) in
292+
guard let stelf = welf else { return }
293293
guard error == nil else {
294-
let error = error as NSError?
295-
switch error!.code {
296-
case Int(CFNetworkErrors.cfurlErrorBadServerResponse.rawValue):
297-
completion(nil, .server(code: 500, message: ""))
298-
case Int(CFNetworkErrors.cfurlErrorSecureConnectionFailed.rawValue) ..< Int(CFNetworkErrors.cfurlErrorUnknown.rawValue):
299-
completion(nil, .connection)
300-
default:
301-
completion(nil, .server(code: error!.code, message: error!.localizedDescription))
302-
}
294+
let apiError = stelf.apiClientError(from: error!)
295+
completion(.failure(apiError))
303296
return
304297
}
305-
298+
306299
guard let data = data else {
307-
completion(nil, .parsing(details: "DataTask: Missing data for \(request.url?.absoluteString ?? "")"))
300+
completion(.failure(.parsing(details: "DataTask: Missing data for \(request.url?.absoluteString ?? "")")))
308301
return
309302
}
310303

304+
if !parseJsonResponse {
305+
completion(.success(data as AnyObject))
306+
return
307+
}
308+
311309
// Attempt parsing to JSON
312310
if let json = try? JSONSerialization.jsonObject(with: data, options: []) {
313311
// Check if there's an error in the response
314312
if let responseDictionary = json as? [String: AnyObject],
315313
let errorDict = responseDictionary["error"] as? [String : AnyObject],
316314
let errorMessage = (errorDict["message"] as? [AnyObject])?.last as? String {
317-
completion(nil, .server(code: (response as? HTTPURLResponse)?.statusCode ?? 0, message: errorMessage))
315+
completion(.failure(.server(code: (response as? HTTPURLResponse)?.statusCode ?? 0, message: errorMessage)))
318316
} else {
319-
completion(json as AnyObject, nil)
317+
completion(.success(json as AnyObject))
320318
}
321-
} else if let image = UIImage(data: data) { // Attempt parsing UIImage
322-
completion(image, nil)
323319
} else { // Parsing error
324320
var details = "DataTask: Bad data for \(request.url?.absoluteString ?? "")"
325321
if let stringData = String(data: data, encoding: String.Encoding.utf8) {
326322
details = details + ": " + stringData
327323
}
328-
completion(nil, .parsing(details: details))
324+
completion(.failure(.parsing(details: details)))
329325
}
330326
}.resume()
331327
}
328+
329+
private func apiClientError(from error: Error?) -> APIClientError {
330+
guard let error = error as NSError? else { return .generic }
331+
332+
switch error.code {
333+
case Int(CFNetworkErrors.cfurlErrorBadServerResponse.rawValue):
334+
return .server(code: 500, message: "")
335+
case Int(CFNetworkErrors.cfurlErrorSecureConnectionFailed.rawValue) ..< Int(CFNetworkErrors.cfurlErrorUnknown.rawValue):
336+
return .connection
337+
default:
338+
return .server(code: error.code, message: error.localizedDescription)
339+
}
340+
}
341+
332342

333343
// MARK: - Public methods
334-
func post(context: APIContext, endpoint: String, parameters: [String : Any]? = nil, headers: [String : String]? = nil, encoding: APIParameterEncoding = .json, completion: @escaping (AnyObject?, APIClientError?) -> ()) {
335-
dataTask(context: context, endpoint: endpoint, parameters: parameters, headers: headers, method: .post, encoding: encoding, completion: completion)
344+
func post(context: APIContext, endpoint: String, parameters: [String : Any]? = nil, headers: [String : String]? = nil, encoding: APIParameterEncoding = .json, parseJsonResponse: Bool = true, completion: @escaping (Result<AnyObject, APIClientError>) -> Void) {
345+
dataTask(context: context, endpoint: endpoint, parameters: parameters, headers: headers, method: .post, encoding: encoding, parseJsonResponse: parseJsonResponse, completion: completion)
336346
}
337347

338-
func get(context: APIContext, endpoint: String, parameters: [String : Any]? = nil, headers: [String : String]? = nil, completion: @escaping (AnyObject?, APIClientError?) -> ()) {
339-
dataTask(context: context, endpoint: endpoint, parameters: parameters, headers: headers, method: .get, completion: completion)
348+
func get(context: APIContext, endpoint: String, parameters: [String : Any]? = nil, headers: [String : String]? = nil, parseJsonResponse: Bool = true, completion: @escaping (Result<AnyObject, APIClientError>) -> Void) {
349+
dataTask(context: context, endpoint: endpoint, parameters: parameters, headers: headers, method: .get, parseJsonResponse: parseJsonResponse, completion: completion)
340350
}
341351

342-
func put(context: APIContext, endpoint: String, parameters: [String : Any]? = nil, headers: [String : String]? = nil, completion: @escaping (AnyObject?, APIClientError?) -> ()) {
352+
func put(context: APIContext, endpoint: String, parameters: [String : Any]? = nil, headers: [String : String]? = nil, completion: @escaping (Result<AnyObject, APIClientError>) -> Void) {
343353
dataTask(context: context, endpoint: endpoint, parameters: parameters, headers: headers, method: .put, completion: completion)
344354
}
345355

346-
func uploadImage(_ data: Data, imageName: String, completion: @escaping (AnyObject?, Error?) -> ()) {
356+
func uploadImage(_ data: Data, imageName: String, completion: @escaping (Result<AnyObject, APIClientError>) -> Void) {
347357
let boundaryString = "Boundary-\(NSUUID().uuidString)"
348358
let fileUrl = createFileWith(imageData: data, imageName: imageName, boundaryString: boundaryString)
349359

@@ -353,28 +363,27 @@ class APIClient: NSObject {
353363
request.setValue("application/json", forHTTPHeaderField: "Accept")
354364
request.setValue("multipart/form-data; boundary=\(boundaryString)", forHTTPHeaderField:"content-type")
355365

356-
URLSession.shared.uploadTask(with: request, fromFile: fileUrl) { (data, response, error) in
366+
URLSession.shared.uploadTask(with: request, fromFile: fileUrl) { [weak welf = self] (data, response, error) in
367+
guard let stelf = welf else { return }
357368
guard let data = data, let json = try? JSONSerialization.jsonObject(with: data, options: []) else {
358-
DispatchQueue.main.async { completion(nil, error) }
369+
let error = stelf.apiClientError(from: error)
370+
DispatchQueue.main.async { completion(.failure(error)) }
359371
return
360372
}
361373

362-
DispatchQueue.main.async { completion(json as AnyObject, nil) }
363-
374+
DispatchQueue.main.async { completion(.success(json as AnyObject)) }
364375
}.resume()
365376
}
366377

367-
func uploadImage(_ image: UIImage, imageName: String, completion: @escaping (AnyObject?, Error?) -> ()) {
368-
378+
func uploadImage(_ image: UIImage, imageName: String, completion: @escaping (Result<AnyObject, APIClientError>) -> Void) {
369379
guard let imageData = imageData(withImage: image, imageName: imageName) else {
370380
print("Image Upload: cannot read image data")
371-
completion(nil, nil)
381+
completion(.failure(APIClientError.generic))
372382
return
373383
}
374-
375384
uploadImage(imageData, imageName: imageName, completion: completion)
376385
}
377-
386+
378387
func uploadImage(_ data: Data, imageName: String, reference: String?) {
379388
let boundaryString = "Boundary-\(NSUUID().uuidString)"
380389
let fileUrl = createFileWith(imageData: data, imageName: imageName, boundaryString: boundaryString)
@@ -422,14 +431,22 @@ class APIClient: NSObject {
422431
uploadImage(fileData, imageName: imageName, reference: reference)
423432
}
424433

425-
func downloadImage(_ imageUrl: URL, completion: @escaping ((UIImage?, Error?) -> Void)) {
426-
SDWebImageManager.shared().loadImage(with: imageUrl, options: [], progress: nil, completed: { image, _, error, _, _, _ in
434+
func downloadImage(_ imageUrl: URL, completion: @escaping ((Result<UIImage, APIClientError>) -> Void)) {
435+
SDWebImageManager.shared().loadImage(with: imageUrl, options: [], progress: nil, completed: { [weak welf = self] image, _, error, _, _, _ in
436+
guard let stelf = welf else { return }
427437
DispatchQueue.main.async {
428-
completion(image, error)
438+
if let image = image, error == nil {
439+
completion(.success(image))
440+
} else if let error = error {
441+
let error = stelf.apiClientError(from: error)
442+
completion(.failure(error))
443+
} else {
444+
completion(.failure(APIClientError.generic))
445+
}
429446
}
430447
})
431448
}
432-
449+
433450
func uploadPdf(_ file: URL, to targetUrl: URL, reference: String?) {
434451
guard let _ = try? Data(contentsOf: file) else {
435452
print("File Upload: cannot read file data")
@@ -456,7 +473,7 @@ class APIClient: NSObject {
456473
dataTask.resume()
457474
}
458475

459-
func pendingBackgroundTaskCount(_ completion: @escaping ((Int)->Void)) {
476+
func pendingBackgroundTaskCount(_ completion: @escaping ((Int) -> Void)) {
460477
backgroundUrlSession.getAllTasks { completion($0.count) }
461478
}
462479

@@ -469,7 +486,7 @@ class APIClient: NSObject {
469486
}
470487
}
471488

472-
func updateTaskReferences(completion: @escaping ()->()) {
489+
func updateTaskReferences(completion: @escaping () -> Void) {
473490
backgroundUrlSession.getAllTasks { [weak welf = self] (tasks) in
474491
guard let stelf = welf else { return }
475492
stelf.taskReferences = stelf.taskReferences.filter({ (key, _) -> Bool in

0 commit comments

Comments
 (0)