Преобразовать метод, который возвращает автореализованный CGColor ARC

В процессе преобразования проекта я использую ARC. У меня есть категория на NSColor с методом, который возвращает автореализованное представление CGColor:

@implementation NSColor (MyCategory)

- (CGColorRef)CGColor
{
    NSColor *colorRGB = [self colorUsingColorSpaceName:NSCalibratedRGBColorSpace];
    CGFloat components[4];
    [colorRGB getRed:&components[0]
               green:&components[1]
                blue:&components[2]
               alpha:&components[3]];
    CGColorSpaceRef space = CGColorSpaceCreateWithName(kCGColorSpaceGenericRGB);
    CGColorRef theColor = CGColorCreate(space, components);
    CGColorSpaceRelease(space);
    return (CGColorRef)[(id)theColor autorelease];
}

@end

Каков правильный способ сделать это с помощью ARC? Я не хочу возвращать сохраненный CGColor.

Конвертер ARC в XCode предлагает использовать

return (CGColorRef)[(__bridge id)theColor autorelease];

но это приводит к следующему сообщению об ошибке:

[rewriter] небезопасно передавать в 'CGColorRef' результат сообщение "autorelease"; листинг __bridge может привести к указателю на уничтоженный объект, а __bridge_retained может пропустить объект

Ответ 1

CGColor - объект Core Foundation. Вы не должны пытаться использовать с ним autorelease. Вместо этого вы должны переименовать свой метод copyCGColor и вернуть сохраненный объект.

Автоматическое освобождение - это концепция Objective-C. Он не существует на уровне Core Foundation.

Так как CGColor не является бесплатным мостом для любого класса Objective-C, очень странно пытаться его автоопределить (даже если это может сработать).

Обновление через несколько лет

Теперь на уровне CoreFoundation есть CFAutorelease() (доступно с Mavericks и iOS 7).

Ответ 2

По существу это потому, что нет хорошего способа конвертировать следующий код в ARC:

CGColorRef a = ...;
id b = [(id)a autorelease];
CGColorRef c = (CGColorRef)b;
// do stuff with c

Конвертор удаляет -autorelease и добавляет некоторые мостовые приведения, но он застревает:

CGColorRef a = ...;
id b = (__bridge_transfer id)a;
CGColorRef c = (__bridge_SOMETHING CGColorRef)b;
// do stuff with c. Except the compiler sees that b is no longer being used!

Но что должен выбрать мигратор для __bridge_SOMETHING?

  • Если он выбирает __bridge, то b больше не используется, поэтому компилятор может немедленно его освободить. Это приведет к сбоям.
  • Если он выбирает __bridge_retained, тогда собственность переносится обратно на "CF-land", но исходный код предполагает, что объект будет принадлежать пулу автоопределений. Код теперь протекает.

Проблема заключается в том, что ARC запрещает вызов -autorelease, но не имеет документального метода, гарантирующего, что объект добавляется в пул автозаполнения - единственная веская причина для этого, чтобы вернуть автореализованный тип CF из метода, но большое количество классов UIKit имеют свойства CF-typed (и MKOverlayPathView имеет атомное свойство CGPathRef, которое должно возвращать значение с автореализацией).

Это один из сложнейших бит ARC, который я действительно хотел, был лучше документирован.

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

  • Определите функцию CFAutorelease() в файле, скомпилированном без ARC (добавьте -fno-objc-arc в флагов компилятора в целевых настройках → Build Phases → Compile Sources). Я оставляю это как упражнение для читателя. Это работает, потому что код ARC должен взаимодействовать с кодом MRC. Это, пожалуй, самое чистое решение. (Это обязано привлечь комментарий, в котором говорится, что он не должен использовать префикс CF, но до тех пор, пока вы не видите ошибку ссылки, коллизии имен символов C, как правило, безопасны, поскольку введенное "двухуровневое пространство имен" в 10.3 или около того.)

  • Различные обручи для отправки сообщения -autorelease или его эквивалента. Все это немного беспорядочно, потому что они полагаются на "обманывающий" ARC, за исключением последнего, который предполагает id, с ABI-совместимым с void*. Они также, вероятно, медленнее, чем указано выше, потому что им нужно искать класс/селектор (objc_lookUpClass() и sel_registerName() может быть быстрее или даже оптимизирован, но я бы не стал делать ставку на него).

    return (__bridge CGColorRef)[(__bridge id)theColor performSelector:NSSelectorFromString(@"autorelease")]
    
    [NSClassFromString(@"NSAutoreleasePool") addObject:(__bridge id)theColor]
    return theColor;
    
    return (__bridge CGColorRef)((id(*)(id,SEL))objc_msgSend)((__bridge id)theColor,NSSelectorFromString(@"autorelease"));
    
    return ((void*(*)(void*,SEL))objc_msgSend)(theColor,NSSelectorFromString(@"autorelease"));
    
  • Заставить его добавить в пул автозапуска путем назначения переменной __autoreleasing, которую компилятор не может оптимизировать. Я не уверен, что это гарантировано (в частности, возможно что-то похожее на objc_autoreleaseReturnValue() и objc_retainAutoreleasedReturnValue(), но я думаю, что это маловероятно, так как это замедлит общий случай (NSError * __autoreleasing *)error).

    -(id)forceAutorelease:(id)o into:(id __autoreleasing*)p
    {
      *p = o;
      return p;
    }
    
    -(CGColorRef)CGColor
    {
      ...
      CGColorRef theColor = CGColorCreate(...);
      CGColorSpaceRelease(space);
      id __autoreleasing temp;
      return (__bridge CGColorRef)[self forceAutorelease:(__bridge_transfer id)theColor into:&temp];
    }
    

    (Также возможно, что компилятор/среда выполнения будут взаимодействовать и использовать статическую отправку/вложение, пока соответствующие методы не будут переопределены, но это кажется сложным и не без значительных накладных расходов.)

  • Используйте typedef с __attribute__((NSObject)). Это наиболее смутно документированные части спецификация ARC, но похоже что-то вроде этого:

    typedef CGColorRef MyCGColorRef __attribute__((NSObject));
    -(MyCGColorRef)CGColor
    {
      ...
      return (__bridge MyCGColorRef)(__bridge_transfer id)theColor;  
    }
    

    Я думаю, вам нужно два моста для этого, чтобы работать (один для передачи права собственности на ARC и другой); если вы просто return theColor;, я подозреваю, что он просочился. Из моего чтения документов вам просто нужно (__bridge_transfer MyCGColorRef), потому что он преобразуется из указателя без ARC (CGColorRef) в указатель ARC (MyCGColorRef), но это заставляет компилятор жаловаться. Увы, в документах нет примеров использования __attribute__((NSObject)) typedefs.

    Обратите внимание, что вам не нужно изменять тип возвращаемого значения в заголовке. Это может позволить оптимизировать оптимизацию возвращаемого значения, но я не уверен, как компилятор обрабатывает преобразование из MyCGColorRef в CGColorRef. Ле вздох.

Ответ 3

Начиная с OS X 10.9 или iOS 7 вы можете просто использовать CFAutorelease() (объявленный в CFBase.h).

Ответ 4

В самом деле, в ручном управлении памятью вы можете retain, release и autorelease любой объект CoreFoundation, потому что все они без использования моста наименьшее NSObject.

Поскольку ARC запрещает использование ручного управления памятью, нам нужно как-то сказать намек на компилятор, что делать. Один из способов - назвать ваш метод - (CGColorRef)copyCGColor;, чтобы компилятор знал, что метод возвращает объект с сохранением +1.

Однако, если вы похожи на меня и предпочитаете простой "CGColor" для таких методов, вы можете просто добавить __attribute__((cf_returns_retained)) к определению метода:

@interface NSColor (MyCategory)

- (CGColorRef)CGColor __attribute__((cf_returns_retained));

@end

Ответ 5

Я думаю, вы хотите использовать __bridge_transfer в этом случае.

Docs