iOS Swift - Презареждане на функцията за местоположение с NSTimer за фон на приложението не работи

Имам проблем с услугите за местоположение. Не мога да настроя функция, която актуализира координатите на местоположението ми във фонов режим чрез NSTimer. Ето моят код от appDelegate:

var locationManager = CLLocationManager()

func applicationDidEnterBackground(application: UIApplication) {

    self.locationManager.delegate = self
    self.locationManager.desiredAccuracy = kCLLocationAccuracyBest

    self.theTimer = NSTimer(fireDate: NSDate(), interval: 40, target: self, selector: "handleTimer", userInfo: nil, repeats: true)
    NSRunLoop.currentRunLoop().addTimer(self.theTimer, forMode: NSDefaultRunLoopMode)

}

    func locationManager(manager: CLLocationManager!, didUpdateLocations locations: [AnyObject]!) {
    var locValue:CLLocationCoordinate2D = manager.location.coordinate
    println("dinBack = \(locValue.latitude) \(locValue.longitude)")
    self.locationManager.stopUpdatingLocation()
}

func handleTimer(){

    println("started")
    self.locationManager.startUpdatingLocation()

}

PS. - Разбира се, че съм импортирал corelocation. - Когато се върна в приложението, конзолата отпечатва това, което трябваше да се отпечата във фонов режим.


person Robert Constantinescu    schedule 02.05.2015    source източник
comment
Какво се случва, когато стартирате кода точно сега?   -  person Brennan Adler    schedule 02.05.2015
comment
Когато се върна в приложението, конзолата отпечатва това, което трябваше да се отпечата във фонов режим. @BrennanAdler   -  person Robert Constantinescu    schedule 02.05.2015
comment
все едно методът nstimer не работи...   -  person Robert Constantinescu    schedule 02.05.2015
comment
Това не е начинът, по който фоновото програмиране на iOS работи от около 2 или повече години.   -  person nhgrif    schedule 02.05.2015


Отговори (1)


Не можете да накарате NSTimer да работи по този начин, докато приложението ви е във фонов режим. NSTimer не са механизми в реално време. От официална документация:

Таймерите работят заедно с цикли за изпълнение. За да използвате ефективно таймера, трябва да сте наясно как работят циклите за изпълнение — вижте NSRunLoop и Ръководство за програмиране на нишки. Обърнете внимание по-специално, че циклите за изпълнение поддържат силни препратки към своите таймери, така че не е нужно да поддържате своя собствена силна препратка към таймер, след като сте го добавили към цикъл за изпълнение.

Таймерът не е механизъм в реално време; той се задейства само когато един от режимите на цикъл на изпълнение, към който е добавен таймерът, работи и може да провери дали времето за задействане на таймера е изтекло. Поради различните входни източници, управлявани от типичния цикъл на изпълнение, ефективната резолюция на времевия интервал за таймер е ограничена до от порядъка на 50-100 милисекунди. Ако времето за задействане на таймера възникне по време на дълго извикване или докато цикълът за изпълнение е в режим, който не наблюдава таймера, таймерът не се задейства до следващия път, когато цикълът за изпълнение провери таймера. Следователно действителното време, в което таймерът потенциално се задейства, може да бъде значителен период от време след планираното време за задействане.

Наблягам мой.

Важният извод от това е, че докато вашето приложение е във фонов режим, всеки цикъл на изпълнение, за който вашият таймер би бил планиран, не се изпълнява активно.

Веднага щом приложението ви се върне на преден план, този цикъл на изпълнение се задейства обратно, вижда, че таймерът ви е закъснял, и изпраща съобщението до селектора.


С iOS 7 и следващи, ако искате да извършвате операции във фонов режим, можете да кажете на операционната система, че искате да извършвате фонови извличания.

За да настроим това, първо трябва да кажем на операционната система колко често искаме да извличаме данни, така че в didFinishLaunching... добавете следния метод:

func application(application: UIApplication, didFinishLaunchingWithOptions launchOptions: [NSObject: AnyObject]?) -> Bool {
    application.setMinimumBackgroundFetchInterval(UIApplicationBackgroundFetchIntervalMinimum)
    return true
}

Тук можем да подадем произволен интервал от време (например, ако искаме да проверяваме само веднъж на ден). Стойността, която предаваме обаче, определя само минимално време, което трябва да измине между проверките. Няма начин да кажете на операционната система максимално време между проверките.

Сега трябва да внедрим метода, който всъщност се извиква, когато операционната система ни даде възможност да вършим работа във фонов режим:

func application(application: UIApplication, performFetchWithCompletionHandler completionHandler: (UIBackgroundFetchResult) -> Void) {
    // do background work
}

Можем да правим каквото пожелаем в рамките на този метод. Има обаче две уловки.

  1. Този метод се извиква, докато нашето приложение е във фонов режим. Операционната система ни ограничава до (вярвам) тридесет секунди. След тридесет секунди времето ни изтече.
  2. Трябва да извикаме completionHandler() (или ОС ще си помисли, че сме използвали цялото си време).

completionHandler, което се предава, приема enum, UIBackgroundFetchResult. Трябва да го предадем или .Failed, .NewData или .NoData, в зависимост от това какви са действителните ни резултати (този подход обикновено се използва за проверка на сървър за свежи данни).

И така, нашият метод може да изглежда така:

func application(application: UIApplication, performFetchWithCompletionHandler completionHandler: (UIBackgroundFetchResult) -> Void) {
    // do stuff
    if let _ = error {
        completionHandler(.Failed)
    } else if results.count > 0 {
        completionHandler(.NewData)
    } else {
        completionHandler(.NoData)
    }
}

Имайте предвид, че имаме абсолютно нулев контрол върху това колко често операционната система действително ще ни позволи да изпълняваме този код във фонов режим. ОС използва няколко показателя, за да оптимизира изживяването на потребителя.

Мисля, че ако приложението ви докладва .Failed на манипулатора за завършване, операционната система може скоро да ви даде втори шанс, но ако злоупотребявате с .Failed, операционната система вероятно може да постави приложението ви в черен списък, за да не използва извличане във фонов режим (и Apple може да откаже вашето приложение).

Ако приложението ви не отчита .NewData, операционната система ще позволи на приложението ви да работи във фонов режим по-рядко. Не казвам това, защото ви препоръчвам винаги да докладвате .NewData. Определено трябва да докладвате точно. Операционната система е много умна по отношение на планирането на работа. Ако предавате .NewData, когато няма нови данни, операционната система ще позволи на приложението ви да работи по-често, отколкото може да е необходимо, което ще изтощи батерията на потребителя по-бързо (и може да доведе до деинсталиране на приложението ви като цяло).

Има обаче и други показатели, свързани с това кога приложението ви ще върши работа във фонов режим. Много малко вероятно е операционната система да позволи на някое приложение да работи във фонов режим, докато потребителят използва активно устройството си, и е по-вероятно да позволи на приложенията да работят във фонов режим, докато потребителят не използва своето устройство. Освен това е по-вероятно операционната система да работи във фонов режим, докато е на WiFi и докато е включена в някакво зарядно устройство.

Операционната система също ще разгледа колко редовно потребителят използва вашето приложение или кога редовно го използва. Ако потребителят използва приложението ви всеки ден в 18:00 ч. и никога по друго време, най-вероятно приложението ви винаги ще получава възможност да работи във фонов режим между 17:30 ч. и 18:00 ч. (точно преди потребителят да използва приложението) и никога през друга част от деня. Ако потребителят много рядко използва вашето приложение, може да минат дни, седмици или месеци между възможностите за работа във фонов режим.

person nhgrif    schedule 02.05.2015
comment
Този отговор отговаря на действителния въпрос, зададен тук. За повече информация относно това, което изглежда, че всъщност правите (проверка на GPS във фонов режим), препоръчвам да разгледате този въпрос. - person nhgrif; 02.05.2015
comment
Благодаря ви за отговора, но в тази ситуация как мога да използвам функция във фонов режим на всеки 5 минути, например. Има ли начин да направите това? - person Robert Constantinescu; 02.05.2015
comment
Не, няма. Има обаче начин да кажете на операционната система, че искате да правите нещо периодично, и операционната система ще реши колко често приложението ви трябва да прави неща във фонов режим. Ще потърся връзка за вас и ще добавя малко информация за това към отговора. - person nhgrif; 02.05.2015
comment
Не искам да използвам класическия начин за актуализиране на местоположението във фонов режим... startUpdatingLocation ще държи GPS винаги отворен. Трябва да спра актуализирането след получаване на координатите и след това да презаредя функцията след определен период от време. Казвате, че няма как да стане. - person Robert Constantinescu; 02.05.2015
comment
Това е страхотно, но защо не използвате beginBackgroundTaskWithExpirationHandler вместо performFetchWithCompletionHandler? - person Honey; 24.04.2017