среда, 18 мая 2011 г.

C++ объекты как мемберы Objective C классов

Я намучался изрядно с плюсовыми объектами, которые объявлял как обычные мемберы Objective C классов. Проблема в том, что вы не можете быть уверены, когда будет вызван деструктор такого мембера в момент прекращения жизни Objective C объекта, и будет вообще ли вызван этот деструктор.

К счастью есть простоё и надёжное решение.

Используйте обычный указатель или std::auto_ptr. Теперь вы сможете контролировать момент вызова конструктора и момент вызова деструктора. Если вам нужно будет уничтожать C++ объект при уничтожении его владельца (Objective C объекта), то самое подходящее место для этого - метод dealloc (аналог деструктора в Objective C классах).

Как выяснилось, я не напрасно предпочёл в качестве мемберов использовать указатели вместо обычных объектов. На http://rsdn.ru/?/forum/apple.os/ мне помогли разобраться с этой ситуацией (привели цитату из официальной документации)

Objective-C classes cannot have instance variables of C++ classes that do not have a default constructor or that have one or more virtual methods, but pointers to C++ objects can be used as instance variables without restriction (allocate them with new in the -init method).

UIActivityIndicator не всегда работает!

В некоторых случаях UIActivityIndicator не всегда работает. С чем это связано, я до сих пор не знаю.

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

Если же программа занялась плотно какими-то задачами в основном потоке (кстати, согласно рекомендациям от Apple в iPhone ГУИ-контролы вообще лучше юзать в основном потоке, если я не ошибаюсь), то активити-индикатор может "не успеть" сделать какую-то подготовительную работу и тогда пользователь его не увидит. Но это лишь мои догадки.

Как я вышел из положения? Приходится программно запускать специально созданный для этого таймер на 1/10 долю секунды (этого времени индикатору хватает), после этого запускать активити-индикатор и просто ничего не делать до срабатывания таймера (то есть до срабатывания функции-обработчика таймера). Этой паузы хватит, чтобы активити-индикатор глотнул воздуха и начал работать. Как только сработает таймер через 1/10 секунды, то останавливайте таймер и запускайте в основном потоке нужные вам функции, это уже не помешает активити-индикатору продолжать показывать, что дескать дела в основном потоке идут.

Конечно, есть риск, что пользователь что-то успеет нажать за 1/10 секунды। Для этого можно перестраховаться с помощью булевой переменной (т.е. заблокировать обработчики нажатий на контролы, вернее выходить из функций обработчиков если эта переменная показывает, что сейчас как раз идёт та самая 1/10 доля секунды).

Добавлено через много месяцев: сейчас я вместо таймера использую более подходящий и простой вызов функции
- (void)performSelector:(SEL)aSelector withObject:(id)anArgument afterDelay:(NSTimeInterval)delay
Смотрите подробнее в документации (эта функция способна вызвать нужный вам код в текущем потоке через нужное время, этого глотка времени хватает, чтобы активити-индикатор закрутился).

Кстати, вся эта статья относится не только к активити-индикаторам, но и многим другим контролам (которым необходим глоток воздуха для того, чтобы успеть отобразиться на экране).

Постоянные читатели