Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Compactly disable arc-retain-cycles warning for self-referencing blocks

I'm writing an API that involves event handling, and I'd like to be able to use blocks for the handlers. The callbacks will often want to access or modify self. In ARC mode, Clang warns that blocks referencing self are likely to create a retain cycle, which seems like a helpful warning that I want to keep on in general.

However, for this portion of my API, the lifecycle of the callback and the containing object are maintained externally. I know I can break the cycle when the object should be deallocated.

I can turn off the retain cycle warning on a per file basis with #pragma clang diagnostic ignored "-Warc-retain-cycles", but that disables the warning for the entire file. I can surround the blocks with a #pragma clang diagnostic push and pop around that warning, but that makes the blocks ugly.

I can also get the warning to go away by referencing a __weak variable pointing to self instead of referencing self directly, but that makes the blocks far less pleasant to use.

The best solution I've come up with is this macro that does the diagnostic disabling around the block:

#define OBSERVE(OBJ, OBSERVEE, PATH, CODE) \
[(OBJ) observeObject:(OBSERVEE) forKeyPath:(PATH) withBlock:^(id obj, NSDictionary *change) { \
_Pragma("clang diagnostic push") \
_Pragma("clang diagnostic ignored \"-Warc-retain-cycles\"") \
do { CODE; } while(0); \
_Pragma("clang diagnostic pop") \
}];

That works, but it's not very discoverable for API users, it doesn't allow nested observers, and it interacts poorly with XCode's editor. Is there a better way to disable or avoid the warning?

like image 961
Charlie Groves Avatar asked Nov 24 '11 01:11

Charlie Groves


3 Answers

To begin with, there is a simple way to disable warnings for certain lines of code using #pragma:

#pragma clang diagnostic push
#pragma clang diagnostic ignored "<#A warning to ignore#>"
<#Code that issues a warning#>
#pragma clang diagnostic pop

But I wouldn't use it in this particular case because it won't fix the issue, it'll just hide it from the developer. I would rather go with solution that Mark proposed. To create a weak reference, you can do one of the following outside of the block:

__weak typeof(self) weakSelf = self; // iOS ≥ 5
__unsafe_unretained typeof(self) unsafeUnretainedSelf = self; // 5 > iOS ≥ 4
__block typeof(self) blockSelf = self; // ARC disabled
like image 145
Sash Zats Avatar answered Nov 14 '22 20:11

Sash Zats


I wrote the following macro, which I think, is pretty clever...

#define CLANG_IGNORE_HELPER0(x) #x
#define CLANG_IGNORE_HELPER1(x) CLANG_IGNORE_HELPER0(clang diagnostic ignored x)
#define CLANG_IGNORE_HELPER2(y) CLANG_IGNORE_HELPER1(#y)

#define CLANG_POP _Pragma("clang diagnostic pop")
#define CLANG_IGNORE(x)\
    _Pragma("clang diagnostic push");\
    _Pragma(CLANG_IGNORE_HELPER2(x))

It allows you to do all sorts of fun things (without Xcode haranguing you), such as..

CLANG_IGNORE(-Warc-retain-cycles)
[object performBlock:^(id obj){ [obj referToSelfWithoutWarning:self]; }];
CLANG_POP

You can put in any warning flag and Clang will heed to your whims...

CLANG_IGNORE(-Warc-performSelector-leaks);
return [self performSelector:someIllBegotSelector withObject:arcFauxPas];
CLANG_POP

Then again, the warnings usually are there for a reason. Party poppers.

like image 2
Alex Gray Avatar answered Nov 14 '22 20:11

Alex Gray


I think disabling the warning is currently the only correct way to do, since it says the compiler: Do not care about this retain cycle, I am aware of it and I will dispose of the observer myself. Introducing a weak reference is a costly solution since it comes with runtime CPU and memory overhead.

like image 1
Frank Avatar answered Nov 14 '22 21:11

Frank