Время от времени замечаю, что случаются зависания, когда в симуляторе пытаюсь выбрать элемент в UIPickerView. Но в той степени, в которой тормоза проявляются сейчас, стало невыносимо наблюдать: изменение выбранного элемента в «барабане» может занять до минуты, в течение которой интерфейс ни на что не реагирует.
Возможно, это недоработка бета-версий.
На чистом проекте специально для исследования этой проблемы наблюдается всё точно то же.
Данная проблема проверялась на 4 вариантах запуска:
Xcode 6.4 + 8.1 проявляется
Xcode 6.4 + 8.3 проявляется
Xcode 7.0 + 8.3 проявляется
Xcode 7.0 + 9.0 не проявляется
Наводит на мысль, что имеет место быть какое-то легкое несоответствие работоспособности версий симулятора, которое в данном случае очень сильно напрягает вариантом проявления.
Попробуем устранить проблему.
Запускаем довольно простой проект, в который добавлен «барабан»:
И теперь пробуем слегка мотнуть список вверх.
В результате он застывает в такой позиции, в данном случае на 28 секунд:
Ну что ж, посмотрим, что заставляет его задуматься.
Ставим на паузу и смотрим backtrace:
Среди виновников торжества прослеживается AudioServicesPlaySystemSound.
Видимо, тогда не стоит и удивляться, потому что издавна интернет пестрит особым отношением к проигрыванию звуков в симуляторе. Вплоть до того, что макросами советуют отключать вызов проигрывания по отношению к симулятору, потому что от него можно ожидать чуть ли не падение.
Попробуем отключить его и здесь. Он вызывается изнутри системного фреймворка, поэтому напрямую выключить, конечно, не получится. Но можно попробовать прервать цепочку до него чуть выше.
Если точнее, то вызывает его метод _playClickIfNecessary. Глядя на название рискну предположить, что ничего больше, чем проигрывание звука, этот метод не делает. Оста��тся заменить его реализацию собственной, которая попросту ничего не делает.
Примера ради попробуем сотворить нужное во время загрузки приложения.
Например, внутри AppDelegate.
Пытался сделать замену через категорию, но не получилось:
— через таблицу — потому что её реализацию перекрывает UIPickerTableView;
— через UIPickerTableView, потому что не смог нормально импортировать скрытый API.
В любом случае, не так важно, каким образом производить замену реализации.
Главное, что после замены теперь всё работает без тормозов и зависаний, что бесконечно приятно.
Впрочем, наверняка кто-то знает более лаконичный способ избавиться от проблемы, тогда буду очень рад дополнениям в комментариях. Потому что я за полчаса не смог ничего толкового найти в Google.
UPD. В комментариях подсказали более лаконичный способ:
Возможно, это недоработка бета-версий.
На чистом проекте специально для исследования этой проблемы наблюдается всё точно то же.
Данная проблема проверялась на 4 вариантах запуска:
Xcode 6.4 + 8.1 проявляется
Xcode 6.4 + 8.3 проявляется
Xcode 7.0 + 8.3 проявляется
Xcode 7.0 + 9.0 не проявляется
Наводит на мысль, что имеет место быть какое-то легкое несоответствие работоспособности версий симулятора, которое в данном случае очень сильно напрягает вариантом проявления.
Попробуем устранить проблему.
Запускаем довольно простой проект, в который добавлен «барабан»:
И теперь пробуем слегка мотнуть список вверх.
В результате он застывает в такой позиции, в данном случае на 28 секунд:
Ну что ж, посмотрим, что заставляет его задуматься.
Ставим на паузу и смотрим backtrace:
Среди виновников торжества прослеживается 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){}));


