Когда сохранить и выпустить CGLayerRef?

У меня есть вопрос, похожий на этот:

CGLayerRef в NSValue - когда вызывать retain() или release()?

Я рисую 24 круга в виде радиальных градиентов на виде. Чтобы ускорить его, я рисую градиент в слое, а затем рисую слой 24 раза. Это очень хорошо работало, чтобы ускорить рендеринг. При последующих вызовах drawRect некоторые круги могут нуждаться в перерисовке с другим оттенком, в то время как другие остаются такими же.

Каждый раз, используя drawRect, я пересчитываю новый градиент с новым оттенком и рисую его в новом слое. Затем я перебираю круги, рисуя их с исходным слоем / градиентом или новым слоем / градиентом в зависимости от ситуации. У меня есть 24 элемента NSMutableArray, который хранит CGLayerRef для каждого круга.

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

Кроме того, я обнаружил, что если я не CGLayerRelease слой, то программа не падает, она работает нормально. Это говорит мне, что что-то не так с управлением памятью слоя. Насколько я понимаю, сохранение объекта в NSArray увеличивает его счетчик ссылок, и он не будет освобожден до тех пор, пока массив не освободит его.

В любом случае, вот соответствующий код от drawRect. Внизу внизу видно, что я закомментировал CGLayerRelease. В этой конфигурации приложение не падает, хотя я думаю, что это утечка ресурсов. Если я раскомментирую этот выпуск, то приложение аварийно завершает работу, хотя drawRect (между первым и вторым вызовами очищается свойство led_info.selected одного из кругов, что указывает на то, что ему следует использовать сохраненный слой, а не новый слой:

NSLog(@"ledView drawing hue=%4f sat=%4f num=%d size=%d",hue_slider_value,sat_slider_value,self.num_leds,self.led_size);
rgb_color = [UIColor colorWithHue:1.0 saturation:1.0 brightness:1.0 alpha:1.0];
end_color = [UIColor colorWithHue:1.0 saturation:1.0 brightness:1.0 alpha:0.0];
NSArray *colors = [NSArray arrayWithObjects:
                   (id)rgb_color.CGColor, (id)end_color.CGColor, nil];
CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceRGB();
CGGradientRef gradient = CGGradientCreateWithColors(colorSpace,(__bridge CFArrayRef) colors, NULL);

CGLayerRef layer = CGLayerCreateWithContext(context, (CGSize){self.led_size,self.led_size}, /*auxiliaryInfo*/ NULL);
if (layer) {
    CGContextRef layer_context = CGLayerGetContext(layer);
    CGContextDrawRadialGradient(layer_context, gradient, led_ctr,self.led_size/8,led_ctr, self.led_size/2,kCGGradientDrawsBeforeStartLocation);
} else {
    NSLog(@"didn't get a layer");
}

for (int led=0;led<[self.led_info_array count];led++) {
    led_info=[self.led_info_array objectAtIndex:led];

// the first time through selected=1 and led_info.cg_layer=nil for all circles,
// so this branch is taken.
    if (led_info.selected || led_info.cg_layer==nil) {
        CGPoint startPoint=led_info.rect.origin;
        CGContextDrawLayerAtPoint(context, startPoint, layer);
        CGContextAddRect(context, led_info.rect);
        led_info.cg_layer=layer;

// the second time through drawRect one or more circles have been deselected.
// They take this path through the if/else
    } else {
        CGPoint startPoint=led_info.rect.origin;
// app crashes on this call to CGContextDrawLayerAtPoint
        CGContextDrawLayerAtPoint(context, startPoint, led_info.cg_layer);
    }

}

// with this commented out the app doesn't crash.
//CGLayerRelease(layer);

Вот объявление led_info:

@interface ledInfo : NSObject
@property CGFloat hue;
@property CGFloat saturation;
@property CGFloat brightness;
@property int selected;
@property CGRect rect;
@property CGPoint center;
@property unsigned index;
@property CGLayerRef cg_layer;
- (NSString *)description;
@end

led_info_array - это NSMutableArray объектов ledInfo, сам массив является свойством представления:

@interface ledView : UIView
@property float hue_slider_value;
@property float sat_slider_value;
@property unsigned num_leds;
@property unsigned led_size;
@property unsigned init_has_been_done;
@property NSMutableArray *led_info_array;
//@property layerPool *layer_pool;
@end

Массив инициализируется следующим образом: self.led_info_array = [[NSMutableArray alloc] init];

Изменить: так как я опубликовал, я обнаружил, что если я помещаю retain/release вокруг assignemt в NSMutableArray, то я также могу оставить в исходном CGLayerRelease, и приложение работает. Так что я предполагаю, что так оно и должно работать, хотя я хотел бы знать, почему необходимо сохранение / освобождение. В целевой книге C, которую я читаю (и ответ на вопрос, связанный выше), я думал, что назначение в NSArray неявно сохранило / освободило. Новый рабочий код выглядит так:

    if (led_info.selected || led_info.cg_layer==nil) {
        CGPoint startPoint=led_info.rect.origin;
        CGContextDrawLayerAtPoint(context, startPoint, layer);
        CGContextAddRect(context, led_info.rect);
        if (led_info.cg_layer) CGLayerRelease(led_info.cg_layer);
        led_info.cg_layer=layer;
        CGLayerRetain(layer);
    } else {
        CGPoint startPoint=led_info.rect.origin;
        CGContextDrawLayerAtPoint(context, startPoint, led_info.cg_layer);
    }

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

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

1) Подсчет ссылок не работает с CGLayerRef. Хорошо, но было бы неплохо знать это во время написания кода, а не после отладки. Что означает, что при использовании "вещей" в Objective C/ какао подсчет ресурсов не работает?

2) Вы говорите, что я храню собственность, а не NSArray. Верно, но местом назначения хранилища является NSArray через свойство, которое является указателем. Значение делает его в массив и обратно. Не работает ли подсчет ресурсов так? т.е. вместо CGLayerRef, если бы я хранил некоторый NSObject в NSArray, используя приведенный выше код, сработал бы подсчет ресурсов? Если нет, то будет ли работать избавление от промежуточного свойства led_info и доступ к массиву непосредственно из цикла?

1 ответ

Решение

Вы не храните слой непосредственно в NSArray, Вы храните это в собственности вашего ledInfo объект.

Проблема в том, что CGLayer на самом деле не является объектом Objective-C, поэтому ни ARC, ни создатель компиляторов ("синтезированный") не позаботятся о его сохранении и выпуске. Предположим, вы делаете это:

CGLayerRef layer = CGLayerCreateWithContext(...);
led_info.cg_layer = layer;
CGLayerRelease(layer);

cg_layer Метод setter, сгенерированный компилятором, просто сохраняет указатель в переменной экземпляра и ничего больше, потому что CGLayerRef не является ссылкой на объект Objective-C. Поэтому, когда вы затем отпускаете слой, его счетчик ссылок становится равным нулю, и он освобождается. Теперь у вас есть висящий указатель в вашем cg_layer свойство, и когда вы используете его позже, вы терпите крах.

Исправление заключается в том, чтобы написать установщик вручную, например так:

- (void)setCg_layer:(CGLayerRef)layer {
    CGLayerRetain(layer);
    CGLayerRelease(_cg_layer);
    _cg_layer = layer;
}

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

ОБНОВИТЬ

В ответ на ваши изменения:

  1. Подсчет ссылок работает с CGLayerRef, Автоматический подсчет ссылок (ARC) не делает. ARC работает только с вещами, которые он считает объектами Objective-C. ARC автоматически не сохраняет и не освобождает CGLayerRefпотому что ARC не думает CGLayerRef является ссылкой на объект Objective-C. Объект Objective C является (вообще говоря) экземпляром класса, объявленного с @interfaceили блок.

    Справочник CGLayer говорит, что CGLayer происходит от CFTypeбазовый тип для всех базовых базовых объектов. (Что касается ARC, объект Core Foundation не является объектом Objective-C.) Вам необходимо прочитать о "Политике владения" и "Управлении жизненным циклом объекта Core Foundation" в Руководстве по программированию управления памятью для Core Foundation.

  2. "Пункт назначения магазина" является переменной экземпляра в вашем ledInfo объект. Это не "NSArray через собственность". Значение не "попадает в массив и обратно". Массив получает указатель на ваш ledInfo объект. Массив сохраняет и освобождает ledInfo объект. Массив никогда не видит и ничего не делает с CGLayerRef, Ваш ledInfo объект отвечает за сохранение и освобождение любых объектов Core Foundation, которыми он хочет владеть, например, слой в его cg_layer имущество.

    Как я уже говорил, если ledInfo не сохраняет слой (с CFRetain или же CGLayerRetain) в своем cg_layer сеттер, это рискует, что слой будет освобожден, оставляя ledInfo с висящим указателем. Вы понимаете, что такое висячий указатель?

Другие вопросы по тегам