implement concurrent mapping

This commit is contained in:
Ivan Smolin 2016-12-27 15:51:00 +03:00
parent 1f9036df7d
commit c2802a42f6
11 changed files with 290 additions and 40 deletions

View File

@ -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 = "<group>"; };
780D23421DA412470084620D /* CGImage+Alpha.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "CGImage+Alpha.swift"; sourceTree = "<group>"; };
780D23451DA416F80084620D /* CGContext+Initializers.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "CGContext+Initializers.swift"; sourceTree = "<group>"; };
780F56C61E0D7608004530B6 /* ResultOperation.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ResultOperation.swift; sourceTree = "<group>"; };
780F56C91E0D76B8004530B6 /* Sequence+ConcurrentMap.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "Sequence+ConcurrentMap.swift"; sourceTree = "<group>"; };
780F56CB1E0D7ACA004530B6 /* ObservableMappable.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ObservableMappable.swift; sourceTree = "<group>"; };
7827C92E1DE4ADB2009DA4E6 /* Alamofire.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Alamofire.framework; path = ../../../Carthage/Build/iOS/Alamofire.framework; sourceTree = "<group>"; };
7827C92F1DE4ADB2009DA4E6 /* CocoaLumberjack.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = CocoaLumberjack.framework; path = ../../../Carthage/Build/iOS/CocoaLumberjack.framework; sourceTree = "<group>"; };
7827C9301DE4ADB2009DA4E6 /* ObjectMapper.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = ObjectMapper.framework; path = ../../../Carthage/Build/iOS/ObjectMapper.framework; sourceTree = "<group>"; };
@ -98,6 +107,8 @@
7845A1591E0BCD9A00B527BB /* KeyboardNotificationValues.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = KeyboardNotificationValues.swift; 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>"; };
7873D1501E112B0D001816EB /* Any+Cast.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "Any+Cast.swift"; sourceTree = "<group>"; };
78753E231DE58A5D006BC0FB /* CursorError.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CursorError.swift; sourceTree = "<group>"; };
78753E2B1DE58BF9006BC0FB /* StaticCursor.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = StaticCursor.swift; sourceTree = "<group>"; };
78753E2D1DE58DBA006BC0FB /* FixedPageCursor.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = FixedPageCursor.swift; sourceTree = "<group>"; };
@ -105,6 +116,7 @@
787682F91CAD40C200532AB3 /* StaticEstimatedViewHeightProtocol.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = StaticEstimatedViewHeightProtocol.swift; sourceTree = "<group>"; };
787783621CA03CA0001CDC9B /* IndexPath+ImmutableIndexPath.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "IndexPath+ImmutableIndexPath.swift"; sourceTree = "<group>"; };
787783661CA04D4A001CDC9B /* String+SizeCalculation.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "String+SizeCalculation.swift"; sourceTree = "<group>"; };
787D87491E10E1A400D6015C /* ImmutableMappable+ObservableMappable.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "ImmutableMappable+ObservableMappable.swift"; sourceTree = "<group>"; };
7884DB9B1DC1439200E52A63 /* UserDefaults+MappableDataTypes.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "UserDefaults+MappableDataTypes.swift"; sourceTree = "<group>"; };
788EC1591CF64528009CFB6B /* UIStoryboard+InstantiateViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "UIStoryboard+InstantiateViewController.swift"; sourceTree = "<group>"; };
789CC6071DE5835600F789D3 /* CursorType.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CursorType.swift; sourceTree = "<group>"; };
@ -195,6 +207,7 @@
78753E231DE58A5D006BC0FB /* CursorError.swift */,
789F5A131DFECD54004A3694 /* KeyboardNotificationValuesError.swift */,
789F5A1F1DFECF9F004A3694 /* KeyboardNotification.swift */,
7873D14E1E1127BC001816EB /* LeadKitError.swift */,
);
path = Enums;
sourceTree = "<group>";
@ -237,6 +250,22 @@
path = CGContext;
sourceTree = "<group>";
};
780F56C51E0D75F7004530B6 /* Operations */ = {
isa = PBXGroup;
children = (
780F56C61E0D7608004530B6 /* ResultOperation.swift */,
);
path = Operations;
sourceTree = "<group>";
};
780F56C81E0D76A5004530B6 /* Sequence */ = {
isa = PBXGroup;
children = (
780F56C91E0D76B8004530B6 /* Sequence+ConcurrentMap.swift */,
);
path = Sequence;
sourceTree = "<group>";
};
786D78E61D53C355006B2CEA /* Alamofire */ = {
isa = PBXGroup;
children = (
@ -273,6 +302,14 @@
path = String;
sourceTree = "<group>";
};
787D87481E10E19000D6015C /* ObjectMapper */ = {
isa = PBXGroup;
children = (
787D87491E10E1A400D6015C /* ImmutableMappable+ObservableMappable.swift */,
);
path = ObjectMapper;
sourceTree = "<group>";
};
7884DB9A1DC1432B00E52A63 /* UserDefaults */ = {
isa = PBXGroup;
children = (
@ -329,6 +366,7 @@
children = (
78B0FC7B1C6B2BAE00358B64 /* Logging */,
78753E2A1DE58BED006BC0FB /* Cursors */,
780F56C51E0D75F7004530B6 /* Operations */,
);
path = Classes;
sourceTree = "<group>";
@ -423,6 +461,8 @@
789CC6091DE584C000F789D3 /* CursorType */,
789F5A0A1DFECB52004A3694 /* UIViewAnimationCurve */,
789F5A1C1DFECF44004A3694 /* NotificationCenter */,
780F56C81E0D76A5004530B6 /* Sequence */,
787D87481E10E19000D6015C /* ObjectMapper */,
);
path = Extensions;
sourceTree = "<group>";
@ -441,6 +481,7 @@
7837F60E1CBCF5C0000D74C1 /* EstimatedViewHeightProtocol.swift */,
783423691DB8D0E100A79643 /* StoryboardProtocol.swift */,
789CC6071DE5835600F789D3 /* CursorType.swift */,
780F56CB1E0D7ACA004530B6 /* ObservableMappable.swift */,
);
path = Protocols;
sourceTree = "<group>";
@ -457,6 +498,7 @@
isa = PBXGroup;
children = (
78D4B5491DA64EAB005B0764 /* Any+TypeName.swift */,
7873D1501E112B0D001816EB /* Any+Cast.swift */,
);
path = Functions;
sourceTree = "<group>";
@ -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 */,

View File

@ -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<T>: 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()
}
}

View File

@ -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)
}

View File

@ -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<DataRequest> {
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<T: ImmutableMappable>(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<T: ObservableMappable>(requestParameters: ApiRequestParameters) ->
Observable<(HTTPURLResponse, T)> where T.ModelType == T {
return apiRequest(requestParameters: requestParameters)
.observeOn(ConcurrentDispatchQueueScheduler(qos: .userInitiated))
.flatMap { $0.rx.apiResponse() }
}
}

View File

@ -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<T: ImmutableMappable>() -> 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<T: ObservableMappable>() -> 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) }
}
}

View File

@ -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<Self> {
return Observable.just(try ModelType(map: map))
}
}

View File

@ -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<R>(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..<numberOfSlices).map {
let start = $0 * step
let end = Swift.min(start + step, array.count)
return ResultOperation { try array[start..<end].map(transform) }
}
operationQueue.addOperations(operations, waitUntilFinished: true)
var results: [R] = [] // var is used for performance optimization
let operationsResults = operations.flatMap { $0.result }
for operationResult in operationsResults {
results += operationResult
}
completion(results)
}
return {
operationQueue.cancelAllOperations()
}
}
}
public extension Sequence {
func concurrentRxMap<R>(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<R>(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)
}
}
}

View File

@ -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.

View File

@ -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<T>(_ value: Any?) throws -> T {
guard let val = value as? T else {
throw LeadKitError.failedToCastValue(expectedType: T.self, givenType: type(of: value))
}
return val
}

View File

@ -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<T>(of type: T) -> String {
let clsName = String(describing: type(of: type))

View File

@ -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<ModelType>
}