Бутонът за действие на iOS UILocalNotification се срива и поврежда данните на приложението

Беше пусната малка актуализация на нашето приложение, тази версия също беше променила своя ID на приложението от уникален ID на нашия Team ID от поддръжката на Apple.

According to Apple, промяната към използването на Team ID трябва да нулира само достъпа до ключодържателя, ние не използваме ключодържателя, така че не би трябвало да има никакъв ефект върху нашето приложение.

Но откакто актуализацията беше пусната, някои от нашите потребители в производството се сблъскват с повреда в данните на приложенията си. Това се случва само когато отговорят на локално известие с помощта на бутон за действие от Центъра за известия или заключен екран. Това може да се случи на всички поддържани версии на iOS.

Потребителите, които изпитват това, имат друг симптом, те получават две локални известия вместо само едно, но приложението вижда само едно известие. Нещо повече, тези известия все още няма да изчезнат след деактивиране на известията и анулиране чрез [[UIApplication sharedApplication] cancelAllLocalNotifications].

Тези дублирани известия бяха планирани преди приложението да бъде актуализирано от App Store, но след актуализирането приложението загуби контрол над тях за някои потребители. Този проблем е описан подробно в това въпрос.

Най-голямата улика може да се крие в докладите за сривове, получени от Apple -

Incident Identifier: ED0E9C.............74B38C
CrashReporter Key:   ae05b............dbc46
Hardware Model:      iPhone4,1
Process:             MY_APP [444]
Path:                /private/var/mobile/Containers/Bundle/Application/65324..................8616/MY_APP.app/MY_APP
Identifier:          com.mycompany.myapp
Version:             X.X
Code Type:           ARM (Native)
Parent Process:      launchd [1]

Date/Time:           2015-10-27 21:45:24.24 -0500
Launch Time:         2015-10-27 21:45:20.20 -0500
OS Version:          iOS 9.1 (13B143)
Report Version:      104

Exception Type:  EXC_CRASH (SIGABRT)
Exception Codes: 0x0000000000000000, 0x0000000000000000
Exception Note:  EXC_CORPSE_NOTIFY
Triggered by Thread:  0

Last Exception Backtrace:
0   CoreFoundation                  0x244b3676 __exceptionPreprocess + 122 (NSException.m:162)
1   libobjc.A.dylib                 0x3582ee12 objc_exception_throw + 34 (objc-exception.mm:531)
2   CoreFoundation                  0x244b354c +[NSException raise:format:arguments:] + 100 (NSException.m:131)
3   Foundation                      0x25240bc4 -[NSAssertionHandler handleFailureInMethod:object:file:lineNumber:description:] + 88 (NSException.m:152)
4   UIKit                           0x28840754 -[UIApplication _runWithMainScene:transitionContext:completion:] + 2928 (UIApplication.m:3299)
5   UIKit                           0x28853a48 __84-[UIApplication _handleApplicationActivationWithScene:transitionContext:completion:]_block_invoke3218 + 32 (UIApplication.m:11920)
6   UIKit                           0x2883d71e -[UIApplication workspaceDidEndTransaction:] + 130 (UIApplication.m:2648)
7   FrontBoardServices              0x2c52dca2 -[FBSSerialQueue _performNext] + 226 (FBSSerialQueue.m:157)
8   FrontBoardServices              0x2c52df94 -[FBSSerialQueue _performNextFromRunLoopSource] + 44 (FBSSerialQueue.m:204)
9   CoreFoundation                  0x24476bfa __CFRUNLOOP_IS_CALLING_OUT_TO_A_SOURCE0_PERFORM_FUNCTION__ + 10 (CFRunLoop.c:1761)
10  CoreFoundation                  0x244767e8 __CFRunLoopDoSources0 + 448 (CFRunLoop.c:1807)
11  CoreFoundation                  0x24474b56 __CFRunLoopRun + 790 (CFRunLoop.c:2536)
12  CoreFoundation                  0x243c8114 CFRunLoopRunSpecific + 516 (CFRunLoop.c:2814)
13  CoreFoundation                  0x243c7f00 CFRunLoopRunInMode + 104 (CFRunLoop.c:2844)
14  UIKit                           0x28610208 -[UIApplication _run] + 520 (UIApplication.m:2489)
15  UIKit                           0x2860af10 UIApplicationMain + 140 (UIApplication.m:3665)
16  MY_APP                          0xc4972 main + 22 (main.m:14)
17  libdyld.dylib                   0x35f9d86e tlv_get_addr + 42 (threadLocalHelpers.s:310)

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

Както се вижда в stacktrace, този срив не е причинен от нашия код, ние не сме променили нищо в кода, той работи добре преди промяната на App ID и се случва на ~2% от нашите потребители.

Ето кода, който управлява бутоните за действие на известията -

- (void)application:(UIApplication *)application handleActionWithIdentifier:(NSString *)identifier forLocalNotification:(UILocalNotification *)notification completionHandler:(void (^)())completionHandler {

    @try {
        if (notification) {
            NSDate *alarmTime = notification.userInfo ? [notification.userInfo objectForKey:@"time"] : nil;
            [logic saveAlarmTime:alarmTime takenAt:[NSDate date]];
        }
    }
    @catch (NSException *exception) {
        NSLog(@"Exception: %@", [exception description]);
    }
    @finally {
        completionHandler();
    }
}

Какво причинява този срив?

(Знам, че е хвърлено в -[UIApplication _runWithMainScene:transitionContext:completion:] + 2928 (UIApplication.m:3299), въпросът е какво има там, което причинява това да се срива.)


person Kof    schedule 28.10.2015    source източник
comment
може това въпрос да бъде полезен? Има същото проследяване на стека за срива, както във вашия въпрос. Възможно ли е вашето приложение да пренасочи потребителя към някакъв контролер за изглед, след като той отвори приложението чрез насоченото известие, но отварянето на този контролер се извършва с [window addSubview:viewController.view]; вместо [window setRootViewController:viewController];? Това не решава проблема с две известия, но може би и двете са грешки/актуализации на Xcode 7   -  person medvedNick    schedule 03.11.2015
comment
Само едно предложение. Проверете всички наблюдатели за известието. Със сигурност извикването на removeObserver: в dealloc от тези обекти Observer. И stackoverflow.com /questions/26371462/   -  person tuledev    schedule 03.11.2015
comment
@anhtu това не е този вид известия, говорим за локални известия, наблюдатели не са използвани за този въпрос.   -  person Kof    schedule 04.11.2015


Отговори (3)


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

Например, това може да възникне, когато приложението се зареди и прозорецът му няма зададен контролер за изглед на root. Може също да се случи, ако е добавено с помощта на addSubview вместо setRootViewController, както се вижда тук.

Ще трябва да проверите с потребителите, които са преживели този срив, какво са правили, ако наистина са отговорили само на бутон за действие за локално известяване или са преживели срива, докато са отваряли приложението.

person gala    schedule 04.11.2015

Причината за повредата на данните е опцията „Защита на данните“. Може да се активира от Xcode или от портала за разработчици на iOS, под Идентификатори -> Идентификационни номера на приложения.

Опция за защита на данните в Xcode

Има три състояния: „Пълна защита“, „Защитен, освен ако не е отворен“ и „Защитен до удостоверяване на първия потребител“. Докато данните са защитени, дори приложението няма достъп до тях.

Портал за разработчици на iOS

Например, ако е избрана „Пълна защита“, както в нашия случай, и се изпълнява фонов процес като бутона за действие за известяване, основните данни няма да имат достъп до базата данни поради грешка в разрешението.

Все още не съм сигурен защо повреди данните, понякога грешката „Разрешението е дефинирано“ не се показва, но пак не успява да прочете SQLite файла. Може би също е записано във файла, но тъй като криптирането е включено, това поврежда файла.

Решението е да изберете или „Защитено до удостоверяване на първия потребител“ или да деактивирате защитата на данните.

Свързан SO въпрос: Грешки/изключения на CoreData, когато приложението работи във фонов режим

PDF файл за сигурност на iOS на Apple

person Kof    schedule 07.11.2015

Този ред причинява срива:

[UIApplication _runWithMainScene:transitionContext:completion:] + 2928 (UIApplication.m:3299)

Ако даден екип се промени за приложение, уникалният идентификатор на приложението за това приложение също се променя, тъй като то е част от него. Местните известия използват уникалния идентификатор на приложението. Така че всъщност това наистина е грешка от Apple, тъй като iOS трябва да разпознае едно известие, което няма повече съответното приложение. И това също е вашият срив, тъй като сте стартирани, но не получавате обекта, с който обикновено работите.

Сляпо предположение, тъй като не знам кода ви. Когато отговаряте на локално известие, проверете дали обектът и всички свързани данни са там, преди да продължите. Знаеш ли, нещо като:

if (object) {
    //do the stuff
} else { 
    // do nothing
}

(много по-добре от изключенията имхо...)

Що се отнася до известията, които вече не ви принадлежат - потребителите могат сами да ги изтрият. Просто се уверете, че няма да се сринете - да не правите нищо е добре. Обикновено ppl ги изтриват сами в даден момент, след което почистват своя център за уведомяване.

person Helge Becker    schedule 30.10.2015
comment
Това основно повтаря заключенията в оригиналния въпрос, в други части дори не съм сигурен как е свързано. "it is basically indeed a bug by Apple" - въз основа на какво? - person Kof; 30.10.2015
comment
Виждам, че не си направил връзката. 1) Това е грешка в Apple, защото има известие за приложението, което не е до екипа. 2) Това е грешка и във вашия код, тъй като някъде не обработвате нула. Ето защо виждате твърдението да се намесва. Вие разчитате на кода си, че има нещо, но не е така. Вероятно известието, което принадлежи на вашето приложение, но не и на вашия екип. - person Helge Becker; 31.10.2015
comment
Не, всички обекти се проверяват за нула, преди да бъдат използвани. Той дори не стига до кода на приложението, 0xc4972 main + 22 (main.m:14) не е моят код, той само инициира UIApplicationMain, който е класът на iOS, който изпълнява приложението, той се срива някъде по пътя, преди да стигне до кода на приложението ми. - person Kof; 31.10.2015
comment
Имате нулева препратка, иначе твърдението няма да се задейства. Къде, трудно е да се каже без кода. gl фиксиране. - person Helge Becker; 02.11.2015
comment
Никой контролер за изглед на root не е нула. Както и да е, радвам се, че откри грешката си - person Helge Becker; 22.11.2015
comment
Да, но rootViewController няма нищо общо с известията и това не е грешка от Apple. - person Kof; 22.11.2015