I'm making use of CLLocation to determine the user's place in PageViewController, and need to use the returned location from locationManager(_:didUpdateLocations:)
writing to Realm database, then using the Realm in my function in PageViewController's content page.The content page is implemented in a separate source file(view controller). so:
first, locating success,
second, write the location to realm,
third, after the second step success, call the function in content page.
And I use the NSOperationQueue with dependencies to control above steps, but the delegate function locationManager(_:didUpdateLocations:)
seems never called, and cause the app crash when the code want to read the realm data:
Pls see following my code:
PageViewController
class PageViewController: UIPageViewController {
static var isFirstLaunch: Bool = true
let locationManager: CLLocationManager = CLLocationManager()
override func viewDidLoad() {
super.viewDidLoad()
locationManager.delegate = self
locationManager.requestWhenInUseAuthorization()
locationManager.desiredAccuracy = kCLLocationAccuracyHundredMeters
locationManager.distanceFilter = 3000
launchQueue = OperationQueue()
let locationOperation = BlockOperation {
OperationQueue.main.addOperation {
self.locationManager.requestLocation()
}
}
locationOperation.completionBlock = {
print("locationOperation finished, finished:\(locationOperation.isFinished)") //"locationOperation finished, finished:true" in console
}
let firstLaunchOperation = BlockOperation {
PageViewController.isFirstLaunch = false
}
firstLaunchOperation.completionBlock = {
print("firstLaunchOperation finished, finished:\(firstLaunchOperation.isFinished)") //"firstLaunchOperation finished, finished:true" in console
}
firstLaunchOperation.addDependency(locationOperation)
launchQueue.addOperation(locationOperation)
launchQueue.addOperation(firstLaunchOperation)
}
ContentViewController(in a separate source file)
class ContentViewController: UIViewController {
let defaultRealm = try! Realm()
let config = Realm.Configuration(fileURL: Bundle.main.url(forResource: "areaID", withExtension: "realm"), readOnly: true)
override func viewDidLoad() {
super.viewDidLoad()
UISetup()
autolayoutView()
if !PageViewController.isFirstLaunch {
upateWeather()
}
}
func upateWeather() {
let userArea = defaultRealm.objects(UserArea.self)
let place = userArea.first?.areas
let locality = place?[currentPage].locality
let subLocality = place?[currentPage].subLocality
let areaIDRealm = try! Realm(configuration: config)
let results = areaIDRealm.objects(RealmObject.self).filter("locality = '\(locality!)' AND subLocality = '\(subLocality!)'")
//Crashed here! fatal error: unexpectedly found nil while unwrapping an Optional value. I opened the realm, and no locality and subLocality write in the Realm.
}
}
CLLocationManagerDelegate
extension PageViewController: CLLocationManagerDelegate {
func locationManager(_ manager: CLLocationManager, didUpdateLocations locations: [CLLocation]) {
let currentLocation: CLLocation = locations[0]
let geocoder: CLGeocoder = CLGeocoder()
if currentLocation.horizontalAccuracy > 0 {
geocoder.reverseGeocodeLocation(currentLocation, completionHandler: {(placeMarks, error) in
if error == nil {
guard let placemark = placeMarks!.first else { return }
let userArea = self.defaultRealm.objects(UserArea.self)
func locationToRealm(place: String, subPlace: String) {
if let gpsLocation = userArea.first?.areas.first {
self.defaultRealm.beginWrite()
gpsLocation.locality = place
gpsLocation.subLocality = subPlace
try! self.defaultRealm.commitWrite()
} else {
try! self.defaultRealm.write {
self.defaultRealm.create(UserArea.self, value: [[["locality": place, "subLocality": subPlace]]])
}
}
}
if let place: String = placemark.locality {
if let subPlace: String = placemark.subLocality {
locationToRealm(place: place, subPlace: subPlace)
} else {
locationToRealm(place: place, subPlace: "---")
}
} else {
if let subPlace: String = placemark.subLocality {
locationToRealm(place: "---", subPlace: subPlace)
}
}
}
})
}
}
func locationManager(_ manager: CLLocationManager, didFailWithError error: Error) {......}
}
How can I use NSOperationQueue to control the code, make them step by step! Thank you very much for the help!
Okay, I've examined the logic and I think I can see what's happening.
There's no point in placing self.locationManager.requestLocation()
in an operation queue (especially on the main queue). According to the documentation, requestLocation()
returns immediately, and the actual request to Location Services happens in a background thread controlled by the system.
When Location Services has determined the device location, the delegate method didUpdateLocations
is called, but this can easily take up to several seconds.
In the meantime, you're loading ContentViewController
instantly, and this is triggering updateWeather()
before Location Services has had a chance to call the delegate and insert the data into Realm for the first time.
This is a completely normal logic model. It would make sense to show the view controller with some kind of loading spinner, and to subsequently update it after Location Services has completed. As such, you should make sure that updateWeather()
is able to work, even if the Realm file is empty, and you should also incorporate logic to update the UI once the Realm file does actually have data.
The easiest thing to do would be to check that locality
and subLocality
aren't nil
and only performing the query when they aren't.
func upateWeather() {
let userArea = defaultRealm.objects(UserArea.self)
let place = userArea.first?.areas
let locality = place?[currentPage].locality
let subLocality = place?[currentPage].subLocality
let areaIDRealm = try! Realm(configuration: config)
// Only proceed if the Realm query variables aren't nil
guard let locality = locality, subLocality = subLocality else {
return
}
let results = areaIDRealm.objects(RealmObject.self).filter("locality = '\(locality!)' AND subLocality = '\(subLocality!)'")
//Crashed here! fatal error: unexpectedly found nil while unwrapping an Optional value. I opened the realm, and no locality and subLocality write in the Realm.
}
You'll then need to call updateWeather()
again once the delegate has completed writing to Realm to try again.