I am authenticating a user via LAContext when an app is launching or when will enter foreground. If a device was locked then the user will be asked twice to authorize himself. To avoid that behavior, I set context.touchIDAuthenticationAllowableReuseDuration value to 240 but It doesn't work as expected. The user still has to authorize himself twice.
What I am doing wrong?
import LocalAuthentication
class AccessControl {
internal var context = LAContext()
private var policy: LAPolicy = .deviceOwnerAuthentication
private var reason: String = NSLocalizedString("auhenticationLocalizedFallbackTitle", comment: "")
init() {
context.touchIDAuthenticationAllowableReuseDuration = 240
}
func evaluateUserWithBiometricsOrPasscode(success: #escaping () -> Void, error: #escaping () -> Void) {
guard context.canEvaluatePolicy(policy, error: nil) else {
error()
return
}
context.evaluatePolicy(policy, localizedReason: reason) { eStatus, eError in
DispatchQueue.main.async {
if eStatus {
success()
} else {
error()
}
}
}
}
}
You need to use the same LAContext object everytime to get that behaviour.
class AccessControl {
// MARK: - Singleton
public static let shared = AccessControl()
// Policy
private var policy: LAPolicy = .deviceOwnerAuthentication
// Reason
private var reason: String = NSLocalizedString("auhenticationLocalizedFallbackTitle", comment: "")
// Context
lazy var context: LAContext = {
let mainContext = LAContext()
if #available(iOS 9.0, *) {
// specify your interval
mainContext.touchIDAuthenticationAllowableReuseDuration = 60
}
return mainContext
}()
// Evaluate
func evaluateUserWithBiometricsOrPasscode(success: #escaping () -> Void, error: #escaping () -> Void) {
guard context.canEvaluatePolicy(policy, error: nil) else {
error()
return
}
context.evaluatePolicy(policy, localizedReason: reason) { eStatus, eError in
DispatchQueue.main.async {
if eStatus {
success()
} else {
error()
}
}
}
}
}
And calling this function like below: This will work for FaceID Authentication also.
AccessControl.shared.evaluateUserWithBiometricsOrPasscode(success: {
}) {
}
Related
I am starting out with unit testing RxSwift Driver. And I am having issues testing a Driver.
This is the code structure of my ViewModel:
import Foundation
import RxSwift
import RxCocoa
class LoginViewViewModel {
private let loginService: LoginService
private let _loading = BehaviorRelay<Bool>(value: false)
private let _loginResponse = BehaviorRelay<LoginResponse?>(value: nil)
private let _phoneMessage = BehaviorRelay<String>(value: "")
private let _pinMessage = BehaviorRelay<String>(value: "")
private let _enableButton = BehaviorRelay<Bool>(value: false)
var loginResponse: Driver<LoginResponse?> { return _loginResponse.asDriver() }
var loading: Driver<Bool> { return _loading.asDriver() }
var phoneMessage: Driver<String> { return _phoneMessage.asDriver() }
var pinMessage: Driver<String> { return _pinMessage.asDriver() }
var enableButton: Driver<Bool> { return _enableButton.asDriver() }
private let phone = BehaviorRelay<String>(value: "")
private let pin = BehaviorRelay<String>(value: "")
private let disposeBag = DisposeBag()
init(phone: Driver<String>, pin: Driver<String>, buttonTapped: Driver<Void>, loginService: LoginService) {
self.loginService = loginService
phone
.throttle(0.5)
.distinctUntilChanged()
.drive(onNext: { [weak self] (phone) in
self?.phone.accept(phone)
self?.validateFields()
}).disposed(by: disposeBag)
pin
.throttle(0.5)
.distinctUntilChanged()
.drive(onNext: { [weak self] (pin) in
self?.pin.accept(pin)
self?.validateFields()
}).disposed(by: disposeBag)
buttonTapped
.drive(onNext: { [weak self] () in
self?.loginUser(phone: self!.phone.value, pin: self!.pin.value)
}).disposed(by: disposeBag)
}
private func validateFields() {
guard phone.value.count > 0 else {
return
}
_enableButton.accept(false)
guard pin.value.count > 0 else {
return
}
_enableButton.accept(true)
_phoneMessage.accept("")
_pinMessage.accept("")
}
private func loginUser(phone: String, pin: String) {
_loading.accept(true)
_phoneMessage.accept("")
_pinMessage.accept("")
loginService.loginUser(phone: phone, pin: pin) { [weak self] (response, error) in
self?._loading.accept(false)
if let error = error {
if error.message! == "Invalid credentials" {
self?._phoneMessage.accept("Invalid Phone Number")
self?._pinMessage.accept("Invalid Pin Provided")
}
} else {
response?.saveUserInfo()
self?._loginResponse.accept(response)
}
}
}
}
And my UnitTest looks like this:
class LoginViewViewModelTest: XCTestCase {
private class MockLoginService: LoginService {
func loginUser(phone: String, pin: String, completion: #escaping LoginService.LoginDataCompletion) {
guard phone == "+17045674568", pin == "1234" else {
let loginresponse = LoginResponse(message: "Login Successfully", status: true, status_code: 200, data: LoginData(access_token: "adadksdewffjfwe", token_type: "bearer", expires_in: 3600, expiry_time: "today", user: User(id: "1dsldsdsjkj", name: "RandomGuy", phone: "12345", pin_set: true, custom_email: false, email: "somerandom#email.com")))
completion(loginresponse, nil)
return
}
let akuError = AKUError(status: false, message: "Invalid Credential.", status_code: "404")
completion(nil, akuError)
}
}
var viewModel: LoginViewViewModel!
var scheduler: SchedulerType!
var phone: BehaviorRelay<String>!
var pin: BehaviorRelay<String>!
var buttonClicked: BehaviorRelay<Void>!
override func setUp() {
super.setUp()
phone = BehaviorRelay<String>(value: "")
pin = BehaviorRelay<String>(value: "")
buttonClicked = BehaviorRelay<Void>(value: ())
let loginService = MockLoginService()
viewModel = LoginViewViewModel(phone: phone.asDriver(), pin: pin.asDriver(), buttonTapped: buttonClicked.asDriver(), loginService: loginService)
scheduler = ConcurrentDispatchQueueScheduler(qos: .default)
}
override func tearDown() {
super.tearDown()
}
func testLoginButtonClicked_Loading() {
let loadingObservable = viewModel.loading.asObservable().subscribeOn(scheduler)
phone.accept("12345")
pin.accept("12345")
buttonClicked.accept(())
let loadingState = try! loadingObservable.skip(0).toBlocking().first()!
XCTAssertNotNil(loadingState)
XCTAssertEqual(loadingState, true)
}
}
My question:
I am trying to track the state of the loading driver variable. But, it's always false. Even after writing a debugger for checking the states, it only prints out one value and, it's always false.
I decided to add a break point to the code, and I noticed
let loadingState = try! loadingObservable.skip(0).toBlocking().first()! only gets called once the function is done executing.
Is there a way to test for the loading state?
Is it necessary to test for the loading state?
Thanks.
I believe the problem is that RxBlocking only deals with the first event that is emitted. You need to look at a series of events. Look into using RxTest instead. Here is a unit test using RxTest that passes with the view model you created:
class LoginLoadingTests: XCTestCase {
var scheduler: TestScheduler!
var result: TestableObserver<Bool>!
var bag: DisposeBag!
override func setUp() {
super.setUp()
scheduler = TestScheduler(initialClock: 0)
result = scheduler.createObserver(Bool.self)
bag = DisposeBag()
}
func testLoading() {
let loginService = MockLoginService { phone, pin, response in
self.scheduler.scheduleAt(20, action: { response(nil, RxError.unknown) })
}
let tap = scheduler.createHotObservable([.next(10, ())])
let viewModel = LoginViewViewModel(phone: Driver.just("9876543210"), pin: Driver.just("1234"), buttonTapped: tap.asDriver(onErrorJustReturn: ()), loginService: loginService)
viewModel.loading
.drive(result)
.disposed(by: bag)
scheduler.start()
XCTAssertEqual(result.events, [
.next(0, false),
.next(10, true),
.next(20, false)
])
}
}
struct MockLoginService: LoginService {
init(loginUser: #escaping (_ phone: String, _ pin: String, _ response: #escaping (LoginResponse?, Error?) -> Void) -> Void) {
_loginUser = loginUser
}
func loginUser(phone: String, pin: String, response: #escaping (LoginResponse?, Error?) -> ()) {
_loginUser(phone, pin, response)
}
let _loginUser: (_ phone: String, _ pin: String, _ response: #escaping (LoginResponse?, Error?) -> Void) -> Void
}
I studying rxSwift, and I want to do service for the interaction of c longpolling server to this service imitating a permanent connection. I wrote it, but it seems to me, is not that the decision could have been done better? Is it possible to somehow repeat the Observable, regardless of the error, and depending on longpoll server response.
Can anyone can share the solution? Or help with advice? How it is better to organize? I would like to see a better solution, since only began studying rxswift
class LongPollingService {
public var messageReciver: PublishSubject<EventProtocol> = PublishSubject<EventProtocol>()
private let transport = DefaultTransport()
private let disposeBag = DisposeBag()
private var currentRequestInfo = Variable<LongpollingServerInfo?>(nil)
private var currentRequestDisposable: Disposable?
private var currentLongpollingConnection: Disposable? // Subsribee for request server info
private var eventListener : Disposable?
private var currentReqursiveConnection: Disposable? // Subscriber for event listener from longpoll server
func startObservableEvents() {
getServerConnection()
subscribeServerInfo()
//testing listen events
eventListener = messageReciver.showMessagesInDebugMode().subscribe()
eventListener?.addDisposableTo(disposeBag)
}
func disconnect() {
currentRequestDisposable?.dispose()
currentLongpollingConnection?.dispose()
currentReqursiveConnection?.dispose()
}
private func subscribeServerInfo() {
currentLongpollingConnection = currentRequestInfo
.asObservable()
.filter({$0 != nil})
.subscribe(onNext: { [weak self] (info) in
guard let sSelf = self else { return }
sSelf.subscribeToEvents(timeStamp: info!.ts)
})
currentLongpollingConnection?.addDisposableTo(disposeBag)
}
private func subscribeToEvents(timeStamp: TimeInterval) {
if let serverInfo = currentRequestInfo.value {
currentReqursiveConnection?.dispose()
currentReqursiveConnection = getEventsFromLongpollServer(serverInfo: serverInfo, with: timeStamp)
.flatMap(parseUpdates)
.flatMap(reciveEvents)
.showErrorsSwiftMessagesInDebugMode()
.subscribe(onNext: { [weak self] updates in
guard let sSelf = self else { return }
sSelf.subscribeToEvents(timeStamp: updates)
},
onError: { [weak self] error in
guard let sSelf = self else { return }
if let error = error as? LongPollError {
switch error {
case .olderHistory(let ts): sSelf.subscribeToEvents(timeStamp: ts)
default: sSelf.getServerConnection()
}
}
})
currentReqursiveConnection?.addDisposableTo(disposeBag)
}
}
private func getServerConnection() {
//get longpolling server info for connection.
currentRequestDisposable = getLongpollServerInfo()
.subscribe(onNext: {[weak self] info in
guard let sSelf = self else { return }
sSelf.currentRequestInfo.value = info
})
currentRequestDisposable?.addDisposableTo(disposeBag)
}
private func parseUpdates(json: Any) throws -> Observable<LongPollingUpdates> {
let response = try Mapper<LongPollingUpdates>().map(JSONObject: json)
return .just(response)
}
private func reciveEvents(updates:LongPollingUpdates) throws -> Observable<TimeInterval> {
if let errors = updates.failed {
throw parseErrors(errors: errors)
}
if let events = updates.updates {
parseUpdates(updates: events)
}
return Observable.just(updates.timeStamp!)
}
private func parseUpdates(updates: [[Any]]) {
updates.forEach { (array) in
let firstElementInUpdate = array.first
if let update = firstElementInUpdate as? Int {
switch update {
case 1: break
case 2: break
case 3: break
case 4: messageReciver.onNext(NewMessage(array: array))
default: break
}
}
}
}
private func parseErrors(errors: [String: Any]) -> LongPollError {
if let error = errors["failed"] as? Int {
switch error {
case 1:
guard let ts = errors["ts"] as? TimeInterval else { return .unkownError }
return .olderHistory(ts: ts)
case 2: return .needNewkey
case 3: return .needCaseAndTs
case 4: return .unkownVersion
default:
return .unkownError
}
}
return .unkownError
}
private func getEventsFromLongpollServer(serverInfo: LongpollingServerInfo, with ts: TimeInterval) -> Observable<Any> {
let url = buildLongPollingServerRoute(from: serverInfo, with: ts)
let request = buldLongPollRequst(route: url)
let requestConvert = try? URLEncoding.default.encode(request!, with: nil)
return transport.makeRequest(request: requestConvert!)
}
private func getEventsFromLongpollServer(serverInfo: LongpollingServerInfo) -> Observable<Any> {
let url = buildLongPollingServerRoute(from: serverInfo)
let request = buldLongPollRequst(route: url)
let requestConvert = try? URLEncoding.default.encode(request!, with: nil)
return transport.makeRequest(request: requestConvert!)
}
private func getLongpollServerInfo() -> Observable<LongpollingServerInfo> {
let request = MessageRouter.getLongpollServer(useSsl: false, needPts: false)
return transport.makeModel(request: request)
}
}
So assuming you have a function like:
func getData() -> Observable<Data>
And you want to long poll it at a specific period, you can do something like this:
Observable<Int>.interval(period, scheduler: MainScheduler.instance)
.map { _ in return }
.flatMap(getData)
.subscribe( /* ... handle data ... */)
.disposed(by: disposeBag)
You can use other schedulers than MainScheduler if that is more appropriate.
Now if you want also handle Errors that getData might emit and you don't want that to necessarily unsubscribe the long polling, then you can do this:
func handleError(error: Error) -> Observable<Data> {
return Observable.empty()
}
Observable<Int>.interval(period, scheduler: MainScheduler.instance)
.map { _ in return }
.flatMap { return getData.catchError(handleError) }
.subscribe( /* ... handle data ... */)
.disposed(by: disposeBag)
You can also analyze the error in handleError and decide if you want to continue by emitting an empty Observable or cancel the long polling by emitting another error.
I need to run a function when the contacts have changed. If the application is active, you can do this with NotificationCenter as narrated in this post (sometimes It works when I add a new number to an existing contact). How do I know that the contact (or contacts) have been changed after the launch of the application?
I made the following functions for my task
#objc private func matchingContacts() {
if isSuccessContactUploading {
contactManager.matchingContacts(notMatch: { [weak self] in
guard let _self = self else { return }
debugPrint("matchingContacts != equals")
_self.isSuccessContactUploading = false
_self.syncContacts()
})
}
}
These functions are in ContactManager
func matchingContacts(notMatch: (() -> Void)?) {
getContacts { (contacts, error) in
if error == nil {
debugPrint("contacts count", contacts.count)
self.getContactsDictionaryFromCache(contacts, notMatch: {
notMatch?()
})
}
}
}
private func getContactsDictionaryFromCache(_ contacts: [CNContact], notMatch: (() -> Void)?) {
var isMatching = true
for contact in contacts {
let key = contact.identifier
do {
let cache = try Cache<NSDictionary>(name: "Contacts")
if let contactDictionary = cache[key] {
if !contactDictionary.isEqual(to: contact.dictionary) {
debugPrint("contactDictionary not matching")
isMatching = false
}
} else {
debugPrint("contactDictionary isn't here")
isMatching = false
}
} catch {
debugPrint(error.localizedDescription)
isMatching = false
}
}
if !isMatching {
notMatch?()
}
cacheContacts(contacts)
}
private func cacheContacts(_ contacts: [CNContact]) {
for contact in contacts {
let contactDictionary = contact.dictionary as NSDictionary
let key = contact.identifier
do {
let cache = try Cache<NSDictionary>(name: "Contacts")
cache[key] = contactDictionary
} catch {
debugPrint(error.localizedDescription)
}
}
}
I am rookie with Swift. I need to change the following method to a boolean function which will return true if connection is ok else false if something is wrong. Thanks
func test() {
var configuration = SessionConfiguration()
configuration.host = "ftp://ftp.mozilla.org:21"
configuration.username = "optimus"
configuration.password = "rollout"
configuration.encoding = NSUTF8StringEncoding
_session = Session(configuration: configuration)
_session.list("/") {
(resources, error) -> Void in
println("List directory with result:\n\(resources), error: \(error)\n\n")
}
}
Since the _session.list("/") have a callback, which is asynchronous, what you can do is this:
func test(_ completion: (Bool) -> Void) {
// ...
_session.list("/") { (resources, error) -> Void in
println("List directory with result:\n\(resources), error: \(error)\n\n")
guard error == nil else {
completion(false) // failed
return
}
completion(true) // succeed
}
}
And so you can call it this way:
test() { (hasSucceeded: Bool) -> Void in
if hasSucceeded {
// Do what you want
} else {
// Failed
}
}
Lots of people have told you what to do, but nobody's clearly explained why.
The function _session.list() is an async function. When you call it it returns immediately, before it has even begun executing. The code runs on a separate thread, and the closure you pass in gets called once the function is complete.
Thus, you can't have your function return a simple bool. Async programming doesn't work that way.
Instead, you need to refactor your test function to take a closure with a bool parameter, as outlined by several other answers. (#KevinHirsch's answer for example.) Then when you invoke your test function you put the code that checks to see if the test passed or failed into the closure that you pass to the test function.
Since, session.list is an async process, add a completion handler and call your function as shown:
typealias theBool = (Bool) -> ()
func test(completion: theBool) {
//your code
_session.list("/") { (resources, error) in
guard error == nil else {
completion(false)
return
}
//your code
completion(true)
}
}
And call it like so:
test { someVar in
if someVar {
//do stuff when it is true
}
else{
//do stuff if it is false
}
}
When sync code:
func test() -> Bool {
return true
}
// Use case below:
let succes = test()
Turn to async use closure:
func test(didTest didTest: (Bool)->Void) {
...
_session.list("/") { (resources, error) -> Void in
if error != nil {
didTest(false)
} else {
didTest(true)
}
}
}
// Use case below:
test(didTest: { result in
let succes = result
})
Wish this may help.
Simply do
func test() -> Bool {
...
_session.list("/") { (resources, error) -> Void in
println("List directory with result:\n\(resources), error: \(error)\n\n")
if error != nil {
return true
} else {
return false
}
}
}
Updated:
I guess you should use closure for this because session.list is an async process. as #Duncan C said in his answer _session.list() When you call it it returns immediately, before it has even begun executing. The code runs on a separate thread, and the closure you pass in gets called once the function is complete.
public typealias SuccessClosure = (data: String) -> (Void)
public typealias FailureClosure = (error: NSError?, data: NSData? = nil, statusCode: Int? = nil) -> (Void)
func test(success: SuccessClosure, failure: FailureClosure) {
...
_session.list("/") { (resources, error) -> Void in
println("List directory with result:\n\(resources), error: \(error)\n\n")
if error != nil {
success("success")
} else {
failure(error)
}
}
}
What I want to achieve is to wait for all service calls to complete. I know that it can be done with GCD, but I'm looking for more Object Oriented Approach. Here is what I've so far:
First services should notify delegate for their completion, so we will need a protocol for that:
protocol ParallelServiceDelegate: class {
func serviceDidCompleted()
}
Services are Alamofire requests and we are getting their response as:
enum ServiceResult {
case Success(NSDictionary)
case Failure(NSError)
}
My design is to add Facade (wrapper) over this methods. This is the abstract:
import ObjectMapper
protocol ParallelService: class {
associatedtype ItemType: Mappable
var item: ItemType? { get }
var error: NSError? { get }
var isCompleted: Bool { get }
weak var delegate: ParallelServiceDelegate? { get set }
// TODO: Pass params
func start()
func handleRequestCompletion(result: ServiceResult)
}
private var psiAssociationKey: UInt8 = 0
private var pseAssociationKey: UInt8 = 0
private var psdAssociationKey: UInt8 = 0
extension ParallelService {
var item: ItemType? {
return _item
}
var error: NSError? {
return _error
}
var isCompleted: Bool {
return item != nil || error != nil
}
weak var delegate: ParallelServiceDelegate? {
get {
let object = objc_getAssociatedObject(self, &psdAssociationKey)
let wrapper = object as? WeakWrapper<ItemType?>
return wrapper?.value as? ParallelServiceDelegate
}
set(newValue) {
objc_setAssociatedObject(self,
&psdAssociationKey,
WeakWrapper(value: newValue),
.OBJC_ASSOCIATION_RETAIN)
}
}
func handleRequestCompletion(result: ServiceResult) {
switch result {
case .Success(let json):
_item = map(json, object: ItemType.self)
case .Failure(let error):
_error = error
}
}
// Degfault is nothing
func start() {}
// MARK: - Private
private var _item: ItemType? {
get {
let object = objc_getAssociatedObject(self, &psiAssociationKey)
let wrapper = object as? WeakWrapper<ItemType?>
return wrapper?.value as? ItemType
}
set(newValue) {
objc_setAssociatedObject(self,
&psiAssociationKey,
WeakWrapper(value: newValue),
.OBJC_ASSOCIATION_RETAIN)
}
}
private var _error: NSError? {
get {
let object = objc_getAssociatedObject(self, &pseAssociationKey)
return object as? NSError
}
set(newValue) {
objc_setAssociatedObject(self,
&pseAssociationKey,
newValue,
.OBJC_ASSOCIATION_RETAIN)
}
}
}
And Specific Service Facade implementation:
class EmployeesParallelService: ParallelService {
typealias ItemType = Employee
func start() {
Service.emploeesList(callback: handleRequestCompletion)
}
}
class InformationParallelService: ParallelService {
typealias ItemType = Information
func start() {
Service.information(callback: handleRequestCompletion)
}
}
Service caller - Knows nothing about services it just starts all and waits for them to complete:
class ParallelServiceCaller {
private var services: [ParallelService] = []
// MARK: - Lifecycle
func addParallelService(service: ParallelService) {
service.delegate = self
self.services.append(service)
}
// MARK: - Public
func start() {
for service in services {
service.start()
}
}
}
extension ParallelServiceCaller: ParallelServiceDelegate {
func serviceDidCompleted() {
for service in services {
// !!! wait
if !service.isCompleted {
return
}
}
// TODO: Notify delegate
}
}
Latter I want to use it like this:
let caller = ParallelServiceCaller()
caller.addParallelService(EmployeesParallelService())
caller.addParallelService(InformationParallelService())
caller.start()
However I got problem in the implementation of the ParallelServiceCaller class. I'm getting the following error:
Protocol 'ParallelService' can only be used as a generic constraint
because it has Self or associated type requirements
Any idea how to avoid this error?
Update 07/07/16:
I'm still not able to understand how to use PATs. However I took slightly different approach and now I'm using visitor pattern. Here is my playground code, it may be helpful for someone:
//: Playground - noun: a place where people can play
import UIKit
// Mocks
enum ServiceResult {
case Success(NSDictionary)
case Failure(NSError)
}
protocol Mappable { }
typealias CompletionHandlerType = (result: ServiceResult) -> Void
class Service {
class func emploeesList(start: Int? = nil,
max: Int? = nil,
timestamp: Int? = nil,
callback: CompletionHandlerType) {
callback(result: .Success(NSDictionary()))
}
class func information(timestamp: Int? = nil,
callback: CompletionHandlerType) {
callback(result: .Failure(NSError(domain: "", code: 1, userInfo: nil)))
}
}
class EmployeesList: Mappable {}
class Information: Mappable {}
// Actual Implementation
// Visitor
protocol ParallelServiceCallerProtocol {
func call(service: EmployeesListParallelService)
func call(service: InformationParallelService)
}
// Element
protocol ParallelServiceProtocol {
func start(visitor: ParallelServiceCallerProtocol)
}
// Concrete Elements
class EmployeesListParallelService: ParallelServiceProtocol {
func start(visitor: ParallelServiceCallerProtocol) { visitor.call(self) }
}
class InformationParallelService: ParallelServiceProtocol {
func start(visitor: ParallelServiceCallerProtocol) { visitor.call(self) }
}
// Concrete Visitor Delegate - defines callback for async tasks
protocol ParallelServiceCallerDelegateProtocol: class {
func didCompleteParellelServiceWithResult(service: ParallelServiceProtocol, result: ServiceResult)
}
// Concrete Visitor - make API calls
class ParallelServiceCaller <T: ParallelServiceCallerDelegateProtocol>: ParallelServiceCallerProtocol {
private unowned let delegate: T
init(delegate: T) {
self.delegate = delegate
}
func call(service: EmployeesListParallelService) {
Service.emploeesList { [unowned self] (result) in
self.delegate.didCompleteParellelServiceWithResult(service, result: result)
}
}
func call(service: InformationParallelService) {
Service.information { (result) in
self.delegate.didCompleteParellelServiceWithResult(service, result: result)
}
}
}
// Service Result In Context
enum SynchronizationServiceResult {
case Employees(ServiceResult)
case Information(ServiceResult)
}
// Concrete Visitor - Wraps API Result And Gives Context
class ParallelServiceParser: ParallelServiceCallerProtocol {
var result: SynchronizationServiceResult?
private let serviceResult: ServiceResult
init(serviceResult: ServiceResult) {
self.serviceResult = serviceResult
}
func call(service: EmployeesListParallelService) {
result = .Employees(serviceResult)
}
func call(service: InformationParallelService) {
result = .Information(serviceResult)
}
}
// Delegate that notifies for completion of all calls
protocol ParallelServiceManagerDelegateProtocol: class {
func didCompleteAllServicesWithResults(results: [SynchronizationServiceResult])
}
// Manager - starts all calls and adds context to returned results - knows nothing about calls
class ParallelServiceManager<T where T: ParallelServiceManagerDelegateProtocol> {
private let services: [ParallelServiceProtocol]
private unowned let delegate: T
// Keep Caller Visitors in Memory or they will be dealocated
private var callers: [ParallelServiceCaller<ParallelServiceManager>] = []
private var completed: [SynchronizationServiceResult] = [] {
didSet {
if completed.count == services.count {
self.delegate.didCompleteAllServicesWithResults(completed)
self.callers.removeAll()
}
}
}
init(services: [ParallelServiceProtocol], delegate: T) {
self.services = services
self.delegate = delegate
}
func start() {
visitAllServices { (service) in
let caller =
ParallelServiceCaller<ParallelServiceManager>(delegate: self)
service.start(caller)
self.callers.append(caller)
}
}
private func visitAllServices(perform: ParallelServiceProtocol -> () ) {
for service in self.services {
perform(service)
}
}
}
extension ParallelServiceManager: ParallelServiceCallerDelegateProtocol {
func didCompleteParellelServiceWithResult(service: ParallelServiceProtocol,
result: ServiceResult) {
// No need to persist parser visitor
let caller = ParallelServiceParser(serviceResult: result)
service.start(caller)
completed.append(caller.result!)
}
}
// Example Usage
class SynchronizationService {
private lazy var services: [ParallelServiceProtocol] = {
return [EmployeesListParallelService(), InformationParallelService()]
}()
func start() {
let manager = ParallelServiceManager<SynchronizationService>(services: services, delegate: self)
manager.start()
}
}
extension SynchronizationService: ParallelServiceManagerDelegateProtocol {
func didCompleteAllServicesWithResults(results: [SynchronizationServiceResult]) {
for result in results {
switch result {
case .Employees(let result):
// TODO:
print("\(result)") // Should Return Success
case .Information(let result):
// TODO:
print("\(result)") // Should Return Failure
}
}
}
}
let sync = SynchronizationService()
sync.start()