Лучшие практики для NSAutoreleasePool в обратных вызовах из другого потока - PullRequest
3 голосов
/ 08 декабря 2011

У меня есть библиотека C ++, которую я хочу представить в качестве среды Objective-C, так что ее будет проще использовать разработчикам Objective-C.Оборачивая библиотеку C ++, я столкнулся с одной конкретной проблемой, связанной с объектами автоматического выпуска и потоковой обработкой.

Одна особенность библиотеки заключается в том, что разработчик может зарегистрировать «регистратор» для получения уведомительных сообщений в виде обратных вызовов отбиблиотека.Уведомление из библиотеки использует типы C ++ и получено из другого потока (POSIX), поэтому я создал закрытый класс-оболочку C ++ для обработки этого: он получает обратный вызов, превращает аргумент char * в NSString и передает егок экземпляру регистратора Objective-C, предоставленному пользователем.Все это работает очень хорошо и выглядит примерно так:

// Is called from the C++ library from another posix thread
void ObjCLoggerWrapper::LogMessage(const char *message)
{
  NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
  // Pass string to the user-provided Objective-C instance called "Logger"
  [Logger logMessage:[[[NSString alloc] initWithUTF8String:message] autorelease]];
  [pool release];
}

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

-(void) logMessage: (NSString*)message
{
  @synchronized(self)
  {
    m_text = [m_text stringByAppendingFormat:@"%04d: %@\r\n", m_lineno++, message];
  }
}

Пока все хорошо.Или я так думал.Но вот претензия:

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

Упс!Это означает, что моя строка m_text, созданная неявно как объект автоматического освобождения с помощью сообщения stringByAppendingFormat, будет освобождена после завершения logMessage и станет зомби.При доступе в следующий раз код будет аварийно завершен.Пользователь, конечно, наверняка и по праву не ожидает этого.Мне самому пришлось несколько раз почесать голову, прежде чем я понял, что происходит.

Поэтому мой вопрос: как мы должны обращаться с автоматически освобожденными объектами при выполнении обратных вызовов к пользовательскому коду из другого потока?

Я вижу несколько возможных вариантов.Ни одно из них не является идеальным, и Google не помогает (отсюда и этот вопрос).

  1. Скажите пользователю «не создавайте объекты автоматического освобождения в вашем коде обратного вызова».Не хорошо: такие объекты часто создаются невольно, например, с помощью stringByAppendingFormat и множества других методов инфраструктуры.Нет предупреждений, кроме более позднего, трудно отлаживаемого сбоя.
  2. Нет NSAutoreleasePool.Отсутствие одного приведет к появлению предупреждений, если пользователь попытается создать автоматически выпущенные объекты.Определенно не очень, но предупреждает пользователя о проблеме надежным способом.И пользователь может «просто» добавить свой NSAutoreleasePool, чтобы исправить ситуацию.Но опять же: не красиво.
  3. Нет NSAutoreleasePool и запустите обратный вызов в главном потоке, используя executeSelectorOnMainThread.Любые новые объекты автоматического выпуска будут попадать в пул основного потока.Я думаю, что это безопасно, но приветствуются комментарии - могут ли, например, обратные вызовы всегда выполняться в главном потоке?Этот подход требует более деликатного кодирования в оболочке, чтобы избежать тупиковых потоков и ждать результата, но пока это мой предпочтительный выбор.

Просто чтобы было ясно: переписывать мою собственную оболочку нетпроблема.Моим главным приоритетом является создание решения, которое будет работать гладко и без проблем для пользователя платформы Objective-C.Спасибо!

Ответы [ 2 ]

2 голосов
/ 08 декабря 2011

Это не проблема с вашим подходом автоматического выпуска. Ваш подход выглядит здоровым. Проблема logMessage, как написано, в корне ошибочна. Он может потерпеть неудачу в любое время, когда между вызовами происходит утечка из пула автоматического выпуска. Основной цикл выполнения (оптимизация по модулю) истощает свой пул авто-релиза при каждом вращении цикла событий, поэтому в этом случае он будет так же неудачным.

Пара FWIW:

[Logger logMessage:[[[NSString alloc] initWithUTF8String:message] autorelease]];

можно написать

[Logger logMessage:[NSString stringWithUTF8String:message]];

и [pool drain] предпочтительнее, чем [pool release]

1 голос
/ 08 декабря 2011

Не зная точно, что делает ваша библиотека, трудно рекомендовать подход.Тем не менее, вызов executeSelectorOnMainThread является довольно безопасной стратегией.Учитывая, что вы не ожидаете, что сообщения журнала потребуют каких-либо немедленных действий, следует подождать, пока основной поток выполнит ваш обратный вызов.

...