11import CoreLocation
22
33extension CLLocationManager {
4- /// Requests the user’s permission to use location services while the app is
5- /// in use.
4+ /// Requests the user’s permission to use location services while the app is in use.
65 ///
7- /// This is a wrapper around
8- /// `CLLocationManager.requestWhenInUseAuthorization()`, providing an async
9- /// API.
6+ /// This is a wrapper around `CLLocationManager.requestWhenInUseAuthorization()`, providing an
7+ /// async API.
108 ///
11- /// While the user is making a selection the `delegate` will be set to a
12- /// custom delegate used for handling the
13- /// `locationManagerDidChangeAuthorization(_:)` function . When the user has
14- /// made a selection the `delegate` will be set back to the original value.
9+ /// While the user is making a selection the `delegate` will be set to a custom delegate used
10+ /// for handling the `locationManagerDidChangeAuthorization(_:)` function. This delegate will
11+ /// forward all calls to the current delegate . When the user has made a selection the
12+ /// `delegate` will be set back to the original value.
1513 public func requestWhenInUseAuthorization( ) async -> CLAuthorizationStatus {
14+ #if os(macOS)
15+ switch authorizationStatus {
16+ case . authorizedAlways, . denied, . restricted:
17+ return authorizationStatus
18+ case . notDetermined:
19+ break
20+ @unknown default :
21+ break
22+ }
23+ #endif
24+
1625 return await withCheckedContinuation { continuation in
17- let previousDelegate = delegate
18- let ownDelegate = LocationDelegate { [ self ] status in
19- self . delegate = previousDelegate
26+ let ownDelegate = LocationDelegate { status in
2027 continuation. resume ( returning: status)
2128 }
22- delegate = ownDelegate
29+ ownDelegate. attach ( to : self )
2330 requestWhenInUseAuthorization ( )
2431 }
2532 }
2633
27- /// Requests the user’s permission to use location services regardless of
28- /// whether the app is in use.
34+ /// Requests the user’s permission to use location services regardless of whether the app is in
35+ /// use.
2936 ///
30- /// This is a wrapper around
31- /// `CLLocationManager.requestAlwaysAuthorization()`, providing an async
32- /// API.
37+ /// This is a wrapper around `CLLocationManager.requestAlwaysAuthorization()`, providing an
38+ /// async API.
3339 ///
34- /// While the user is making a selection the `delegate` will be set to a
35- /// custom delegate used for handling the
36- /// `locationManagerDidChangeAuthorization(_:)` function . When the user has
37- /// made a selection the `delegate` will be set back to the original value.
40+ /// While the user is making a selection the `delegate` will be set to a custom delegate used
41+ /// for handling the `locationManagerDidChangeAuthorization(_:)` function. This delegate will
42+ /// forward all calls to the current delegate . When the user has made a selection the
43+ /// `delegate` will be set back to the original value.
3844 public func requestAlwaysAuthorization( ) async -> CLAuthorizationStatus {
45+ #if os(macOS)
46+ switch authorizationStatus {
47+ case . authorizedAlways, . denied, . restricted:
48+ return authorizationStatus
49+ case . notDetermined:
50+ break
51+ @unknown default :
52+ break
53+ }
54+ #else
55+ switch authorizationStatus {
56+ case . authorizedAlways, . authorizedWhenInUse, . denied, . restricted:
57+ return authorizationStatus
58+ case . notDetermined:
59+ break
60+ @unknown default :
61+ break
62+ }
63+ #endif
64+
3965 return await withCheckedContinuation { continuation in
40- let previousDelegate = delegate
41- let ownDelegate = LocationDelegate { [ self ] status in
42- self . delegate = previousDelegate
66+ let ownDelegate = LocationDelegate { status in
4367 continuation. resume ( returning: status)
4468 }
45- delegate = ownDelegate
69+ ownDelegate. attach ( to : self )
4670 requestAlwaysAuthorization ( )
4771 }
4872 }
@@ -51,20 +75,70 @@ extension CLLocationManager {
5175private final class LocationDelegate : NSObject , CLLocationManagerDelegate {
5276 private let authorizationDidChangeHandler : ( _ status: CLAuthorizationStatus ) -> Void
5377
54- /// A reference to `self`, used to prevent the delegate for being
55- /// deallocated while the user is making a selection.
56- private var strongSelf : LocationDelegate ?
78+ /// The original delegate to forward calls to while this delegate is installed.
79+ private weak var originalDelegate : CLLocationManagerDelegate ?
5780
5881 init ( authorizationDidChangeHandler: @escaping ( _ status: CLAuthorizationStatus ) -> Void ) {
5982 self . authorizationDidChangeHandler = authorizationDidChangeHandler
6083
6184 super. init ( )
85+ }
6286
63- strongSelf = self
87+ func attach( to locationManager: CLLocationManager ) {
88+ originalDelegate = locationManager. delegate
89+ locationManager. delegate = self
90+ // Keep a strong reference via the location manager to enable it to be deinitialised.
91+ locationManager. _gatheredKitLocationDelegate = self
6492 }
6593
94+ // MARK: - Authorization changes
95+
6696 func locationManagerDidChangeAuthorization( _ manager: CLLocationManager ) {
6797 authorizationDidChangeHandler ( manager. authorizationStatus)
68- strongSelf = nil
98+ // Forward to original delegate if it implements this method
99+ originalDelegate? . locationManagerDidChangeAuthorization ? ( manager)
100+ manager. delegate = originalDelegate
101+ // This will release `self`.
102+ manager. _gatheredKitLocationDelegate = nil
103+ }
104+
105+ // MARK: - Forwarding other delegate methods
106+
107+ override func responds( to aSelector: Selector ! ) -> Bool {
108+ if originalDelegate? . responds ( to: aSelector) == true {
109+ return true
110+ }
111+ return super. responds ( to: aSelector)
112+ }
113+
114+ override func forwardingTarget( for aSelector: Selector ! ) -> Any ? {
115+ if originalDelegate? . responds ( to: aSelector) == true {
116+ return originalDelegate
117+ }
118+ return super. forwardingTarget ( for: aSelector)
119+ }
120+ }
121+
122+ extension CLLocationManager {
123+ @inline ( never) private static var __associated_gatheredKitLocationDelegateKey : UnsafeRawPointer {
124+ let closure : @convention ( c) ( ) -> Void = { }
125+ return unsafeBitCast ( closure, to: UnsafeRawPointer . self)
126+ }
127+
128+ fileprivate var _gatheredKitLocationDelegate : LocationDelegate ? {
129+ get {
130+ objc_getAssociatedObject (
131+ self ,
132+ Self . __associated_gatheredKitLocationDelegateKey
133+ ) as? LocationDelegate
134+ }
135+ set {
136+ objc_setAssociatedObject (
137+ self ,
138+ Self . __associated_gatheredKitLocationDelegateKey,
139+ newValue,
140+ . OBJC_ASSOCIATION_RETAIN_NONATOMIC
141+ )
142+ }
69143 }
70144}
0 commit comments