Pull to refresh

Как убрать зависания UIPickerView в симуляторе iOS

Reading time3 min
Views3.3K
Время от времени замечаю, что случаются зависания, когда в симуляторе пытаюсь выбрать элемент в UIPickerView. Но в той степени, в которой тормоза проявляются сейчас, стало невыносимо наблюдать: изменение выбранного элемента в «барабане» может занять до минуты, в течение которой интерфейс ни на что не реагирует.

Возможно, это недоработка бета-версий.
На чистом проекте специально для исследования этой проблемы наблюдается всё точно то же.

Данная проблема проверялась на 4 вариантах запуска:
Xcode 6.4 + 8.1 проявляется
Xcode 6.4 + 8.3 проявляется
Xcode 7.0 + 8.3 проявляется
Xcode 7.0 + 9.0 не проявляется

Наводит на мысль, что имеет место быть какое-то легкое несоответствие работоспособности версий симулятора, которое в данном случае очень сильно напрягает вариантом проявления.

Попробуем устранить проблему.

Запускаем довольно простой проект, в который добавлен «барабан»:
image

И теперь пробуем слегка мотнуть список вверх.
В результате он застывает в такой позиции, в данном случае на 28 секунд:
image

Ну что ж, посмотрим, что заставляет его задуматься.
Ставим на паузу и смотрим backtrace:
image

Среди виновников торжества прослеживается AudioServicesPlaySystemSound.
Видимо, тогда не стоит и удивляться, потому что издавна интернет пестрит особым отношением к проигрыванию звуков в симуляторе. Вплоть до того, что макросами советуют отключать вызов проигрывания по отношению к симулятору, потому что от него можно ожидать чуть ли не падение.

Попробуем отключить его и здесь. Он вызывается изнутри системного фреймворка, поэтому напрямую выключить, конечно, не получится. Но можно попробовать прервать цепочку до него чуть выше.

Если точнее, то вызывает его метод _playClickIfNecessary. Глядя на название рискну предположить, что ничего больше, чем проигрывание звука, этот метод не делает. Остается заменить его реализацию собственной, которая попросту ничего не делает.

Примера ради попробуем сотворить нужное во время загрузки приложения.
Например, внутри AppDelegate.

#if TARGET_IPHONE_SIMULATOR
- (void)UIPickerTableView__playClickIfNecessary
{
    // nothing to do
}
#endif


+ (void)initialize
{
#   if TARGET_IPHONE_SIMULATOR
    Class srcClass = self;
    Class dstClass = NSClassFromString(@"UIPickerTableView");
    
    if (srcClass && dstClass) {
        SEL srcSelector = NSSelectorFromString(@"UIPickerTableView__playClickIfNecessary");
        SEL dstSelector = NSSelectorFromString(@"_playClickIfNecessary");
        
        Method srcMethod = class_getInstanceMethod(srcClass, srcSelector);
        Method dstMethod = class_getInstanceMethod(dstClass, dstSelector);
        
        method_exchangeImplementations(srcMethod, dstMethod);
    }
#   endif
}


Пытался сделать замену через категорию, но не получилось:
— через таблицу — потому что её реализацию перекрывает UIPickerTableView;
— через UIPickerTableView, потому что не смог нормально импортировать скрытый API.

В любом случае, не так важно, каким образом производить замену реализации.
Главное, что после замены теперь всё работает без тормозов и зависаний, что бесконечно приятно.

Впрочем, наверняка кто-то знает более лаконичный способ избавиться от проблемы, тогда буду очень рад дополнениям в комментариях. Потому что я за полчаса не смог ничего толкового найти в Google.

UPD. В комментариях подсказали более лаконичный способ:

Method m = class_getInstanceMethod(objc_lookUpClass("UIPickerTableView"), sel_getUid("_playClickIfNecessary"));
method_setImplementation(m, imp_implementationWithBlock(^(id _self){}));
Tags:
Hubs:
If this publication inspired you and you want to support the author, do not hesitate to click on the button
+10
Comments1

Articles

Change theme settings