├── IKAsyncable.xcodeproj
├── project.xcworkspace
│ └── contents.xcworkspacedata
└── project.pbxproj
├── IKAsyncableApp
├── AppDelegate.swift
├── MyDelegate.swift
├── MyDataSource.swift
├── Images.xcassets
│ └── AppIcon.appiconset
│ │ └── Contents.json
├── ViewController.swift
├── MyCell.swift
├── Info.plist
└── Base.lproj
│ ├── LaunchScreen.xib
│ └── Main.storyboard
├── IKAsyncable
├── IKAsyncOperationState.swift
├── IKAsyncable.swift
├── IKAsyncOperationManager.swift
├── IKAsyncOperation.swift
├── IKAsyncTableViewDelegate.swift
└── IKAsyncCollectionViewDelegate.swift
├── IKAsyncableTests
├── Info.plist
├── IKAsyncOperationManagerTests.swift
└── IKAsyncOperationTests.swift
├── .gitignore
└── README.md
/IKAsyncable.xcodeproj/project.xcworkspace/contents.xcworkspacedata:
--------------------------------------------------------------------------------
1 |
2 |
4 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/IKAsyncableApp/AppDelegate.swift:
--------------------------------------------------------------------------------
1 | //
2 | // AppDelegate.swift
3 | // IKAsyncTableViewDelegate_temp
4 | //
5 | // Created by Ian Keen on 27/08/2015.
6 | // Copyright (c) 2015 Mustard. All rights reserved.
7 | //
8 |
9 | import UIKit
10 |
11 | @UIApplicationMain
12 | class AppDelegate: UIResponder, UIApplicationDelegate {
13 | var window: UIWindow?
14 | func application(application: UIApplication, didFinishLaunchingWithOptions launchOptions: [NSObject: AnyObject]?) -> Bool {
15 | // Override point for customization after application launch.
16 | return true
17 | }
18 | }
19 |
20 |
--------------------------------------------------------------------------------
/IKAsyncable/IKAsyncOperationState.swift:
--------------------------------------------------------------------------------
1 | //
2 | // IKAsyncOperationState.swift
3 | // IKAsyncable
4 | //
5 | // Created by Ian Keen on 1/09/2015.
6 | // Copyright (c) 2015 Mustard. All rights reserved.
7 | //
8 |
9 | import Foundation
10 |
11 | public enum IKAsyncOperationState : CustomStringConvertible {
12 | case InProgress
13 | case Complete(Any)
14 | case Failed(NSError)
15 |
16 | public var description: String {
17 | switch self {
18 | case .InProgress: return "In Progress"
19 | case .Complete(let value): return "Complete: \(value)"
20 | case .Failed(let error): return "Failure: \(error)"
21 | }
22 | }
23 | }
--------------------------------------------------------------------------------
/IKAsyncableApp/MyDelegate.swift:
--------------------------------------------------------------------------------
1 | //
2 | // MyDelegate.swift
3 | //
4 | // Created by Ian Keen on 1/09/2015.
5 | // Copyright (c) 2015 Mustard. All rights reserved.
6 | //
7 |
8 | import UIKit
9 |
10 | public class MyDelegate: IKAsyncTableViewDelegate {
11 | var urls: [String]?
12 |
13 | override public func tableView(tableView: UITableView, willDisplayCell cell: UITableViewCell, forRowAtIndexPath indexPath: NSIndexPath) {
14 | if let cell = cell as? MyCell, let url = self.urls?[indexPath.row] {
15 | cell.setup(url)
16 | }
17 |
18 | super.tableView(tableView, willDisplayCell: cell, forRowAtIndexPath: indexPath)
19 | }
20 | }
21 |
--------------------------------------------------------------------------------
/IKAsyncableApp/MyDataSource.swift:
--------------------------------------------------------------------------------
1 | //
2 | // MyDataSource.swift
3 | //
4 | // Created by Ian Keen on 1/09/2015.
5 | // Copyright (c) 2015 Mustard. All rights reserved.
6 | //
7 |
8 | import UIKit
9 |
10 | class MyDataSource: NSObject, UITableViewDataSource {
11 | var urls: [String]?
12 |
13 | func numberOfSectionsInTableView(tableView: UITableView) -> Int { return 1 }
14 | func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int { return self.urls?.count ?? 0 }
15 | func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
16 | return tableView.dequeueReusableCellWithIdentifier("MyCell", forIndexPath: indexPath) as! MyCell
17 | }
18 | }
19 |
--------------------------------------------------------------------------------
/IKAsyncable/IKAsyncable.swift:
--------------------------------------------------------------------------------
1 | //
2 | // IKAsyncable.swift
3 | // IKAsyncable
4 | //
5 | // Created by Ian Keen on 27/08/2015.
6 | // Copyright (c) 2015 Mustard. All rights reserved.
7 | //
8 |
9 | import UIKit
10 |
11 | public typealias IKAsyncSuccess = (Any) -> Void
12 | public typealias IKAsyncFailure = (NSError) -> Void
13 | public typealias IKAsyncOperationClosure = (success: IKAsyncSuccess, failure: IKAsyncFailure) -> Void
14 | public typealias IKAsyncStateChange = (IKAsyncOperation) -> Void
15 |
16 | public protocol IKAsyncableManager {
17 | func resetOperation(asyncable: IKAsyncable)
18 | }
19 |
20 | public protocol IKAsyncable {
21 | func ikAsyncOperation() -> IKAsyncOperationClosure
22 | func ikAsyncOperationState(manager: IKAsyncableManager, state: IKAsyncOperationState)
23 | }
24 |
--------------------------------------------------------------------------------
/IKAsyncableTests/Info.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | CFBundleDevelopmentRegion
6 | en
7 | CFBundleExecutable
8 | $(EXECUTABLE_NAME)
9 | CFBundleIdentifier
10 | $(PRODUCT_BUNDLE_IDENTIFIER)
11 | CFBundleInfoDictionaryVersion
12 | 6.0
13 | CFBundleName
14 | $(PRODUCT_NAME)
15 | CFBundlePackageType
16 | BNDL
17 | CFBundleShortVersionString
18 | 1.0
19 | CFBundleSignature
20 | ????
21 | CFBundleVersion
22 | 1
23 |
24 |
25 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # Created by http://www.gitignore.io
2 |
3 | ### Objective-C ###
4 | # Xcode
5 | #
6 | build/
7 | *.pbxuser
8 | !default.pbxuser
9 | *.mode1v3
10 | !default.mode1v3
11 | *.mode2v3
12 | !default.mode2v3
13 | *.perspectivev3
14 | !default.perspectivev3
15 | xcuserdata
16 | *.xccheckout
17 | *.moved-aside
18 | DerivedData
19 | *.hmap
20 | *.ipa
21 | *.xcuserstate
22 |
23 | # CocoaPods
24 | #
25 | # We recommend against adding the Pods directory to your .gitignore. However
26 | # you should judge for yourself, the pros and cons are mentioned at:
27 | # http://guides.cocoapods.org/using/using-cocoapods.html#should-i-ignore-the-pods-directory-in-source-control
28 | #
29 | # Pods/
30 |
31 |
32 | ### OSX ###
33 | .DS_Store
34 | .AppleDouble
35 | .LSOverride
36 |
37 | # Icon must end with two \r
38 | Icon
39 |
40 |
41 | # Thumbnails
42 | ._*
43 |
44 | # Files that might appear on external disk
45 | .Spotlight-V100
46 | .Trashes
47 |
48 | # Directories potentially created on remote AFP share
49 | .AppleDB
50 | .AppleDesktop
51 | Network Trash Folder
52 | Temporary Items
53 | .apdisk
--------------------------------------------------------------------------------
/IKAsyncableApp/Images.xcassets/AppIcon.appiconset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "idiom" : "iphone",
5 | "size" : "29x29",
6 | "scale" : "2x"
7 | },
8 | {
9 | "idiom" : "iphone",
10 | "size" : "29x29",
11 | "scale" : "3x"
12 | },
13 | {
14 | "idiom" : "iphone",
15 | "size" : "40x40",
16 | "scale" : "2x"
17 | },
18 | {
19 | "idiom" : "iphone",
20 | "size" : "40x40",
21 | "scale" : "3x"
22 | },
23 | {
24 | "idiom" : "iphone",
25 | "size" : "60x60",
26 | "scale" : "2x"
27 | },
28 | {
29 | "idiom" : "iphone",
30 | "size" : "60x60",
31 | "scale" : "3x"
32 | },
33 | {
34 | "idiom" : "ipad",
35 | "size" : "29x29",
36 | "scale" : "1x"
37 | },
38 | {
39 | "idiom" : "ipad",
40 | "size" : "29x29",
41 | "scale" : "2x"
42 | },
43 | {
44 | "idiom" : "ipad",
45 | "size" : "40x40",
46 | "scale" : "1x"
47 | },
48 | {
49 | "idiom" : "ipad",
50 | "size" : "40x40",
51 | "scale" : "2x"
52 | },
53 | {
54 | "idiom" : "ipad",
55 | "size" : "76x76",
56 | "scale" : "1x"
57 | },
58 | {
59 | "idiom" : "ipad",
60 | "size" : "76x76",
61 | "scale" : "2x"
62 | }
63 | ],
64 | "info" : {
65 | "version" : 1,
66 | "author" : "xcode"
67 | }
68 | }
--------------------------------------------------------------------------------
/IKAsyncableApp/ViewController.swift:
--------------------------------------------------------------------------------
1 | //
2 | // ViewController.swift
3 | //
4 | // Created by Ian Keen on 27/08/2015.
5 | // Copyright (c) 2015 Mustard. All rights reserved.
6 | //
7 |
8 | import UIKit
9 |
10 | class ViewController: UIViewController {
11 | @IBOutlet private var tableView: UITableView!
12 | @IBOutlet private var dataSource: MyDataSource!
13 | @IBOutlet private var delegate: MyDelegate!
14 |
15 | override func viewWillAppear(animated: Bool) {
16 | super.viewWillAppear(animated)
17 | self.getPhotos { urls in
18 | self.dataSource.urls = urls
19 | self.delegate.urls = urls
20 | self.tableView.reloadData()
21 | }
22 | }
23 |
24 | let apiUrl = "http://jsonplaceholder.typicode.com/photos"
25 | private func getPhotos(result: [String] -> Void) {
26 | dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0)) {
27 | if
28 | let url = NSURL(string: self.apiUrl),
29 | let data = NSData(contentsOfURL: url),
30 | let object = try! NSJSONSerialization.JSONObjectWithData(data, options: []) as? NSArray,
31 | let urls = object.valueForKeyPath("url") as? [String] {
32 | dispatch_async(dispatch_get_main_queue()) {
33 | result(urls)
34 | }
35 | }
36 | }
37 | }
38 | }
39 |
--------------------------------------------------------------------------------
/IKAsyncableApp/MyCell.swift:
--------------------------------------------------------------------------------
1 | //
2 | // MyCell.swift
3 | //
4 | // Created by Ian Keen on 1/09/2015.
5 | // Copyright (c) 2015 Mustard. All rights reserved.
6 | //
7 |
8 | import UIKit
9 |
10 | class MyCell : UITableViewCell, IKAsyncable {
11 | @IBOutlet private var imgView: UIImageView!
12 |
13 | private var url: String!
14 | func setup(url: String) {
15 | self.url = url
16 | }
17 |
18 | func ikAsyncOperation() -> IKAsyncOperationClosure {
19 | return { success, failure in
20 | if
21 | let url = NSURL(string: self.url),
22 | let data = NSData(contentsOfURL: url),
23 | let image = UIImage(data: data) {
24 | success(image)
25 |
26 | } else {
27 | failure(NSError(domain: "domain", code: 0, userInfo: [NSLocalizedDescriptionKey: "IMAGE"]))
28 | }
29 | }
30 | }
31 | func ikAsyncOperationState(manager: IKAsyncableManager, state: IKAsyncOperationState) {
32 | self.imgView.image = nil
33 |
34 | let color: UIColor
35 | switch state {
36 | case .InProgress:
37 | color = .yellowColor()
38 | case .Complete(let result):
39 | self.imgView.image = result as? UIImage
40 | color = .whiteColor()
41 | case .Failed(_):
42 | color = .redColor()
43 | //manager.resetOperation(self)
44 | }
45 |
46 | self.backgroundColor = color
47 | }
48 | }
49 |
--------------------------------------------------------------------------------
/IKAsyncable/IKAsyncOperationManager.swift:
--------------------------------------------------------------------------------
1 | //
2 | // IKAsyncOperationManager.swift
3 | // IKAsyncable
4 | //
5 | // Created by Ian Keen on 1/09/2015.
6 | // Copyright (c) 2015 Mustard. All rights reserved.
7 | //
8 |
9 | import Foundation
10 |
11 | public class IKAsyncOperationManager : NSObject {
12 | //MARK : - Constants
13 | public static let UnlimitedRetries = Int.max
14 |
15 | //MARK : - Internal Properties
16 | internal var operations = [NSIndexPath: IKAsyncOperation]()
17 | internal var stateChange: IKAsyncStateChange?
18 |
19 | //MARK : - Public Properties
20 | @IBInspectable public var maxNumberOfFailures: Int = 3
21 |
22 | //MARK : - Public Functions
23 | public func addOperationIfNeeded(indexPath: NSIndexPath, operation: IKAsyncOperationClosure) -> IKAsyncOperation {
24 | if let asyncOperation = self.operations[indexPath] {
25 | return asyncOperation
26 |
27 | } else {
28 | let newOperation = IKAsyncOperation(indexPath: indexPath, operation: operation) { op in
29 | self.stateChange?(op)
30 | }
31 | self.operations[indexPath] = newOperation
32 | return newOperation
33 | }
34 | }
35 | public func resetOperations() {
36 | self.operations.removeAll()
37 | }
38 |
39 | //MARK : - Internal - Operations
40 | internal func handleAsyncableState(indexPath: NSIndexPath) {
41 | if let operation = self.operations[indexPath] {
42 | operation.performOperation(self.maxNumberOfFailures)
43 | }
44 | }
45 | }
46 |
--------------------------------------------------------------------------------
/IKAsyncableApp/Info.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | CFBundleDevelopmentRegion
6 | en
7 | CFBundleExecutable
8 | $(EXECUTABLE_NAME)
9 | CFBundleIdentifier
10 | $(PRODUCT_BUNDLE_IDENTIFIER)
11 | CFBundleInfoDictionaryVersion
12 | 6.0
13 | CFBundleName
14 | $(PRODUCT_NAME)
15 | CFBundlePackageType
16 | APPL
17 | CFBundleShortVersionString
18 | 1.0
19 | CFBundleSignature
20 | ????
21 | CFBundleVersion
22 | 1
23 | LSRequiresIPhoneOS
24 |
25 | UILaunchStoryboardName
26 | LaunchScreen
27 | UIMainStoryboardFile
28 | Main
29 | UIRequiredDeviceCapabilities
30 |
31 | armv7
32 |
33 | UISupportedInterfaceOrientations
34 |
35 | UIInterfaceOrientationPortrait
36 |
37 | UISupportedInterfaceOrientations~ipad
38 |
39 | UIInterfaceOrientationPortrait
40 | UIInterfaceOrientationPortraitUpsideDown
41 | UIInterfaceOrientationLandscapeLeft
42 | UIInterfaceOrientationLandscapeRight
43 |
44 | NSAppTransportSecurity
45 |
46 | NSAllowsArbitraryLoads
47 |
48 |
49 |
50 |
51 |
--------------------------------------------------------------------------------
/IKAsyncable/IKAsyncOperation.swift:
--------------------------------------------------------------------------------
1 | //
2 | // IKAsyncOperation.swift
3 | // IKAsyncable
4 | //
5 | // Created by Ian Keen on 1/09/2015.
6 | // Copyright (c) 2015 Mustard. All rights reserved.
7 | //
8 |
9 | import Foundation
10 |
11 | public final class IKAsyncOperation {
12 | //MARK : - Public Properties
13 | let operation: IKAsyncOperationClosure
14 | let indexPath: NSIndexPath
15 |
16 | private(set) var state: IKAsyncOperationState? {
17 | didSet(newValue) {
18 | self.dispatchStateOnMainThread()
19 | }
20 | }
21 |
22 | //MARK : - Private Properties
23 | private var failureCount: Int = 0
24 | private let stateChange: IKAsyncStateChange
25 |
26 | //MARK : - Lifecycle
27 | init(indexPath: NSIndexPath, operation: IKAsyncOperationClosure, stateChange: IKAsyncStateChange) {
28 | self.indexPath = indexPath
29 | self.stateChange = stateChange
30 | self.operation = operation
31 | self.dispatchStateOnMainThread()
32 | }
33 |
34 | //MARK : - Public Functions
35 | public func performOperation(maxFailures: Int) {
36 | if let _ = self.state {
37 | self.dispatchStateOnMainThread()
38 | } else {
39 | self.executeOperation(maxFailures)
40 | }
41 | }
42 |
43 | //MARK : - Private Functions
44 | private func executeOperation(maxFailures: Int) {
45 | self.state = .InProgress
46 |
47 | dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0)) {
48 | self.operation(
49 | success: { result in
50 | self.state = .Complete(result)
51 |
52 | }, failure: { error in
53 | self.failureCount++
54 | if (maxFailures != IKAsyncOperationManager.UnlimitedRetries &&
55 | self.failureCount >= maxFailures) {
56 | self.state = .Failed(error)
57 | } else {
58 | self.executeOperation(maxFailures)
59 | }
60 | }
61 | )
62 | }
63 | }
64 | private func dispatchStateOnMainThread() {
65 | if (NSThread.currentThread().isMainThread) {
66 | self.stateChange(self)
67 | } else {
68 | dispatch_async(dispatch_get_main_queue()) {
69 | self.stateChange(self)
70 | }
71 | }
72 | }
73 | }
--------------------------------------------------------------------------------
/IKAsyncableTests/IKAsyncOperationManagerTests.swift:
--------------------------------------------------------------------------------
1 | //
2 | // IKAsyncOperationManagerTests.swift
3 | // IKAsyncable
4 | //
5 | // Created by Ryan Arana on 9/1/15.
6 | // Copyright (c) 2015 Mustard. All rights reserved.
7 | //
8 |
9 | import UIKit
10 | import XCTest
11 |
12 | class IKAsyncOperationManagerTests: XCTestCase {
13 |
14 | var mgr = IKAsyncOperationManager()
15 | override func setUp() {
16 | super.setUp()
17 | // Put setup code here. This method is called before the invocation of each test method in the class.
18 | mgr = IKAsyncOperationManager()
19 | }
20 |
21 | override func tearDown() {
22 | // Put teardown code here. This method is called after the invocation of each test method in the class.
23 | super.tearDown()
24 | }
25 |
26 | func testAddOperationIfNeeded_whenGivenANewIndexPath_shouldAddTheOperationToTheCache() {
27 | // given
28 | let closure: IKAsyncOperationClosure = { success, failure in }
29 | let path = NSIndexPath(forItem: 0, inSection: 0)
30 |
31 | // sanity check
32 | XCTAssertEqual(mgr.operations.count, 0, "There should be nothing in the cache to start with")
33 |
34 | // when
35 | _ = mgr.addOperationIfNeeded(path, operation: closure)
36 |
37 | // then
38 | XCTAssertEqual(mgr.operations.count, 1, "There should be 1 operation in the cache after add the operation")
39 | }
40 |
41 | func testAddOperationIfNeeded_whenGivenAnExistingIndexPath_shouldNotAddTheOperationToTheCache() {
42 | // given
43 | let closure1: IKAsyncOperationClosure = { success, failure in }
44 | let path1 = NSIndexPath(forItem: 0, inSection: 0)
45 | let closure2: IKAsyncOperationClosure = { success, failure in }
46 | let path2 = NSIndexPath(forItem: 0, inSection: 0)
47 |
48 | // sanity check
49 | XCTAssertEqual(mgr.operations.count, 0, "There should be nothing in the cache to start with")
50 |
51 | // when
52 | _ = mgr.addOperationIfNeeded(path1, operation: closure1)
53 | _ = mgr.addOperationIfNeeded(path2, operation: closure2)
54 |
55 | // then
56 | XCTAssertEqual(mgr.operations.count, 1, "There should be 1 operation in the cache after adding two operations with the same path")
57 | }
58 |
59 | func testResetOperations_shouldRemoveAllOperations() {
60 | // given
61 | let closure: IKAsyncOperationClosure = { success, failure in }
62 | mgr.addOperationIfNeeded(NSIndexPath(forItem: 0, inSection: 0), operation: closure)
63 |
64 | // sanity check
65 | XCTAssertEqual(mgr.operations.count, 1, "There should be 1 operation in the cache after adding 1")
66 |
67 | // when
68 | mgr.resetOperations()
69 |
70 | // then
71 | XCTAssertEqual(mgr.operations.count, 0, "There should be no operations in the cache after resetting them")
72 | }
73 | }
74 |
--------------------------------------------------------------------------------
/IKAsyncable/IKAsyncTableViewDelegate.swift:
--------------------------------------------------------------------------------
1 | //
2 | // IKAsyncTableViewDelegate.swift
3 | // IKAsyncable
4 | //
5 | // Created by Ian Keen on 1/09/2015.
6 | // Copyright (c) 2015 Mustard. All rights reserved.
7 | //
8 |
9 | import UIKit
10 |
11 | public class IKAsyncTableViewDelegate : IKAsyncOperationManager, IKAsyncableManager, UITableViewDelegate {
12 | //MARK : - Private Properties
13 | private weak var tableView: UITableView?
14 |
15 | //MARK : - Public Functions
16 | public func resetOperation(asyncable: IKAsyncable) {
17 | if
18 | let asyncable = asyncable as? UITableViewCell,
19 | let tableView = self.tableView,
20 | let indexPath = tableView.indexPathForCell(asyncable) {
21 | self.operations.removeValueForKey(indexPath)
22 |
23 | if let cell = tableView.cellForRowAtIndexPath(indexPath) {
24 | self.handleCell(cell, indexPath: indexPath)
25 | }
26 | }
27 | }
28 | override public func resetOperations() {
29 | super.resetOperations()
30 |
31 | if let tableView = self.tableView,
32 | let indexPaths = tableView.indexPathsForVisibleRows {
33 | for indexPath in indexPaths {
34 | if let cell = tableView.cellForRowAtIndexPath(indexPath) {
35 | self.handleCell(cell, indexPath: indexPath)
36 | }
37 | }
38 | }
39 | }
40 | public func resetOperation(indexPath: NSIndexPath) {
41 | self.operations.removeValueForKey(indexPath)
42 |
43 | if let tableView = self.tableView {
44 | if let cell = tableView.cellForRowAtIndexPath(indexPath) {
45 | self.handleCell(cell, indexPath: indexPath)
46 | }
47 | }
48 | }
49 |
50 | //MARK : - UITableViewDelegate
51 | public func tableView(tableView: UITableView, willDisplayCell cell: UITableViewCell, forRowAtIndexPath indexPath: NSIndexPath) {
52 | self.tableView = tableView
53 | self.stateChange = self.operationStateChanged
54 |
55 | self.handleCell(cell, indexPath: indexPath)
56 | }
57 |
58 | //MARK: - Private Functions
59 | private func handleCell(cell: UITableViewCell, indexPath: NSIndexPath) {
60 | if let cell = cell as? IKAsyncable {
61 | let operation = self.addOperationIfNeeded(indexPath, operation: cell.ikAsyncOperation())
62 | self.handleAsyncableState(indexPath)
63 | self.dispatchState(operation, asyncable: cell)
64 | }
65 | }
66 | private func operationStateChanged(operation: IKAsyncOperation) {
67 | let indexPath = NSIndexPath(forRow: operation.indexPath.row, inSection: operation.indexPath.section)
68 |
69 | if
70 | let tableView = self.tableView,
71 | let cell = tableView.cellForRowAtIndexPath(indexPath),
72 | let asyncable = cell as? IKAsyncable {
73 | self.dispatchState(operation, asyncable: asyncable)
74 | }
75 | }
76 | private func dispatchState(operation: IKAsyncOperation, asyncable: IKAsyncable) {
77 | if let state = operation.state {
78 | asyncable.ikAsyncOperationState(self, state: state)
79 | }
80 | }
81 | }
82 |
83 |
--------------------------------------------------------------------------------
/IKAsyncable/IKAsyncCollectionViewDelegate.swift:
--------------------------------------------------------------------------------
1 | //
2 | // IKAsyncCollectionViewDelegate.swift
3 | // IKAsyncable
4 | //
5 | // Created by Ian Keen on 1/09/2015.
6 | // Copyright (c) 2015 Mustard. All rights reserved.
7 | //
8 |
9 | import UIKit
10 |
11 | @available(iOS, introduced=8.0)
12 | public class IKAsyncCollectionViewDelegate : IKAsyncOperationManager, IKAsyncableManager, UICollectionViewDelegate {
13 | //MARK : - Private Properties
14 | private weak var collectionView: UICollectionView?
15 |
16 | //MARK : - Public Functions
17 | public func resetOperation(asyncable: IKAsyncable) {
18 | if
19 | let asyncable = asyncable as? UICollectionViewCell,
20 | let collectionView = self.collectionView,
21 | let indexPath = collectionView.indexPathForCell(asyncable) {
22 | self.operations.removeValueForKey(indexPath)
23 |
24 | if let cell = collectionView.cellForItemAtIndexPath(indexPath) {
25 | self.handleCell(cell, indexPath: indexPath)
26 | }
27 | }
28 | }
29 | override public func resetOperations() {
30 | super.resetOperations()
31 |
32 | if let collectionView = self.collectionView {
33 | for indexPath in collectionView.indexPathsForVisibleItems() {
34 | if let cell = collectionView.cellForItemAtIndexPath(indexPath) {
35 | self.handleCell(cell, indexPath: indexPath)
36 | }
37 | }
38 | }
39 | }
40 | public func resetOperation(indexPath: NSIndexPath) {
41 | self.operations.removeValueForKey(indexPath)
42 |
43 | if let collectionView = self.collectionView {
44 | if let cell = collectionView.cellForItemAtIndexPath(indexPath) {
45 | self.handleCell(cell, indexPath: indexPath)
46 | }
47 | }
48 | }
49 |
50 | //MARK : - UICollectionViewDelegate
51 | public func collectionView(collectionView: UICollectionView, willDisplayCell cell: UICollectionViewCell, forItemAtIndexPath indexPath: NSIndexPath) {
52 | self.collectionView = collectionView
53 | self.stateChange = self.operationStateChanged
54 |
55 | self.handleCell(cell, indexPath: indexPath)
56 | }
57 |
58 | //MARK: - Private Functions
59 | private func handleCell(cell: UICollectionViewCell, indexPath: NSIndexPath) {
60 | if let cell = cell as? IKAsyncable {
61 | let operation = self.addOperationIfNeeded(indexPath, operation: cell.ikAsyncOperation())
62 | self.handleAsyncableState(indexPath)
63 | self.dispatchState(operation, asyncable: cell)
64 | }
65 | }
66 | private func operationStateChanged(operation: IKAsyncOperation) {
67 | let indexPath = NSIndexPath(forRow: operation.indexPath.row, inSection: operation.indexPath.section)
68 |
69 | if
70 | let collectionView = self.collectionView,
71 | let cell = collectionView.cellForItemAtIndexPath(indexPath),
72 | let asyncable = cell as? IKAsyncable {
73 | self.dispatchState(operation, asyncable: asyncable)
74 | }
75 | }
76 | private func dispatchState(operation: IKAsyncOperation, asyncable: IKAsyncable) {
77 | if let state = operation.state {
78 | asyncable.ikAsyncOperationState(self, state: state)
79 | }
80 | }
81 | }
82 |
83 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # IKAsyncable
2 | An (almost) drop in UITableViewDelegate & UICollectionViewDelegate for better async cell operations
3 |
4 | ## Why?
5 | This is the basically the result of my blog post on how to properly
6 | handle async tasks in table views:
7 | [Make your UIViewController Awesynchronous!](http://blog.ios-developers.io/make-your-uiviewcontroller-awesynchronous/).
8 |
9 | ## How to use
10 | Getting up and running is a peice of cake... No matter if you are working with a new or existing codebase!
11 | If you currently don't use a delegate in your `UITableView` or `UICollectionView`
12 | you can simply hook up an instance of `IKAsyncTableViewDelegate` or `IKAsyncCollectionViewDelegate`
13 |
14 | otherwise...
15 |
16 | ### UITableViewDelegate
17 |
18 | * Make your existing `UITableViewDelegate` inherit from `IKAsyncTableViewDelegate`
19 | * If you are using the `willDisplayCell` method you just need to mark it `overrides` and call the super method
20 |
21 | ### UITableViewCell
22 |
23 | * Have your cell conform to the `IKAsyncable` protocol
24 | * Implement `func ikAsyncOperation() -> IKAsyncOperationClosure` and place all your async operations here.
25 | This will allow the code to stay with the cell, but it will be executed outside the lifecycle of the cell.
26 | The code is automatically executed on a background thread so you don't need to do any GCD wrapping.
27 | The sucessful result or error from failure will be cached for the next time the cell needs it.
28 |
29 | ```swift
30 | func ikAsyncOperation() -> IKAsyncOperationClosure {
31 | return { success, failure in
32 | /*
33 | perform async code here
34 | and call success(result) or failure(error)
35 | depending on the outcome.
36 | */
37 | }
38 | }
39 | ```
40 |
41 | * Implement `func ikAsyncOperationState(state: IKAsyncOperationState)` to be notified when the state of the operation changes.
42 | This will be called each time the state changes; if the cell is visible it will receive the change right away,
43 | otherwise it will get the state the next time the cell is visible. This is always delivered safely on the main thread.
44 |
45 | ```swift
46 | func ikAsyncOperationState(state: IKAsyncOperationState) {
47 | let color: UIColor
48 | switch state {
49 | case .InProgress: color = .yellowColor()
50 | case .Complete(let result): color = .greenColor()
51 | case .Failed(let error): color = .redColor()
52 | }
53 |
54 | self.backgroundColor = color
55 | self.textLabel?.text = "\(state)"
56 | }
57 | ```
58 |
59 | ### UICollectionViewDelegate
60 |
61 | The setup for a UICollectionView is exactly the same :)
62 |
63 | ## Invalidating the Cache
64 |
65 | If you implement a pull to refresh or you have some other
66 | reason for needing to reset the async operations you can call:
67 |
68 | `delegate.resetOperations()` - This will reset all async operations causing them to start again
69 | `delegate.resetOperation(cell)` - This will reset the operation for the passed item and cause it to start again
70 |
71 | ## Handling failure
72 |
73 | You can configure `IKAsyncable` operations to retry a given number of times before reporting failure.
74 | This can be adjusted by setting the `delegate.maxNumberOfFailures` property (the default is 3).
75 | If you want operations to retry indefinitely you can set `maxNumberOfFailures` to `IKAsyncOperationManager.UnlimitedRetries`
76 |
77 | ## The rest...
78 |
79 | This is written for Swift 2
80 |
81 | Check out the included app to see it in action;
82 | I've tried to make this simple to use and as easy to integrate into existing systems as I can.
83 |
84 | Feel free to submit any feedback or pull requests!
85 |
86 |
--------------------------------------------------------------------------------
/IKAsyncableApp/Base.lproj/LaunchScreen.xib:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
20 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
--------------------------------------------------------------------------------
/IKAsyncableApp/Base.lproj/Main.storyboard:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
49 |
50 |
51 |
52 |
53 |
54 |
55 |
56 |
57 |
58 |
59 |
60 |
61 |
62 |
63 |
64 |
65 |
66 |
67 |
68 |
69 |
70 |
71 |
--------------------------------------------------------------------------------
/IKAsyncableTests/IKAsyncOperationTests.swift:
--------------------------------------------------------------------------------
1 | //
2 | // IKAsyncOperationTests.swift
3 | // IKAsyncable
4 | //
5 | // Created by Ian Keen on 3/09/2015.
6 | // Copyright (c) 2015 Mustard. All rights reserved.
7 | //
8 |
9 | import UIKit
10 | import XCTest
11 |
12 | class IKAsyncOperationTests: XCTestCase {
13 | override func setUp() {
14 | super.setUp()
15 | // Put setup code here. This method is called before the invocation of each test method in the class.
16 | }
17 | override func tearDown() {
18 | // Put teardown code here. This method is called after the invocation of each test method in the class.
19 | super.tearDown()
20 | }
21 |
22 | func testPerformOperation_whenClosureOperationIsSuccessful_shouldUpdateStateWithCompletion() {
23 | // given
24 | let expectation = self.expectationWithDescription("")
25 |
26 | let indexPath = NSIndexPath(forRow: 0, inSection: 0)
27 | let operation = IKAsyncOperation(
28 | indexPath: indexPath,
29 | operation: exampleSuccessfulOperation(0)) { op in
30 | if let state = op.state {
31 | switch state {
32 | case .Complete(let result):
33 | if (result as! Bool) {
34 | expectation.fulfill()
35 | }
36 | default: break;
37 | }
38 | }
39 | }
40 |
41 | // when
42 | operation.performOperation(0)
43 |
44 | // then
45 | self.waitForExpectationsWithTimeout(2.0, handler: nil)
46 | }
47 |
48 | func testPerformOperation_whenClosureOperationIsUnsuccessful_shouldUpdateStateWithFailure() {
49 | // given
50 | let expectation = self.expectationWithDescription("")
51 |
52 | let indexPath = NSIndexPath(forRow: 0, inSection: 0)
53 | let operation = IKAsyncOperation(
54 | indexPath: indexPath,
55 | operation: exampleUnsuccessfulOperation()) { op in
56 | if let state = op.state {
57 | switch state {
58 | case .Failed:
59 | expectation.fulfill()
60 | default: break;
61 | }
62 | }
63 | }
64 |
65 | // when
66 | operation.performOperation(0)
67 |
68 | // then
69 | self.waitForExpectationsWithTimeout(2.0, handler: nil)
70 | }
71 |
72 | func testPerformOperation_whenClosureOperationIsSuccessfulWithinMaxFailures_shouldUpdateStateWithCompletion() {
73 | // given
74 | let expectation = self.expectationWithDescription("")
75 |
76 | let indexPath = NSIndexPath(forRow: 0, inSection: 0)
77 | let operation = IKAsyncOperation(
78 | indexPath: indexPath,
79 | operation: exampleSuccessfulOperation(3)) { op in //fail 3 times, then succeed
80 | if let state = op.state {
81 | switch state {
82 | case .Complete(let result):
83 | if (result as! Bool) {
84 | expectation.fulfill()
85 | }
86 | default: break;
87 | }
88 | }
89 | }
90 |
91 | // when
92 | operation.performOperation(4)
93 |
94 | // then
95 | self.waitForExpectationsWithTimeout(5.0, handler: nil)
96 | }
97 |
98 | func testPerformOperation_whenClosureOperationIsUnsuccessfulWithinMaxFailures_shouldUpdateStateWithFailure() {
99 | // given
100 | let expectation = self.expectationWithDescription("")
101 |
102 | let indexPath = NSIndexPath(forRow: 0, inSection: 0)
103 | let operation = IKAsyncOperation(
104 | indexPath: indexPath,
105 | operation: exampleSuccessfulOperation(3)) { op in //fail 3 times, then succeed
106 | if let state = op.state {
107 | switch state {
108 | case .Failed:
109 | expectation.fulfill()
110 | default: break;
111 | }
112 | }
113 | }
114 |
115 | // when
116 | operation.performOperation(2)
117 |
118 | // then
119 | self.waitForExpectationsWithTimeout(5.0, handler: nil)
120 | }
121 |
122 | func testPerformOperation_whenClosureOperationIsUnsuccessfulAndMaxFailuresIsUnlimited_shouldNotUpdateStateWithCompletionOrFailure() {
123 | // given
124 | let expectation = self.expectationWithDescription("")
125 |
126 | let indexPath = NSIndexPath(forRow: 0, inSection: 0)
127 | let operation = IKAsyncOperation(
128 | indexPath: indexPath,
129 | operation: exampleUnsuccessfulOperation()) { op in
130 | if let state = op.state {
131 | switch state {
132 | case .InProgress: break;
133 | default:
134 | XCTFail("a state other than InProgress should never be sent")
135 | }
136 | }
137 | }
138 |
139 | // when
140 | operation.performOperation(IKAsyncOperationManager.UnlimitedRetries)
141 |
142 | // then
143 | let delay = dispatch_time(DISPATCH_TIME_NOW, Int64(9.5 * Double(NSEC_PER_SEC)))
144 | dispatch_after(delay, dispatch_get_main_queue()) {
145 | expectation.fulfill()
146 | }
147 | self.waitForExpectationsWithTimeout(10.0, handler: nil)
148 | }
149 |
150 | //MARK : - Helpers
151 | func exampleSuccessfulOperation(failCount: Int) -> IKAsyncOperationClosure {
152 | var failures = 0
153 |
154 | return { success, failure in
155 | let delay = dispatch_time(DISPATCH_TIME_NOW, Int64(1.0 * Double(NSEC_PER_SEC)))
156 | dispatch_after(delay, dispatch_get_main_queue()) {
157 | failures++
158 | if (failures > failCount) {
159 | success(true)
160 | } else {
161 | failure(self.exampleError())
162 | }
163 | }
164 | }
165 | }
166 | func exampleUnsuccessfulOperation() -> IKAsyncOperationClosure {
167 | return { success, failure in
168 | let delay = dispatch_time(DISPATCH_TIME_NOW, Int64(1.0 * Double(NSEC_PER_SEC)))
169 | dispatch_after(delay, dispatch_get_main_queue()) {
170 | failure(self.exampleError())
171 | }
172 | }
173 | }
174 | func exampleError() -> NSError {
175 | let error = NSError(domain: "Domain", code: 0, userInfo: [NSLocalizedDescriptionKey: "Error"])
176 | return error
177 | }
178 | }
179 |
--------------------------------------------------------------------------------
/IKAsyncable.xcodeproj/project.pbxproj:
--------------------------------------------------------------------------------
1 | // !$*UTF8*$!
2 | {
3 | archiveVersion = 1;
4 | classes = {
5 | };
6 | objectVersion = 46;
7 | objects = {
8 |
9 | /* Begin PBXBuildFile section */
10 | 32F65A511B968AA000614E7E /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 32F65A491B968AA000614E7E /* AppDelegate.swift */; };
11 | 32F65A521B968AA000614E7E /* LaunchScreen.xib in Resources */ = {isa = PBXBuildFile; fileRef = 32F65A4A1B968AA000614E7E /* LaunchScreen.xib */; };
12 | 32F65A531B968AA000614E7E /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 32F65A4C1B968AA000614E7E /* Main.storyboard */; };
13 | 32F65A541B968AA000614E7E /* Images.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 32F65A4E1B968AA000614E7E /* Images.xcassets */; };
14 | 32F65A561B968AA000614E7E /* ViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 32F65A501B968AA000614E7E /* ViewController.swift */; };
15 | 32F65A591B968C3600614E7E /* MyCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 32F65A571B968C3600614E7E /* MyCell.swift */; };
16 | 32F65A5A1B968C3600614E7E /* MyDataSource.swift in Sources */ = {isa = PBXBuildFile; fileRef = 32F65A581B968C3600614E7E /* MyDataSource.swift */; };
17 | 32F65A5C1B968C4800614E7E /* IKAsyncable.swift in Sources */ = {isa = PBXBuildFile; fileRef = 32F65A5B1B968C4800614E7E /* IKAsyncable.swift */; };
18 | 32F65A5E1B968C8300614E7E /* IKAsyncTableViewDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 32F65A5D1B968C8300614E7E /* IKAsyncTableViewDelegate.swift */; };
19 | 32F65A601B968D5400614E7E /* IKAsyncCollectionViewDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 32F65A5F1B968D5400614E7E /* IKAsyncCollectionViewDelegate.swift */; };
20 | 32F65A621B96919000614E7E /* IKAsyncOperationState.swift in Sources */ = {isa = PBXBuildFile; fileRef = 32F65A611B96919000614E7E /* IKAsyncOperationState.swift */; };
21 | 32F65A641B96920900614E7E /* IKAsyncOperation.swift in Sources */ = {isa = PBXBuildFile; fileRef = 32F65A631B96920900614E7E /* IKAsyncOperation.swift */; };
22 | 32F65A661B96922A00614E7E /* IKAsyncOperationManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 32F65A651B96922A00614E7E /* IKAsyncOperationManager.swift */; };
23 | 32F65A681B96AB6A00614E7E /* MyDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 32F65A671B96AB6A00614E7E /* MyDelegate.swift */; };
24 | 32F65A781B991D7600614E7E /* IKAsyncOperationTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 32F65A771B991D7600614E7E /* IKAsyncOperationTests.swift */; };
25 | 94E493561B96D16C00FA67D8 /* IKAsyncOperationManagerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 94E493551B96D16C00FA67D8 /* IKAsyncOperationManagerTests.swift */; };
26 | 94E493571B96D48800FA67D8 /* IKAsyncable.swift in Sources */ = {isa = PBXBuildFile; fileRef = 32F65A5B1B968C4800614E7E /* IKAsyncable.swift */; };
27 | 94E493581B96D48800FA67D8 /* IKAsyncTableViewDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 32F65A5D1B968C8300614E7E /* IKAsyncTableViewDelegate.swift */; };
28 | 94E493591B96D48800FA67D8 /* IKAsyncCollectionViewDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 32F65A5F1B968D5400614E7E /* IKAsyncCollectionViewDelegate.swift */; };
29 | 94E4935A1B96D48800FA67D8 /* IKAsyncOperationState.swift in Sources */ = {isa = PBXBuildFile; fileRef = 32F65A611B96919000614E7E /* IKAsyncOperationState.swift */; };
30 | 94E4935B1B96D48800FA67D8 /* IKAsyncOperation.swift in Sources */ = {isa = PBXBuildFile; fileRef = 32F65A631B96920900614E7E /* IKAsyncOperation.swift */; };
31 | 94E4935C1B96D48800FA67D8 /* IKAsyncOperationManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 32F65A651B96922A00614E7E /* IKAsyncOperationManager.swift */; };
32 | /* End PBXBuildFile section */
33 |
34 | /* Begin PBXContainerItemProxy section */
35 | 32F65A381B968A7400614E7E /* PBXContainerItemProxy */ = {
36 | isa = PBXContainerItemProxy;
37 | containerPortal = 32F65A1A1B968A7400614E7E /* Project object */;
38 | proxyType = 1;
39 | remoteGlobalIDString = 32F65A211B968A7400614E7E;
40 | remoteInfo = IKAsyncable;
41 | };
42 | /* End PBXContainerItemProxy section */
43 |
44 | /* Begin PBXFileReference section */
45 | 32F65A221B968A7400614E7E /* IKAsyncable.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = IKAsyncable.app; sourceTree = BUILT_PRODUCTS_DIR; };
46 | 32F65A371B968A7400614E7E /* IKAsyncableTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = IKAsyncableTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; };
47 | 32F65A3C1B968A7400614E7E /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; };
48 | 32F65A491B968AA000614E7E /* AppDelegate.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; };
49 | 32F65A4B1B968AA000614E7E /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.xib; name = Base; path = Base.lproj/LaunchScreen.xib; sourceTree = ""; };
50 | 32F65A4D1B968AA000614E7E /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = ""; };
51 | 32F65A4E1B968AA000614E7E /* Images.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Images.xcassets; sourceTree = ""; };
52 | 32F65A4F1B968AA000614E7E /* Info.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; };
53 | 32F65A501B968AA000614E7E /* ViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ViewController.swift; sourceTree = ""; };
54 | 32F65A571B968C3600614E7E /* MyCell.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MyCell.swift; sourceTree = ""; };
55 | 32F65A581B968C3600614E7E /* MyDataSource.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MyDataSource.swift; sourceTree = ""; };
56 | 32F65A5B1B968C4800614E7E /* IKAsyncable.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = IKAsyncable.swift; sourceTree = ""; };
57 | 32F65A5D1B968C8300614E7E /* IKAsyncTableViewDelegate.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = IKAsyncTableViewDelegate.swift; sourceTree = ""; };
58 | 32F65A5F1B968D5400614E7E /* IKAsyncCollectionViewDelegate.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = IKAsyncCollectionViewDelegate.swift; sourceTree = ""; };
59 | 32F65A611B96919000614E7E /* IKAsyncOperationState.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = IKAsyncOperationState.swift; sourceTree = ""; };
60 | 32F65A631B96920900614E7E /* IKAsyncOperation.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = IKAsyncOperation.swift; sourceTree = ""; };
61 | 32F65A651B96922A00614E7E /* IKAsyncOperationManager.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = IKAsyncOperationManager.swift; sourceTree = ""; };
62 | 32F65A671B96AB6A00614E7E /* MyDelegate.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MyDelegate.swift; sourceTree = ""; };
63 | 32F65A771B991D7600614E7E /* IKAsyncOperationTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = IKAsyncOperationTests.swift; sourceTree = ""; };
64 | 94E493551B96D16C00FA67D8 /* IKAsyncOperationManagerTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = IKAsyncOperationManagerTests.swift; sourceTree = ""; };
65 | /* End PBXFileReference section */
66 |
67 | /* Begin PBXFrameworksBuildPhase section */
68 | 32F65A1F1B968A7400614E7E /* Frameworks */ = {
69 | isa = PBXFrameworksBuildPhase;
70 | buildActionMask = 2147483647;
71 | files = (
72 | );
73 | runOnlyForDeploymentPostprocessing = 0;
74 | };
75 | 32F65A341B968A7400614E7E /* Frameworks */ = {
76 | isa = PBXFrameworksBuildPhase;
77 | buildActionMask = 2147483647;
78 | files = (
79 | );
80 | runOnlyForDeploymentPostprocessing = 0;
81 | };
82 | /* End PBXFrameworksBuildPhase section */
83 |
84 | /* Begin PBXGroup section */
85 | 32F65A191B968A7400614E7E = {
86 | isa = PBXGroup;
87 | children = (
88 | 32F65A471B968AA000614E7E /* IKAsyncable */,
89 | 32F65A481B968AA000614E7E /* IKAsyncableApp */,
90 | 32F65A3A1B968A7400614E7E /* IKAsyncableTests */,
91 | 32F65A231B968A7400614E7E /* Products */,
92 | );
93 | sourceTree = "";
94 | };
95 | 32F65A231B968A7400614E7E /* Products */ = {
96 | isa = PBXGroup;
97 | children = (
98 | 32F65A221B968A7400614E7E /* IKAsyncable.app */,
99 | 32F65A371B968A7400614E7E /* IKAsyncableTests.xctest */,
100 | );
101 | name = Products;
102 | sourceTree = "";
103 | };
104 | 32F65A3A1B968A7400614E7E /* IKAsyncableTests */ = {
105 | isa = PBXGroup;
106 | children = (
107 | 94E493551B96D16C00FA67D8 /* IKAsyncOperationManagerTests.swift */,
108 | 32F65A771B991D7600614E7E /* IKAsyncOperationTests.swift */,
109 | 32F65A3B1B968A7400614E7E /* Supporting Files */,
110 | );
111 | path = IKAsyncableTests;
112 | sourceTree = "";
113 | };
114 | 32F65A3B1B968A7400614E7E /* Supporting Files */ = {
115 | isa = PBXGroup;
116 | children = (
117 | 32F65A3C1B968A7400614E7E /* Info.plist */,
118 | );
119 | name = "Supporting Files";
120 | sourceTree = "";
121 | };
122 | 32F65A471B968AA000614E7E /* IKAsyncable */ = {
123 | isa = PBXGroup;
124 | children = (
125 | 32F65A5B1B968C4800614E7E /* IKAsyncable.swift */,
126 | 32F65A5D1B968C8300614E7E /* IKAsyncTableViewDelegate.swift */,
127 | 32F65A5F1B968D5400614E7E /* IKAsyncCollectionViewDelegate.swift */,
128 | 32F65A611B96919000614E7E /* IKAsyncOperationState.swift */,
129 | 32F65A631B96920900614E7E /* IKAsyncOperation.swift */,
130 | 32F65A651B96922A00614E7E /* IKAsyncOperationManager.swift */,
131 | );
132 | path = IKAsyncable;
133 | sourceTree = "";
134 | };
135 | 32F65A481B968AA000614E7E /* IKAsyncableApp */ = {
136 | isa = PBXGroup;
137 | children = (
138 | 32F65A571B968C3600614E7E /* MyCell.swift */,
139 | 32F65A581B968C3600614E7E /* MyDataSource.swift */,
140 | 32F65A671B96AB6A00614E7E /* MyDelegate.swift */,
141 | 32F65A501B968AA000614E7E /* ViewController.swift */,
142 | 32F65A491B968AA000614E7E /* AppDelegate.swift */,
143 | 32F65A4A1B968AA000614E7E /* LaunchScreen.xib */,
144 | 32F65A4C1B968AA000614E7E /* Main.storyboard */,
145 | 32F65A4E1B968AA000614E7E /* Images.xcassets */,
146 | 32F65A4F1B968AA000614E7E /* Info.plist */,
147 | );
148 | path = IKAsyncableApp;
149 | sourceTree = "";
150 | };
151 | /* End PBXGroup section */
152 |
153 | /* Begin PBXNativeTarget section */
154 | 32F65A211B968A7400614E7E /* IKAsyncable */ = {
155 | isa = PBXNativeTarget;
156 | buildConfigurationList = 32F65A411B968A7400614E7E /* Build configuration list for PBXNativeTarget "IKAsyncable" */;
157 | buildPhases = (
158 | 32F65A1E1B968A7400614E7E /* Sources */,
159 | 32F65A1F1B968A7400614E7E /* Frameworks */,
160 | 32F65A201B968A7400614E7E /* Resources */,
161 | );
162 | buildRules = (
163 | );
164 | dependencies = (
165 | );
166 | name = IKAsyncable;
167 | productName = IKAsyncable;
168 | productReference = 32F65A221B968A7400614E7E /* IKAsyncable.app */;
169 | productType = "com.apple.product-type.application";
170 | };
171 | 32F65A361B968A7400614E7E /* IKAsyncableTests */ = {
172 | isa = PBXNativeTarget;
173 | buildConfigurationList = 32F65A441B968A7400614E7E /* Build configuration list for PBXNativeTarget "IKAsyncableTests" */;
174 | buildPhases = (
175 | 32F65A331B968A7400614E7E /* Sources */,
176 | 32F65A341B968A7400614E7E /* Frameworks */,
177 | 32F65A351B968A7400614E7E /* Resources */,
178 | );
179 | buildRules = (
180 | );
181 | dependencies = (
182 | 32F65A391B968A7400614E7E /* PBXTargetDependency */,
183 | );
184 | name = IKAsyncableTests;
185 | productName = IKAsyncableTests;
186 | productReference = 32F65A371B968A7400614E7E /* IKAsyncableTests.xctest */;
187 | productType = "com.apple.product-type.bundle.unit-test";
188 | };
189 | /* End PBXNativeTarget section */
190 |
191 | /* Begin PBXProject section */
192 | 32F65A1A1B968A7400614E7E /* Project object */ = {
193 | isa = PBXProject;
194 | attributes = {
195 | LastSwiftMigration = 0700;
196 | LastSwiftUpdateCheck = 0700;
197 | LastUpgradeCheck = 0700;
198 | ORGANIZATIONNAME = Mustard;
199 | TargetAttributes = {
200 | 32F65A211B968A7400614E7E = {
201 | CreatedOnToolsVersion = 6.4;
202 | };
203 | 32F65A361B968A7400614E7E = {
204 | CreatedOnToolsVersion = 6.4;
205 | TestTargetID = 32F65A211B968A7400614E7E;
206 | };
207 | };
208 | };
209 | buildConfigurationList = 32F65A1D1B968A7400614E7E /* Build configuration list for PBXProject "IKAsyncable" */;
210 | compatibilityVersion = "Xcode 3.2";
211 | developmentRegion = English;
212 | hasScannedForEncodings = 0;
213 | knownRegions = (
214 | en,
215 | Base,
216 | );
217 | mainGroup = 32F65A191B968A7400614E7E;
218 | productRefGroup = 32F65A231B968A7400614E7E /* Products */;
219 | projectDirPath = "";
220 | projectRoot = "";
221 | targets = (
222 | 32F65A211B968A7400614E7E /* IKAsyncable */,
223 | 32F65A361B968A7400614E7E /* IKAsyncableTests */,
224 | );
225 | };
226 | /* End PBXProject section */
227 |
228 | /* Begin PBXResourcesBuildPhase section */
229 | 32F65A201B968A7400614E7E /* Resources */ = {
230 | isa = PBXResourcesBuildPhase;
231 | buildActionMask = 2147483647;
232 | files = (
233 | 32F65A541B968AA000614E7E /* Images.xcassets in Resources */,
234 | 32F65A521B968AA000614E7E /* LaunchScreen.xib in Resources */,
235 | 32F65A531B968AA000614E7E /* Main.storyboard in Resources */,
236 | );
237 | runOnlyForDeploymentPostprocessing = 0;
238 | };
239 | 32F65A351B968A7400614E7E /* Resources */ = {
240 | isa = PBXResourcesBuildPhase;
241 | buildActionMask = 2147483647;
242 | files = (
243 | );
244 | runOnlyForDeploymentPostprocessing = 0;
245 | };
246 | /* End PBXResourcesBuildPhase section */
247 |
248 | /* Begin PBXSourcesBuildPhase section */
249 | 32F65A1E1B968A7400614E7E /* Sources */ = {
250 | isa = PBXSourcesBuildPhase;
251 | buildActionMask = 2147483647;
252 | files = (
253 | 32F65A5E1B968C8300614E7E /* IKAsyncTableViewDelegate.swift in Sources */,
254 | 32F65A5C1B968C4800614E7E /* IKAsyncable.swift in Sources */,
255 | 32F65A621B96919000614E7E /* IKAsyncOperationState.swift in Sources */,
256 | 32F65A591B968C3600614E7E /* MyCell.swift in Sources */,
257 | 32F65A601B968D5400614E7E /* IKAsyncCollectionViewDelegate.swift in Sources */,
258 | 32F65A681B96AB6A00614E7E /* MyDelegate.swift in Sources */,
259 | 32F65A561B968AA000614E7E /* ViewController.swift in Sources */,
260 | 32F65A661B96922A00614E7E /* IKAsyncOperationManager.swift in Sources */,
261 | 32F65A641B96920900614E7E /* IKAsyncOperation.swift in Sources */,
262 | 32F65A5A1B968C3600614E7E /* MyDataSource.swift in Sources */,
263 | 32F65A511B968AA000614E7E /* AppDelegate.swift in Sources */,
264 | );
265 | runOnlyForDeploymentPostprocessing = 0;
266 | };
267 | 32F65A331B968A7400614E7E /* Sources */ = {
268 | isa = PBXSourcesBuildPhase;
269 | buildActionMask = 2147483647;
270 | files = (
271 | 94E493571B96D48800FA67D8 /* IKAsyncable.swift in Sources */,
272 | 94E493581B96D48800FA67D8 /* IKAsyncTableViewDelegate.swift in Sources */,
273 | 94E493591B96D48800FA67D8 /* IKAsyncCollectionViewDelegate.swift in Sources */,
274 | 94E4935A1B96D48800FA67D8 /* IKAsyncOperationState.swift in Sources */,
275 | 32F65A781B991D7600614E7E /* IKAsyncOperationTests.swift in Sources */,
276 | 94E4935B1B96D48800FA67D8 /* IKAsyncOperation.swift in Sources */,
277 | 94E4935C1B96D48800FA67D8 /* IKAsyncOperationManager.swift in Sources */,
278 | 94E493561B96D16C00FA67D8 /* IKAsyncOperationManagerTests.swift in Sources */,
279 | );
280 | runOnlyForDeploymentPostprocessing = 0;
281 | };
282 | /* End PBXSourcesBuildPhase section */
283 |
284 | /* Begin PBXTargetDependency section */
285 | 32F65A391B968A7400614E7E /* PBXTargetDependency */ = {
286 | isa = PBXTargetDependency;
287 | target = 32F65A211B968A7400614E7E /* IKAsyncable */;
288 | targetProxy = 32F65A381B968A7400614E7E /* PBXContainerItemProxy */;
289 | };
290 | /* End PBXTargetDependency section */
291 |
292 | /* Begin PBXVariantGroup section */
293 | 32F65A4A1B968AA000614E7E /* LaunchScreen.xib */ = {
294 | isa = PBXVariantGroup;
295 | children = (
296 | 32F65A4B1B968AA000614E7E /* Base */,
297 | );
298 | name = LaunchScreen.xib;
299 | sourceTree = "";
300 | };
301 | 32F65A4C1B968AA000614E7E /* Main.storyboard */ = {
302 | isa = PBXVariantGroup;
303 | children = (
304 | 32F65A4D1B968AA000614E7E /* Base */,
305 | );
306 | name = Main.storyboard;
307 | sourceTree = "";
308 | };
309 | /* End PBXVariantGroup section */
310 |
311 | /* Begin XCBuildConfiguration section */
312 | 32F65A3F1B968A7400614E7E /* Debug */ = {
313 | isa = XCBuildConfiguration;
314 | buildSettings = {
315 | ALWAYS_SEARCH_USER_PATHS = NO;
316 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x";
317 | CLANG_CXX_LIBRARY = "libc++";
318 | CLANG_ENABLE_MODULES = YES;
319 | CLANG_ENABLE_OBJC_ARC = YES;
320 | CLANG_WARN_BOOL_CONVERSION = YES;
321 | CLANG_WARN_CONSTANT_CONVERSION = YES;
322 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
323 | CLANG_WARN_EMPTY_BODY = YES;
324 | CLANG_WARN_ENUM_CONVERSION = YES;
325 | CLANG_WARN_INT_CONVERSION = YES;
326 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
327 | CLANG_WARN_UNREACHABLE_CODE = YES;
328 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
329 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
330 | COPY_PHASE_STRIP = NO;
331 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
332 | ENABLE_STRICT_OBJC_MSGSEND = YES;
333 | ENABLE_TESTABILITY = YES;
334 | GCC_C_LANGUAGE_STANDARD = gnu99;
335 | GCC_DYNAMIC_NO_PIC = NO;
336 | GCC_NO_COMMON_BLOCKS = YES;
337 | GCC_OPTIMIZATION_LEVEL = 0;
338 | GCC_PREPROCESSOR_DEFINITIONS = (
339 | "DEBUG=1",
340 | "$(inherited)",
341 | );
342 | GCC_SYMBOLS_PRIVATE_EXTERN = NO;
343 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
344 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
345 | GCC_WARN_UNDECLARED_SELECTOR = YES;
346 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
347 | GCC_WARN_UNUSED_FUNCTION = YES;
348 | GCC_WARN_UNUSED_VARIABLE = YES;
349 | IPHONEOS_DEPLOYMENT_TARGET = 8.4;
350 | MTL_ENABLE_DEBUG_INFO = YES;
351 | ONLY_ACTIVE_ARCH = YES;
352 | SDKROOT = iphoneos;
353 | SWIFT_OPTIMIZATION_LEVEL = "-Onone";
354 | TARGETED_DEVICE_FAMILY = "1,2";
355 | };
356 | name = Debug;
357 | };
358 | 32F65A401B968A7400614E7E /* Release */ = {
359 | isa = XCBuildConfiguration;
360 | buildSettings = {
361 | ALWAYS_SEARCH_USER_PATHS = NO;
362 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x";
363 | CLANG_CXX_LIBRARY = "libc++";
364 | CLANG_ENABLE_MODULES = YES;
365 | CLANG_ENABLE_OBJC_ARC = YES;
366 | CLANG_WARN_BOOL_CONVERSION = YES;
367 | CLANG_WARN_CONSTANT_CONVERSION = YES;
368 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
369 | CLANG_WARN_EMPTY_BODY = YES;
370 | CLANG_WARN_ENUM_CONVERSION = YES;
371 | CLANG_WARN_INT_CONVERSION = YES;
372 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
373 | CLANG_WARN_UNREACHABLE_CODE = YES;
374 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
375 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
376 | COPY_PHASE_STRIP = NO;
377 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
378 | ENABLE_NS_ASSERTIONS = NO;
379 | ENABLE_STRICT_OBJC_MSGSEND = YES;
380 | GCC_C_LANGUAGE_STANDARD = gnu99;
381 | GCC_NO_COMMON_BLOCKS = YES;
382 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
383 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
384 | GCC_WARN_UNDECLARED_SELECTOR = YES;
385 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
386 | GCC_WARN_UNUSED_FUNCTION = YES;
387 | GCC_WARN_UNUSED_VARIABLE = YES;
388 | IPHONEOS_DEPLOYMENT_TARGET = 8.4;
389 | MTL_ENABLE_DEBUG_INFO = NO;
390 | SDKROOT = iphoneos;
391 | TARGETED_DEVICE_FAMILY = "1,2";
392 | VALIDATE_PRODUCT = YES;
393 | };
394 | name = Release;
395 | };
396 | 32F65A421B968A7400614E7E /* Debug */ = {
397 | isa = XCBuildConfiguration;
398 | buildSettings = {
399 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
400 | INFOPLIST_FILE = "$(SRCROOT)/IKAsyncableApp/Info.plist";
401 | IPHONEOS_DEPLOYMENT_TARGET = 8.0;
402 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks";
403 | PRODUCT_BUNDLE_IDENTIFIER = "com.mustard.$(PRODUCT_NAME:rfc1034identifier)";
404 | PRODUCT_NAME = "$(TARGET_NAME)";
405 | };
406 | name = Debug;
407 | };
408 | 32F65A431B968A7400614E7E /* Release */ = {
409 | isa = XCBuildConfiguration;
410 | buildSettings = {
411 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
412 | INFOPLIST_FILE = "$(SRCROOT)/IKAsyncableApp/Info.plist";
413 | IPHONEOS_DEPLOYMENT_TARGET = 8.0;
414 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks";
415 | PRODUCT_BUNDLE_IDENTIFIER = "com.mustard.$(PRODUCT_NAME:rfc1034identifier)";
416 | PRODUCT_NAME = "$(TARGET_NAME)";
417 | };
418 | name = Release;
419 | };
420 | 32F65A451B968A7400614E7E /* Debug */ = {
421 | isa = XCBuildConfiguration;
422 | buildSettings = {
423 | BUNDLE_LOADER = "$(TEST_HOST)";
424 | FRAMEWORK_SEARCH_PATHS = (
425 | "$(SDKROOT)/Developer/Library/Frameworks",
426 | "$(inherited)",
427 | );
428 | GCC_PREPROCESSOR_DEFINITIONS = (
429 | "DEBUG=1",
430 | "$(inherited)",
431 | );
432 | INFOPLIST_FILE = IKAsyncableTests/Info.plist;
433 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks";
434 | PRODUCT_BUNDLE_IDENTIFIER = "com.mustard.$(PRODUCT_NAME:rfc1034identifier)";
435 | PRODUCT_NAME = "$(TARGET_NAME)";
436 | TEST_HOST = "$(BUILT_PRODUCTS_DIR)/IKAsyncable.app/IKAsyncable";
437 | };
438 | name = Debug;
439 | };
440 | 32F65A461B968A7400614E7E /* Release */ = {
441 | isa = XCBuildConfiguration;
442 | buildSettings = {
443 | BUNDLE_LOADER = "$(TEST_HOST)";
444 | FRAMEWORK_SEARCH_PATHS = (
445 | "$(SDKROOT)/Developer/Library/Frameworks",
446 | "$(inherited)",
447 | );
448 | INFOPLIST_FILE = IKAsyncableTests/Info.plist;
449 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks";
450 | PRODUCT_BUNDLE_IDENTIFIER = "com.mustard.$(PRODUCT_NAME:rfc1034identifier)";
451 | PRODUCT_NAME = "$(TARGET_NAME)";
452 | TEST_HOST = "$(BUILT_PRODUCTS_DIR)/IKAsyncable.app/IKAsyncable";
453 | };
454 | name = Release;
455 | };
456 | /* End XCBuildConfiguration section */
457 |
458 | /* Begin XCConfigurationList section */
459 | 32F65A1D1B968A7400614E7E /* Build configuration list for PBXProject "IKAsyncable" */ = {
460 | isa = XCConfigurationList;
461 | buildConfigurations = (
462 | 32F65A3F1B968A7400614E7E /* Debug */,
463 | 32F65A401B968A7400614E7E /* Release */,
464 | );
465 | defaultConfigurationIsVisible = 0;
466 | defaultConfigurationName = Release;
467 | };
468 | 32F65A411B968A7400614E7E /* Build configuration list for PBXNativeTarget "IKAsyncable" */ = {
469 | isa = XCConfigurationList;
470 | buildConfigurations = (
471 | 32F65A421B968A7400614E7E /* Debug */,
472 | 32F65A431B968A7400614E7E /* Release */,
473 | );
474 | defaultConfigurationIsVisible = 0;
475 | defaultConfigurationName = Release;
476 | };
477 | 32F65A441B968A7400614E7E /* Build configuration list for PBXNativeTarget "IKAsyncableTests" */ = {
478 | isa = XCConfigurationList;
479 | buildConfigurations = (
480 | 32F65A451B968A7400614E7E /* Debug */,
481 | 32F65A461B968A7400614E7E /* Release */,
482 | );
483 | defaultConfigurationIsVisible = 0;
484 | defaultConfigurationName = Release;
485 | };
486 | /* End XCConfigurationList section */
487 | };
488 | rootObject = 32F65A1A1B968A7400614E7E /* Project object */;
489 | }
490 |
--------------------------------------------------------------------------------