Merge pull request #32 from TouchInstinct/feature/cursors
rx cursors implementation
This commit is contained in:
commit
9e4e9f0cf8
|
|
@ -21,11 +21,17 @@
|
|||
7837F60F1CBCF5C0000D74C1 /* EstimatedViewHeightProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7837F60E1CBCF5C0000D74C1 /* EstimatedViewHeightProtocol.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 */; };
|
||||
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 */; };
|
||||
78753E301DE594B4006BC0FB /* MapCursor.swift in Sources */ = {isa = PBXBuildFile; fileRef = 78753E2F1DE594B4006BC0FB /* MapCursor.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 */; };
|
||||
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 */; };
|
||||
789CC60B1DE584F800F789D3 /* CursorType+Slice.swift in Sources */ = {isa = PBXBuildFile; fileRef = 789CC60A1DE584F800F789D3 /* CursorType+Slice.swift */; };
|
||||
78A0FCC71DC366A10070B5E1 /* StoryboardProtocol+DefaultBundle.swift in Sources */ = {isa = PBXBuildFile; fileRef = 78A0FCC51DC366A10070B5E1 /* StoryboardProtocol+DefaultBundle.swift */; };
|
||||
78A0FCC81DC366A10070B5E1 /* StoryboardProtocol+Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 78A0FCC61DC366A10070B5E1 /* StoryboardProtocol+Extensions.swift */; };
|
||||
78A74EA91C6B373700FE9724 /* UIView+DefaultNibName.swift in Sources */ = {isa = PBXBuildFile; fileRef = 78A74EA81C6B373700FE9724 /* UIView+DefaultNibName.swift */; };
|
||||
|
|
@ -83,11 +89,17 @@
|
|||
7837F60E1CBCF5C0000D74C1 /* EstimatedViewHeightProtocol.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = EstimatedViewHeightProtocol.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>"; };
|
||||
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>"; };
|
||||
78753E2F1DE594B4006BC0FB /* MapCursor.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MapCursor.swift; sourceTree = "<group>"; };
|
||||
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>"; };
|
||||
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>"; };
|
||||
789CC60A1DE584F800F789D3 /* CursorType+Slice.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "CursorType+Slice.swift"; sourceTree = "<group>"; };
|
||||
78A0FCC51DC366A10070B5E1 /* StoryboardProtocol+DefaultBundle.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "StoryboardProtocol+DefaultBundle.swift"; sourceTree = "<group>"; };
|
||||
78A0FCC61DC366A10070B5E1 /* StoryboardProtocol+Extensions.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "StoryboardProtocol+Extensions.swift"; sourceTree = "<group>"; };
|
||||
78A74EA81C6B373700FE9724 /* UIView+DefaultNibName.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = "UIView+DefaultNibName.swift"; path = "LeadKit/Extensions/UIView/UIView+DefaultNibName.swift"; sourceTree = SOURCE_ROOT; };
|
||||
|
|
@ -164,6 +176,7 @@
|
|||
78011A651D47AF3000EA16A2 /* Enums */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
78753E231DE58A5D006BC0FB /* CursorError.swift */,
|
||||
);
|
||||
path = Enums;
|
||||
sourceTree = "<group>";
|
||||
|
|
@ -214,6 +227,16 @@
|
|||
path = Alamofire;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
78753E2A1DE58BED006BC0FB /* Cursors */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
78753E2B1DE58BF9006BC0FB /* StaticCursor.swift */,
|
||||
78753E2D1DE58DBA006BC0FB /* FixedPageCursor.swift */,
|
||||
78753E2F1DE594B4006BC0FB /* MapCursor.swift */,
|
||||
);
|
||||
path = Cursors;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
787783611CA03C84001CDC9B /* IndexPath */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
|
|
@ -239,6 +262,14 @@
|
|||
path = UserDefaults;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
789CC6091DE584C000F789D3 /* CursorType */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
789CC60A1DE584F800F789D3 /* CursorType+Slice.swift */,
|
||||
);
|
||||
path = CursorType;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
78A0FCC41DC366A10070B5E1 /* StoryboardProtocol */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
|
|
@ -252,6 +283,7 @@
|
|||
isa = PBXGroup;
|
||||
children = (
|
||||
78B0FC7B1C6B2BAE00358B64 /* Logging */,
|
||||
78753E2A1DE58BED006BC0FB /* Cursors */,
|
||||
);
|
||||
path = Classes;
|
||||
sourceTree = "<group>";
|
||||
|
|
@ -343,6 +375,7 @@
|
|||
78A0FCC41DC366A10070B5E1 /* StoryboardProtocol */,
|
||||
786D78E61D53C355006B2CEA /* Alamofire */,
|
||||
7884DB9A1DC1432B00E52A63 /* UserDefaults */,
|
||||
789CC6091DE584C000F789D3 /* CursorType */,
|
||||
);
|
||||
path = Extensions;
|
||||
sourceTree = "<group>";
|
||||
|
|
@ -360,6 +393,7 @@
|
|||
787682F91CAD40C200532AB3 /* StaticEstimatedViewHeightProtocol.swift */,
|
||||
7837F60E1CBCF5C0000D74C1 /* EstimatedViewHeightProtocol.swift */,
|
||||
783423691DB8D0E100A79643 /* StoryboardProtocol.swift */,
|
||||
789CC6071DE5835600F789D3 /* CursorType.swift */,
|
||||
);
|
||||
path = Protocols;
|
||||
sourceTree = "<group>";
|
||||
|
|
@ -592,6 +626,9 @@
|
|||
7834236A1DB8D0E100A79643 /* StoryboardProtocol.swift in Sources */,
|
||||
78CFEE521C5C45E500F50370 /* UITableView+CellRegistration.swift in Sources */,
|
||||
78B0FC7F1C6B2C4D00358B64 /* Log.swift in Sources */,
|
||||
78753E2E1DE58DBA006BC0FB /* FixedPageCursor.swift in Sources */,
|
||||
789CC60B1DE584F800F789D3 /* CursorType+Slice.swift in Sources */,
|
||||
78753E2C1DE58BF9006BC0FB /* StaticCursor.swift in Sources */,
|
||||
78D4B54A1DA64EAB005B0764 /* Any+TypeName.swift in Sources */,
|
||||
78CFEE571C5C45E500F50370 /* StaticNibNameProtocol.swift in Sources */,
|
||||
788EC15A1CF64528009CFB6B /* UIStoryboard+InstantiateViewController.swift in Sources */,
|
||||
|
|
@ -601,6 +638,7 @@
|
|||
786D78EC1D53C46E006B2CEA /* AlamofireManager+Extensions.swift in Sources */,
|
||||
78B0FC811C6B2CD500358B64 /* App.swift in Sources */,
|
||||
78B036491DA562C30021D5CC /* CGImage+Template.swift in Sources */,
|
||||
78753E301DE594B4006BC0FB /* MapCursor.swift in Sources */,
|
||||
780D23461DA416F80084620D /* CGContext+Initializers.swift in Sources */,
|
||||
95B39A861D9D51250057BD54 /* String+Localization.swift in Sources */,
|
||||
78C36F7E1D801E3E00E7EBEA /* Double+Rounding.swift in Sources */,
|
||||
|
|
@ -609,6 +647,7 @@
|
|||
78A0FCC81DC366A10070B5E1 /* StoryboardProtocol+Extensions.swift in Sources */,
|
||||
78B036411DA4D7060021D5CC /* UIImage+Extensions.swift in Sources */,
|
||||
78A0FCC71DC366A10070B5E1 /* StoryboardProtocol+DefaultBundle.swift in Sources */,
|
||||
78753E241DE58A5D006BC0FB /* CursorError.swift in Sources */,
|
||||
786D78E81D53C378006B2CEA /* AlamofireRequest+Extensions.swift in Sources */,
|
||||
78C36F811D8021DD00E7EBEA /* UIColor+Hex.swift in Sources */,
|
||||
78CFEE5B1C5C45E500F50370 /* ViewModelProtocol.swift in Sources */,
|
||||
|
|
@ -621,6 +660,7 @@
|
|||
78CFEE581C5C45E500F50370 /* StaticViewHeightProtocol.swift in Sources */,
|
||||
787783631CA03CA0001CDC9B /* IndexPath+ImmutableIndexPath.swift in Sources */,
|
||||
78B036471DA5624D0021D5CC /* CGImage+Creation.swift in Sources */,
|
||||
789CC6081DE5835600F789D3 /* CursorType.swift in Sources */,
|
||||
78B0364B1DA61EDE0021D5CC /* CGImage+Crop.swift in Sources */,
|
||||
78B036451DA561D00021D5CC /* CGImage+Utils.swift in Sources */,
|
||||
78CFEE591C5C45E500F50370 /* StoryboardIdentifierProtocol.swift in Sources */,
|
||||
|
|
|
|||
|
|
@ -0,0 +1,60 @@
|
|||
//
|
||||
// FixedPageCursor.swift
|
||||
// LeadKit
|
||||
//
|
||||
// Created by Ivan Smolin on 23/11/16.
|
||||
// Copyright © 2016 Touch Instinct. All rights reserved.
|
||||
//
|
||||
|
||||
import RxSwift
|
||||
|
||||
/// Paging cursor implementation with enclosed cursor for fetching results
|
||||
public class FixedPageCursor<Cursor: CursorType>: CursorType where Cursor.LoadResultType == CountableRange<Int> {
|
||||
|
||||
public typealias LoadResultType = CountableRange<Int>
|
||||
|
||||
private let cursor: Cursor
|
||||
|
||||
private let pageSize: Int
|
||||
|
||||
/// Initializer with enclosed cursor
|
||||
///
|
||||
/// - Parameters:
|
||||
/// - cursor: enclosed cursor
|
||||
/// - pageSize: number of items loaded at once
|
||||
public init(cursor: Cursor, pageSize: Int) {
|
||||
self.cursor = cursor
|
||||
self.pageSize = pageSize
|
||||
}
|
||||
|
||||
public var exhausted: Bool {
|
||||
return cursor.exhausted && cursor.count == count
|
||||
}
|
||||
|
||||
public private(set) var count: Int = 0
|
||||
|
||||
public subscript(index: Int) -> Cursor.Element {
|
||||
return cursor[index]
|
||||
}
|
||||
|
||||
public func loadNextBatch() -> Observable<LoadResultType> {
|
||||
return Observable.deferred {
|
||||
if self.exhausted {
|
||||
throw CursorError.exhausted
|
||||
}
|
||||
|
||||
let restOfLoaded = self.cursor.count - self.count
|
||||
|
||||
if restOfLoaded >= self.pageSize || self.cursor.exhausted {
|
||||
let startIndex = self.count
|
||||
self.count += min(restOfLoaded, self.pageSize)
|
||||
|
||||
return Observable.just(startIndex..<self.count)
|
||||
}
|
||||
|
||||
return self.cursor.loadNextBatch()
|
||||
.flatMap { _ in self.loadNextBatch() }
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,69 @@
|
|||
//
|
||||
// MapCursor.swift
|
||||
// LeadKit
|
||||
//
|
||||
// Created by Ivan Smolin on 23/11/16.
|
||||
// Copyright © 2016 Touch Instinct. All rights reserved.
|
||||
//
|
||||
|
||||
import RxSwift
|
||||
|
||||
public typealias MapCursorLoadResultType = CountableRange<Int>
|
||||
|
||||
public extension CursorType where Self.LoadResultType == MapCursorLoadResultType {
|
||||
|
||||
/// Creates MapCursor with current cursor
|
||||
///
|
||||
/// - Parameter transform: closure to transform elements
|
||||
/// - Returns: new MapCursorInstance
|
||||
func flatMap<T>(transform: @escaping MapCursor<Self, T>.Transform) -> MapCursor<Self, T> {
|
||||
return MapCursor(cursor: self, transform: transform)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/// Map cursor implementation with enclosed cursor for fetching results
|
||||
public class MapCursor<Cursor: CursorType, T>: CursorType where Cursor.LoadResultType == MapCursorLoadResultType {
|
||||
|
||||
public typealias LoadResultType = Cursor.LoadResultType
|
||||
|
||||
public typealias Transform = (Cursor.Element) -> T?
|
||||
|
||||
private let cursor: Cursor
|
||||
|
||||
private let transform: Transform
|
||||
|
||||
private var elements: [T] = []
|
||||
|
||||
/// Initializer with enclosed cursor
|
||||
///
|
||||
/// - Parameters:
|
||||
/// - cursor: enclosed cursor
|
||||
/// - transform: closure to transform elements
|
||||
public init(cursor: Cursor, transform: @escaping Transform) {
|
||||
self.cursor = cursor
|
||||
self.transform = transform
|
||||
}
|
||||
|
||||
public var exhausted: Bool {
|
||||
return cursor.exhausted
|
||||
}
|
||||
|
||||
public var count: Int {
|
||||
return elements.count
|
||||
}
|
||||
|
||||
public subscript(index: Int) -> T {
|
||||
return elements[index]
|
||||
}
|
||||
|
||||
public func loadNextBatch() -> Observable<LoadResultType> {
|
||||
return cursor.loadNextBatch().map { loadedRange in
|
||||
let startIndex = self.elements.count
|
||||
self.elements += self.cursor[loadedRange].flatMap(self.transform)
|
||||
|
||||
return startIndex..<self.elements.count
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,47 @@
|
|||
//
|
||||
// StaticCursor.swift
|
||||
// LeadKit
|
||||
//
|
||||
// Created by Ivan Smolin on 23/11/16.
|
||||
// Copyright © 2016 Touch Instinct. All rights reserved.
|
||||
//
|
||||
|
||||
import RxSwift
|
||||
|
||||
/// Stub cursor implementation for array content type
|
||||
public class StaticCursor<Element>: CursorType {
|
||||
|
||||
public typealias LoadResultType = CountableRange<Int>
|
||||
|
||||
private let content: [Element]
|
||||
|
||||
/// Initializer for array content type
|
||||
///
|
||||
/// - Parameter content: array with elements of Elemet type
|
||||
public init(content: [Element]) {
|
||||
self.content = content
|
||||
}
|
||||
|
||||
public private(set) var exhausted = false
|
||||
|
||||
public private(set) var count = 0
|
||||
|
||||
public subscript(index: Int) -> Element {
|
||||
return content[index]
|
||||
}
|
||||
|
||||
public func loadNextBatch() -> Observable<LoadResultType> {
|
||||
return Observable.deferred {
|
||||
if self.exhausted {
|
||||
throw CursorError.exhausted
|
||||
}
|
||||
|
||||
self.count = self.content.count
|
||||
|
||||
self.exhausted = true
|
||||
|
||||
return Observable.just(0..<self.count)
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,20 @@
|
|||
//
|
||||
// CursorError.swift
|
||||
// LeadKit
|
||||
//
|
||||
// Created by Ivan Smolin on 23/11/16.
|
||||
// Copyright © 2016 Touch Instinct. All rights reserved.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
|
||||
/// A type representing an possible errors that can be thrown during working with cursor object
|
||||
///
|
||||
/// - busy: cursor is currently processing another request
|
||||
/// - exhausted: cursor did load all available results
|
||||
public enum CursorError: Error {
|
||||
|
||||
case busy
|
||||
case exhausted
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,33 @@
|
|||
//
|
||||
// CursorType+Slice.swift
|
||||
// LeadKit
|
||||
//
|
||||
// Created by Ivan Smolin on 23/11/16.
|
||||
// Copyright © 2016 Touch Instinct. All rights reserved.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
|
||||
public extension CursorType where LoadResultType == CountableRange<Int> {
|
||||
|
||||
subscript(range: LoadResultType) -> [Self.Element] {
|
||||
return range.map { self[$0] }
|
||||
}
|
||||
|
||||
var loadedElements: [Self.Element] {
|
||||
return self[0..<count]
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public extension CursorType where LoadResultType == CountableClosedRange<Int> {
|
||||
|
||||
subscript(range: LoadResultType) -> [Self.Element] {
|
||||
return range.map { self[$0] }
|
||||
}
|
||||
|
||||
var loadedElements: [Self.Element] {
|
||||
return self[0...count - 1]
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,31 @@
|
|||
//
|
||||
// CursorType.swift
|
||||
// LeadKit
|
||||
//
|
||||
// Created by Ivan Smolin on 23/11/16.
|
||||
// Copyright © 2016 Touch Instinct. All rights reserved.
|
||||
//
|
||||
|
||||
import RxSwift
|
||||
|
||||
/// Protocol which describes Cursor data type
|
||||
public protocol CursorType {
|
||||
|
||||
associatedtype Element
|
||||
|
||||
associatedtype LoadResultType
|
||||
|
||||
/// Indicates that cursor load all available results
|
||||
var exhausted: Bool { get }
|
||||
|
||||
/// Current number of items in cursor
|
||||
var count: Int { get }
|
||||
|
||||
subscript(index: Int) -> Self.Element { get }
|
||||
|
||||
/// Loads next batch of results
|
||||
///
|
||||
/// - Returns: Observable of LoadResultType
|
||||
func loadNextBatch() -> Observable<LoadResultType>
|
||||
|
||||
}
|
||||
Loading…
Reference in New Issue