Я неправильно обрабатываю обновления местоположения?

В настоящее время мой код местоположения является частью синглтона. Мне любопытно, если это то, что вызывает мои проблемы. Я использую синглтон местоположения как часть регионов геозоны и iBeacons. У меня есть несколько iBeacons в месте, которое я тестирую, и кажется, что иногда вход в область маяка прерывает выход из предыдущей области. Мне любопытно, происходит ли это, потому что это синглтон, и если я должен изменить его на стандартный класс, который инициализирует делегат приложения, чтобы начать мониторинг, и приложение будет обрабатывать обратные вызовы для каждого обратного вызова. Я думаю, что, поскольку это синглтон, второй обратный вызов останавливает первый обратный вызов, если он не завершен.

Из приведенного ниже журнала видно, что я вышел из подвала, и он запустил событие по времени, чтобы убедиться, что я действительно вышел из подвала (программный фильтр, чтобы избавиться от быстрого выхода/входа, который происходит с iBeacons). Затем я вошел в свою гостиную, которая запустила другое региональное событие, но мы так и не получили сообщение didExitRegion: Basement log out.

2014-04-25 15:35:22.757 [2761:707] Just Requested Background Time
2014-04-25 15:35:22.761 [2761:707] Basement
2014-04-25 15:35:22.767 [2761:707] Start Timed Event To See If Its A False Exit
2014-04-25 15:35:22.777 [2761:707] Just Requested Background Time
2014-04-25 15:35:22.778 [2761:707] Basement
2014-04-25 15:35:22.780 [2761:707] Start Timed Event To See If Its A False Exit
2014-04-25 15:35:23.751 [2761:707] Just Requested Background Time
2014-04-25 15:35:23.753 [2761:707] didEnterRegion: Living Room
2014-04-25 15:35:23.772 [2761:707] Just Requested Background Time
2014-04-25 15:35:23.774 [2761:707] didEnterRegion: Living Room
2014-04-25 15:35:24.270 [2761:707] Job ID Value
2014-04-25 15:35:24.272 [2761:707] 3208

Новый лог после внесения изменений в ntimer:

2014-04-25 17:13:06.873 [3243:707] Just Requested Background Time
2014-04-25 17:13:06.875 [3243:707] Kitchen
2014-04-25 17:13:06.877 [3243:707] Start Timed Event To See If Its A False Exit
2014-04-25 17:13:06.884 [3243:707] Just Requested Background Time
2014-04-25 17:13:06.885 [3243:707] Kitchen
2014-04-25 17:13:06.886 [3243:707] Start Timed Event To See If Its A False Exit
2014-04-25 17:13:09.146 [3243:707] didExitRegion Basement
2014-04-25 17:13:09.149 [3243:707] Cancelled Exit Of Region: Basement
2014-04-25 17:13:09.881 [3243:707] Job ID Value
2014-04-25 17:13:09.883 [3243:707] 4040
2014-04-25 17:13:09.884 [3243:707] Connection Successful 
2014-04-25 17:13:10.878 [3243:707] Cancelled Exit Of Region: Kitchen
2014-04-25 17:13:10.889 [3243:707] Cancelled Exit Of Region: Kitchen

Код обновлен:

- (void)locationManager:(CLLocationManager *)manager didEnterRegion:(CLRegion *)region
{
    currentRegion = region;
    GeofenceObject *gObject = [[GeofenceObject alloc] init];
    gObject = [self getObjectForGeofence:region];

    //stop multiple notification deliveries
    if (notificationDelivered && ![gObject getIsBeacon])
    {
        notificationDelivered = NO;
        return;
    }

    BOOL alwaysNotify = [[NSUserDefaults standardUserDefaults] boolForKey:@"alwaysNotifyLocation"];
    if (![self checkWifi] && ![self checkWWLAN] && ![gObject getIsBeacon])
    {
        [self notifyNoNetwork:gObject forState:[NSNumber numberWithInt:1]];
        return;
    }

    if ((alwaysNotify && ![gObject getIsBeacon]))
    {
        [self notifyAlways:gObject forState:[NSNumber numberWithInt:1]];
        return;
    }

    BOOL isInBackground = NO;
    if ([UIApplication sharedApplication].applicationState == UIApplicationStateBackground)
    {
        isInBackground = YES;
    }

    NSTimer *timer = (NSTimer *)[self.timers objectForKey:[gObject getGeofenceName]];
    if (timer != nil)
    {
        [timer invalidate];
        [self.timers removeObjectForKey:[gObject getGeofenceName]];
    }

    if (isInBackground && [gObject getIsBeacon])
    {
        [self beginBackgroundTask];
        NSLog(@"%@", [@"didEnterRegion: " stringByAppendingString:[gObject getGeofenceName]]);
        [self didEnterRegion:region forObject:gObject];
        return;
    }

    if (isInBackground)
    {
        [self beginBackgroundTask];
        NSLog(@"%@",[@"didEnterRegion " stringByAppendingString:[gObject getGeofenceName]]);
        [self didEnterRegion:region forObject:gObject];
        return;
    }

    if ([gObject getIsBeacon])
    {
        NSLog(@"%@", [@"didEnterRegion: " stringByAppendingString:[gObject getGeofenceName]]);
        [self didEnterRegion:region forObject:gObject];
    }

    NSLog(@"%@",[@"didEnterRegion " stringByAppendingString:[gObject getGeofenceName]]);
    [self didEnterRegion:region forObject:gObject];
}

- (void)locationManager:(CLLocationManager *)manager didExitRegion:(CLRegion *)region
{
    currentRegion = region;
    GeofenceObject *gObject = [[GeofenceObject alloc] init];
    gObject = [self getObjectForGeofence:region];

    //stop multiple notification deliveries
    if (notificationDelivered && ![gObject getIsBeacon])
    {
        notificationDelivered = NO;
        return;
    }

    BOOL alwaysNotify = [[NSUserDefaults standardUserDefaults] boolForKey:@"alwaysNotifyLocation"];
    if (![self checkWifi] && ![self checkWWLAN] && ![gObject getIsBeacon])
    {
        [self notifyNoNetwork:gObject forState:[NSNumber numberWithInt:0]];
        return;
    }

    if ((alwaysNotify && ![gObject getIsBeacon]))
    {
        [self notifyAlways:gObject forState:[NSNumber numberWithInt:0]];
        return;
    }

    BOOL isInBackground = NO;
    if ([UIApplication sharedApplication].applicationState == UIApplicationStateBackground)
    {
        isInBackground = YES;
    }

    if (isInBackground && [gObject getIsBeacon])
    {
        [self beginBackgroundTask];
        [gObject setJustExitedRegion:YES];
        [self replaceObjectWithUpdate:gObject];
        NSDictionary *info = [NSDictionary dictionaryWithObject:region forKey:@"region"];
        NSLog(@"%@", [gObject getGeofenceName]);
        NSLog(@"%@", @"Start Timed Event To See If Its A False Exit");
        [self.timers setObject:[NSTimer scheduledTimerWithTimeInterval:4.0f target:self selector:@selector(checkBackOnExit:) userInfo:info repeats:NO] forKey:[gObject getGeofenceName]];
        return;
    }

    if (isInBackground)
    {
        [self beginBackgroundTask];
        NSLog(@"%@",[@"didExitRegion " stringByAppendingString:[gObject getGeofenceName]]);
        [self didExitRegion:region forObject:gObject];
        return;
    }

    if ([gObject getIsBeacon])
    {
        [gObject setJustExitedRegion:YES];
        [self replaceObjectWithUpdate:gObject];
        NSDictionary *info = [NSDictionary dictionaryWithObject:region forKey:@"region"];
        NSLog(@"%@", [gObject getGeofenceName]);
        NSLog(@"%@", @"Start Timed Event To See If Its A False Exit");
        [self.timers setObject:[NSTimer scheduledTimerWithTimeInterval:4.0f target:self selector:@selector(checkBackOnExit:) userInfo:info repeats:NO] forKey:[gObject getGeofenceName]];
        return;
    }

    NSLog(@"%@",[@"didExitRegion " stringByAppendingString:[gObject getGeofenceName]]);
    [self didExitRegion:region forObject:gObject];
}

- (void)beginBackgroundTask
{
    NSLog(@"%@", @"Just Requested Background Time");
    bgTask = [[UIApplication sharedApplication] beginBackgroundTaskWithExpirationHandler:^{
        NSLog(@"%@", @"Just Cancelled Background Time");
        [[UIApplication sharedApplication] endBackgroundTask:bgTask];
        bgTask = UIBackgroundTaskInvalid;
    }];
}

- (void)checkBackOnExit:(NSTimer *)timer
{
    NSDictionary *info = [timer userInfo];
    CLRegion *region = [info objectForKey:@"region"];
    GeofenceObject *gObject = [[GeofenceObject alloc] init];
    gObject = [self getObjectForGeofence:region];
    if ([gObject getJustExitedRegion])
    {
        [self didExitRegion:region forObject:gObject];
        NSLog(@"%@",[@"didExitRegion " stringByAppendingString:[gObject getGeofenceName]]);
        [gObject setJustExitedRegion:NO];
        [self replaceObjectWithUpdate:gObject];
        [timer invalidate];
        [self.timers removeObjectForKey:[gObject getGeofenceName]];
        info = nil;
    }else
    {
        [timer invalidate];
        [self.timers removeObjectForKey:[gObject getGeofenceName]];
        NSLog(@"%@", [@"Cancelled Exit Of Region: " stringByAppendingString:[gObject getGeofenceName]]);
        info = nil;
        return;
    }
}

person Jarod    schedule 25.04.2014    source источник


Ответы (1)


Ваша проблема в этой строке -

[NSTimer scheduledTimerWithTimeInterval:4.0f target:self selector:@selector(checkBackOnExit:) userInfo:info repeats:NO];

Вы нигде не храните новый NSTimer, который возвращается из scheduledTimerWithTimeInterval, поэтому, как только метод выйдет, он будет освобожден. Вам нужно добавить NSTimer к свойству. Поскольку у вас может одновременно работать несколько таймеров, я бы предложил NSMutableDictionary.

@property (retain,nonatomic)  NSMutableDictionary *timers;

Инициализируйте это везде, где это уместно (например, init)

self.timers=[[NSMutableDictionary alloc]init];

затем в didExitRegion:

[self.timers addObject:[NSTimer scheduledTimerWithTimeInterval:4.0f target:self selector:@selector(checkBackOnExit:) userInfo:info repeats:NO] forKey:gObject.geoFenceName];

In didEnterRegion:

NSTimer *timer=(NSTimer *)[self.timers objectForKey:gObject.geoFenceName]
if (timer != nil) {
    [timer invalidate];
    [self.timers removeObjectForKey:gObject.geoFenceName];
}

А также в вашем методе checkBackOnExit:

[timer invalidate];
[self.timers removeObjectForKey:gObject.geoFenceName];
person Paulw11    schedule 25.04.2014
comment
Я только что реализовал то, что вы сказали, и это полностью имеет смысл, однако я пошел и протестировал, и новый журнал обновлен в моем основном вопросе. - person Jarod; 26.04.2014
comment
Я добавил NSLog в код таймера, чтобы увидеть, отменен ли он или продолжается ли он с выходом, откуда взялись отмененные. - person Jarod; 26.04.2014
comment
Можете ли вы обновить свой код в своем вопросе (по крайней мере, новый checkBackOnExit)? На первый взгляд, хотя я не уверен, что вам нужно делать какие-либо проверки в этом методе. Поскольку didEnterRegion отменит любой незавершенный таймер для этого региона, если таймер сработает, это означает, что вы не получили ввод в течение периода времени, поэтому вы выбыли. - person Paulw11; 26.04.2014
comment
Вообще-то, нет. Запланированный таймер сохраняется в цикле выполнения. У вас нет ссылки на него, поэтому вы не можете аннулировать его, но вам не нужно сохранять его только для того, чтобы он работал. - person matt; 26.04.2014
comment
Это привело меня к реальной проблеме. Я вызывал повторное заполнение массива после успешного подключения, в этом повторном заполнении я устанавливал для параметра justExitedRegion значение NO. Таким образом, если бы было 2 выхода относительно близко друг к другу, первый выход сбросил бы это значение на NO на втором выходе до того, как таймер сработает, что приведет к сбою его работы. - person Jarod; 26.04.2014
comment
Из журнала кажется, что getJustExitedRegion НЕТ. Вероятно, это связано с тем, что объект в вашем словаре является ссылкой на объект, который обновляется в другом месте. Как я уже сказал в своем предыдущем комментарии, я не уверен, почему вы проверяете логическое значение. Если таймер срабатывает, значит, регион был закрыт и повторно не вошел. - person Paulw11; 26.04.2014
comment
@Paulw11Paulw11 Я изменил код, чтобы избавиться от логического значения и просто заставить его работать с таймером. Причина, по которой я сохранил этот код, заключается в том, что я только что изменил ранее ваш ответ выше и не подумал об этом. После просмотра я заметил, что он обновляется в другом месте, и в моем посте над вашим рассказывается о том, как я это обнаружил. Спасибо за помощь! - person Jarod; 26.04.2014
comment
Есть ли способ сделать то же самое с bgTask (фоновая задача)? Поскольку я могу сделать несколько запросов на фоновое время одновременно, в зависимости от того, насколько быстро я перемещаюсь из одного региона в другой, я не хочу отменять экземпляр bgTask, поскольку он отменит их все. Тем не менее, мое приложение выдает журналы сбоев о том, что оно слишком долго работает в фоновом режиме, и я думаю, что это потому, что я каждый раз даю срок действия bgTask, а не отменяю его. Кажется, я не могу создать NSMutableDictionary с фоновой задачей, связанной с gObjects, поэтому какие-либо рекомендации, как отслеживать? - person Jarod; 26.04.2014
comment
Вы можете создать объект-оболочку со свойствами для вашего NSTimer и идентификатора фоновой задачи и добавить его в словарь, или вы можете создать второй NSMutableDictionary и добавить идентификатор задачи bg, используя тот же ключ, но поскольку идентификатор BGTask на самом деле является NSUInteger, вы придется обернуть его в NSNumber, прежде чем вы сможете добавить его в словарь - person Paulw11; 26.04.2014