add base network service

This commit is contained in:
Ivan Smolin 2017-02-01 12:03:31 +03:00
parent c29ff18758
commit 84b4d9dc86
8 changed files with 193 additions and 9 deletions

View File

@ -2,3 +2,4 @@ github "CocoaLumberjack/CocoaLumberjack" ~> 3.0.0
github "ReactiveX/RxSwift" "3.0.1"
github "RxSwiftCommunity/RxAlamofire" "3.0.1"
github "Hearst-DD/ObjectMapper" ~> 2.1
github "scalessec/Toast-Swift" ~> 2.0.0

View File

@ -1,5 +1,6 @@
github "Alamofire/Alamofire" "4.2.0"
github "Alamofire/Alamofire" "4.3.0"
github "CocoaLumberjack/CocoaLumberjack" "3.0.0"
github "Hearst-DD/ObjectMapper" "2.2.1"
github "Hearst-DD/ObjectMapper" "2.2.2"
github "ReactiveX/RxSwift" "3.0.1"
github "scalessec/Toast-Swift" "2.0.0"
github "RxSwiftCommunity/RxAlamofire" "3.0.1"

View File

@ -1,6 +1,6 @@
Pod::Spec.new do |s|
s.name = "LeadKit"
s.version = "0.3.2"
s.version = "0.4.0"
s.summary = "iOS framework with a bunch of tools for rapid development"
s.homepage = "https://github.com/TouchInstinct/LeadKit"
s.license = "Apache License, Version 2.0"
@ -14,4 +14,5 @@ Pod::Spec.new do |s|
s.dependency "RxCocoa", '3.0.1'
s.dependency "RxAlamofire", '3.0.0'
s.dependency "ObjectMapper", '~> 2.1'
s.dependency "Toast-Swift", '~> 2.0.0'
end

View File

@ -21,6 +21,9 @@
7827C9391DE4ADB2009DA4E6 /* RxSwift.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 7827C9331DE4ADB2009DA4E6 /* RxSwift.framework */; };
7834236A1DB8D0E100A79643 /* StoryboardProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 783423691DB8D0E100A79643 /* StoryboardProtocol.swift */; };
7837F60F1CBCF5C0000D74C1 /* EstimatedViewHeightProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7837F60E1CBCF5C0000D74C1 /* EstimatedViewHeightProtocol.swift */; };
783AF06B1E41CE6C00EC5ADE /* Observable+ToastErrorLogging.swift in Sources */ = {isa = PBXBuildFile; fileRef = 783AF06A1E41CE6C00EC5ADE /* Observable+ToastErrorLogging.swift */; };
783AF06D1E41CF5B00EC5ADE /* NetworkService.swift in Sources */ = {isa = PBXBuildFile; fileRef = 783AF06C1E41CF5B00EC5ADE /* NetworkService.swift */; };
783AF06F1E41D84A00EC5ADE /* ToastSwiftFramework.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 783AF06E1E41D84A00EC5ADE /* ToastSwiftFramework.framework */; };
786D78E81D53C378006B2CEA /* AlamofireRequest+Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 786D78E71D53C378006B2CEA /* AlamofireRequest+Extensions.swift */; };
786D78EC1D53C46E006B2CEA /* AlamofireManager+Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 786D78EB1D53C46E006B2CEA /* AlamofireManager+Extensions.swift */; };
7873D14F1E1127BC001816EB /* LeadKitError.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7873D14E1E1127BC001816EB /* LeadKitError.swift */; };
@ -100,6 +103,9 @@
7827C9331DE4ADB2009DA4E6 /* RxSwift.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = RxSwift.framework; path = ../../../Carthage/Build/iOS/RxSwift.framework; sourceTree = "<group>"; };
783423691DB8D0E100A79643 /* StoryboardProtocol.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = StoryboardProtocol.swift; sourceTree = "<group>"; };
7837F60E1CBCF5C0000D74C1 /* EstimatedViewHeightProtocol.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = EstimatedViewHeightProtocol.swift; sourceTree = "<group>"; };
783AF06A1E41CE6C00EC5ADE /* Observable+ToastErrorLogging.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "Observable+ToastErrorLogging.swift"; sourceTree = "<group>"; };
783AF06C1E41CF5B00EC5ADE /* NetworkService.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = NetworkService.swift; sourceTree = "<group>"; };
783AF06E1E41D84A00EC5ADE /* ToastSwiftFramework.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = ToastSwiftFramework.framework; path = ../../../Carthage/Build/iOS/ToastSwiftFramework.framework; sourceTree = "<group>"; };
786D78E71D53C378006B2CEA /* AlamofireRequest+Extensions.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "AlamofireRequest+Extensions.swift"; sourceTree = "<group>"; };
786D78EB1D53C46E006B2CEA /* AlamofireManager+Extensions.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "AlamofireManager+Extensions.swift"; sourceTree = "<group>"; };
7873D14E1E1127BC001816EB /* LeadKitError.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = LeadKitError.swift; sourceTree = "<group>"; };
@ -163,6 +169,7 @@
buildActionMask = 2147483647;
files = (
7827C9361DE4ADB2009DA4E6 /* ObjectMapper.framework in Frameworks */,
783AF06F1E41D84A00EC5ADE /* ToastSwiftFramework.framework in Frameworks */,
7827C9351DE4ADB2009DA4E6 /* CocoaLumberjack.framework in Frameworks */,
7827C9391DE4ADB2009DA4E6 /* RxSwift.framework in Frameworks */,
7827C9341DE4ADB2009DA4E6 /* Alamofire.framework in Frameworks */,
@ -191,6 +198,7 @@
7827C9311DE4ADB2009DA4E6 /* RxAlamofire.framework */,
7827C9321DE4ADB2009DA4E6 /* RxCocoa.framework */,
7827C9331DE4ADB2009DA4E6 /* RxSwift.framework */,
783AF06E1E41D84A00EC5ADE /* ToastSwiftFramework.framework */,
);
path = Frameworks;
sourceTree = "<group>";
@ -249,6 +257,14 @@
path = Sequence;
sourceTree = "<group>";
};
783AF0591E40824300EC5ADE /* Services */ = {
isa = PBXGroup;
children = (
783AF06C1E41CF5B00EC5ADE /* NetworkService.swift */,
);
path = Services;
sourceTree = "<group>";
};
786D78E61D53C355006B2CEA /* Alamofire */ = {
isa = PBXGroup;
children = (
@ -272,6 +288,7 @@
isa = PBXGroup;
children = (
787609211E1403830093CE36 /* Observable+DeferredJust.swift */,
783AF06A1E41CE6C00EC5ADE /* Observable+ToastErrorLogging.swift */,
);
path = Observable;
sourceTree = "<group>";
@ -329,6 +346,7 @@
78A74EAA1C6B401800FE9724 /* Classes */ = {
isa = PBXGroup;
children = (
783AF0591E40824300EC5ADE /* Services */,
78B0FC7B1C6B2BAE00358B64 /* Logging */,
78753E2A1DE58BED006BC0FB /* Cursors */,
);
@ -670,6 +688,7 @@
"$(SRCROOT)/../Carthage/Build/iOS/Alamofire.framework",
"$(SRCROOT)/../Carthage/Build/iOS/ObjectMapper.framework",
"$(SRCROOT)/../Carthage/Build/iOS/RxAlamofire.framework",
"$(SRCROOT)/../Carthage/Build/iOS/ToastSwiftFramework.framework",
);
name = "Carthage copy-frameworks";
outputPaths = (
@ -714,6 +733,7 @@
78C36F7E1D801E3E00E7EBEA /* Double+Rounding.swift in Sources */,
78CFEE551C5C45E500F50370 /* NibNameProtocol.swift in Sources */,
787609221E1403830093CE36 /* Observable+DeferredJust.swift in Sources */,
783AF06B1E41CE6C00EC5ADE /* Observable+ToastErrorLogging.swift in Sources */,
78CFEE561C5C45E500F50370 /* ReuseIdentifierProtocol.swift in Sources */,
78A0FCC81DC366A10070B5E1 /* StoryboardProtocol+Extensions.swift in Sources */,
78B036411DA4D7060021D5CC /* UIImage+Extensions.swift in Sources */,
@ -723,6 +743,7 @@
78C36F811D8021DD00E7EBEA /* UIColor+Hex.swift in Sources */,
78CFEE5B1C5C45E500F50370 /* ViewModelProtocol.swift in Sources */,
EF5FB5691E0141610030E4BE /* UIView+Rotation.swift in Sources */,
783AF06D1E41CF5B00EC5ADE /* NetworkService.swift in Sources */,
780F56CC1E0D7ACA004530B6 /* ObservableMappable.swift in Sources */,
780D23431DA412470084620D /* CGImage+Alpha.swift in Sources */,
78CFEE5A1C5C45E500F50370 /* ViewHeightProtocol.swift in Sources */,

View File

@ -0,0 +1,119 @@
//
// Copyright (c) 2017 Touch Instinct
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.
//
import RxSwift
import RxCocoa
import Alamofire
import ObjectMapper
import RxAlamofire
/// Base network service implementation build on top of LeadKit extensions for Alamofire.
/// Has an ability to automatically show / hide network activity indicator
/// and shows errors in DEBUG mode
open class NetworkService {
private let disposeBag = DisposeBag()
private let requestCount = Variable<Int>(0)
public let sessionManager: Alamofire.SessionManager
/// Let netwrok service automatically show / hide activity indicator
public func bindActivityIndicator() {
return requestCount.asDriver()
.map { $0 != 0 }
.drive(UIApplication.shared.rx.isNetworkActivityIndicatorVisible)
.addDisposableTo(disposeBag)
}
/// Creates new instance of NetworkService with given Alamofire session manager
///
/// - Parameter sessionManager: Alamofire.SessionManager to use for requests
public init(sessionManager: Alamofire.SessionManager) {
self.sessionManager = sessionManager
}
/// Perform reactive request to get mapped ObservableMappable model and http response
///
/// - Parameter parameters: api parameters to pass Alamofire
/// - Returns: Observable of tuple containing (HTTPURLResponse, ObservableMappable)
public func rxRequest<T: ObservableMappable>(with parameters: ApiRequestParameters)
-> Observable<(response: HTTPURLResponse, model: T)> where T.ModelType == T {
return sessionManager.rx.responseObservableModel(requestParameters: parameters)
.counterTracking(for: self)
.showErrorsInToastInDebugMode()
}
/// Perform reactive request to get mapped ImmutableMappable model and http response
///
/// - Parameter parameters: api parameters to pass Alamofire
/// - Returns: Observable of tuple containing (HTTPURLResponse, ImmutableMappable)
public func rxRequest<T: ImmutableMappable>(with parameters: ApiRequestParameters)
-> Observable<(response: HTTPURLResponse, model: T)> {
return sessionManager.rx.responseModel(requestParameters: parameters)
.counterTracking(for: self)
.showErrorsInToastInDebugMode()
}
/// Perform reactive request to get UIImage and http response
///
/// - Parameter url: An object adopting `URLConvertible`
/// - Returns: Observable of tuple containing (HTTPURLResponse, UIImage?)
public func rxLoadImage(url: String) -> Observable<(HTTPURLResponse, UIImage?)> {
let request = RxAlamofire.requestData(.get, url, headers: [:])
return request
.observeOn(ConcurrentDispatchQueueScheduler(qos: .background))
.map { (response, data) -> (HTTPURLResponse, UIImage?) in
(response, UIImage(data: data))
}
.counterTracking(for: self)
.showErrorsInToastInDebugMode()
}
fileprivate func increaseRequestCounter() {
requestCount.value += 1
}
fileprivate func decreaseRequestCounter() {
requestCount.value -= 1
}
}
public extension Observable {
/// Increase and descrease NetworkService request counter on subscribe and dispose
/// (used to show / hide activity indicator)
///
/// - Parameter networkService: NetworkService to operate on it
/// - Returns: The source sequence with the side-effecting behavior applied.
func counterTracking(for networkService: NetworkService) -> Observable<Observable.E> {
return `do`(onSubscribe: {
networkService.increaseRequestCounter()
}, onDispose: {
networkService.decreaseRequestCounter()
})
}
}

View File

@ -45,7 +45,9 @@ public extension Reactive where Base: Alamofire.SessionManager {
/// - Parameter mappingQueue: The dispatch queue to use for mapping
/// - Returns: Observable with HTTP URL Response and target object
func responseModel<T: ImmutableMappable>(requestParameters: ApiRequestParameters,
mappingQueue: DispatchQueue = DispatchQueue.global()) -> Observable<(HTTPURLResponse, T)> {
mappingQueue: DispatchQueue = DispatchQueue.global())
-> Observable<(response: HTTPURLResponse, model: T)> {
return apiRequest(requestParameters: requestParameters)
.flatMap { $0.rx.apiResponse(mappingQueue: mappingQueue) }
}
@ -56,8 +58,8 @@ public extension Reactive where Base: Alamofire.SessionManager {
/// - Parameter mappingQueue: The dispatch queue to use for mapping
/// - Returns: Observable with HTTP URL Response and target object
func responseObservableModel<T: ObservableMappable>(requestParameters: ApiRequestParameters,
mappingQueue: DispatchQueue = DispatchQueue.global()) -> Observable<(HTTPURLResponse, T)>
where T.ModelType == T {
mappingQueue: DispatchQueue = DispatchQueue.global())
-> Observable<(response: HTTPURLResponse, model: T)> where T.ModelType == T {
return apiRequest(requestParameters: requestParameters)
.flatMap { $0.rx.apiResponse(mappingQueue: mappingQueue) }

View File

@ -32,7 +32,7 @@ public extension Reactive where Base: DataRequest {
/// - Parameter mappingQueue: The dispatch queue to use for mapping
/// - Returns: Observable with HTTP URL Response and target object
func apiResponse<T: ImmutableMappable>(mappingQueue: DispatchQueue = DispatchQueue.global())
-> Observable<(HTTPURLResponse, T)> {
-> Observable<(response: HTTPURLResponse, model: T)> {
return responseJSONOnQueue(mappingQueue)
.map { resp, value in
@ -47,10 +47,10 @@ public extension Reactive where Base: DataRequest {
/// - Parameter mappingQueue: The dispatch queue to use for mapping
/// - Returns: Observable with HTTP URL Response and target object
func apiResponse<T: ObservableMappable>(mappingQueue: DispatchQueue = DispatchQueue.global())
-> Observable<(HTTPURLResponse, T)> where T.ModelType == T {
-> Observable<(response: HTTPURLResponse, model: T)> where T.ModelType == T {
return responseJSONOnQueue(mappingQueue)
.flatMap { resp, value -> Observable<(HTTPURLResponse, T)> in
.flatMap { resp, value -> Observable<(response: HTTPURLResponse, model: T)> in
let json = try cast(value) as [String: Any]
return T.createFrom(map: Map(mappingType: .fromJSON, JSON: json))

View File

@ -0,0 +1,39 @@
//
// Copyright (c) 2017 Touch Instinct
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.
//
import RxSwift
import Toast_Swift
public extension Observable {
/// Method which shows toast with localized description of error in DEBUG mode
///
/// - Returns: The source sequence with the side-effecting behavior applied.
func showErrorsInToastInDebugMode() -> Observable<Observable.E> {
return `do`(onError: { (error) in
#if DEBUG
UIApplication.shared.keyWindow?.makeToast(error.localizedDescription)
#endif
})
}
}