Merge pull request #32 from TouchInstinct/feature/cursors

rx cursors implementation
This commit is contained in:
Ivan Smolin 2016-11-24 18:33:32 +04:00 committed by GitHub
commit 9e4e9f0cf8
7 changed files with 300 additions and 0 deletions

View File

@ -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 */,

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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