Skip to content

Commit 9e7dca5

Browse files
committed
'Cancel' for PromiseKit --
* Eliminate duplicate code in the cancellable extensions * When using the 'cancellable' function with the extensions, ensure that the underlying task (if any) is always cancelled * When using the 'cancellable' function, call 'reject' on the underlying promise (wherever possible) if 'cancel' is invoked * Create cancellable wrappers for the extensions only for the cases where there is an underlying task that supports cancellation
1 parent 86b8076 commit 9e7dca5

File tree

4 files changed

+30
-276
lines changed

4 files changed

+30
-276
lines changed

Sources/CLGeocoder+Promise.swift

Lines changed: 0 additions & 51 deletions
Original file line numberDiff line numberDiff line change
@@ -71,54 +71,3 @@ extension CLGeocoder {
7171
// return self == .geocodeCanceled
7272
// }
7373
//}
74-
75-
//////////////////////////////////////////////////////////// Cancellation
76-
77-
extension CLGeocoder {
78-
/// Submits a reverse-geocoding request for the specified location.
79-
public func cancellableReverseGeocode(location: CLLocation) -> CancellablePromise<[CLPlacemark]> {
80-
return CancellablePromise { seal in
81-
reverseGeocodeLocation(location, completionHandler: seal.resolve)
82-
}
83-
}
84-
85-
/// Submits a forward-geocoding request using the specified address dictionary.
86-
@available(iOS, deprecated: 11.0)
87-
public func cancellableGeocode(_ addressDictionary: [String: String]) -> CancellablePromise<[CLPlacemark]> {
88-
return CancellablePromise { seal in
89-
geocodeAddressDictionary(addressDictionary, completionHandler: seal.resolve)
90-
}
91-
}
92-
93-
/// Submits a forward-geocoding request using the specified address string.
94-
public func cancellableGeocode(_ addressString: String) -> CancellablePromise<[CLPlacemark]> {
95-
return CancellablePromise { seal in
96-
geocodeAddressString(addressString, completionHandler: seal.resolve)
97-
}
98-
}
99-
100-
/// Submits a forward-geocoding request using the specified address string within the specified region.
101-
public func cancellableGeocode(_ addressString: String, region: CLRegion?) -> CancellablePromise<[CLPlacemark]> {
102-
return CancellablePromise { seal in
103-
geocodeAddressString(addressString, in: region, completionHandler: seal.resolve)
104-
}
105-
}
106-
107-
#if !os(tvOS) && swift(>=3.2)
108-
/// Submits a forward-geocoding request using the specified postal address.
109-
@available(iOS 11.0, OSX 10.13, watchOS 4.0, *)
110-
public func cancellableGeocodePostalAddress(_ postalAddress: CNPostalAddress) -> CancellablePromise<[CLPlacemark]> {
111-
return CancellablePromise { seal in
112-
geocodePostalAddress(postalAddress, completionHandler: seal.resolve)
113-
}
114-
}
115-
116-
/// Submits a forward-geocoding requesting using the specified locale and postal address
117-
@available(iOS 11.0, OSX 10.13, watchOS 4.0, *)
118-
public func cancellableGeocodePostalAddress(_ postalAddress: CNPostalAddress, preferredLocale locale: Locale?) -> CancellablePromise<[CLPlacemark]> {
119-
return CancellablePromise { seal in
120-
geocodePostalAddress(postalAddress, preferredLocale: locale, completionHandler: seal.resolve)
121-
}
122-
}
123-
#endif
124-
}

Sources/CLLocationManager+Promise.swift

Lines changed: 24 additions & 219 deletions
Original file line numberDiff line numberDiff line change
@@ -97,7 +97,7 @@ extension CLLocationManager {
9797
}
9898
}
9999

100-
private class LocationManager: CLLocationManager, CLLocationManagerDelegate {
100+
private class LocationManager: CLLocationManager, CLLocationManagerDelegate, CancellableTask {
101101
let (promise, seal) = Promise<[CLLocation]>.pending()
102102
let satisfyingBlock: ((CLLocation) -> Bool)?
103103

@@ -120,6 +120,9 @@ private class LocationManager: CLLocationManager, CLLocationManagerDelegate {
120120
satisfyingBlock = block
121121
super.init()
122122
delegate = self
123+
124+
promise.setCancellableTask(self, reject: seal.reject)
125+
123126
#if !os(tvOS)
124127
startUpdatingLocation()
125128
#else
@@ -138,6 +141,13 @@ private class LocationManager: CLLocationManager, CLLocationManagerDelegate {
138141
seal.reject(error)
139142
}
140143
}
144+
145+
func cancel() {
146+
self.stopUpdatingLocation()
147+
isCancelled = true
148+
}
149+
150+
var isCancelled = false
141151
}
142152

143153

@@ -203,14 +213,16 @@ extension CLLocationManager {
203213
}
204214

205215
@available(iOS 8, *)
206-
private class AuthorizationCatcher: CLLocationManager, CLLocationManagerDelegate {
216+
private class AuthorizationCatcher: CLLocationManager, CLLocationManagerDelegate, CancellableTask {
207217
let (promise, fulfill) = Guarantee<CLAuthorizationStatus>.pending()
208218
var retainCycle: AuthorizationCatcher?
209219
let initialAuthorizationState = CLLocationManager.authorizationStatus()
210220

211221
init(type: PMKCLAuthorizationType) {
212222
super.init()
213223

224+
promise.setCancellableTask(self)
225+
214226
func ask(type: PMKCLAuthorizationType) {
215227
delegate = self
216228
retainCycle = self
@@ -263,6 +275,13 @@ private class AuthorizationCatcher: CLLocationManager, CLLocationManagerDelegate
263275
fulfill(status)
264276
}
265277
}
278+
279+
func cancel() {
280+
self.retainCycle = nil
281+
isCancelled = true
282+
}
283+
284+
var isCancelled = false
266285
}
267286

268287
#endif
@@ -306,7 +325,7 @@ private enum PMKCLAuthorizationType {
306325
case whenInUse
307326
}
308327

309-
//////////////////////////////////////////////////////////// Cancellation
328+
//////////////////////////////////////////////////////////// Cancellable wrappers
310329

311330
extension CLLocationManager {
312331
/**
@@ -325,105 +344,10 @@ extension CLLocationManager {
325344
last location.
326345
*/
327346
public class func cancellableRequestLocation(authorizationType: RequestAuthorizationType = .automatic, satisfying block: ((CLLocation) -> Bool)? = nil) -> CancellablePromise<[CLLocation]> {
328-
329-
func std() -> CancellablePromise<[CLLocation]> {
330-
return CancellableLocationManager(satisfying: block).promise
331-
}
332-
333-
func auth() -> CancellablePromise<Void> {
334-
#if os(macOS)
335-
return CancellablePromise { seal in seal.fulfill(()) }
336-
#else
337-
func auth(type: PMKCLAuthorizationType) -> CancellablePromise<Void> {
338-
return CancellableAuthorizationCatcher(type: type).promise.done(on: nil) {
339-
switch $0 {
340-
case .restricted, .denied:
341-
throw PMKError.notAuthorized
342-
default:
343-
break
344-
}
345-
}
346-
}
347-
348-
switch authorizationType {
349-
case .automatic:
350-
switch Bundle.main.permissionType {
351-
case .always, .both:
352-
return auth(type: .always)
353-
case .whenInUse:
354-
return auth(type: .whenInUse)
355-
}
356-
case .whenInUse:
357-
return auth(type: .whenInUse)
358-
case .always:
359-
return auth(type: .always)
360-
}
361-
#endif
362-
}
363-
364-
switch CLLocationManager.authorizationStatus() {
365-
case .authorizedAlways, .authorizedWhenInUse:
366-
return std()
367-
case .notDetermined:
368-
return auth().then(std)
369-
case .denied, .restricted:
370-
return CancellablePromise(error: PMKError.notAuthorized)
371-
}
347+
return cancellable(requestLocation(authorizationType: authorizationType, satisfying: block))
372348
}
373349
}
374350

375-
private class CancellableLocationManager: CLLocationManager, CLLocationManagerDelegate, CancellableTask {
376-
let (promise, seal) = CancellablePromise<[CLLocation]>.pending()
377-
let satisfyingBlock: ((CLLocation) -> Bool)?
378-
379-
init(satisfying block: ((CLLocation) -> Bool)? = nil) {
380-
satisfyingBlock = block
381-
super.init()
382-
delegate = self
383-
384-
promise.appendCancellableTask(task: self, reject: seal.reject)
385-
386-
#if !os(tvOS)
387-
startUpdatingLocation()
388-
#else
389-
requestLocation()
390-
#endif
391-
_ = self.promise.ensure {
392-
self.stopUpdatingLocation()
393-
}
394-
}
395-
396-
@objc fileprivate func locationManager(_ manager: CLLocationManager, didUpdateLocations locations: [CLLocation]) {
397-
if let block = satisfyingBlock {
398-
let satisfiedLocations = locations.filter(block)
399-
if !satisfiedLocations.isEmpty {
400-
seal.fulfill(satisfiedLocations)
401-
} else {
402-
#if os(tvOS)
403-
requestLocation()
404-
#endif
405-
}
406-
} else {
407-
seal.fulfill(locations)
408-
}
409-
}
410-
411-
@objc func locationManager(_ manager: CLLocationManager, didFailWithError error: Error) {
412-
let (domain, code) = { ($0.domain, $0.code) }(error as NSError)
413-
if code == CLError.locationUnknown.rawValue && domain == kCLErrorDomain {
414-
// Apple docs say you should just ignore this error
415-
} else {
416-
seal.reject(error)
417-
}
418-
}
419-
420-
func cancel() {
421-
self.stopUpdatingLocation()
422-
isCancelled = true
423-
}
424-
425-
var isCancelled = false
426-
}
427351

428352
#if !os(macOS)
429353

@@ -435,127 +359,8 @@ extension CLLocationManager {
435359
*/
436360
@available(iOS 8, tvOS 9, watchOS 2, *)
437361
public class func cancellableRequestAuthorization(type requestedAuthorizationType: RequestAuthorizationType = .automatic) -> CancellablePromise<CLAuthorizationStatus> {
438-
439-
let currentStatus = CLLocationManager.authorizationStatus()
440-
441-
func std(type: PMKCLAuthorizationType) -> CancellablePromise<CLAuthorizationStatus> {
442-
if currentStatus == .notDetermined {
443-
return CancellableAuthorizationCatcher(type: type).promise
444-
} else {
445-
return cancellable(Promise.value(currentStatus))
446-
}
447-
}
448-
449-
switch requestedAuthorizationType {
450-
case .always:
451-
func iOS11Check() -> CancellablePromise<CLAuthorizationStatus> {
452-
switch currentStatus {
453-
case .notDetermined, .authorizedWhenInUse:
454-
return CancellableAuthorizationCatcher(type: .always).promise
455-
default:
456-
return cancellable(Promise.value(currentStatus))
457-
}
458-
}
459-
#if PMKiOS11
460-
// ^^ define PMKiOS11 if you deploy against the iOS 11 SDK
461-
// otherwise the warning you get below cannot be removed
462-
return iOS11Check()
463-
#else
464-
if #available(iOS 11, *) {
465-
return iOS11Check()
466-
} else {
467-
return std(type: .always)
468-
}
469-
#endif
470-
471-
case .whenInUse:
472-
return std(type: .whenInUse)
473-
474-
case .automatic:
475-
if currentStatus == .notDetermined {
476-
switch Bundle.main.permissionType {
477-
case .both, .whenInUse:
478-
return CancellableAuthorizationCatcher(type: .whenInUse).promise
479-
case .always:
480-
return CancellableAuthorizationCatcher(type: .always).promise
481-
}
482-
} else {
483-
return cancellable(Promise.value(currentStatus))
484-
}
485-
}
486-
}
487-
}
488-
489-
@available(iOS 8, *)
490-
private class CancellableAuthorizationCatcher: CLLocationManager, CLLocationManagerDelegate, CancellableTask {
491-
let (promise, seal) = CancellablePromise<CLAuthorizationStatus>.pending()
492-
var retainCycle: CancellableAuthorizationCatcher?
493-
let initialAuthorizationState = CLLocationManager.authorizationStatus()
494-
495-
init(type: PMKCLAuthorizationType) {
496-
super.init()
497-
498-
promise.appendCancellableTask(task: self, reject: seal.reject)
499-
500-
func ask(type: PMKCLAuthorizationType) {
501-
delegate = self
502-
retainCycle = self
503-
504-
switch type {
505-
case .always:
506-
#if os(tvOS)
507-
fallthrough
508-
#else
509-
requestAlwaysAuthorization()
510-
#endif
511-
case .whenInUse:
512-
requestWhenInUseAuthorization()
513-
}
514-
515-
_ = promise.done { _ in
516-
self.retainCycle = nil
517-
}
518-
}
519-
520-
func iOS11Check() {
521-
switch (initialAuthorizationState, type) {
522-
case (.notDetermined, .always), (.authorizedWhenInUse, .always), (.notDetermined, .whenInUse):
523-
ask(type: type)
524-
default:
525-
seal.fulfill(initialAuthorizationState)
526-
}
527-
}
528-
529-
#if PMKiOS11
530-
// ^^ define PMKiOS11 if you deploy against the iOS 11 SDK
531-
// otherwise the warning you get below cannot be removed
532-
iOS11Check()
533-
#else
534-
if #available(iOS 11, *) {
535-
iOS11Check()
536-
} else {
537-
if initialAuthorizationState == .notDetermined {
538-
ask(type: type)
539-
} else {
540-
seal.fulfill(initialAuthorizationState)
541-
}
542-
}
543-
#endif
362+
return cancellable(requestAuthorization(type: requestedAuthorizationType))
544363
}
545-
546-
@objc fileprivate func locationManager(_ manager: CLLocationManager, didChangeAuthorization status: CLAuthorizationStatus) {
547-
// `didChange` is a lie; it fires this immediately with the current status.
548-
if status != initialAuthorizationState {
549-
seal.fulfill(status)
550-
}
551-
}
552-
553-
func cancel() {
554-
self.retainCycle = nil
555-
isCancelled = true
556-
}
557-
558-
var isCancelled = false
559364
}
560365

561366
#endif

0 commit comments

Comments
 (0)