Fix infinite loop when monitoring regions when exceeding maximums (#1286)

At least on iOS 13 and 14, initially monitoring a region causes the system to always fire a did-change-state delegate method for that zone. When users exceed the 20 region threshold, the delegate firing can cause us to bounce back and forth between sets of zones that we want to monitor, and each chain can cause another set of updates to occur. This is especially apparent for users using the iCloud3 integration which creates a multitude of 1m zones.

Fixes #1267
This commit is contained in:
Zac West
2020-11-20 13:11:32 -08:00
committed by GitHub
parent a842552d67
commit 8d6f78e954
4 changed files with 33 additions and 0 deletions

View File

@@ -139,6 +139,8 @@ class ZoneManager {
"region": String(describing: region)
]
))
collector.ignoreNextState(for: region)
locationManager.startMonitoring(for: region)
}

View File

@@ -9,11 +9,18 @@ protocol ZoneManagerCollectorDelegate: AnyObject {
protocol ZoneManagerCollector: CLLocationManagerDelegate {
var delegate: ZoneManagerCollectorDelegate? { get set }
func ignoreNextState(for region: CLRegion)
}
class ZoneManagerCollectorImpl: NSObject, ZoneManagerCollector {
weak var delegate: ZoneManagerCollectorDelegate?
private var ignoredNextRegions = Set<CLRegion>()
func ignoreNextState(for region: CLRegion) {
ignoredNextRegions.insert(region)
}
func locationManager(
_ manager: CLLocationManager,
didFailWithError error: Error
@@ -41,6 +48,11 @@ class ZoneManagerCollectorImpl: NSObject, ZoneManagerCollector {
didDetermineState state: CLRegionState,
for region: CLRegion
) {
guard !ignoredNextRegions.contains(region) else {
ignoredNextRegions.remove(region)
return
}
let zone = Current.realm()
.objects(RLMZone.self)
.first(where: {

View File

@@ -119,6 +119,7 @@ class ZoneManagerTests: XCTestCase {
XCTAssertEqual(locationManager.monitoredRegions, currentRegions)
XCTAssertEqual(locationManager.stopMonitoringRegions.hackilySorted(), removedRegions.hackilySorted())
XCTAssertEqual(locationManager.startMonitoringRegions.hackilySorted(), addedRegions.hackilySorted())
XCTAssertEqual(collector.ignoringNextStates, Set(addedRegions))
// remove a zone
try realm.write {
@@ -132,6 +133,7 @@ class ZoneManagerTests: XCTestCase {
XCTAssertEqual(locationManager.monitoredRegions, currentRegions)
XCTAssertEqual(locationManager.stopMonitoringRegions.hackilySorted(), removedRegions.hackilySorted())
XCTAssertEqual(locationManager.startMonitoringRegions.hackilySorted(), addedRegions.hackilySorted())
XCTAssertEqual(collector.ignoringNextStates, Set(addedRegions))
withExtendedLifetime(manager) { /* silences unused variable */ }
}
@@ -409,7 +411,11 @@ private extension Array where Element: CLRegion {
private class FakeCollector: NSObject, ZoneManagerCollector {
var delegate: ZoneManagerCollectorDelegate?
var ignoringNextStates = Set<CLRegion>()
func ignoreNextState(for region: CLRegion) {
ignoringNextStates.insert(region)
}
}
private class FakeProcessor: ZoneManagerProcessor {

View File

@@ -162,7 +162,20 @@ class ZoneManagerCollectorTests: XCTestCase {
XCTAssertEqual(event.eventType, .locationChange(locations))
XCTAssertNil(event.associatedZone)
}
func testIgnoredRegions() {
let region1 = CLCircularRegion(center: .init(latitude: 1, longitude: 2), radius: 30, identifier: "1")
let region2 = CLCircularRegion(center: .init(latitude: 2, longitude: 1), radius: 30, identifier: "2")
collector.ignoreNextState(for: region1)
collector.locationManager(locationManager, didDetermineState: .inside, for: region1)
collector.locationManager(locationManager, didDetermineState: .inside, for: region2)
XCTAssertEqual(delegate.events, [.init(eventType: .region(region2, .inside), associatedZone: nil)])
collector.locationManager(locationManager, didDetermineState: .outside, for: region1)
XCTAssertEqual(delegate.events, [
.init(eventType: .region(region2, .inside), associatedZone: nil),
.init(eventType: .region(region1, .outside), associatedZone: nil)
])
}
}