Skip to content

Commit

Permalink
First attempt at a parallel promises implementation.
Browse files Browse the repository at this point in the history
  • Loading branch information
jkolb committed Nov 16, 2016
1 parent 548dc5a commit b540ad3
Show file tree
Hide file tree
Showing 8 changed files with 600 additions and 27 deletions.
12 changes: 12 additions & 0 deletions FranticApparatus.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,9 @@
objects = {

/* Begin PBXBuildFile section */
E540D8691DDC78B700907960 /* AllPromises.swift in Sources */ = {isa = PBXBuildFile; fileRef = E540D8671DDC78B700907960 /* AllPromises.swift */; };
E540D86A1DDC78B700907960 /* AnyPromises.swift in Sources */ = {isa = PBXBuildFile; fileRef = E540D8681DDC78B700907960 /* AnyPromises.swift */; };
E540D86C1DDC78BE00907960 /* RacePromises.swift in Sources */ = {isa = PBXBuildFile; fileRef = E540D86B1DDC78BE00907960 /* RacePromises.swift */; };
E55CA21719C676DC00E66980 /* FranticApparatus.h in Headers */ = {isa = PBXBuildFile; fileRef = E55CA21619C676DC00E66980 /* FranticApparatus.h */; settings = {ATTRIBUTES = (Public, ); }; };
E55CA21D19C676DC00E66980 /* FranticApparatus.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = E55CA21119C676DC00E66980 /* FranticApparatus.framework */; };
E57F6CE41D8DDA49006807C4 /* Deferred.swift in Sources */ = {isa = PBXBuildFile; fileRef = E57F6CD81D8DDA49006807C4 /* Deferred.swift */; };
Expand Down Expand Up @@ -34,6 +37,9 @@
/* End PBXContainerItemProxy section */

/* Begin PBXFileReference section */
E540D8671DDC78B700907960 /* AllPromises.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AllPromises.swift; sourceTree = "<group>"; };
E540D8681DDC78B700907960 /* AnyPromises.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AnyPromises.swift; sourceTree = "<group>"; };
E540D86B1DDC78BE00907960 /* RacePromises.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = RacePromises.swift; sourceTree = "<group>"; };
E55CA21119C676DC00E66980 /* FranticApparatus.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = FranticApparatus.framework; sourceTree = BUILT_PRODUCTS_DIR; };
E55CA21519C676DC00E66980 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
E55CA21619C676DC00E66980 /* FranticApparatus.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = FranticApparatus.h; sourceTree = "<group>"; };
Expand Down Expand Up @@ -132,6 +138,8 @@
E57F6CD71D8DDA49006807C4 /* Sources */ = {
isa = PBXGroup;
children = (
E540D8671DDC78B700907960 /* AllPromises.swift */,
E540D8681DDC78B700907960 /* AnyPromises.swift */,
E57F6CD81D8DDA49006807C4 /* Deferred.swift */,
E57F6CD91D8DDA49006807C4 /* Dispatcher.swift */,
E57F6CDB1D8DDA49006807C4 /* GCDDispatcher.swift */,
Expand All @@ -140,6 +148,7 @@
E57F6CDE1D8DDA49006807C4 /* Promise.swift */,
E57F6CDF1D8DDA49006807C4 /* PromiseError.swift */,
E57F6CE01D8DDA49006807C4 /* PromiseMaker.swift */,
E540D86B1DDC78BE00907960 /* RacePromises.swift */,
E57F6CE11D8DDA49006807C4 /* Result.swift */,
E57F6CE21D8DDA49006807C4 /* State.swift */,
E57F6CE31D8DDA49006807C4 /* Thenable.swift */,
Expand Down Expand Up @@ -266,7 +275,9 @@
isa = PBXSourcesBuildPhase;
buildActionMask = 2147483647;
files = (
E540D86C1DDC78BE00907960 /* RacePromises.swift in Sources */,
E57F6CEF1D8DDA49006807C4 /* Thenable.swift in Sources */,
E540D86A1DDC78B700907960 /* AnyPromises.swift in Sources */,
E57F6CE81D8DDA49006807C4 /* Lock.swift in Sources */,
E57F6CEA1D8DDA49006807C4 /* Promise.swift in Sources */,
E57F6CEE1D8DDA49006807C4 /* State.swift in Sources */,
Expand All @@ -277,6 +288,7 @@
E57F6CEB1D8DDA49006807C4 /* PromiseError.swift in Sources */,
E57F6CE71D8DDA49006807C4 /* GCDDispatcher.swift in Sources */,
E57F6CEC1D8DDA49006807C4 /* PromiseMaker.swift in Sources */,
E540D8691DDC78B700907960 /* AllPromises.swift in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
Expand Down
13 changes: 13 additions & 0 deletions FranticApparatusDemo/FranticApparatusDemo/Info.plist
Original file line number Diff line number Diff line change
Expand Up @@ -51,5 +51,18 @@
<string>UIInterfaceOrientationLandscapeLeft</string>
<string>UIInterfaceOrientationLandscapeRight</string>
</array>
<key>NSAppTransportSecurity</key>
<dict>
<key>NSExceptionDomains</key>
<dict>
<key>redditmedia.com</key>
<dict>
<key>NSExceptionAllowsInsecureHTTPLoads</key>
<true/>
<key>NSIncludesSubdomains</key>
<true/>
</dict>
</dict>
</dict>
</dict>
</plist>
133 changes: 112 additions & 21 deletions FranticApparatusDemo/FranticApparatusDemo/RootViewController.swift
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,10 @@
import UIKit
import FranticApparatus

enum JSONError : Error {
case unexpectedJSON
}

class RootViewController : UIViewController, UICollectionViewDataSource, UICollectionViewDelegateFlowLayout {
var networkAPI: NetworkAPI!
var collectionView: UICollectionView!
Expand All @@ -41,6 +45,8 @@ class RootViewController : UIViewController, UICollectionViewDataSource, UIColle
var images = [IndexPath : UIImage](minimumCapacity: 8)
var errors = [IndexPath : Error](minimumCapacity: 8)
var promises = [IndexPath : Promise<UIImage>](minimumCapacity: 8)
var thumbnailsPromise: Promise<[UIImage]>?
var thumbnails = [UIImage]()

override func viewDidLoad() {
super.viewDidLoad()
Expand All @@ -65,12 +71,58 @@ class RootViewController : UIViewController, UICollectionViewDataSource, UIColle
networkAPI = NetworkAPI(dispatcher: networkDispatcher, networkLayer: networkLayer)
}

override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)

if thumbnailsPromise == nil {
thumbnailsPromise = PromiseMaker.makeUsing(context: self) { (makePromise) in
makePromise { (context) -> Promise<NSDictionary> in
context.networkAPI.requestJSONObjectForURL(URL(string: "https://reddit.com/.json")!)
}.whenFulfilledThenPromise { (context, object) -> Promise<[UIImage]> in
let thumbnailURLs = try context.thumbnailsFromJSON(object: object)
let thumbnailPromises = thumbnailURLs.map({ context.networkAPI.requestImageForURL($0) })
return all(thumbnailPromises)
}.whenFulfilled { (context, thumbnails) in
context.thumbnails = thumbnails
context.collectionView.reloadData()
}.whenRejected { (context, reason) in
NSLog("\(reason)")
}.whenComplete { (context) in
context.thumbnailsPromise = nil
}
}
}
}

func thumbnailsFromJSON(object: NSDictionary) throws -> [URL] {
guard let data = object["data"] as? NSDictionary else { throw JSONError.unexpectedJSON }
guard let children = data["children"] as? NSArray else { throw JSONError.unexpectedJSON }
var thumbnailURLs = [URL]()
thumbnailURLs.reserveCapacity(children.count)

for child in children {
if let childObject = child as? NSDictionary {
if let childData = childObject["data"] as? NSDictionary {
if let thumbnail = childData["thumbnail"] as? NSString {
if thumbnail.hasPrefix("http") {
if let thumbnailURL = URL(string: thumbnail as String) {
thumbnailURLs.append(thumbnailURL)
}
}
}
}
}
}

return thumbnailURLs
}

func loadImage(at indexPath: IndexPath) {
let model = models[indexPath.item]

promises[indexPath] = PromiseMaker.makeUsing(context: self) { (makePromise) in
makePromise { (context) in
return context.networkAPI.requestImageForURL(model.url)
makePromise { (context) -> Promise<UIImage> in
context.networkAPI.requestImageForURL(model.url)
}.whenFulfilled { (context, image) in
context.images[indexPath] = image
context.showImage(at: indexPath)
Expand Down Expand Up @@ -125,38 +177,77 @@ class RootViewController : UIViewController, UICollectionViewDataSource, UIColle
}
}

func numberOfSections(in collectionView: UICollectionView) -> Int {
if thumbnails.count == 0 {
return 1
}
else {
return 2
}
}

func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return models.count
if section == 0 {
return models.count
}
else if section == 1 {
return thumbnails.count
}

return 0
}

func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
if let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "imageCell", for: indexPath) as? ImageCell {
if let image = images[indexPath] {
cell.hideActivity()
cell.image = image
if indexPath.section == 0 {
if let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "imageCell", for: indexPath) as? ImageCell {
if let image = images[indexPath] {
cell.hideActivity()
cell.image = image
}
else if let error = errors[indexPath] {
cell.hideActivity()
cell.error = messageFor(error: error)
}
else {
cell.showActivity()

if promises[indexPath] == nil {
loadImage(at: indexPath)
}
}

return cell
}
else if let error = errors[indexPath] {
else {
fatalError("No cell to display")
}
}
else if indexPath.section == 1 {
if let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "imageCell", for: indexPath) as? ImageCell {
let thumbnail = thumbnails[indexPath.item]
cell.hideActivity()
cell.error = messageFor(error: error)
cell.image = thumbnail

return cell
}
else {
cell.showActivity()

if promises[indexPath] == nil {
loadImage(at: indexPath)
}
fatalError("No cell to display")
}

return cell
}
else {
fatalError("No cell to display")
}

fatalError("Unexpected indexPath \(indexPath)")
}

func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {
let model = models[indexPath.item]
if indexPath.section == 0 {
let model = models[indexPath.item]

return CGSize(width: model.width, height: model.height)
}
else if indexPath.section == 1 {
return thumbnails[indexPath.item].size
}

return CGSize(width: model.width, height: model.height)
return CGSize.zero
}
}
Loading

0 comments on commit b540ad3

Please sign in to comment.