Compare commits

...

2 Commits

Author SHA1 Message Date
Anton 39e789c3ae Add image cache to image loader init params 2016-11-28 12:49:04 +03:00
Anton a885479a2c ImageLoader implementation added 2016-11-26 23:33:02 +03:00
23 changed files with 1058 additions and 0 deletions

View File

@ -2,3 +2,4 @@ github "CocoaLumberjack/CocoaLumberjack" ~> 3.0.0
github "ReactiveX/RxSwift" "3.0.1"
github "RxSwiftCommunity/RxAlamofire" "3.0.1"
github "Hearst-DD/ObjectMapper" ~> 2.1
github "Alamofire/AlamofireImage" ~> 3.2

View File

@ -2,4 +2,5 @@ github "Alamofire/Alamofire" "4.2.0"
github "CocoaLumberjack/CocoaLumberjack" "3.0.0"
github "Hearst-DD/ObjectMapper" "2.2.1"
github "ReactiveX/RxSwift" "3.0.1"
github "Alamofire/AlamofireImage" "3.2.0"
github "RxSwiftCommunity/RxAlamofire" "3.0.1"

View File

@ -7,6 +7,26 @@
objects = {
/* Begin PBXBuildFile section */
24F5EFDA1DEA148A00B9ADB6 /* ObservableType+Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 24F5EFD91DEA148A00B9ADB6 /* ObservableType+Extensions.swift */; };
24F5EFE11DEA187D00B9ADB6 /* ImageLoader.swift in Sources */ = {isa = PBXBuildFile; fileRef = 24F5EFE01DEA187D00B9ADB6 /* ImageLoader.swift */; };
24F5EFE41DEA18F300B9ADB6 /* MemoryStore.swift in Sources */ = {isa = PBXBuildFile; fileRef = 24F5EFE31DEA18F300B9ADB6 /* MemoryStore.swift */; };
24F5EFEA1DEA191A00B9ADB6 /* Sync.swift in Sources */ = {isa = PBXBuildFile; fileRef = 24F5EFE91DEA191A00B9ADB6 /* Sync.swift */; };
24F5EFF41DEA199300B9ADB6 /* ImageOperationWrapper.swift in Sources */ = {isa = PBXBuildFile; fileRef = 24F5EFEE1DEA199300B9ADB6 /* ImageOperationWrapper.swift */; };
24F5EFF51DEA199300B9ADB6 /* ImageOperation.swift in Sources */ = {isa = PBXBuildFile; fileRef = 24F5EFEF1DEA199300B9ADB6 /* ImageOperation.swift */; };
24F5EFF61DEA199300B9ADB6 /* ImageOperationPerformer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 24F5EFF01DEA199300B9ADB6 /* ImageOperationPerformer.swift */; };
24F5EFF71DEA199300B9ADB6 /* ImageLoadingOperation.swift in Sources */ = {isa = PBXBuildFile; fileRef = 24F5EFF11DEA199300B9ADB6 /* ImageLoadingOperation.swift */; };
24F5EFF81DEA199300B9ADB6 /* ImageFakeOperation.swift in Sources */ = {isa = PBXBuildFile; fileRef = 24F5EFF21DEA199300B9ADB6 /* ImageFakeOperation.swift */; };
24F5EFF91DEA199300B9ADB6 /* ImageStartOperation.swift in Sources */ = {isa = PBXBuildFile; fileRef = 24F5EFF31DEA199300B9ADB6 /* ImageStartOperation.swift */; };
24F5EFFE1DEA1A1500B9ADB6 /* ImageSourceType.swift in Sources */ = {isa = PBXBuildFile; fileRef = 24F5EFFA1DEA1A1500B9ADB6 /* ImageSourceType.swift */; };
24F5F0011DEA1A1500B9ADB6 /* NetworkImageSource.swift in Sources */ = {isa = PBXBuildFile; fileRef = 24F5EFFD1DEA1A1500B9ADB6 /* NetworkImageSource.swift */; };
24F5F0081DEA1A1F00B9ADB6 /* ImagePluginOperation.swift in Sources */ = {isa = PBXBuildFile; fileRef = 24F5F0021DEA1A1F00B9ADB6 /* ImagePluginOperation.swift */; };
24F5F0091DEA1A1F00B9ADB6 /* ImagePlugin.swift in Sources */ = {isa = PBXBuildFile; fileRef = 24F5F0031DEA1A1F00B9ADB6 /* ImagePlugin.swift */; };
24F5F00A1DEA1A1F00B9ADB6 /* ImageResizingPlugin.swift in Sources */ = {isa = PBXBuildFile; fileRef = 24F5F0041DEA1A1F00B9ADB6 /* ImageResizingPlugin.swift */; };
24F5F00B1DEA1A1F00B9ADB6 /* ImageRoundingPlugin.swift in Sources */ = {isa = PBXBuildFile; fileRef = 24F5F0051DEA1A1F00B9ADB6 /* ImageRoundingPlugin.swift */; };
24F5F00C1DEA1A1F00B9ADB6 /* ImageFilteringPlugin.swift in Sources */ = {isa = PBXBuildFile; fileRef = 24F5F0061DEA1A1F00B9ADB6 /* ImageFilteringPlugin.swift */; };
24F5F00D1DEA1A1F00B9ADB6 /* ImageBluringPlugin.swift in Sources */ = {isa = PBXBuildFile; fileRef = 24F5F0071DEA1A1F00B9ADB6 /* ImageBluringPlugin.swift */; };
24F5F00F1DEA228F00B9ADB6 /* AbstractMethod.swift in Sources */ = {isa = PBXBuildFile; fileRef = 24F5F00E1DEA228F00B9ADB6 /* AbstractMethod.swift */; };
24F5F0121DEA249900B9ADB6 /* UIImageView+ImageLoader.swift in Sources */ = {isa = PBXBuildFile; fileRef = 24F5F0111DEA249900B9ADB6 /* UIImageView+ImageLoader.swift */; };
78011A641D47ABC500EA16A2 /* UIView+DefaultReuseIdentifier.swift in Sources */ = {isa = PBXBuildFile; fileRef = 78011A631D47ABC500EA16A2 /* UIView+DefaultReuseIdentifier.swift */; };
78011AB31D48B53600EA16A2 /* ApiRequestParameters.swift in Sources */ = {isa = PBXBuildFile; fileRef = 78011AB21D48B53600EA16A2 /* ApiRequestParameters.swift */; };
780D23431DA412470084620D /* CGImage+Alpha.swift in Sources */ = {isa = PBXBuildFile; fileRef = 780D23421DA412470084620D /* CGImage+Alpha.swift */; };
@ -75,6 +95,26 @@
/* End PBXContainerItemProxy section */
/* Begin PBXFileReference section */
24F5EFD91DEA148A00B9ADB6 /* ObservableType+Extensions.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "ObservableType+Extensions.swift"; sourceTree = "<group>"; };
24F5EFE01DEA187D00B9ADB6 /* ImageLoader.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ImageLoader.swift; sourceTree = "<group>"; };
24F5EFE31DEA18F300B9ADB6 /* MemoryStore.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MemoryStore.swift; sourceTree = "<group>"; };
24F5EFE91DEA191A00B9ADB6 /* Sync.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Sync.swift; sourceTree = "<group>"; };
24F5EFEE1DEA199300B9ADB6 /* ImageOperationWrapper.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ImageOperationWrapper.swift; sourceTree = "<group>"; };
24F5EFEF1DEA199300B9ADB6 /* ImageOperation.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ImageOperation.swift; sourceTree = "<group>"; };
24F5EFF01DEA199300B9ADB6 /* ImageOperationPerformer.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ImageOperationPerformer.swift; sourceTree = "<group>"; };
24F5EFF11DEA199300B9ADB6 /* ImageLoadingOperation.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ImageLoadingOperation.swift; sourceTree = "<group>"; };
24F5EFF21DEA199300B9ADB6 /* ImageFakeOperation.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ImageFakeOperation.swift; sourceTree = "<group>"; };
24F5EFF31DEA199300B9ADB6 /* ImageStartOperation.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ImageStartOperation.swift; sourceTree = "<group>"; };
24F5EFFA1DEA1A1500B9ADB6 /* ImageSourceType.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ImageSourceType.swift; sourceTree = "<group>"; };
24F5EFFD1DEA1A1500B9ADB6 /* NetworkImageSource.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = NetworkImageSource.swift; sourceTree = "<group>"; };
24F5F0021DEA1A1F00B9ADB6 /* ImagePluginOperation.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ImagePluginOperation.swift; sourceTree = "<group>"; };
24F5F0031DEA1A1F00B9ADB6 /* ImagePlugin.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ImagePlugin.swift; sourceTree = "<group>"; };
24F5F0041DEA1A1F00B9ADB6 /* ImageResizingPlugin.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ImageResizingPlugin.swift; sourceTree = "<group>"; };
24F5F0051DEA1A1F00B9ADB6 /* ImageRoundingPlugin.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ImageRoundingPlugin.swift; sourceTree = "<group>"; };
24F5F0061DEA1A1F00B9ADB6 /* ImageFilteringPlugin.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ImageFilteringPlugin.swift; sourceTree = "<group>"; };
24F5F0071DEA1A1F00B9ADB6 /* ImageBluringPlugin.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ImageBluringPlugin.swift; sourceTree = "<group>"; };
24F5F00E1DEA228F00B9ADB6 /* AbstractMethod.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AbstractMethod.swift; sourceTree = "<group>"; };
24F5F0111DEA249900B9ADB6 /* UIImageView+ImageLoader.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "UIImageView+ImageLoader.swift"; sourceTree = "<group>"; };
78011A631D47ABC500EA16A2 /* UIView+DefaultReuseIdentifier.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "UIView+DefaultReuseIdentifier.swift"; sourceTree = "<group>"; };
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>"; };
@ -173,6 +213,95 @@
path = Frameworks;
sourceTree = "<group>";
};
24F5EFCA1DEA0F3200B9ADB6 /* ImageLoader */ = {
isa = PBXGroup;
children = (
24F5F0101DEA23E200B9ADB6 /* UIImageView */,
24F5EFEB1DEA197400B9ADB6 /* ImageOperation */,
24F5EFEC1DEA197400B9ADB6 /* ImagePlugin */,
24F5EFED1DEA197400B9ADB6 /* ImageSource */,
24F5EFE01DEA187D00B9ADB6 /* ImageLoader.swift */,
);
path = ImageLoader;
sourceTree = "<group>";
};
24F5EFD81DEA144100B9ADB6 /* Rx */ = {
isa = PBXGroup;
children = (
24F5EFD91DEA148A00B9ADB6 /* ObservableType+Extensions.swift */,
);
path = Rx;
sourceTree = "<group>";
};
24F5EFDB1DEA176500B9ADB6 /* Helpers */ = {
isa = PBXGroup;
children = (
24F5EFE21DEA18F300B9ADB6 /* MemoryStore */,
24F5EFE81DEA191A00B9ADB6 /* Sync */,
);
path = Helpers;
sourceTree = "<group>";
};
24F5EFE21DEA18F300B9ADB6 /* MemoryStore */ = {
isa = PBXGroup;
children = (
24F5EFE31DEA18F300B9ADB6 /* MemoryStore.swift */,
);
path = MemoryStore;
sourceTree = "<group>";
};
24F5EFE81DEA191A00B9ADB6 /* Sync */ = {
isa = PBXGroup;
children = (
24F5EFE91DEA191A00B9ADB6 /* Sync.swift */,
);
path = Sync;
sourceTree = "<group>";
};
24F5EFEB1DEA197400B9ADB6 /* ImageOperation */ = {
isa = PBXGroup;
children = (
24F5EFEF1DEA199300B9ADB6 /* ImageOperation.swift */,
24F5EFEE1DEA199300B9ADB6 /* ImageOperationWrapper.swift */,
24F5EFF01DEA199300B9ADB6 /* ImageOperationPerformer.swift */,
24F5EFF11DEA199300B9ADB6 /* ImageLoadingOperation.swift */,
24F5EFF21DEA199300B9ADB6 /* ImageFakeOperation.swift */,
24F5EFF31DEA199300B9ADB6 /* ImageStartOperation.swift */,
);
path = ImageOperation;
sourceTree = "<group>";
};
24F5EFEC1DEA197400B9ADB6 /* ImagePlugin */ = {
isa = PBXGroup;
children = (
24F5F0021DEA1A1F00B9ADB6 /* ImagePluginOperation.swift */,
24F5F0031DEA1A1F00B9ADB6 /* ImagePlugin.swift */,
24F5F0041DEA1A1F00B9ADB6 /* ImageResizingPlugin.swift */,
24F5F0051DEA1A1F00B9ADB6 /* ImageRoundingPlugin.swift */,
24F5F0061DEA1A1F00B9ADB6 /* ImageFilteringPlugin.swift */,
24F5F0071DEA1A1F00B9ADB6 /* ImageBluringPlugin.swift */,
);
path = ImagePlugin;
sourceTree = "<group>";
};
24F5EFED1DEA197400B9ADB6 /* ImageSource */ = {
isa = PBXGroup;
children = (
24F5EFFA1DEA1A1500B9ADB6 /* ImageSourceType.swift */,
24F5EFFD1DEA1A1500B9ADB6 /* NetworkImageSource.swift */,
);
path = ImageSource;
sourceTree = "<group>";
};
24F5F0101DEA23E200B9ADB6 /* UIImageView */ = {
isa = PBXGroup;
children = (
24F5F0111DEA249900B9ADB6 /* UIImageView+ImageLoader.swift */,
);
name = UIImageView;
path = "UIImageView+Loader";
sourceTree = "<group>";
};
78011A651D47AF3000EA16A2 /* Enums */ = {
isa = PBXGroup;
children = (
@ -284,6 +413,7 @@
children = (
78B0FC7B1C6B2BAE00358B64 /* Logging */,
78753E2A1DE58BED006BC0FB /* Cursors */,
24F5EFCA1DEA0F3200B9ADB6 /* ImageLoader */,
);
path = Classes;
sourceTree = "<group>";
@ -342,6 +472,7 @@
78011A651D47AF3000EA16A2 /* Enums */,
78CFEE491C5C45E500F50370 /* Protocols */,
78D4B54B1DA650FC005B0764 /* Functions */,
24F5EFDB1DEA176500B9ADB6 /* Helpers */,
78CFEE2D1C5C456B00F50370 /* LeadKit.h */,
78CFEE2F1C5C456B00F50370 /* Info.plist */,
);
@ -376,6 +507,7 @@
786D78E61D53C355006B2CEA /* Alamofire */,
7884DB9A1DC1432B00E52A63 /* UserDefaults */,
789CC6091DE584C000F789D3 /* CursorType */,
24F5EFD81DEA144100B9ADB6 /* Rx */,
);
path = Extensions;
sourceTree = "<group>";
@ -410,6 +542,7 @@
isa = PBXGroup;
children = (
78D4B5491DA64EAB005B0764 /* Any+TypeName.swift */,
24F5F00E1DEA228F00B9ADB6 /* AbstractMethod.swift */,
);
path = Functions;
sourceTree = "<group>";
@ -624,34 +757,47 @@
78CFEE541C5C45E500F50370 /* UIView+LoadFromNib.swift in Sources */,
78D4B5461DA64D49005B0764 /* UIViewController+DefaultStoryboardIdentifier.swift in Sources */,
7834236A1DB8D0E100A79643 /* StoryboardProtocol.swift in Sources */,
24F5EFE41DEA18F300B9ADB6 /* MemoryStore.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 */,
24F5F00D1DEA1A1F00B9ADB6 /* ImageBluringPlugin.swift in Sources */,
78D4B54A1DA64EAB005B0764 /* Any+TypeName.swift in Sources */,
78CFEE571C5C45E500F50370 /* StaticNibNameProtocol.swift in Sources */,
24F5F00B1DEA1A1F00B9ADB6 /* ImageRoundingPlugin.swift in Sources */,
788EC15A1CF64528009CFB6B /* UIStoryboard+InstantiateViewController.swift in Sources */,
787783671CA04D4A001CDC9B /* String+SizeCalculation.swift in Sources */,
78B036431DA4FEC90021D5CC /* CGImage+Transform.swift in Sources */,
78011A641D47ABC500EA16A2 /* UIView+DefaultReuseIdentifier.swift in Sources */,
786D78EC1D53C46E006B2CEA /* AlamofireManager+Extensions.swift in Sources */,
78B0FC811C6B2CD500358B64 /* App.swift in Sources */,
24F5F00A1DEA1A1F00B9ADB6 /* ImageResizingPlugin.swift in Sources */,
78B036491DA562C30021D5CC /* CGImage+Template.swift in Sources */,
78753E301DE594B4006BC0FB /* MapCursor.swift in Sources */,
24F5EFEA1DEA191A00B9ADB6 /* Sync.swift in Sources */,
780D23461DA416F80084620D /* CGContext+Initializers.swift in Sources */,
24F5F00F1DEA228F00B9ADB6 /* AbstractMethod.swift in Sources */,
95B39A861D9D51250057BD54 /* String+Localization.swift in Sources */,
24F5F0091DEA1A1F00B9ADB6 /* ImagePlugin.swift in Sources */,
24F5EFFE1DEA1A1500B9ADB6 /* ImageSourceType.swift in Sources */,
78C36F7E1D801E3E00E7EBEA /* Double+Rounding.swift in Sources */,
78CFEE551C5C45E500F50370 /* NibNameProtocol.swift in Sources */,
24F5EFDA1DEA148A00B9ADB6 /* ObservableType+Extensions.swift in Sources */,
78CFEE561C5C45E500F50370 /* ReuseIdentifierProtocol.swift in Sources */,
24F5EFF51DEA199300B9ADB6 /* ImageOperation.swift in Sources */,
78A0FCC81DC366A10070B5E1 /* StoryboardProtocol+Extensions.swift in Sources */,
24F5EFF61DEA199300B9ADB6 /* ImageOperationPerformer.swift in Sources */,
78B036411DA4D7060021D5CC /* UIImage+Extensions.swift in Sources */,
24F5EFF81DEA199300B9ADB6 /* ImageFakeOperation.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 */,
780D23431DA412470084620D /* CGImage+Alpha.swift in Sources */,
24F5F0011DEA1A1500B9ADB6 /* NetworkImageSource.swift in Sources */,
78CFEE5A1C5C45E500F50370 /* ViewHeightProtocol.swift in Sources */,
787682FA1CAD40C300532AB3 /* StaticEstimatedViewHeightProtocol.swift in Sources */,
78A74EA91C6B373700FE9724 /* UIView+DefaultNibName.swift in Sources */,
@ -659,13 +805,20 @@
E126CBB31DB68DDA00E1B2F8 /* UICollectionView+CellRegistration.swift in Sources */,
78CFEE581C5C45E500F50370 /* StaticViewHeightProtocol.swift in Sources */,
787783631CA03CA0001CDC9B /* IndexPath+ImmutableIndexPath.swift in Sources */,
24F5F0081DEA1A1F00B9ADB6 /* ImagePluginOperation.swift in Sources */,
24F5EFE11DEA187D00B9ADB6 /* ImageLoader.swift in Sources */,
78B036471DA5624D0021D5CC /* CGImage+Creation.swift in Sources */,
24F5EFF41DEA199300B9ADB6 /* ImageOperationWrapper.swift in Sources */,
789CC6081DE5835600F789D3 /* CursorType.swift in Sources */,
24F5F00C1DEA1A1F00B9ADB6 /* ImageFilteringPlugin.swift in Sources */,
78B0364B1DA61EDE0021D5CC /* CGImage+Crop.swift in Sources */,
78B036451DA561D00021D5CC /* CGImage+Utils.swift in Sources */,
24F5EFF71DEA199300B9ADB6 /* ImageLoadingOperation.swift in Sources */,
78CFEE591C5C45E500F50370 /* StoryboardIdentifierProtocol.swift in Sources */,
78011AB31D48B53600EA16A2 /* ApiRequestParameters.swift in Sources */,
24F5EFF91DEA199300B9ADB6 /* ImageStartOperation.swift in Sources */,
78B0FC7D1C6B2BE200358B64 /* LogFormatter.swift in Sources */,
24F5F0121DEA249900B9ADB6 /* UIImageView+ImageLoader.swift in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
};

View File

@ -0,0 +1,78 @@
//
// ImageLoader.swift
// LeadKit
//
// Created by Anton on 26.11.16.
// Copyright © 2016 Touch Instinct. All rights reserved.
//
import Foundation
import RxSwift
public class ImageLoader<TKey: CustomStringConvertible> {
private let imageSource: ImageSource<TKey>
private let imageCache: MemoryStore<String, UIImage>
private var loadingObservablesCache = [String : Observable<UIImage>]()
private let loadingSyncLock = Sync.Lock()
public func loadImage(key: TKey) -> ImageOperation {
let loadingObservable = Observable.deferred({ () -> Observable<UIImage> in
let imageCacheKey = key.description
return self.beginLoadingImage(key: key, imageCacheKey: imageCacheKey)
})
.subcribeOnBackgroundScheduler()
return ImageLoadingOperation(imageLoader: self, key: key, loadingObservable: loadingObservable)
}
internal func performImageOperation(operation: ImageOperation) -> Observable<UIImage> {
return Observable.deferred({ () -> Observable<UIImage> in
let operationCachingKey = operation._cachingKey
if let cachedImage = self.imageCache.loadData(key: operationCachingKey) {
return Observable.just(cachedImage)
} else {
return operation._imageSource
.observeOnBackgroundScheduler()
.do(onNext: { (image) in
self.imageCache.storeData(key: operationCachingKey, data: image)
})
}
})
.subcribeOnBackgroundScheduler()
}
private func beginLoadingImage(key: TKey, imageCacheKey: String) -> Observable<UIImage> {
return loadingSyncLock.sync {
if let loadingObservable = loadingObservablesCache[imageCacheKey] {
return loadingObservable
}
let loadingObservable = imageSource.loadImage(key)
.observeOnBackgroundScheduler()
.do(onDispose: {
self.removeLoadingObservable(cacheKey: imageCacheKey)
})
.shareReplay(1)
loadingObservablesCache[imageCacheKey] = loadingObservable
return loadingObservable
}
}
private func removeLoadingObservable(cacheKey: String) {
loadingSyncLock.sync {
loadingObservablesCache.removeValue(forKey: cacheKey)
return
}
}
public init<TSource: ImageSourceType>(imageSource: TSource,
imageCache: MemoryStore<String, UIImage> = MemoryStore()) where TSource.KeyType == TKey {
self.imageSource = imageSource.asImageSource()
self.imageCache = imageCache
}
}

View File

@ -0,0 +1,32 @@
//
// ImageFakeOperation.swift
// LeadKit
//
// Created by Anton on 26.11.16.
// Copyright © 2016 Touch Instinct. All rights reserved.
//
import Foundation
import RxSwift
public class ImageFakeOperation: ImageOperationWrapper {
public typealias PerformerObservableHandler = (Observable<UIImage>) -> Observable<UIImage>
private let performerObservableHandler: PerformerObservableHandler
public override func _preparePerformerObservable(performObservable: Observable<UIImage>) -> Observable<UIImage> {
return super._preparePerformerObservable(performObservable: performerObservableHandler(performObservable))
}
public init(originalOperation: ImageOperation, performerObservableHandler: @escaping PerformerObservableHandler) {
self.performerObservableHandler = performerObservableHandler
super.init(originalOperation: originalOperation)
}
}
public extension ImageOperation {
public func performWith(_ perfrormHandler: @escaping ImageFakeOperation.PerformerObservableHandler) -> ImageOperation {
return ImageFakeOperation(originalOperation: self, performerObservableHandler: perfrormHandler)
}
}

View File

@ -0,0 +1,38 @@
//
// ImageLoadingOperation.swift
// LeadKit
//
// Created by Anton on 26.11.16.
// Copyright © 2016 Touch Instinct. All rights reserved.
//
import Foundation
import RxSwift
public class ImageLoadingOperation<TKey: CustomStringConvertible>: ImageOperation, ImageOperationPerformer {
public override var _imageSource: Observable<UIImage> {
return loadingObservable
}
public override var _performer: ImageOperationPerformer {
return self
}
public override var _cachingKey: String {
return key.description
}
public func performImageOperation(operation: ImageOperation) -> Observable<UIImage> {
return imageLoader.performImageOperation(operation: operation)
}
private let loadingObservable: Observable<UIImage>
private let imageLoader: ImageLoader<TKey>
private let key: TKey
public init(imageLoader: ImageLoader<TKey>, key: TKey, loadingObservable: Observable<UIImage>) {
self.imageLoader = imageLoader
self.key = key
self.loadingObservable = loadingObservable
}
}

View File

@ -0,0 +1,35 @@
//
// ImageOperation.swift
// LeadKit
//
// Created by Anton on 26.11.16.
// Copyright © 2016 Touch Instinct. All rights reserved.
//
import Foundation
import RxSwift
public class ImageOperation: ObservableType {
public typealias E = UIImage
open var _imageSource: Observable<UIImage> {
abstractMethod("_imageSource")
}
open var _performer: ImageOperationPerformer {
abstractMethod("_performer")
}
open var _cachingKey: String {
abstractMethod("_cachingKey")
}
open func _preparePerformerObservable(performObservable: Observable<UIImage>) -> Observable<UIImage> {
return performObservable
}
public func subscribe<O: ObserverType>(_ observer: O) -> Disposable where O.E == UIImage {
return _preparePerformerObservable(performObservable: _performer.performImageOperation(operation: self))
.subscribe(observer)
}
}

View File

@ -0,0 +1,14 @@
//
// ImageOperationPerformer.swift
// LeadKit
//
// Created by Anton on 26.11.16.
// Copyright © 2016 Touch Instinct. All rights reserved.
//
import Foundation
import RxSwift
public protocol ImageOperationPerformer {
func performImageOperation(operation: ImageOperation) -> Observable<UIImage>
}

View File

@ -0,0 +1,43 @@
//
// ImageOperationWrapper.swift
// LeadKit
//
// Created by Anton on 26.11.16.
// Copyright © 2016 Touch Instinct. All rights reserved.
//
import Foundation
import RxSwift
public class ImageOperationWrapper: ImageOperation {
open override var _imageSource: Observable<UIImage> {
return _prepareWorkingObservable(workingObservable: originalOperation._imageSource)
}
open override var _performer: ImageOperationPerformer {
return originalOperation._performer
}
open override var _cachingKey: String {
return _prepareCachingKey(cachingKey: originalOperation._cachingKey)
}
open func _prepareWorkingObservable(workingObservable: Observable<UIImage>) -> Observable<UIImage> {
return workingObservable
}
open override func _preparePerformerObservable(performObservable: Observable<UIImage>) -> Observable<UIImage> {
return super._preparePerformerObservable(performObservable:
originalOperation._preparePerformerObservable(performObservable: performObservable))
}
open func _prepareCachingKey(cachingKey: String) -> String {
return cachingKey
}
private let originalOperation: ImageOperation
public init(originalOperation: ImageOperation) {
self.originalOperation = originalOperation
}
}

View File

@ -0,0 +1,54 @@
//
// ImageStartOperation.swift
// LeadKit
//
// Created by Anton on 26.11.16.
// Copyright © 2016 Touch Instinct. All rights reserved.
//
import Foundation
import RxSwift
public class ImageStartOperation: ImageOperation, ImageOperationPerformer {
public override var _imageSource: Observable<UIImage> {
return source
}
public override var _performer: ImageOperationPerformer {
return self
}
public override var _cachingKey: String {
return key
}
public func performImageOperation(operation: ImageOperation) -> Observable<UIImage> {
return operation._imageSource
}
private let source: Observable<UIImage>
private let key: String
public init(source: Observable<UIImage>, key: String? = nil) {
self.key = key ?? String()
self.source = source
}
}
public extension UIImage {
public func asImageOperation(key: String? = nil) -> ImageOperation {
return Observable.just(self).asImageOperation()
}
}
public extension ImageOperation {
public func asImageOperation(key: String? = nil) -> ImageOperation {
return self
}
}
public extension ObservableConvertibleType where E == UIImage {
public func asImageOperation(key: String? = nil) -> ImageOperation {
return ImageStartOperation(source: self.asObservable(), key: key)
}
}

View File

@ -0,0 +1,75 @@
//
// ImageBluringPlugin.swift
// LeadKit
//
// Created by Anton on 26.11.16.
// Copyright © 2016 Touch Instinct. All rights reserved.
//
import Foundation
public class ImageBluringPlugin: ImageFilteringPlugin {
public enum BlurType {
case box(radius: CGFloat)
case disc(radius: CGFloat)
case gaussian(radius: CGFloat)
case masked(radius: CGFloat, maskImage: UIImage)
case median()
case motion(radius: CGFloat, angle: CGFloat)
public func filterParams() -> [String : Any] {
var resultRadius: CGFloat?
var resultParams = [String : Any]()
switch self {
case .box(let radius):
resultRadius = radius
case .disc(let radius):
resultRadius = radius
case .gaussian(let radius):
resultRadius = radius
case .masked(let radius, let maskImage):
resultRadius = radius
resultParams["inputMask"] = maskImage.cgImage
case.median:
break
case .motion(let radius, let angle):
resultRadius = radius
resultParams["inputAngle"] = angle
}
if let resultRadius = resultRadius {
resultParams["inputRadius"] = resultRadius
}
return resultParams
}
public func filterName() -> String {
switch self {
case .box:
return "CIBoxBlur"
case .disc:
return "CIDiscBlur"
case .gaussian:
return "CIGaussianBlur"
case .masked:
return "CIMaskedVariableBlur"
case.median:
return "CIMedianFilter"
case .motion:
return "CIMotionBlur"
}
}
}
public init(blurType: BlurType) {
super.init(name: blurType.filterName(), params: blurType.filterParams())
}
}
public extension ImageOperation {
public func blur(blurType: ImageBluringPlugin.BlurType) -> ImageOperation {
return ImagePluginOperation(originalOperation: self, plugin: ImageBluringPlugin(blurType: blurType))
}
}

View File

@ -0,0 +1,54 @@
//
// ImageFilteringPlugin.swift
// LeadKit
//
// Created by Anton on 26.11.16.
// Copyright © 2016 Touch Instinct. All rights reserved.
//
import Foundation
import AlamofireImage
import RxSwift
public class ImageFilteringPlugin: ImagePluginType {
public enum FilterError: Error {
case unknownError(description: String)
}
public typealias FitlerParamsType = [String : Any]
private let filterName: String
private let filterParams: FitlerParamsType?
public var pluginKey: String {
var resultKey = filterName
if let filterParams = filterParams {
resultKey += filterParams.description
}
return resultKey
}
public func perform(image: UIImage) -> Observable<UIImage> {
return Observable.deferred({ () -> Observable<UIImage> in
guard let reusltImage = image.af_imageFiltered(withCoreImageFilter: self.filterName, parameters: self.filterParams) else {
throw FilterError.unknownError(description: "ImageFilterPlugin failed with filter named \(self.filterName)")
}
return Observable.just(reusltImage)
})
.subcribeOnBackgroundScheduler()
}
public init(name: String, params: FitlerParamsType? = nil) {
filterName = name
filterParams = params
}
}
public extension ImageOperation {
public func filter(name: String, params: ImageFilteringPlugin.FitlerParamsType? = nil) -> ImageOperation {
return ImagePluginOperation(originalOperation: self, plugin: ImageFilteringPlugin(name: name, params: params))
}
}

View File

@ -0,0 +1,31 @@
//
// ImagePlugin.swift
// LeadKit
//
// Created by Anton on 26.11.16.
// Copyright © 2016 Touch Instinct. All rights reserved.
//
import Foundation
import RxSwift
public protocol ImagePluginType {
func perform(image: UIImage) -> Observable<UIImage>
var pluginKey: String { get }
}
extension UIImage {
public func performPlugins(plugins: [ImagePluginType]) -> Observable<UIImage> {
var observable = Observable.just(self)
for plugin in plugins {
observable = observable.flatMap(plugin.perform)
}
return observable
}
public func performPlugins(plugins: ImagePluginType ...) -> Observable<UIImage> {
return performPlugins(plugins: plugins)
}
}

View File

@ -0,0 +1,30 @@
//
// ImagePluginOperation.swift
// LeadKit
//
// Created by Anton on 26.11.16.
// Copyright © 2016 Touch Instinct. All rights reserved.
//
import Foundation
import RxSwift
public class ImagePluginOperation: ImageOperationWrapper {
public override func _prepareCachingKey(cachingKey: String) -> String {
return cachingKey + plugin.pluginKey
}
public override func _prepareWorkingObservable(workingObservable: Observable<UIImage>) -> Observable<UIImage> {
return workingObservable
.flatMap({ (image) -> Observable<UIImage> in
return self.plugin.perform(image: image)
})
}
private let plugin: ImagePluginType
public init(originalOperation: ImageOperation, plugin: ImagePluginType) {
self.plugin = plugin
super.init(originalOperation: originalOperation)
}
}

View File

@ -0,0 +1,59 @@
//
// ImageResizingPlugin.swift
// LeadKit
//
// Created by Anton on 26.11.16.
// Copyright © 2016 Touch Instinct. All rights reserved.
//
import Foundation
import AlamofireImage
import RxSwift
public class ImageResizingPlugin: ImagePluginType {
public enum Mode: String {
case scale
case aspectFit
case aspectFill
static var `default`: Mode {
return .scale
}
}
private let mode: Mode
private let size: CGSize
public var pluginKey: String {
return mode.rawValue + String(describing: size.width) + String(describing: size.height)
}
public func perform(image: UIImage) -> Observable<UIImage> {
return Observable.deferred({ () -> Observable<UIImage> in
return Observable.just(self.beginPerform(image: image))
})
.subcribeOnBackgroundScheduler()
}
private func beginPerform(image: UIImage) -> UIImage {
switch mode {
case .scale:
return image.af_imageScaled(to: size)
case .aspectFit:
return image.af_imageAspectScaled(toFit: size)
case .aspectFill:
return image.af_imageAspectScaled(toFill: size)
}
}
public init(size: CGSize, mode: Mode = .default) {
self.size = size
self.mode = mode;
}
}
public extension ImageOperation {
public func resize(size: CGSize, mode: ImageResizingPlugin.Mode = .default) -> ImageOperation {
return ImagePluginOperation(originalOperation: self, plugin: ImageResizingPlugin(size: size, mode: mode))
}
}

View File

@ -0,0 +1,63 @@
//
// ImageRoundingPlugin.swift
// LeadKit
//
// Created by Anton on 26.11.16.
// Copyright © 2016 Touch Instinct. All rights reserved.
//
import Foundation
import AlamofireImage
import RxSwift
public class ImageRoundingPlugin: ImagePluginType {
public enum Mode {
case cicrular
case corners(radius: CGFloat)
static var `default`: Mode {
return .cicrular
}
public var key: String {
switch self {
case .cicrular:
return "Circular"
case .corners(let radius):
return "Corners" + String(describing: radius)
}
}
}
private let mode: Mode
public var pluginKey: String {
return mode.key
}
public func perform(image: UIImage) -> Observable<UIImage> {
return Observable.deferred({ () -> Observable<UIImage> in
return Observable.just(self.beginPerform(image: image))
})
.subcribeOnBackgroundScheduler()
}
private func beginPerform(image: UIImage) -> UIImage {
switch mode {
case .cicrular:
return image.af_imageRoundedIntoCircle()
case .corners(let radius):
return image.af_imageRounded(withCornerRadius: radius)
}
}
public init(mode: Mode = .default) {
self.mode = mode;
}
}
public extension ImageOperation {
public func round(mode: ImageRoundingPlugin.Mode = .default) -> ImageOperation {
return ImagePluginOperation(originalOperation: self, plugin: ImageRoundingPlugin(mode: mode))
}
}

View File

@ -0,0 +1,49 @@
//
// ImageSource.swift
// LeadKit
//
// Created by Anton on 26.11.16.
// Copyright © 2016 Touch Instinct. All rights reserved.
//
import Foundation
import RxSwift
public protocol ImageSourceType {
associatedtype KeyType
func loadImage(_ key: KeyType) -> Observable<UIImage>
func asImageSource() -> ImageSource<KeyType>
}
extension ImageSourceType {
public func asImageSource() -> ImageSource<KeyType> {
return ImageSource(source: self)
}
}
public class ImageSource<TKey>: ImageSourceType {
public typealias KeyType = TKey
public typealias ImageLoadingHandler = (KeyType) -> Observable<UIImage>
private let sourceLoadImageHandler: ImageLoadingHandler
public func loadImage(_ key: KeyType) -> Observable<UIImage> {
return sourceLoadImageHandler(key)
}
public init(imageLoadingHandler: @escaping ImageLoadingHandler) {
sourceLoadImageHandler = imageLoadingHandler
}
public convenience init<TSource: ImageSourceType>(source: TSource) where TSource.KeyType == KeyType {
self.init(imageLoadingHandler: source.loadImage)
}
}
extension ImageSource {
public func asImageSource() -> ImageSource<KeyType> {
return self
}
}

View File

@ -0,0 +1,46 @@
//
// NetworkImageSource.swift
// LeadKit
//
// Created by Anton on 26.11.16.
// Copyright © 2016 Touch Instinct. All rights reserved.
//
import RxSwift
import Alamofire
import AlamofireImage
public class NetworkImageSource<TKey: URLConvertible>: ImageSourceType where TKey: CustomStringConvertible {
public typealias KeyType = TKey
private let sessionManager: SessionManager
public func loadImage(_ key: KeyType) -> Observable<UIImage> {
return Observable.deferred({ () -> Observable<UIImage> in
return self.beginLoadImage(try key.asURL())
})
}
private func beginLoadImage(_ url: URL) -> Observable<UIImage> {
return Observable.create({ (observer) -> Disposable in
let request = self.sessionManager.request(url)
.responseImage(completionHandler: { (response) in
switch response.result {
case .success(let image):
observer.onNext(image)
observer.onCompleted()
case .failure(let error):
observer.onError(error)
}
})
return Disposables.create {
request.cancel()
}
})
}
public init(sessionManager: SessionManager = SessionManager.default) {
self.sessionManager = sessionManager
}
}

View File

@ -0,0 +1,51 @@
//
// UIImageView+ImageLoader.swift
// LeadKit
//
// Created by Anton on 26.11.16.
// Copyright © 2016 Touch Instinct. All rights reserved.
//
import Foundation
import RxSwift
public extension ImageResizingPlugin.Mode {
public static func fromUIContentMode(contentMode: UIViewContentMode) -> ImageResizingPlugin.Mode {
switch contentMode {
case .scaleAspectFill:
return .aspectFill
case .scaleAspectFit:
return .aspectFit
default:
return .scale
}
}
}
public extension UIImageView {
public func loadImage<TKey: CustomStringConvertible>(key: TKey, loader: ImageLoader<TKey>, resizeImage: Bool = true,
placeholder: UIImage? = nil) -> ImageOperation {
var resultOperation = loader.loadImage(key: key)
if resizeImage {
resultOperation = resultOperation.resize(size: frame.size, mode: ImageResizingPlugin.Mode.fromUIContentMode(contentMode: contentMode))
}
resultOperation = resultOperation.performWith({ (observable) -> Observable<UIImage> in
var resultObservable = observable.observeOnMainScheduler()
.do(onNext: { [weak self] (image) in
self?.image = image
})
.takeUntil(self.rx.deallocated)
if let placeholder = placeholder {
resultObservable = resultObservable.startWith(placeholder)
}
return resultObservable
})
return resultOperation
}
}

View File

@ -0,0 +1,28 @@
//
// ObservableType+Extensions.swift
// LeadKit
//
// Created by Anton on 26.11.16.
// Copyright © 2016 Touch Instinct. All rights reserved.
//
import Foundation
import RxSwift
public extension ObservableType {
public func subcribeOnBackgroundScheduler() -> Observable<E> {
return self.subscribeOn(ConcurrentDispatchQueueScheduler(qos: .default))
}
public func observeOnBackgroundScheduler() -> Observable<E> {
return self.observeOn(ConcurrentDispatchQueueScheduler(qos: .default))
}
public func subcribeOnMainScheduler() -> Observable<E> {
return self.subscribeOn(MainScheduler.instance)
}
public func observeOnMainScheduler() -> Observable<E> {
return self.observeOn(MainScheduler.instance)
}
}

View File

@ -0,0 +1,13 @@
//
// AbstractMethod.swift
// LeadKit
//
// Created by Anton on 26.11.16.
// Copyright © 2016 Touch Instinct. All rights reserved.
//
import Foundation
public func abstractMethod(_ methodName: String = "Method") -> Never {
fatalError("\(methodName) has not been implemented")
}

View File

@ -0,0 +1,60 @@
//
// MemoryStore.swift
// LeadKit
//
// Created by Anton on 26.11.16.
// Copyright © 2016 Touch Instinct. All rights reserved.
//
import Foundation
public class MemoryStore<TKey: Hashable, TData> {
public typealias KeyType = TKey
public typealias DataType = TData
private var syncLock = Sync.Lock()
private var dictionaryStore: [KeyType : DataType] = [:]
private let clearOnMemoryWarning: Bool
public init(clearOnMemoryWarning: Bool = false) {
self.clearOnMemoryWarning = clearOnMemoryWarning
if clearOnMemoryWarning {
NotificationCenter.default.addObserver(self, selector: #selector(onMemoryWarning),
name: NSNotification.Name.UIApplicationDidReceiveMemoryWarning, object: UIApplication.shared)
}
}
public func removeAllData() {
syncLock.sync {
return dictionaryStore.removeAll()
}
}
public func loadData(key: KeyType) -> DataType? {
return syncLock.sync {
return dictionaryStore[key]
}
}
public func storeData(key: TKey, data: DataType?) {
syncLock.sync {
guard let data = data else {
dictionaryStore.removeValue(forKey: key)
return
}
dictionaryStore[key] = data
}
}
@objc private func onMemoryWarning() {
removeAllData()
}
deinit {
if clearOnMemoryWarning {
NotificationCenter.default.removeObserver(self, name: NSNotification.Name.UIApplicationDidReceiveMemoryWarning, object: UIApplication.shared)
}
}
}

View File

@ -0,0 +1,50 @@
//
// Sync.swift
// LeadKit
//
// Created by Anton on 26.11.16.
// Copyright © 2016 Touch Instinct. All rights reserved.
//
public final class Sync {
public final class Lock {
public func lock() {
Sync.beginSyncLock(self)
}
public func unlock() {
Sync.endSyncLock(self)
}
public func sync(block: () throws -> Void) rethrows {
try Sync.synchronized(lockObj: self, block: block)
}
public func sync<T>(block: () throws -> T) rethrows -> T {
return try Sync.synchronized(lockObj: self, block: block)
}
}
public static func beginSyncLock(_ lockObj: Any) -> Void {
objc_sync_enter(lockObj)
}
public static func endSyncLock(_ lockObj: Any) -> Void {
objc_sync_exit(lockObj)
}
public static func synchronized(lockObj: Any, block: () throws -> Void) rethrows -> Void {
beginSyncLock(lockObj)
try block()
endSyncLock(lockObj)
}
public static func synchronized<T>(lockObj: Any, block: () throws -> T) rethrows -> T {
beginSyncLock(lockObj)
let result: T = try block()
endSyncLock(lockObj)
return result
}
}