From c2802a42f6795d91e9f6fed60208da3300dfd173 Mon Sep 17 00:00:00 2001 From: Ivan Smolin Date: Tue, 27 Dec 2016 15:51:00 +0300 Subject: [PATCH] implement concurrent mapping --- LeadKit/LeadKit.xcodeproj/project.pbxproj | 48 +++++++++++ .../Classes/Operations/ResultOperation.swift | 29 +++++++ LeadKit/LeadKit/Enums/LeadKitError.swift | 18 ++++ .../AlamofireManager+Extensions.swift | 40 +++++---- .../AlamofireRequest+Extensions.swift | 34 ++++---- ...ImmutableMappable+ObservableMappable.swift | 23 +++++ .../Sequence/Sequence+ConcurrentMap.swift | 85 +++++++++++++++++++ .../UserDefaults+MappableDataTypes.swift | 8 +- LeadKit/LeadKit/Functions/Any+Cast.swift | 22 +++++ LeadKit/LeadKit/Functions/Any+TypeName.swift | 4 + .../Protocols/ObservableMappable.swift | 19 +++++ 11 files changed, 290 insertions(+), 40 deletions(-) create mode 100644 LeadKit/LeadKit/Classes/Operations/ResultOperation.swift create mode 100644 LeadKit/LeadKit/Enums/LeadKitError.swift create mode 100644 LeadKit/LeadKit/Extensions/ObjectMapper/ImmutableMappable+ObservableMappable.swift create mode 100644 LeadKit/LeadKit/Extensions/Sequence/Sequence+ConcurrentMap.swift create mode 100644 LeadKit/LeadKit/Functions/Any+Cast.swift create mode 100644 LeadKit/LeadKit/Protocols/ObservableMappable.swift diff --git a/LeadKit/LeadKit.xcodeproj/project.pbxproj b/LeadKit/LeadKit.xcodeproj/project.pbxproj index f9ef1920..7bef1994 100644 --- a/LeadKit/LeadKit.xcodeproj/project.pbxproj +++ b/LeadKit/LeadKit.xcodeproj/project.pbxproj @@ -11,6 +11,9 @@ 78011AB31D48B53600EA16A2 /* ApiRequestParameters.swift in Sources */ = {isa = PBXBuildFile; fileRef = 78011AB21D48B53600EA16A2 /* ApiRequestParameters.swift */; }; 780D23431DA412470084620D /* CGImage+Alpha.swift in Sources */ = {isa = PBXBuildFile; fileRef = 780D23421DA412470084620D /* CGImage+Alpha.swift */; }; 780D23461DA416F80084620D /* CGContext+Initializers.swift in Sources */ = {isa = PBXBuildFile; fileRef = 780D23451DA416F80084620D /* CGContext+Initializers.swift */; }; + 780F56C71E0D7608004530B6 /* ResultOperation.swift in Sources */ = {isa = PBXBuildFile; fileRef = 780F56C61E0D7608004530B6 /* ResultOperation.swift */; }; + 780F56CA1E0D76B8004530B6 /* Sequence+ConcurrentMap.swift in Sources */ = {isa = PBXBuildFile; fileRef = 780F56C91E0D76B8004530B6 /* Sequence+ConcurrentMap.swift */; }; + 780F56CC1E0D7ACA004530B6 /* ObservableMappable.swift in Sources */ = {isa = PBXBuildFile; fileRef = 780F56CB1E0D7ACA004530B6 /* ObservableMappable.swift */; }; 7827C9341DE4ADB2009DA4E6 /* Alamofire.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 7827C92E1DE4ADB2009DA4E6 /* Alamofire.framework */; }; 7827C9351DE4ADB2009DA4E6 /* CocoaLumberjack.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 7827C92F1DE4ADB2009DA4E6 /* CocoaLumberjack.framework */; }; 7827C9361DE4ADB2009DA4E6 /* ObjectMapper.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 7827C9301DE4ADB2009DA4E6 /* ObjectMapper.framework */; }; @@ -22,6 +25,8 @@ 7845A15A1E0BCD9A00B527BB /* KeyboardNotificationValues.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7845A1591E0BCD9A00B527BB /* KeyboardNotificationValues.swift */; }; 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 */; }; + 7873D1511E112B0D001816EB /* Any+Cast.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7873D1501E112B0D001816EB /* Any+Cast.swift */; }; 78753E241DE58A5D006BC0FB /* CursorError.swift in Sources */ = {isa = PBXBuildFile; fileRef = 78753E231DE58A5D006BC0FB /* CursorError.swift */; }; 78753E2C1DE58BF9006BC0FB /* StaticCursor.swift in Sources */ = {isa = PBXBuildFile; fileRef = 78753E2B1DE58BF9006BC0FB /* StaticCursor.swift */; }; 78753E2E1DE58DBA006BC0FB /* FixedPageCursor.swift in Sources */ = {isa = PBXBuildFile; fileRef = 78753E2D1DE58DBA006BC0FB /* FixedPageCursor.swift */; }; @@ -29,6 +34,7 @@ 787682FA1CAD40C300532AB3 /* StaticEstimatedViewHeightProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 787682F91CAD40C200532AB3 /* StaticEstimatedViewHeightProtocol.swift */; }; 787783631CA03CA0001CDC9B /* IndexPath+ImmutableIndexPath.swift in Sources */ = {isa = PBXBuildFile; fileRef = 787783621CA03CA0001CDC9B /* IndexPath+ImmutableIndexPath.swift */; }; 787783671CA04D4A001CDC9B /* String+SizeCalculation.swift in Sources */ = {isa = PBXBuildFile; fileRef = 787783661CA04D4A001CDC9B /* String+SizeCalculation.swift */; }; + 787D874A1E10E1A400D6015C /* ImmutableMappable+ObservableMappable.swift in Sources */ = {isa = PBXBuildFile; fileRef = 787D87491E10E1A400D6015C /* ImmutableMappable+ObservableMappable.swift */; }; 7884DB9C1DC1439200E52A63 /* UserDefaults+MappableDataTypes.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7884DB9B1DC1439200E52A63 /* UserDefaults+MappableDataTypes.swift */; }; 788EC15A1CF64528009CFB6B /* UIStoryboard+InstantiateViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 788EC1591CF64528009CFB6B /* UIStoryboard+InstantiateViewController.swift */; }; 789CC6081DE5835600F789D3 /* CursorType.swift in Sources */ = {isa = PBXBuildFile; fileRef = 789CC6071DE5835600F789D3 /* CursorType.swift */; }; @@ -87,6 +93,9 @@ 78011AB21D48B53600EA16A2 /* ApiRequestParameters.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ApiRequestParameters.swift; sourceTree = ""; }; 780D23421DA412470084620D /* CGImage+Alpha.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "CGImage+Alpha.swift"; sourceTree = ""; }; 780D23451DA416F80084620D /* CGContext+Initializers.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "CGContext+Initializers.swift"; sourceTree = ""; }; + 780F56C61E0D7608004530B6 /* ResultOperation.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ResultOperation.swift; sourceTree = ""; }; + 780F56C91E0D76B8004530B6 /* Sequence+ConcurrentMap.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "Sequence+ConcurrentMap.swift"; sourceTree = ""; }; + 780F56CB1E0D7ACA004530B6 /* ObservableMappable.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ObservableMappable.swift; sourceTree = ""; }; 7827C92E1DE4ADB2009DA4E6 /* Alamofire.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Alamofire.framework; path = ../../../Carthage/Build/iOS/Alamofire.framework; sourceTree = ""; }; 7827C92F1DE4ADB2009DA4E6 /* CocoaLumberjack.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = CocoaLumberjack.framework; path = ../../../Carthage/Build/iOS/CocoaLumberjack.framework; sourceTree = ""; }; 7827C9301DE4ADB2009DA4E6 /* ObjectMapper.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = ObjectMapper.framework; path = ../../../Carthage/Build/iOS/ObjectMapper.framework; sourceTree = ""; }; @@ -98,6 +107,8 @@ 7845A1591E0BCD9A00B527BB /* KeyboardNotificationValues.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = KeyboardNotificationValues.swift; sourceTree = ""; }; 786D78E71D53C378006B2CEA /* AlamofireRequest+Extensions.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "AlamofireRequest+Extensions.swift"; sourceTree = ""; }; 786D78EB1D53C46E006B2CEA /* AlamofireManager+Extensions.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "AlamofireManager+Extensions.swift"; sourceTree = ""; }; + 7873D14E1E1127BC001816EB /* LeadKitError.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = LeadKitError.swift; sourceTree = ""; }; + 7873D1501E112B0D001816EB /* Any+Cast.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "Any+Cast.swift"; sourceTree = ""; }; 78753E231DE58A5D006BC0FB /* CursorError.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CursorError.swift; sourceTree = ""; }; 78753E2B1DE58BF9006BC0FB /* StaticCursor.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = StaticCursor.swift; sourceTree = ""; }; 78753E2D1DE58DBA006BC0FB /* FixedPageCursor.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = FixedPageCursor.swift; sourceTree = ""; }; @@ -105,6 +116,7 @@ 787682F91CAD40C200532AB3 /* StaticEstimatedViewHeightProtocol.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = StaticEstimatedViewHeightProtocol.swift; sourceTree = ""; }; 787783621CA03CA0001CDC9B /* IndexPath+ImmutableIndexPath.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "IndexPath+ImmutableIndexPath.swift"; sourceTree = ""; }; 787783661CA04D4A001CDC9B /* String+SizeCalculation.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "String+SizeCalculation.swift"; sourceTree = ""; }; + 787D87491E10E1A400D6015C /* ImmutableMappable+ObservableMappable.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "ImmutableMappable+ObservableMappable.swift"; sourceTree = ""; }; 7884DB9B1DC1439200E52A63 /* UserDefaults+MappableDataTypes.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "UserDefaults+MappableDataTypes.swift"; sourceTree = ""; }; 788EC1591CF64528009CFB6B /* UIStoryboard+InstantiateViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "UIStoryboard+InstantiateViewController.swift"; sourceTree = ""; }; 789CC6071DE5835600F789D3 /* CursorType.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CursorType.swift; sourceTree = ""; }; @@ -195,6 +207,7 @@ 78753E231DE58A5D006BC0FB /* CursorError.swift */, 789F5A131DFECD54004A3694 /* KeyboardNotificationValuesError.swift */, 789F5A1F1DFECF9F004A3694 /* KeyboardNotification.swift */, + 7873D14E1E1127BC001816EB /* LeadKitError.swift */, ); path = Enums; sourceTree = ""; @@ -237,6 +250,22 @@ path = CGContext; sourceTree = ""; }; + 780F56C51E0D75F7004530B6 /* Operations */ = { + isa = PBXGroup; + children = ( + 780F56C61E0D7608004530B6 /* ResultOperation.swift */, + ); + path = Operations; + sourceTree = ""; + }; + 780F56C81E0D76A5004530B6 /* Sequence */ = { + isa = PBXGroup; + children = ( + 780F56C91E0D76B8004530B6 /* Sequence+ConcurrentMap.swift */, + ); + path = Sequence; + sourceTree = ""; + }; 786D78E61D53C355006B2CEA /* Alamofire */ = { isa = PBXGroup; children = ( @@ -273,6 +302,14 @@ path = String; sourceTree = ""; }; + 787D87481E10E19000D6015C /* ObjectMapper */ = { + isa = PBXGroup; + children = ( + 787D87491E10E1A400D6015C /* ImmutableMappable+ObservableMappable.swift */, + ); + path = ObjectMapper; + sourceTree = ""; + }; 7884DB9A1DC1432B00E52A63 /* UserDefaults */ = { isa = PBXGroup; children = ( @@ -329,6 +366,7 @@ children = ( 78B0FC7B1C6B2BAE00358B64 /* Logging */, 78753E2A1DE58BED006BC0FB /* Cursors */, + 780F56C51E0D75F7004530B6 /* Operations */, ); path = Classes; sourceTree = ""; @@ -423,6 +461,8 @@ 789CC6091DE584C000F789D3 /* CursorType */, 789F5A0A1DFECB52004A3694 /* UIViewAnimationCurve */, 789F5A1C1DFECF44004A3694 /* NotificationCenter */, + 780F56C81E0D76A5004530B6 /* Sequence */, + 787D87481E10E19000D6015C /* ObjectMapper */, ); path = Extensions; sourceTree = ""; @@ -441,6 +481,7 @@ 7837F60E1CBCF5C0000D74C1 /* EstimatedViewHeightProtocol.swift */, 783423691DB8D0E100A79643 /* StoryboardProtocol.swift */, 789CC6071DE5835600F789D3 /* CursorType.swift */, + 780F56CB1E0D7ACA004530B6 /* ObservableMappable.swift */, ); path = Protocols; sourceTree = ""; @@ -457,6 +498,7 @@ isa = PBXGroup; children = ( 78D4B5491DA64EAB005B0764 /* Any+TypeName.swift */, + 7873D1501E112B0D001816EB /* Any+Cast.swift */, ); path = Functions; sourceTree = ""; @@ -668,6 +710,8 @@ isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( + 787D874A1E10E1A400D6015C /* ImmutableMappable+ObservableMappable.swift in Sources */, + 780F56CA1E0D76B8004530B6 /* Sequence+ConcurrentMap.swift in Sources */, 7837F60F1CBCF5C0000D74C1 /* EstimatedViewHeightProtocol.swift in Sources */, 78CFEE541C5C45E500F50370 /* UIView+LoadFromNib.swift in Sources */, 78D4B5461DA64D49005B0764 /* UIViewController+DefaultStoryboardIdentifier.swift in Sources */, @@ -683,13 +727,16 @@ 78CFEE571C5C45E500F50370 /* StaticNibNameProtocol.swift in Sources */, 788EC15A1CF64528009CFB6B /* UIStoryboard+InstantiateViewController.swift in Sources */, 787783671CA04D4A001CDC9B /* String+SizeCalculation.swift in Sources */, + 7873D1511E112B0D001816EB /* Any+Cast.swift in Sources */, 78B036431DA4FEC90021D5CC /* CGImage+Transform.swift in Sources */, + 780F56C71E0D7608004530B6 /* ResultOperation.swift in Sources */, 78011A641D47ABC500EA16A2 /* UIView+DefaultReuseIdentifier.swift in Sources */, 786D78EC1D53C46E006B2CEA /* AlamofireManager+Extensions.swift in Sources */, 78B0FC811C6B2CD500358B64 /* App.swift in Sources */, 7845A15A1E0BCD9A00B527BB /* KeyboardNotificationValues.swift in Sources */, 78B036491DA562C30021D5CC /* CGImage+Template.swift in Sources */, 789F5A1E1DFECF5F004A3694 /* NotificationCenter+RxKeyboardExtensions.swift in Sources */, + 7873D14F1E1127BC001816EB /* LeadKitError.swift in Sources */, 78753E301DE594B4006BC0FB /* MapCursor.swift in Sources */, 780D23461DA416F80084620D /* CGContext+Initializers.swift in Sources */, 95B39A861D9D51250057BD54 /* String+Localization.swift in Sources */, @@ -705,6 +752,7 @@ 78CFEE5B1C5C45E500F50370 /* ViewModelProtocol.swift in Sources */, 789F5A121DFECD11004A3694 /* KeyboardDidNotificationValues.swift in Sources */, EF5FB5691E0141610030E4BE /* UIView+Rotation.swift in Sources */, + 780F56CC1E0D7ACA004530B6 /* ObservableMappable.swift in Sources */, 780D23431DA412470084620D /* CGImage+Alpha.swift in Sources */, 78CFEE5A1C5C45E500F50370 /* ViewHeightProtocol.swift in Sources */, 787682FA1CAD40C300532AB3 /* StaticEstimatedViewHeightProtocol.swift in Sources */, diff --git a/LeadKit/LeadKit/Classes/Operations/ResultOperation.swift b/LeadKit/LeadKit/Classes/Operations/ResultOperation.swift new file mode 100644 index 00000000..2d1798e2 --- /dev/null +++ b/LeadKit/LeadKit/Classes/Operations/ResultOperation.swift @@ -0,0 +1,29 @@ +// +// ResultOperation.swift +// LeadKit +// +// Created by Ivan Smolin on 23/12/16. +// Copyright © 2016 Touch Instinct. All rights reserved. +// + +import Foundation + +/// Subclass of Operation which contains result of executed operation +public class ResultOperation: Operation { + + /// Result of executed operation or nil if operation is not finished yet or throw error + public var result: T? + + public typealias ExecutionClosure = () throws -> T + + private let executionClosure: ExecutionClosure + + public init(executionClosure: @escaping ExecutionClosure) { + self.executionClosure = executionClosure + } + + override public func main() { + result = try? executionClosure() + } + +} diff --git a/LeadKit/LeadKit/Enums/LeadKitError.swift b/LeadKit/LeadKit/Enums/LeadKitError.swift new file mode 100644 index 00000000..11feaa26 --- /dev/null +++ b/LeadKit/LeadKit/Enums/LeadKitError.swift @@ -0,0 +1,18 @@ +// +// LeadKitError.swift +// LeadKit +// +// Created by Ivan Smolin on 26/12/16. +// Copyright © 2016 Touch Instinct. All rights reserved. +// + +import Foundation + +/// Enum which represents common errors in LeadKit framework +/// +/// - failedToCastValue: attampt to cast was failed +public enum LeadKitError: Error { + + case failedToCastValue(expectedType: Any.Type, givenType: Any.Type) + +} diff --git a/LeadKit/LeadKit/Extensions/Alamofire/AlamofireManager+Extensions.swift b/LeadKit/LeadKit/Extensions/Alamofire/AlamofireManager+Extensions.swift index a638706d..95b04a64 100644 --- a/LeadKit/LeadKit/Extensions/Alamofire/AlamofireManager+Extensions.swift +++ b/LeadKit/LeadKit/Extensions/Alamofire/AlamofireManager+Extensions.swift @@ -11,15 +11,12 @@ import RxSwift import RxAlamofire import ObjectMapper -public extension Alamofire.SessionManager { +public extension Reactive where Base: Alamofire.SessionManager { - /** - method which executes request with given api parameters - - - parameter requestParameters: api parameters to pass Alamofire - - - returns: Observable with request - */ + /// Method which executes request with given api parameters + /// + /// - Parameter requestParameters: api parameters to pass Alamofire + /// - Returns: Observable with request func apiRequest(requestParameters: ApiRequestParameters) -> Observable { return RxAlamofire.request(requestParameters.method, requestParameters.url, @@ -28,15 +25,26 @@ public extension Alamofire.SessionManager { headers: requestParameters.headers) } - /** - method which executes request and serialize response into target object - - - parameter requestParameters: api parameters to pass Alamofire - - - returns: Observable with HTTP URL Response and target object - */ + /// Method which executes request and serialize response into target object + /// + /// - Parameter requestParameters: api parameters to pass Alamofire + /// - Returns: Observable with HTTP URL Response and target object func responseModel(requestParameters: ApiRequestParameters) -> Observable<(HTTPURLResponse, T)> { - return apiRequest(requestParameters: requestParameters).flatMap { $0.rx.apiResponse() } + return apiRequest(requestParameters: requestParameters) + .observeOn(ConcurrentDispatchQueueScheduler(qos: .userInitiated)) + .flatMap { $0.rx.apiResponse() } + } + + /// Method which executes request and serialize response into target object + /// + /// - Parameter requestParameters: api parameters to pass Alamofire + /// - Returns: Observable with HTTP URL Response and target object + func responseObservableModel(requestParameters: ApiRequestParameters) -> + Observable<(HTTPURLResponse, T)> where T.ModelType == T { + + return apiRequest(requestParameters: requestParameters) + .observeOn(ConcurrentDispatchQueueScheduler(qos: .userInitiated)) + .flatMap { $0.rx.apiResponse() } } } diff --git a/LeadKit/LeadKit/Extensions/Alamofire/AlamofireRequest+Extensions.swift b/LeadKit/LeadKit/Extensions/Alamofire/AlamofireRequest+Extensions.swift index b263b5b5..d43f9a0b 100644 --- a/LeadKit/LeadKit/Extensions/Alamofire/AlamofireRequest+Extensions.swift +++ b/LeadKit/LeadKit/Extensions/Alamofire/AlamofireRequest+Extensions.swift @@ -11,28 +11,28 @@ import RxSwift import ObjectMapper import RxAlamofire -public enum ApiResponseMappingError: Error { - - case incorrectValueType(message: String) - -} - public extension Reactive where Base: DataRequest { - /** - method which serialize response into target object - - - returns: Observable with HTTP URL Response and target object - */ + /// Method which serialize response into target object + /// + /// - Returns: Observable with HTTP URL Response and target object func apiResponse() -> Observable<(HTTPURLResponse, T)> { return responseJSON().map { resp, value in - if let json = value as? [String: Any] { - return (resp, try T(JSON: json)) - } else { - let failureReason = "Value has incorrect type: \(type(of: value)), expected: [String: Any]" + let json = try cast(value) as [String: Any] - throw ApiResponseMappingError.incorrectValueType(message: failureReason) - } + return (resp, try T(JSON: json)) + } + } + + /// Method which serialize response into target object + /// + /// - Returns: Observable with HTTP URL Response and target object + func apiResponse() -> Observable<(HTTPURLResponse, T)> where T.ModelType == T { + return responseJSON().flatMap { resp, value -> Observable<(HTTPURLResponse, T)> in + let json = try cast(value) as [String: Any] + + return try T.createFrom(map: Map(mappingType: .fromJSON, JSON: json)) + .map { (resp, $0) } } } diff --git a/LeadKit/LeadKit/Extensions/ObjectMapper/ImmutableMappable+ObservableMappable.swift b/LeadKit/LeadKit/Extensions/ObjectMapper/ImmutableMappable+ObservableMappable.swift new file mode 100644 index 00000000..a4021f90 --- /dev/null +++ b/LeadKit/LeadKit/Extensions/ObjectMapper/ImmutableMappable+ObservableMappable.swift @@ -0,0 +1,23 @@ +// +// ImmutableMappable+ObservableMappable.swift +// LeadKit +// +// Created by Ivan Smolin on 26/12/16. +// Copyright © 2016 Touch Instinct. All rights reserved. +// + +import ObjectMapper +import RxSwift + +public extension ObservableMappable where Self: ImmutableMappable, ModelType == Self { + + /// Default implementation of ObservableMappable protocol for ImmutableMappable protocol + /// + /// - Parameter map: ObjectMapper.Map object + /// - Returns: Observable with value of ModelType(map: ObjectMapper.Map) + /// - Throws: error of ModelType(map: ObjectMapper.Map) + static func createFrom(map: Map) throws -> Observable { + return Observable.just(try ModelType(map: map)) + } + +} diff --git a/LeadKit/LeadKit/Extensions/Sequence/Sequence+ConcurrentMap.swift b/LeadKit/LeadKit/Extensions/Sequence/Sequence+ConcurrentMap.swift new file mode 100644 index 00000000..0b388517 --- /dev/null +++ b/LeadKit/LeadKit/Extensions/Sequence/Sequence+ConcurrentMap.swift @@ -0,0 +1,85 @@ +// +// Sequence+ConcurrentMap.swift +// LeadKit +// +// Created by Ivan Smolin on 23/12/16. +// Copyright © 2016 Touch Instinct. All rights reserved. +// + +import RxSwift + +public typealias CancelClosureType = () -> () + +public extension Sequence { + + func concurrentMap(transform: @escaping ((Iterator.Element) throws -> R), + concurrentOperationCount: Int = ProcessInfo.processInfo.activeProcessorCount, + completion: @escaping (([R]) -> ())) -> CancelClosureType { + + let operationsCount = Swift.max(1, concurrentOperationCount) + + let operationQueue = OperationQueue() + operationQueue.maxConcurrentOperationCount = operationsCount + + let array = Array(self) + + let step = Int(ceil(Double(array.count) / Double(operationsCount))) + let numberOfSlices = Int(ceil(Double(array.count) / Double(step))) + + DispatchQueue.global().async { + let operations: [ResultOperation<[R]>] = (0..(transform: @escaping ((Iterator.Element) throws -> R), + concurrentOperationCount: Int = ProcessInfo.processInfo.activeProcessorCount) -> Observable<[R]> { + + return Observable<[R]>.create { observer in + let disposeHandler = self.concurrentMap(transform: transform, + concurrentOperationCount: concurrentOperationCount) { + observer.onNext($0) + observer.onCompleted() + } + + return Disposables.create(with: disposeHandler) + } + } + + func concurrentRxMap(transform: @escaping ((Iterator.Element) throws -> R)) -> Observable<[R]> { + + return Observable<[R]>.create { observer in + let disposeHandler = self.concurrentMap(transform: transform) { + observer.onNext($0) + observer.onCompleted() + } + + return Disposables.create(with: disposeHandler) + } + } + +} diff --git a/LeadKit/LeadKit/Extensions/UserDefaults/UserDefaults+MappableDataTypes.swift b/LeadKit/LeadKit/Extensions/UserDefaults/UserDefaults+MappableDataTypes.swift index 8714cde7..53ce00f7 100644 --- a/LeadKit/LeadKit/Extensions/UserDefaults/UserDefaults+MappableDataTypes.swift +++ b/LeadKit/LeadKit/Extensions/UserDefaults/UserDefaults+MappableDataTypes.swift @@ -14,12 +14,10 @@ import RxSwift /// model or array of specified type from UserDefaults. /// /// - noSuchValue: there is no such value for given key -/// - wrongStoredValueType: the stored value type is unsuitable for performing mapping with it /// - unableToMap: the value cannot be mapped to given type for some reason public enum UserDefaultsError: Error { case noSuchValue(key: String) - case wrongStoredValueType(expected: Any.Type, received: Any.Type) case unableToMap(mappingError: Error) } @@ -33,11 +31,7 @@ public extension UserDefaults { throw UserDefaultsError.noSuchValue(key: key) } - guard let storedValue = objectForKey as? ST else { - throw UserDefaultsError.wrongStoredValueType(expected: ST.self, received: type(of: objectForKey)) - } - - return storedValue + return try cast(objectForKey) as ST } /// Returns the object with specified type associated with the first occurrence of the specified default. diff --git a/LeadKit/LeadKit/Functions/Any+Cast.swift b/LeadKit/LeadKit/Functions/Any+Cast.swift new file mode 100644 index 00000000..71316d9f --- /dev/null +++ b/LeadKit/LeadKit/Functions/Any+Cast.swift @@ -0,0 +1,22 @@ +// +// Any+Cast.swift +// LeadKit +// +// Created by Ivan Smolin on 26/12/16. +// Copyright © 2016 Touch Instinct. All rights reserved. +// + +import Foundation + +/// Function which attempts to cast given value to specific type +/// +/// - Parameter value: value to cast +/// - Returns: value casted to specific type +/// - Throws: LeadKitError.failedToCastValue cast fails +public func cast(_ value: Any?) throws -> T { + guard let val = value as? T else { + throw LeadKitError.failedToCastValue(expectedType: T.self, givenType: type(of: value)) + } + + return val +} diff --git a/LeadKit/LeadKit/Functions/Any+TypeName.swift b/LeadKit/LeadKit/Functions/Any+TypeName.swift index cb726fff..04c22300 100644 --- a/LeadKit/LeadKit/Functions/Any+TypeName.swift +++ b/LeadKit/LeadKit/Functions/Any+TypeName.swift @@ -8,6 +8,10 @@ import Foundation +/// Function which return string representation of type without ".Type" suffix +/// +/// - Parameter type: a type +/// - Returns: string representation of type without ".Type" suffix public func className(of type: T) -> String { let clsName = String(describing: type(of: type)) diff --git a/LeadKit/LeadKit/Protocols/ObservableMappable.swift b/LeadKit/LeadKit/Protocols/ObservableMappable.swift new file mode 100644 index 00000000..f149e1f0 --- /dev/null +++ b/LeadKit/LeadKit/Protocols/ObservableMappable.swift @@ -0,0 +1,19 @@ +// +// ObservableMappable.swift +// LeadKit +// +// Created by Ivan Smolin on 23/12/16. +// Copyright © 2016 Touch Instinct. All rights reserved. +// + +import ObjectMapper +import RxSwift + +/// Protocol for concurrent model mapping +public protocol ObservableMappable { + + associatedtype ModelType + + static func createFrom(map: Map) throws -> Observable + +}