Модульные тесты для управления памятью в Какао / Objective-C - PullRequest
27 голосов
/ 05 июля 2011

Как бы вы написали модульный тест, используя, например, OCUnit , чтобы убедиться, что объекты правильно высвобождаются / сохраняются в Какао / Objective-C?

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

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

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

Об ответах

Я принял Б.Дж. Гомера ответ , потому что я нашел, что это самый простой и краткий способ выполнить то, что я имел в виду, учитывая предостережение, которое давали слабые указателис Автоматический подсчет ссылок недоступны в рабочих версиях XCode (до 4.2?) по состоянию на 23 июля 2011 года. Я также был впечатлен, узнав, что

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

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

Ответы [ 3 ]

17 голосов
/ 05 июля 2011

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

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

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

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

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

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

Кроме того, какие у вас есть гарантии относительно времени, в которое объекты фактически освобождаются?

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

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

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

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

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

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

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

Даже еслиВы могли бы написать модульный тест, который бы тщательно проверял управление памятью, но он все еще был бы несовершенным, потому что он мог бы тестировать только тестируемый код - объекты модели и, возможно, некоторые контроллеры. У вас все еще могут быть утечки и сбои в вашем приложении, вызванные кодом представления, связанными с перьями ссылками и некоторыми параметрами (на ум приходит «Release By Closed») и т. Д.

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

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

14 голосов
/ 05 июля 2011

Если вы можете использовать недавно введенный автоматический подсчет ссылок (пока недоступный в рабочих версиях 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 просто не вариант, вы можете сделать что-то похожее с MAZeroingWeakRef Майка Эша. Я не очень много его использовал, но, похоже, он обеспечивает функциональность, аналогичную __weak указателям, обратно совместимым способом.

1 голос
/ 15 июля 2011

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

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

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

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

...