Пулы Autorelease и когда релиз вызывается под iOS

Я хотел кое-что уточнить.

Допустим, у меня есть следующий код:

- (void) viewDidAppear:(BOOL)animated {
  [super viewDidAppear:animated];
  for (int i = 0; i < 5000000; i++) {
    NSString *s = [NSString stringWithFormat:@"Hello, %@!", @"World"];
  }
}

В этом вызове функции будет создано 5 миллионов строк с автореализацией. Я ожидал, что это сохранит эти объекты до окончания приложения, так как единственное @autoreleasepool, которое я вижу, - это тот, который обертывает экземпляр приложения в main.m. Впрочем, это не так. В конце вызова этой функции кажется, что все они вызывают их освобождение и удаляются из памяти.

Этот документ:

https://developer.apple.com/library/mac/documentation/cocoa/reference/foundation/Classes/NSAutoreleasePool_Class/Reference/Reference.html

Сообщает, что "Application Kit создает пул автозапуска в основном потоке в начале каждого цикла цикла событий и истощает его в конце, тем самым высвобождая любые объекты с автореализацией, сгенерированные при обработке события".

Это имеет смысл для меня, но это под UIKit, а не Application Kit. Мой вопрос: делает ли UIKit/ Cocoa Touch то же самое в этом случае, или есть другое объяснение, что мои объекты освобождаются?

Спасибо!

Ответ 1

Да, UIKit делает то же самое. Созданный системой пул авторесурсов основного потока сливается в конце каждого цикла цикла цикла. Вероятно, лучше не полагаться на эту точную жизнь в вашем собственном коде. Если вы создаете новый поток вручную (используя, например, NSThread), вы несете ответственность за создание пула автозапуска в этом потоке.

РЕДАКТИРОВАТЬ: Ответ Роба дает некоторую хорошую дополнительную информацию о поведении в ARC. В целом, справедливо сказать, что объекты с меньшей вероятностью попадают в пул авторезистов из-за некоторых оптимизаций, которые ARC может сделать.

Ответ 2

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

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

  • В прошлом (и все еще требуемом для взаимодействия ARC-MRC) при возврате объектов из методов, имена которых не начинались с alloc, new, copy или mutableCopy, эти объекты будут объектами автоопределения, освобожденными только тогда, когда пул авторесурсов был истощен (т.е. когда вы вернулись в цикл выполнения).

  • Но ARC умнее сводит к минимуму необходимость в пулах автозапуска (см. http://rentzsch.tumblr.com/post/75082194868/arcs-fast-autorelease, в котором обсуждается callerAcceptsFastAutorelease, теперь вызванный callerAcceptsOptimizedReturn, вызываемый prepareOptimizedReturn), поэтому вы часто не увидите этого поведения autorelease. Таким образом, если и библиотека, и вызывающая сторона используют ARC, объекты не могут быть помещены в пул автозаполнения, но, скорее всего, ARC умственно освободит их немедленно, если они не понадобятся.

    В современных проектах ARC пулы авторефератов обычно не нужны. Но в некоторых особых случаях все равно можно использовать пулы авторефератов. Я расскажу об одном из следующих случаев.

Рассмотрим следующий код:

#import "ViewController.h"
#import <sys/kdebug_signpost.h>

typedef enum : NSUInteger {
    InnerLoop = 1,
    MainLoop = 2
} MySignPostCodes;

@implementation ViewController

- (void)viewDidLoad {
    [super viewDidLoad];

    NSURL *fileURL = [[NSBundle mainBundle] URLForResource:@"test" withExtension:@"png"];

    dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
        kdebug_signpost_start(MainLoop, 0, 0, 0, 1);
        for (int j = 0; j < 500; i++) {
            NSData *data = [NSData dataWithContentsOfURL:fileURL];
            UIImage *image = [[UIImage alloc] initWithData:data];
            NSLog(@"%p", NSStringFromCGSize(image.size));  // so it not optimized out
            [NSThread sleepForTimeInterval:0.01];
        }
        kdebug_signpost_end(MainLoop, 0, 0, 0, 1);
    });
}

@end

Следующий код добавит 500 000 объектов в пул автозаполнения, который будет только слит, когда я вернусь к циклу запуска:

no pool

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

@implementation ViewController

- (void)viewDidLoad {
    [super viewDidLoad];

    NSURL *fileURL = [[NSBundle mainBundle] URLForResource:@"test" withExtension:@"png"];

    dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
        kdebug_signpost_start(MainLoop, 0, 0, 0, 1);
        for (int j = 0; j < 5; j++) {
            @autoreleasepool {
                kdebug_signpost_start(InnerLoop, 0, 0, 0, 2);
                for (long i = 0; i < 100; i++) {
                    NSData *data = [NSData dataWithContentsOfURL:fileURL];
                    UIImage *image = [[UIImage alloc] initWithData:data];
                    NSLog(@"%p", NSStringFromCGSize(image.size));  // so it not optimized out
                    [NSThread sleepForTimeInterval:0.01];
                }
                kdebug_signpost_end(InnerLoop, 0, 0, 0, 2);
            }
        }
        kdebug_signpost_end(MainLoop, 0, 0, 0, 1);
    });
}

@end

pool

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

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

Ответ 3

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

MyObject *object = [[MyObject alloc] init]; // obj1, ref count 1 because strong
object = [[MyObject alloc] init]; // obj2, ref count of obj1 should be 0
                                  // so obj1 gets released

Apple отмечает в Переход к заметкам о выпуске ARC

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

Звучит так, что release вызывается на объект, когда ему присваивается новое значение, от clang Документация Clang 3.4 OBJECTIVE-C АВТОМАТИЧЕСКИЙ СЧЕТЧИК (ARC)

Присвоение выполняется при оценке оператора присваивания. семантика варьируется в зависимости от квалификации:

Для объектов __strong новый указатель сначала сохраняется; во-вторых, lvalue загружается примитивной семантикой; в-третьих, новый хранится в lvalue с примитивной семантикой; и, наконец, старый pointee выпущен. Это не выполняется атомарно; внешний синхронизация должна использоваться, чтобы сделать это безопасным в одновременные нагрузки и магазины.