From c2802a42f6795d91e9f6fed60208da3300dfd173 Mon Sep 17 00:00:00 2001 From: Ivan Smolin Date: Tue, 27 Dec 2016 15:51:00 +0300 Subject: [PATCH 1/5] 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 + +} From fff115b2db96156d00cc1ba102010857fc6d3a49 Mon Sep 17 00:00:00 2001 From: Ivan Smolin Date: Wed, 28 Dec 2016 15:10:47 +0300 Subject: [PATCH 2/5] add docs and refactor a little bit --- .../Sequence/Sequence+ConcurrentMap.swift | 35 ++++++++++--------- 1 file changed, 19 insertions(+), 16 deletions(-) diff --git a/LeadKit/LeadKit/Extensions/Sequence/Sequence+ConcurrentMap.swift b/LeadKit/LeadKit/Extensions/Sequence/Sequence+ConcurrentMap.swift index 0b388517..4c13495b 100644 --- a/LeadKit/LeadKit/Extensions/Sequence/Sequence+ConcurrentMap.swift +++ b/LeadKit/LeadKit/Extensions/Sequence/Sequence+ConcurrentMap.swift @@ -8,13 +8,22 @@ import RxSwift -public typealias CancelClosureType = () -> () +public typealias ConcurrentMapCancelClosure = () -> () public extension Sequence { + /// Method which asynchronous transforms sequence using given transform closure + /// and given number of concurrent operations + /// + /// - Parameters: + /// - transform: Transform closure + /// - concurrentOperationCount: Number of concurrent operations + /// - completion: Completion handler with results of transform + /// - Returns: Closure whitch can be called to cancel asynchronous operation + @discardableResult func concurrentMap(transform: @escaping ((Iterator.Element) throws -> R), concurrentOperationCount: Int = ProcessInfo.processInfo.activeProcessorCount, - completion: @escaping (([R]) -> ())) -> CancelClosureType { + completion: @escaping (([R]) -> ())) -> ConcurrentMapCancelClosure { let operationsCount = Swift.max(1, concurrentOperationCount) @@ -56,8 +65,14 @@ public extension Sequence { public extension Sequence { - func concurrentRxMap(transform: @escaping ((Iterator.Element) throws -> R), - concurrentOperationCount: Int = ProcessInfo.processInfo.activeProcessorCount) -> Observable<[R]> { + /// Reactive version of concurrentMap(transform:concurrentOperationCount:completion:) + /// + /// - Parameters: + /// - concurrentOperationCount: Number of concurrent operations + /// - transform: Transform closure + /// - Returns: Observable of transform return type + func concurrentRxMap(concurrentOperationCount: Int = ProcessInfo.processInfo.activeProcessorCount, + transform: @escaping ((Iterator.Element) throws -> R)) -> Observable<[R]> { return Observable<[R]>.create { observer in let disposeHandler = self.concurrentMap(transform: transform, @@ -69,17 +84,5 @@ public extension Sequence { 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) - } - } } From d705fd184e4cdd603da77919543020f3e040ada1 Mon Sep 17 00:00:00 2001 From: Ivan Smolin Date: Wed, 28 Dec 2016 16:34:36 +0300 Subject: [PATCH 3/5] fix typos --- LeadKit/LeadKit/Enums/LeadKitError.swift | 2 +- .../Extensions/Alamofire/AlamofireManager+Extensions.swift | 4 ++-- .../Extensions/Alamofire/AlamofireRequest+Extensions.swift | 4 ++-- LeadKit/LeadKit/Functions/Any+TypeName.swift | 2 +- LeadKit/LeadKit/Protocols/StaticViewHeightProtocol.swift | 2 +- 5 files changed, 7 insertions(+), 7 deletions(-) diff --git a/LeadKit/LeadKit/Enums/LeadKitError.swift b/LeadKit/LeadKit/Enums/LeadKitError.swift index 11feaa26..83489b69 100644 --- a/LeadKit/LeadKit/Enums/LeadKitError.swift +++ b/LeadKit/LeadKit/Enums/LeadKitError.swift @@ -10,7 +10,7 @@ import Foundation /// Enum which represents common errors in LeadKit framework /// -/// - failedToCastValue: attampt to cast was failed +/// - failedToCastValue: attempt 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 95b04a64..f48d09bb 100644 --- a/LeadKit/LeadKit/Extensions/Alamofire/AlamofireManager+Extensions.swift +++ b/LeadKit/LeadKit/Extensions/Alamofire/AlamofireManager+Extensions.swift @@ -25,7 +25,7 @@ public extension Reactive where Base: Alamofire.SessionManager { headers: requestParameters.headers) } - /// Method which executes request and serialize response into target object + /// Method which executes request and serializes response into target object /// /// - Parameter requestParameters: api parameters to pass Alamofire /// - Returns: Observable with HTTP URL Response and target object @@ -35,7 +35,7 @@ public extension Reactive where Base: Alamofire.SessionManager { .flatMap { $0.rx.apiResponse() } } - /// Method which executes request and serialize response into target object + /// Method which executes request and serializes response into target object /// /// - Parameter requestParameters: api parameters to pass Alamofire /// - Returns: Observable with HTTP URL Response and target object diff --git a/LeadKit/LeadKit/Extensions/Alamofire/AlamofireRequest+Extensions.swift b/LeadKit/LeadKit/Extensions/Alamofire/AlamofireRequest+Extensions.swift index d43f9a0b..4c730ea5 100644 --- a/LeadKit/LeadKit/Extensions/Alamofire/AlamofireRequest+Extensions.swift +++ b/LeadKit/LeadKit/Extensions/Alamofire/AlamofireRequest+Extensions.swift @@ -13,7 +13,7 @@ import RxAlamofire public extension Reactive where Base: DataRequest { - /// Method which serialize response into target object + /// Method which serializes response into target object /// /// - Returns: Observable with HTTP URL Response and target object func apiResponse() -> Observable<(HTTPURLResponse, T)> { @@ -24,7 +24,7 @@ public extension Reactive where Base: DataRequest { } } - /// Method which serialize response into target object + /// Method which serializes response into target object /// /// - Returns: Observable with HTTP URL Response and target object func apiResponse() -> Observable<(HTTPURLResponse, T)> where T.ModelType == T { diff --git a/LeadKit/LeadKit/Functions/Any+TypeName.swift b/LeadKit/LeadKit/Functions/Any+TypeName.swift index 04c22300..01697269 100644 --- a/LeadKit/LeadKit/Functions/Any+TypeName.swift +++ b/LeadKit/LeadKit/Functions/Any+TypeName.swift @@ -8,7 +8,7 @@ import Foundation -/// Function which return string representation of type without ".Type" suffix +/// Function which returns string representation of type without ".Type" suffix /// /// - Parameter type: a type /// - Returns: string representation of type without ".Type" suffix diff --git a/LeadKit/LeadKit/Protocols/StaticViewHeightProtocol.swift b/LeadKit/LeadKit/Protocols/StaticViewHeightProtocol.swift index 0cad5416..83fe1798 100644 --- a/LeadKit/LeadKit/Protocols/StaticViewHeightProtocol.swift +++ b/LeadKit/LeadKit/Protocols/StaticViewHeightProtocol.swift @@ -17,5 +17,5 @@ public protocol StaticViewHeightProtocol { - returns: view height */ - static func viewHeight() -> CGFloat + static var viewHeight: CGFloat { get } } From 3810c27ae6c3e1f4c750290ac672ed56b8cd694e Mon Sep 17 00:00:00 2001 From: Ivan Smolin Date: Wed, 28 Dec 2016 17:57:32 +0300 Subject: [PATCH 4/5] deferred just --- LeadKit/LeadKit.xcodeproj/project.pbxproj | 12 +++++++ .../AlamofireRequest+Extensions.swift | 2 +- ...ImmutableMappable+ObservableMappable.swift | 4 +-- .../Observable/Observable+DeferredJust.swift | 31 +++++++++++++++++++ .../Protocols/ObservableMappable.swift | 2 +- 5 files changed, 47 insertions(+), 4 deletions(-) create mode 100644 LeadKit/LeadKit/Extensions/Observable/Observable+DeferredJust.swift diff --git a/LeadKit/LeadKit.xcodeproj/project.pbxproj b/LeadKit/LeadKit.xcodeproj/project.pbxproj index 7bef1994..4d8342ec 100644 --- a/LeadKit/LeadKit.xcodeproj/project.pbxproj +++ b/LeadKit/LeadKit.xcodeproj/project.pbxproj @@ -31,6 +31,7 @@ 78753E2C1DE58BF9006BC0FB /* StaticCursor.swift in Sources */ = {isa = PBXBuildFile; fileRef = 78753E2B1DE58BF9006BC0FB /* StaticCursor.swift */; }; 78753E2E1DE58DBA006BC0FB /* FixedPageCursor.swift in Sources */ = {isa = PBXBuildFile; fileRef = 78753E2D1DE58DBA006BC0FB /* FixedPageCursor.swift */; }; 78753E301DE594B4006BC0FB /* MapCursor.swift in Sources */ = {isa = PBXBuildFile; fileRef = 78753E2F1DE594B4006BC0FB /* MapCursor.swift */; }; + 787609221E1403830093CE36 /* Observable+DeferredJust.swift in Sources */ = {isa = PBXBuildFile; fileRef = 787609211E1403830093CE36 /* Observable+DeferredJust.swift */; }; 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 */; }; @@ -113,6 +114,7 @@ 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 = ""; }; 78753E2F1DE594B4006BC0FB /* MapCursor.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MapCursor.swift; sourceTree = ""; }; + 787609211E1403830093CE36 /* Observable+DeferredJust.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "Observable+DeferredJust.swift"; sourceTree = ""; }; 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 = ""; }; @@ -285,6 +287,14 @@ path = Cursors; sourceTree = ""; }; + 787609201E1403460093CE36 /* Observable */ = { + isa = PBXGroup; + children = ( + 787609211E1403830093CE36 /* Observable+DeferredJust.swift */, + ); + path = Observable; + sourceTree = ""; + }; 787783611CA03C84001CDC9B /* IndexPath */ = { isa = PBXGroup; children = ( @@ -463,6 +473,7 @@ 789F5A1C1DFECF44004A3694 /* NotificationCenter */, 780F56C81E0D76A5004530B6 /* Sequence */, 787D87481E10E19000D6015C /* ObjectMapper */, + 787609201E1403460093CE36 /* Observable */, ); path = Extensions; sourceTree = ""; @@ -742,6 +753,7 @@ 95B39A861D9D51250057BD54 /* String+Localization.swift in Sources */, 78C36F7E1D801E3E00E7EBEA /* Double+Rounding.swift in Sources */, 78CFEE551C5C45E500F50370 /* NibNameProtocol.swift in Sources */, + 787609221E1403830093CE36 /* Observable+DeferredJust.swift in Sources */, 78CFEE561C5C45E500F50370 /* ReuseIdentifierProtocol.swift in Sources */, 78A0FCC81DC366A10070B5E1 /* StoryboardProtocol+Extensions.swift in Sources */, 78B036411DA4D7060021D5CC /* UIImage+Extensions.swift in Sources */, diff --git a/LeadKit/LeadKit/Extensions/Alamofire/AlamofireRequest+Extensions.swift b/LeadKit/LeadKit/Extensions/Alamofire/AlamofireRequest+Extensions.swift index 4c730ea5..cf33202d 100644 --- a/LeadKit/LeadKit/Extensions/Alamofire/AlamofireRequest+Extensions.swift +++ b/LeadKit/LeadKit/Extensions/Alamofire/AlamofireRequest+Extensions.swift @@ -31,7 +31,7 @@ public extension Reactive where Base: DataRequest { 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)) + return 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 index a4021f90..0fafe3eb 100644 --- a/LeadKit/LeadKit/Extensions/ObjectMapper/ImmutableMappable+ObservableMappable.swift +++ b/LeadKit/LeadKit/Extensions/ObjectMapper/ImmutableMappable+ObservableMappable.swift @@ -16,8 +16,8 @@ public extension ObservableMappable where Self: ImmutableMappable, ModelType == /// - 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)) + static func createFrom(map: Map) -> Observable { + return Observable.deferredJust { try ModelType(map: map) } } } diff --git a/LeadKit/LeadKit/Extensions/Observable/Observable+DeferredJust.swift b/LeadKit/LeadKit/Extensions/Observable/Observable+DeferredJust.swift new file mode 100644 index 00000000..986e7bbe --- /dev/null +++ b/LeadKit/LeadKit/Extensions/Observable/Observable+DeferredJust.swift @@ -0,0 +1,31 @@ +// +// Observable+DeferredJust.swift +// LeadKit +// +// Created by Ivan Smolin on 28/12/16. +// Copyright © 2016 Touch Instinct. All rights reserved. +// + +import RxSwift + +public extension Observable { + + /// Returns an observable sequence that invokes the specified factory function whenever a new observer subscribes. + /// + /// - Parameter elementFactory: Element factory function to invoke for each observer + /// that subscribes to the resulting sequence. + /// - Returns: An observable sequence whose observers trigger an invocation of the given element factory function. + static func deferredJust(_ elementFactory: @escaping () throws -> Element) -> Observable { + return create { (observer) -> Disposable in + do { + observer.onNext(try elementFactory()) + observer.onCompleted() + } catch { + observer.onError(error) + } + + return Disposables.create() + } + } + +} diff --git a/LeadKit/LeadKit/Protocols/ObservableMappable.swift b/LeadKit/LeadKit/Protocols/ObservableMappable.swift index f149e1f0..755032d7 100644 --- a/LeadKit/LeadKit/Protocols/ObservableMappable.swift +++ b/LeadKit/LeadKit/Protocols/ObservableMappable.swift @@ -14,6 +14,6 @@ public protocol ObservableMappable { associatedtype ModelType - static func createFrom(map: Map) throws -> Observable + static func createFrom(map: Map) -> Observable } From c8114d92ec1497eda170c49fbf368fc7bccac125 Mon Sep 17 00:00:00 2001 From: Ivan Smolin Date: Thu, 29 Dec 2016 16:12:23 +0300 Subject: [PATCH 5/5] fully use rx for concurrent array processing --- LeadKit/LeadKit.xcodeproj/project.pbxproj | 12 --- .../Classes/Operations/ResultOperation.swift | 29 -------- .../Sequence/Sequence+ConcurrentMap.swift | 73 +++++-------------- 3 files changed, 18 insertions(+), 96 deletions(-) delete mode 100644 LeadKit/LeadKit/Classes/Operations/ResultOperation.swift diff --git a/LeadKit/LeadKit.xcodeproj/project.pbxproj b/LeadKit/LeadKit.xcodeproj/project.pbxproj index 4d8342ec..7d6ce209 100644 --- a/LeadKit/LeadKit.xcodeproj/project.pbxproj +++ b/LeadKit/LeadKit.xcodeproj/project.pbxproj @@ -11,7 +11,6 @@ 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 */; }; @@ -94,7 +93,6 @@ 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 = ""; }; @@ -252,14 +250,6 @@ path = CGContext; sourceTree = ""; }; - 780F56C51E0D75F7004530B6 /* Operations */ = { - isa = PBXGroup; - children = ( - 780F56C61E0D7608004530B6 /* ResultOperation.swift */, - ); - path = Operations; - sourceTree = ""; - }; 780F56C81E0D76A5004530B6 /* Sequence */ = { isa = PBXGroup; children = ( @@ -376,7 +366,6 @@ children = ( 78B0FC7B1C6B2BAE00358B64 /* Logging */, 78753E2A1DE58BED006BC0FB /* Cursors */, - 780F56C51E0D75F7004530B6 /* Operations */, ); path = Classes; sourceTree = ""; @@ -740,7 +729,6 @@ 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 */, diff --git a/LeadKit/LeadKit/Classes/Operations/ResultOperation.swift b/LeadKit/LeadKit/Classes/Operations/ResultOperation.swift deleted file mode 100644 index 2d1798e2..00000000 --- a/LeadKit/LeadKit/Classes/Operations/ResultOperation.swift +++ /dev/null @@ -1,29 +0,0 @@ -// -// 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/Extensions/Sequence/Sequence+ConcurrentMap.swift b/LeadKit/LeadKit/Extensions/Sequence/Sequence+ConcurrentMap.swift index 4c13495b..f9bc15ac 100644 --- a/LeadKit/LeadKit/Extensions/Sequence/Sequence+ConcurrentMap.swift +++ b/LeadKit/LeadKit/Extensions/Sequence/Sequence+ConcurrentMap.swift @@ -8,81 +8,44 @@ import RxSwift -public typealias ConcurrentMapCancelClosure = () -> () - public extension Sequence { /// Method which asynchronous transforms sequence using given transform closure /// and given number of concurrent operations /// /// - Parameters: - /// - transform: Transform closure /// - concurrentOperationCount: Number of concurrent operations - /// - completion: Completion handler with results of transform - /// - Returns: Closure whitch can be called to cancel asynchronous operation - @discardableResult - func concurrentMap(transform: @escaping ((Iterator.Element) throws -> R), - concurrentOperationCount: Int = ProcessInfo.processInfo.activeProcessorCount, - completion: @escaping (([R]) -> ())) -> ConcurrentMapCancelClosure { + /// - qos: Target global dispatch queue, by quality of service class. + /// - transform: Transform closure + /// - Returns: Observable of array which contains transform return type + func concurrentRxMap(concurrentOperationCount: Int = ProcessInfo.processInfo.activeProcessorCount, + qos: DispatchQoS = .default, + transform: @escaping ((Iterator.Element) throws -> R)) -> Observable<[R]> { 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..)] = (0..(transform:concurrentOperationCount:completion:) - /// - /// - Parameters: - /// - concurrentOperationCount: Number of concurrent operations - /// - transform: Transform closure - /// - Returns: Observable of transform return type - func concurrentRxMap(concurrentOperationCount: Int = ProcessInfo.processInfo.activeProcessorCount, - transform: @escaping ((Iterator.Element) throws -> R)) -> Observable<[R]> { - - return Observable<[R]>.create { observer in - let disposeHandler = self.concurrentMap(transform: transform, - concurrentOperationCount: concurrentOperationCount) { - observer.onNext($0) - observer.onCompleted() + return Observable.from(indexedRanges) + .flatMap { indexedRange -> Observable<(idx: Int, results: [R])> in + return Observable.just(indexedRange) + .observeOn(scheduler) + .map { (idx: $0.idx, results: try array[$0.range].map(transform)) } } - - return Disposables.create(with: disposeHandler) - } + .toArray() + .map { $0.sorted { $0.0.idx < $0.1.idx }.flatMap { $0.results } } } }