Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 6 additions & 1 deletion Sources/Apollo/Promise.swift
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import Dispatch
func whenAll<Value>(_ promises: [Promise<Value>], notifyOn queue: DispatchQueue = .global()) -> Promise<[Value]> {
return Promise { (fulfill, reject) in
let group = DispatchGroup()
var rejected = false

for promise in promises {
group.enter()
Expand All @@ -11,11 +12,15 @@ func whenAll<Value>(_ promises: [Promise<Value>], notifyOn queue: DispatchQueue
group.leave()
}.catch { error in
reject(error)
rejected = true
group.leave()
}
}

group.notify(queue: queue) {
fulfill(promises.map { $0.result!.value! })
if !rejected {
fulfill(promises.map { $0.result!.value! })
}
}
}
}
Expand Down
7 changes: 6 additions & 1 deletion Sources/Apollo/ResultOrPromise.swift
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ func whenAll<Value>(_ resultsOrPromises: [ResultOrPromise<Value>], notifyOn queu

return .promise(Promise { (fulfill, reject) in
let group = DispatchGroup()
var rejected = false

for resultOrPromise in resultsOrPromises {
group.enter()
Expand All @@ -27,11 +28,15 @@ func whenAll<Value>(_ resultsOrPromises: [ResultOrPromise<Value>], notifyOn queu
group.leave()
}.catch { error in
reject(error)
rejected = true
group.leave()
}
}

group.notify(queue: queue) {
fulfill(resultsOrPromises.map { $0.result!.value! })
if !rejected {
fulfill(resultsOrPromises.map { $0.result!.value! })
}
}
})
}
Expand Down
30 changes: 30 additions & 0 deletions Tests/ApolloTests/PromiseTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -249,4 +249,34 @@ class PromiseTests: XCTestCase {

waitForExpectations(timeout: 1)
}

func testWhenAllRejectsWhenAnyOfThePromisesRejectsAsync_doesNotCreateMemoryLeak() throws {
var executeReject: ((Error) -> Void)?

var promises: [Promise<String>] = [Promise(fulfilled: "foo"),
Promise<String> { _, reject in executeReject = reject },
Promise(fulfilled: "bar")]
weak var weakPromise0 = promises[0]
weak var weakPromise1 = promises[1]
weak var weakPromise2 = promises[2]

let expectation = self.expectation(description: "whenAll catch handler invoked")

whenAll(promises).catch { error in
XCTAssert(error is TestError)

expectation.fulfill()
}

promises = []
executeReject?(TestError())
executeReject = nil

waitForExpectations(timeout: 1)

XCTAssertNil(weakPromise0)
XCTAssertNil(weakPromise1)
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So before this fix, this particular assert would fail, correct?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actually, all three of the asserts were failing. The notify block references the entire array of promises, so all of them are retained.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

oof, ouch. well this is definitely an improvement.

XCTAssertNil(weakPromise2)
}

}