diff --git a/Example/Tests/IntegratedTest.swift b/Example/Tests/IntegratedTest.swift index 5026f29..72b91f9 100644 --- a/Example/Tests/IntegratedTest.swift +++ b/Example/Tests/IntegratedTest.swift @@ -113,6 +113,81 @@ class IntegratedTestSpec: QuickSpec { } expect(sResult.isSucceed).to(beTrue()) } + it("should invoke observer method") { + let randomNumber: Int = .random(in: 0..<10) + var requestResult: URLResult? + var observer: SpyObserver! + let request = Ness(onDuplicated: .keepAllCompletion) + .httpRequest(.get, withUrl: "https://jsonplaceholder.typicode.com/todos/\(randomNumber)") + .prepareDataRequest(with: CounterRetryControl(maxRetryCount: 3)) + .validate(statusCodes: 200..<300) + waitUntil(timeout: .seconds(15)) { done in + observer = SpyObserver { result in + requestResult = result + done() + } + request.then(observing: observer, call: SpyObserver.invoke(with:)) + } + guard let result = requestResult else { + fail("Fail to get data") + return + } + expect(result.isSucceed).to(beTrue()) + } + it("should invoke one of observer method") { + let randomNumber: Int = .random(in: 0..<10) + var requestResult: URLResult? + var observer: SpyObserver! + let request = Ness(onDuplicated: .keepAllCompletion) + .httpRequest(.get, withUrl: "https://jsonplaceholder.typicode.com/todos/\(randomNumber)") + .prepareDataRequest(with: CounterRetryControl(maxRetryCount: 3)) + .validate(statusCodes: 200..<300) + waitUntil(timeout: .seconds(15)) { done in + observer = SpyObserver { result in + requestResult = result + done() + } + request.then( + observing: observer, + call: SpyObserver.invoke(with:), + whenFailedCall: SpyObserver.invoke(with:) + ) + } + guard let result = requestResult else { + fail("Fail to get data") + return + } + expect(result.isSucceed).to(beTrue()) + } + it("should invoke observer method twice") { + let randomNumber: Int = .random(in: 0..<10) + var requestResult: URLResult? + var observer: SpyObserver! + let request = Ness(onDuplicated: .keepAllCompletion) + .httpRequest(.get, withUrl: "https://jsonplaceholder.typicode.com/todos/\(randomNumber)") + .prepareDataRequest(with: CounterRetryControl(maxRetryCount: 3)) + .validate(statusCodes: 200..<300) + waitUntil(timeout: .seconds(15)) { done in + observer = SpyObserver { result in + requestResult = result + if observer.invokedTime == 2 { + done() + } + } + request.then( + observing: observer, + call: SpyObserver.invoke(with:), + whenFailedCall: SpyObserver.invoke(with:), + finallyCall: SpyObserver.invoke(with:) + ) + } + guard let result = requestResult else { + fail("Fail to get data") + return + } + expect(result.isSucceed).to(beTrue()) + expect(observer.invokedTime).toEventually(equal(2)) + } it("should get json data") { let randomNumber: Int = .random(in: 40..<50) var requestResult: URLResult? diff --git a/Example/Tests/Mock.swift b/Example/Tests/Mock.swift index 287109e..f950c76 100644 --- a/Example/Tests/Mock.swift +++ b/Example/Tests/Mock.swift @@ -55,3 +55,15 @@ extension String { return randomString } } + +class SpyObserver { + var invokedTime: Int = 0 + let invoked: (Result) -> Void + init(_ invoked: @escaping (Result) -> Void) { + self.invoked = invoked + } + func invoke(with result: Result) { + invokedTime += 1 + invoked(result) + } +} diff --git a/README.md b/README.md index 344c150..50aa364 100644 --- a/README.md +++ b/README.md @@ -91,16 +91,17 @@ Ness.default .executeAndForget() ``` -you can do something very readable like this by separating all closure using function: +you can do something very readable like this by using observer method call: ```swift Ness.default .httpRequest(.get, withUrl: "https://myurl.com") .prepareDataRequest() .then( - run: updateTheViewWithData, - whenFailed: showFailureAlert, - finally: removeLoading + observing: self, + call: Some.updateTheView(withResult:), + whenFailedCall: Some.showFailureAlert, + finallyCall: Some.removeLoading ) ``` @@ -269,7 +270,24 @@ Ness.default ) ``` -With custom dispatcher which will be the thread where completion run: +You could do something similar with observer and its method reference. It will be weak refer to observer and do nothing if the observer is already deinitialised by ARC: +```swift +Ness.default + .httpRequest(.get, withUrl: "https://myurl.com") + .. + .. + .prepareDataRequest() + .then( + observing: self, + call: Some.updateTheView(withResult:), + whenFailedCall: Some.showFailureAlert, + finallyCall: Some.removeLoading + ) +``` + +All of the complement method (whenFailedCall, finallyCall) are optional. Use it when you need it. + +You could give custom dispatcher which will be the thread where completion run: ```swift Ness.default diff --git a/iONess.podspec b/iONess.podspec index 2a77fd1..fc482b6 100644 --- a/iONess.podspec +++ b/iONess.podspec @@ -8,7 +8,7 @@ Pod::Spec.new do |s| s.name = 'iONess' - s.version = '1.2.2' + s.version = '1.2.3' s.summary = 'iOS Network Session' # This description is used to generate tags and improve search results. diff --git a/iONess/Classes/DropableAndResumable/DropableURLRequest.swift b/iONess/Classes/DropableAndResumable/DropableURLRequest.swift index d56a617..6fb47b7 100644 --- a/iONess/Classes/DropableAndResumable/DropableURLRequest.swift +++ b/iONess/Classes/DropableAndResumable/DropableURLRequest.swift @@ -77,7 +77,8 @@ extension BaseDropableURLRequest { ) { retryStatus in switch retryStatus { case .retryAfter(let delay): - DispatchQueue.main.asyncAfter(deadline: .now() + delay, execute: onRetry) + let dispatcher: DispatchQueue = OperationQueue.current?.underlyingQueue ?? .main + dispatcher.asyncAfter(deadline: .now() + delay, execute: onRetry) case .retry: onRetry() case .noRetry: diff --git a/iONess/Classes/Thenable/Thenable.swift b/iONess/Classes/Thenable/Thenable.swift index fd67804..3d6776d 100644 --- a/iONess/Classes/Thenable/Thenable.swift +++ b/iONess/Classes/Thenable/Thenable.swift @@ -32,6 +32,31 @@ public protocol Thenable { /// - deferClosure: closure which will be run after closure or failClosure @discardableResult func then(run closure: @escaping (Result) -> Void, whenFailed failClosure: @escaping (Result) -> Void, finally deferClosure: @escaping (Result) -> Void) -> DropablePromise + + + /// Method to execute request and then run any method passed + /// - Parameters: + /// - observer: object that observe request + /// - method: observer method to call when request completed + @discardableResult + func then(observing observer: Observer, call method: @escaping (Observer) -> ((Result) -> Void)) -> DropablePromise + + /// Method to execute request and then run any method passed + /// - Parameters: + /// - observer: object that observe request + /// - method: observer method to call when succeed + /// - failMethod: observer method to call when fail + @discardableResult + func then(observing observer: Observer, call method: @escaping (Observer) -> ((Result) -> Void), whenFailedCall failMethod: @escaping (Observer) -> ((Result) -> Void)) -> DropablePromise + + /// Method to execute request and then run any method passed + /// - Parameters: + /// - observer: object that observe request + /// - method: observer method to call when succeed + /// - failMethod: observer method to call when fail + /// - finalMethod: observer method to call when request completed + @discardableResult + func then(observing observer: Observer, call method: @escaping (Observer) -> ((Result) -> Void), whenFailedCall failMethod: @escaping (Observer) -> ((Result) -> Void), finallyCall finalMethod: @escaping (Observer) -> ((Result) -> Void)) -> DropablePromise } public extension Thenable { @@ -54,4 +79,55 @@ public extension Thenable { } ) } + + /// Method to execute request and then run any method passed + /// - Parameters: + /// - observer: object that observe task + /// - method: observer method to call when task completed + /// - Returns: DropablePromise object + @discardableResult + func then(observing observer: Observer, call method: @escaping (Observer) -> ((Result) -> Void)) -> DropablePromise { + return then { [weak observer] result in + guard let observer = observer else { return } + method(observer)(result) + } + } + + /// Method to execute request and then run any method passed + /// - Parameters: + /// - observer: object that observe task + /// - method: observer method to call when succeed + /// - failMethod: observer method to call when fail + /// - Returns: DropablePromise object + @discardableResult + func then(observing observer: Observer, call method: @escaping (Observer) -> ((Result) -> Void), whenFailedCall failMethod: @escaping (Observer) -> ((Result) -> Void)) -> DropablePromise { + return then(run: { [weak observer] result in + guard let observer = observer else { return } + method(observer)(result) + }, whenFailed: { [weak observer] result in + guard let observer = observer else { return } + failMethod(observer)(result) + }) + } + + /// Method to execute request and then run any method passed + /// - Parameters: + /// - observer: object that observe task + /// - method: observer method to call when succeed + /// - failMethod: observer method to call when fail + /// - finalMethod: observer method to call when task completed + /// - Returns: DropablePromise object + @discardableResult + func then(observing observer: Observer, call method: @escaping (Observer) -> ((Result) -> Void), whenFailedCall failMethod: @escaping (Observer) -> ((Result) -> Void), finallyCall finalMethod: @escaping (Observer) -> ((Result) -> Void)) -> DropablePromise { + return then(run: { [weak observer] result in + guard let observer = observer else { return } + method(observer)(result) + }, whenFailed: { [weak observer] result in + guard let observer = observer else { return } + failMethod(observer)(result) + }, finally: { [weak observer] result in + guard let observer = observer else { return } + finalMethod(observer)(result) + }) + } }