Подтвердить что ты не робот

Модульные тесты для управления памятью в Cocoa/Objective-C

Как вы могли бы написать unit test -using OCUnit, чтобы гарантировать, что объекты будут выпущены/сохранены должным образом в Cocoa/Objective-C?

Наивный способ сделать это - проверить значение retainCount, но, конечно, вы никогда не должны использовать retainCount. Можете ли вы просто проверить, присвоено ли объектной ссылке значение nil, чтобы указать, что она была выпущена? Кроме того, какие гарантии у вас есть о сроках, когда объекты фактически освобождены?

Я надеюсь на краткое решение только нескольких строк кода, так как я, вероятно, буду использовать это широко. На самом деле могут быть два ответа: один, который использует пул автозапуска, а другой - нет.

Чтобы уточнить, я не ищу способ всесторонне протестировать каждый объект, который я создаю. Невозможно unit test провести какое-либо поведение всесторонне, не говоря уже о управлении памятью. По крайней мере, было бы неплохо проверить поведение выпущенных объектов для регрессионного тестирования (и убедиться, что одна и та же ошибка, связанная с памятью, не происходит дважды).

Об ответах

Я принял BJ Homer ответ, потому что я нашел, что это самый простой и сжатый способ достижения что я имел в виду, учитывая предостережение о том, что слабые указатели, снабженные Automatic Reference Counting, недоступны в производственных версиях XCode (до 4.2?) По состоянию на 23 июля 2011 года. Мне также было приятно узнать, что

ARC может быть включена для каждого файла; это не требует, чтобы ваш весь проект использует его. Вы можете скомпилировать ваши модульные тесты с помощью ARC и оставьте свой основной проект на ручном удержании, и этот тест будет все еще работают.

При этом для более детального изучения потенциальных проблем, связанных с управлением памятью модулей тестирования в Objective-C, я настоятельно рекомендую Peter Hosey подробный отклик.

4b9b3361

Ответ 1

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

- (void)testMemory {
    __weak id testingPointer = nil;
    id someObject = // some object with a 'foo' property

    @autoreleasepool {
        // Point the weak pointer to the thing we expect to be dealloc'd
        // when we're done.
        id theFoo = [someObject theFoo];
        testingPointer = theFoo;

        [someObject setTheFoo:somethingElse];

        // At this point, we still have a reference to 'theFoo',
        // so 'testingPointer' is still valid. We need to nil it out.
        STAssertNotNil(testingPointer, @"This will never happen, since we're still holding it.")

        theFoo = nil;
    }


    // Now the last strong reference to 'theFoo' should be gone, so 'testingPointer' will revert to nil
    STAssertNil(testingPointer, @"Something didn't release %@ when it should have", testingPointer);
}

Обратите внимание, что это работает в ARC из-за этого изменения семантики языка:

Сохраняемый указатель объекта - это либо нулевой указатель, либо указатель на действительный объект.

Таким образом, действие установки указателя на nil гарантированно освободит объект, на который он указывает, и нет способа (при ARC) освободить объект, не удаляя указатель на него.

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

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

Если ARC просто не вариант, вы можете сделать что-то подобное с Mike Ash MAZeroingWeakRef. Я не использовал его много, но он, похоже, обеспечивает аналогичную функциональность для указателей __weak обратно совместимым способом.

Ответ 2

Можете ли вы просто проверить, присвоено ли объектной ссылке значение nil, чтобы указать, что она была выпущена?

Нет, потому что отправка сообщения release объекту и присвоение переменной nil - это две разные и несвязанные вещи.

Самое близкое, что вы можете получить, это то, что присвоение чего-либо сильному/сохраняющему или копирующему свойству, которое преобразуется в сообщение доступа, приводит к тому, что предыдущее значение свойства должно быть освобождено (что выполняется установщиком). Тем не менее, наблюдая за значением свойства, используя KVO, скажем, не означает, что вы узнаете, когда объект будет выпущен; в частности, когда объект-владелец освобождается, вы не получите уведомление, когда он отправит release непосредственно принадлежащему ему объекту. Вы также получите предупреждающее сообщение на своей консоли (поскольку объект-владелец умер, когда вы его наблюдали), и вы не хотите получать шумные предупреждающие сообщения от unit test. Кроме того, вам нужно будет специально наблюдать за каждым свойством каждого объекта, чтобы вытащить этот пропущенный, и вам может не хватать ошибку.

A release сообщение об объекте не влияет на любые переменные, указывающие на этот объект. Также не освобождается.

Это немного меняется при ARC: переменные с слабой репутацией автоматически присваиваются nil, когда объект, на который ссылается, уходит. Тем не менее это мало вам помогает, потому что сильно привязывающие переменные по определению не будут: если есть сильная ссылка на объект, объект не будет (ну, не должен) уйти, потому что сильная ссылка будет (должен) держать его в живых. Объект, который умирает перед ним, является одной из проблем, которые вы ищете, а не тем, что вы хотите использовать в качестве инструмента.

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

Кроме того, какие гарантии у вас есть о сроках, когда объекты действительно выпущены?

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

Возможно, вы имели в виду "освобождение". Освобождение просто приближает его к этому моменту; объект может быть выпущен много раз и все еще имеет долгую жизнь впереди, если каждый релиз просто уравновешивает предыдущее сохранение.

Объект освобождается при его освобождении в последний раз. Это происходит немедленно. Печально известный retainCount даже не опускается до 0, как выяснилось много умных людей, которые пытались написать while ([obj retainCount] > 0) [obj release];.

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

Решение, использующее пул авторесурсов, работает только для объектов, которые автореализуются; по определению объекты, не автореализованные, не входят в пул. Весьма правдиво и иногда желательно, чтобы никогда не автореклассировать определенные объекты (особенно те, что вы создаете много тысяч). Более того, вы не можете заглянуть в пул, чтобы увидеть, что в нем, а что нет, или попытаться совать каждый объект, чтобы увидеть, если он мертв.

Как вы могли бы написать unit test -использование OCUnit, например, для обеспечения того, чтобы объекты были правильно выпущены/сохранены в Cocoa/Objective-C?

Лучшее, что вы можете сделать, это установить NSZombieEnabled в YES в setUp и восстановить его предыдущее значение в tearDown. Это приведет к перегрузкам/недодержкам, но не к утечкам.

Даже если вы можете написать unit test, который тщательно проверяет управление памятью, он все равно будет несовершенным, поскольку он может тестировать только тестируемые объекты модели кода и, возможно, определенные контроллеры. В вашем приложении все еще могут быть утечки и сбои, вызванные кодом вида, ссылками на nib-борделей и некоторыми опциями ( "Release When Closed" ) и т.д.

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

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

Ответ 3

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

  • создать переопределение NSObject dealloc
  • создайте CFMutableSetRef и настройте пользовательские функции сохранения/выпуска, чтобы ничего не делать
  • выполните процедуру unit test, например registerForRRTracking: (id) object
  • выполните процедуру unit test, например clearRRTrackingReportingLeaks: (BOOL) report, которая сообщит о любом объекте в наборе в тот момент времени.
  • вызов [tracker clearRRTrackignReportingLeaks: NO]; в начале вашего unit test
  • вызовите метод register в вашем unit test для каждого объекта, который вы хотите отслеживать, и он будет удален автоматически на dealloc.
  • В конце вашего тестового вызова [tracker clearRRTrackingReportingLeaks: YES]; и он перечислит все объекты, которые не были удалены должным образом.

вы можете переопределить NSObject alloc и просто отследить все, но я думаю, что ваш набор будет слишком большим (!!!).

Еще лучше было бы поместить CFMutableSetRef в отдельный процесс и, таким образом, не повлиять на объем вашей памяти во время работы слишком много. Добавляет сложность и время выполнения межпроцессного взаимодействия. Могу ли использовать частную кучу (или зону - все еще существуют?), Чтобы изолировать ее в меньшей степени.