Skip to content

Commit e12221b

Browse files
authored
Network layer refactoring (#262)
* Added APIEndpoint inheritance to all network classes * working implementation * working adapter * Updated JSONSerializable & added generic update() method * More update refactoring * Added generic delete request * Added create() method * Create comment reworked * Added views create() method * minor * refactored create() for attempts and submissions * Added retrieve() for one object by id * Added retrieve with parameters * minor * Added retrieve() with fetch to NotificationsAPI * minor * Refactored UnitsAPI * multiple fixes * Refactored UserActivitiesAPI * Refactor DiscussionProxiesAPI * Refactored getObjectsByIds * Refactored CommentsAPI * Added deprecations to CommentsAPI * Added default implementation of async fetch using DatabaseFetchService * fixed GET requests & added deprecations to CoursesAPI * Refactored CertificatesAPI * Refactored CourseListsAPI * Refactored NotificationsStatusesAPI * fixed formatting * Removed some checkToken() calls * Fixed JSONSerializables * AsyncFetchService -> DatabaseFetchService * Cleaned up UserActivitiesAPI * minor format * fixed force unwrap * Removed forced unwraps * Renamed IdType & some more * Added CoreDataRepresentable protocol for IdType of IDFetchable * fixed "Submit" localization * Deleted not passing tests file * minor
1 parent 583d669 commit e12221b

File tree

79 files changed

+1136
-1293
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

79 files changed

+1136
-1293
lines changed

Stepic.xcodeproj/project.pbxproj

Lines changed: 69 additions & 47 deletions
Large diffs are not rendered by default.

Stepic.xcodeproj/xcuserdata/Ostrenkiy.xcuserdatad/xcschemes/xcschememanagement.plist

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -118,6 +118,11 @@
118118
<key>orderHint</key>
119119
<integer>86</integer>
120120
</dict>
121+
<key>Adaptive GMAT.xcscheme</key>
122+
<dict>
123+
<key>orderHint</key>
124+
<integer>95</integer>
125+
</dict>
121126
<key>SberbankUniversity.xcscheme</key>
122127
<dict>
123128
<key>isShown</key>
@@ -154,7 +159,7 @@
154159
<key>StepikTV.xcscheme_^#shared#^_</key>
155160
<dict>
156161
<key>orderHint</key>
157-
<integer>87</integer>
162+
<integer>96</integer>
158163
</dict>
159164
<key>StickerPackExtension.xcscheme</key>
160165
<dict>

Stepic/APIEndpoint.swift

Lines changed: 27 additions & 114 deletions
Original file line numberDiff line numberDiff line change
@@ -36,10 +36,23 @@ class APIEndpoint {
3636

3737
let manager: Alamofire.SessionManager
3838

39+
var update: UpdateRequestMaker
40+
var delete: DeleteRequestMaker
41+
var create: CreateRequestMaker
42+
var retrieve: RetrieveRequestMaker
43+
3944
init() {
4045
let configuration = URLSessionConfiguration.default
4146
configuration.timeoutIntervalForRequest = 15
4247
manager = Alamofire.SessionManager(configuration: configuration)
48+
let retrier = ApiRequestRetrier()
49+
manager.retrier = retrier
50+
manager.adapter = retrier
51+
52+
update = UpdateRequestMaker()
53+
delete = DeleteRequestMaker()
54+
create = CreateRequestMaker()
55+
retrieve = RetrieveRequestMaker()
4356
}
4457

4558
func cancelAllTasks() {
@@ -60,123 +73,23 @@ class APIEndpoint {
6073
return result
6174
}
6275

63-
func getObjectsByIds<T: JSONInitializable>(ids: [T.idType], updating: [T], headers: [String: String] = AuthInfo.shared.initialHTTPHeaders, printOutput: Bool = false) -> Promise<([T])> {
64-
let name = self.name
65-
return Promise<([T])> {
66-
fulfill, reject in
67-
let params: Parameters = [
68-
"ids": ids
69-
]
70-
71-
manager.request("\(StepicApplicationsInfo.apiURL)/\(name)", parameters: params, encoding: URLEncoding.default, headers: headers).validate().responseSwiftyJSON { response in
72-
switch response.result {
73-
74-
case .failure(let error):
75-
reject(RetrieveError(error: error))
76-
77-
case .success(let json):
78-
let jsonArray: [JSON] = json[name].array ?? []
79-
let resultArray: [T] = jsonArray.map {
80-
objectJSON in
81-
if let recoveredIndex = updating.index(where: { $0.hasEqualId(json: objectJSON) }) {
82-
updating[recoveredIndex].update(json: objectJSON)
83-
return updating[recoveredIndex]
84-
} else {
85-
return T(json: objectJSON)
86-
}
87-
}
88-
89-
CoreDataHelper.instance.save()
90-
fulfill((resultArray))
91-
}
92-
93-
}
94-
}
76+
//TODO: Remove this in next refactoring iterations
77+
func getObjectsByIds<T: JSONSerializable>(ids: [T.IdType], updating: [T], printOutput: Bool = false) -> Promise<([T])> {
78+
return retrieve.request(requestEndpoint: name, paramName: name, ids: ids, updating: updating, withManager: manager)
9579
}
9680

97-
func getObjectsByIds<T: JSONInitializable>(requestString: String, headers: [String: String] = AuthInfo.shared.initialHTTPHeaders, printOutput: Bool = false, ids: [T.idType], deleteObjects: [T], refreshMode: RefreshMode, success: (([T]) -> Void)?, failure : @escaping (_ error: RetrieveError) -> Void) -> Request? {
98-
99-
let params: Parameters = [:]
100-
101-
let idString = constructIdsString(array: ids)
102-
if idString == "" {
103-
success?([])
104-
return nil
105-
}
106-
107-
return manager.request("\(StepicApplicationsInfo.apiURL)/\(requestString)?\(idString)", parameters: params, encoding: URLEncoding.default, headers: headers).responseSwiftyJSON({
108-
response in
109-
110-
var error = response.result.error
111-
var json: JSON = [:]
112-
if response.result.value == nil {
113-
if error == nil {
114-
error = NSError()
115-
}
116-
} else {
117-
json = response.result.value!
118-
}
119-
let response = response.response
120-
121-
if printOutput {
122-
print(json)
123-
}
124-
125-
if let e = error as NSError? {
126-
print("RETRIEVE \(requestString)?\(ids): error \(e.domain) \(e.code): \(e.localizedDescription)")
127-
if e.code == -999 {
128-
failure(.cancelled)
129-
return
130-
} else {
131-
failure(.connectionError)
132-
return
133-
}
134-
}
135-
136-
if response?.statusCode != 200 {
137-
print("RETRIEVE \(requestString)?\(ids)): bad response status code \(String(describing: response?.statusCode))")
138-
failure(.badStatus)
81+
func getObjectsByIds<T: JSONSerializable>(requestString: String, printOutput: Bool = false, ids: [T.IdType], deleteObjects: [T], refreshMode: RefreshMode, success: (([T]) -> Void)?, failure : @escaping (_ error: RetrieveError) -> Void) -> Request? {
82+
getObjectsByIds(ids: ids, updating: deleteObjects).then {
83+
objects in
84+
success?(objects)
85+
}.catch {
86+
error in
87+
guard let e = error as? RetrieveError else {
88+
failure(RetrieveError(error: error))
13989
return
14090
}
141-
142-
var newObjects: [T] = []
143-
144-
switch refreshMode {
145-
146-
case .delete:
147-
148-
for object in deleteObjects {
149-
CoreDataHelper.instance.deleteFromStore(object as! NSManagedObject, save: false)
150-
}
151-
152-
for objectJSON in json[requestString].arrayValue {
153-
newObjects += [T(json: objectJSON)]
154-
}
155-
156-
case .update:
157-
158-
for objectJSON in json[requestString].arrayValue {
159-
let existing = deleteObjects.filter({obj in obj.hasEqualId(json: objectJSON)})
160-
161-
switch existing.count {
162-
case 0:
163-
newObjects += [T(json: objectJSON)]
164-
case 1:
165-
let obj = existing[0]
166-
obj.update(json: objectJSON)
167-
newObjects += [obj]
168-
default:
169-
//TODO: Fix this in the next releases! We have some problems with deleting entities from CoreData
170-
let obj = existing[0]
171-
obj.update(json: objectJSON)
172-
newObjects += [obj]
173-
print("More than 1 object with the same id!")
174-
}
175-
}
176-
}
177-
178-
CoreDataHelper.instance.save()
179-
success?(newObjects)
180-
})
91+
failure(e)
92+
}
93+
return nil
18194
}
18295
}

Stepic/AdaptiveRatingsAPI.swift

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ import Alamofire
1111
import SwiftyJSON
1212
import PromiseKit
1313

14+
//TODO: Better refactor this to two classes
1415
class AdaptiveRatingsAPI: APIEndpoint {
1516
override var name: String { return "rating" }
1617
var restoreName: String { return "rating-restore" }

Stepic/ApiRequestRetrier.swift

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
//
2+
// ApiRequestRetrier.swift
3+
// Stepic
4+
//
5+
// Created by Ostrenkiy on 18.03.2018.
6+
// Copyright © 2018 Alex Karpov. All rights reserved.
7+
//
8+
import Foundation
9+
import Alamofire
10+
import PromiseKit
11+
12+
class ApiRequestRetrier: RequestRetrier, RequestAdapter {
13+
14+
func adapt(_ urlRequest: URLRequest) throws -> URLRequest {
15+
var urlRequest = urlRequest
16+
for (headerField, value) in AuthInfo.shared.initialHTTPHeaders {
17+
urlRequest.setValue(value, forHTTPHeaderField: headerField)
18+
}
19+
return urlRequest
20+
}
21+
22+
func should(_ manager: SessionManager, retry request: Request, with error: Error, completion: @escaping RequestRetryCompletion) {
23+
if let response = request.task?.response as? HTTPURLResponse, response.statusCode == 401 && request.retryCount == 0 {
24+
checkToken().then {
25+
completion(true, 0.0)
26+
}.catch {
27+
_ in
28+
completion(false, 0.0)
29+
}
30+
} else {
31+
completion(false, 0.0)
32+
}
33+
}
34+
}

Stepic/AppDelegate.swift

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -97,9 +97,7 @@ class AppDelegate: UIResponder, UIApplicationDelegate {
9797
return
9898
}
9999

100-
checkToken().then {
101-
ApiDataDownloader.notificationsStatusAPI.retrieve()
102-
}.then { result -> Void in
100+
ApiDataDownloader.notificationsStatusAPI.retrieve().then { result -> Void in
103101
NotificationsBadgesManager.shared.set(number: result.totalCount)
104102
}.catch { _ in
105103
print("notifications: unable to fetch badges count on launch")

Stepic/Assignment.swift

Lines changed: 3 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -10,9 +10,10 @@ import Foundation
1010
import CoreData
1111
import SwiftyJSON
1212

13-
class Assignment: NSManagedObject, JSONInitializable {
13+
@objc
14+
class Assignment: NSManagedObject, JSONSerializable {
1415

15-
typealias idType = Int
16+
typealias IdType = Int
1617

1718
convenience required init(json: JSON) {
1819
self.init()
@@ -28,8 +29,4 @@ class Assignment: NSManagedObject, JSONInitializable {
2829
func update(json: JSON) {
2930
initialize(json)
3031
}
31-
32-
func hasEqualId(json: JSON) -> Bool {
33-
return id == json["id"].intValue
34-
}
3532
}

Stepic/AssignmentsAPI.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,6 @@ class AssignmentsAPI: APIEndpoint {
1414
override var name: String { return "assignments" }
1515

1616
@discardableResult func retrieve(ids: [Int], headers: [String: String] = AuthInfo.shared.initialHTTPHeaders, existing: [Assignment], refreshMode: RefreshMode, success: @escaping (([Assignment]) -> Void), error errorHandler: @escaping ((RetrieveError) -> Void)) -> Request? {
17-
return getObjectsByIds(requestString: name, headers: headers, printOutput: false, ids: ids, deleteObjects: existing, refreshMode: refreshMode, success: success, failure: errorHandler)
17+
return getObjectsByIds(requestString: name, printOutput: false, ids: ids, deleteObjects: existing, refreshMode: refreshMode, success: success, failure: errorHandler)
1818
}
1919
}

Stepic/Attempt.swift

Lines changed: 37 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -9,17 +9,45 @@
99
import UIKit
1010
import SwiftyJSON
1111

12-
class Attempt: NSObject {
12+
class Attempt: JSONSerializable {
1313

14-
var id: Int?
14+
typealias IdType = Int
15+
16+
var id: Int = 0
1517
var dataset: Dataset?
1618
var datasetUrl: String?
1719
var time: String?
1820
var status: String?
19-
var step: Int
21+
var step: Int = 0
2022
var timeLeft: String?
2123
var user: Int?
2224

25+
func update(json: JSON) {
26+
id = json["id"].intValue
27+
datasetUrl = json["dataset_url"].string
28+
time = json["time"].string
29+
status = json["status"].string
30+
step = json["step"].intValue
31+
timeLeft = json["time_left"].string
32+
user = json["user"].int
33+
}
34+
35+
func hasEqualId(json: JSON) -> Bool {
36+
return id == json["id"].int
37+
}
38+
39+
init(step: Int) {
40+
self.step = step
41+
}
42+
43+
required init(json: JSON) {
44+
self.update(json: json)
45+
}
46+
47+
func initDataset(json: JSON, stepName: String) {
48+
dataset = getDatasetFromJSON(json, stepName: stepName)
49+
}
50+
2351
init(json: JSON, stepName: String) {
2452
id = json["id"].intValue
2553
dataset = nil
@@ -29,10 +57,15 @@ class Attempt: NSObject {
2957
step = json["step"].intValue
3058
timeLeft = json["time_left"].string
3159
user = json["user"].int
32-
super.init()
3360
dataset = getDatasetFromJSON(json["dataset"], stepName: stepName)
3461
}
3562

63+
var json: JSON {
64+
return [
65+
"step": step
66+
]
67+
}
68+
3669
fileprivate func getDatasetFromJSON(_ json: JSON, stepName: String) -> Dataset? {
3770
switch stepName {
3871
case "choice" :

0 commit comments

Comments
 (0)