Skip to content

Instantly share code, notes, and snippets.

@adamkaplan
Last active September 5, 2015 00:29
Show Gist options
  • Save adamkaplan/bac888e9a0e126086eee to your computer and use it in GitHub Desktop.
Save adamkaplan/bac888e9a0e126086eee to your computer and use it in GitHub Desktop.
Exploratory examination of a way to prevent accidental retain cycles in blocks.
//
// YFObjCUtils.c
//
// Created by Adam Kaplan on 7/31/15.
// Licensed under the MIT license.
//
@implementation UsageExample
- (void)usageMethod {
// Self must not be captured strongly by the handler block. To do so would create a
// retain cycle since the block is retained by self. If self were captured stronly
// the desctructor (-dealloc) might never get called.
@weakify(self);
self.handler = ^{
@strictStrongify(self);
// Traditional @strongify(self); would still work when needed.
strong_self.showsUsage = YES; // ok! Explicit use of strong reference.
self.showsUsage = YES; // exception (except in release builds)
});
}
@end
/*
2015-09-04 20:12:16.782 Y! Finance *** Terminating app due to uncaught exception 'NSInternalInconsistencyException', reason: 'Improper use of weakified self-reference sentinel.'
*/
//
// YFObjCUtils.h
//
// Created by Adam Kaplan on 7/31/15.
// Licensed under the MIT license.
//
@weakify(self);
dispatch_sync(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
@strictStrongify(self);
strong_self.pollingEnabled = YES; // ok
self.pollingEnabled = YES; // exception
});
#import "EXTScope.h"
#ifndef YFObjCUtils_h
#define YFObjCUtils_h
extern id sharedSentinelObject(void);
#define strictStrongify(...) \
yfmacro_preamble \
metamacro_foreach(yfmacro_strongify_,, __VA_ARGS__) \
metamacro_foreach(yfmacro_setSentinel_,, __VA_ARGS__) \
yfmacro_epilogue
#if !defined(NS_BLOCK_ASSERTIONS)
#define yfmacro_setSentinel_(INDEX, REF) \
yfmacro_pragma("clang diagnostic push") \
yfmacro_pragma("clang diagnostic ignored \"-Wshadow\"") \
__unsafe_unretained __typeof__(REF) REF = sharedSentinelObject();\
yfmacro_pragma("clang diagnostic push") \
#else // NS_BLOCK_ASSERTIONS
// Disable sentinels whenever assertions are disabled.
#define yfmacro_setSentinel_(INDEX, REF) \
__strong __typeof__(REF) REF = metamacro_concat(REF, _weak_);
#endif // NS_BLOCK_ASSERTIONS
#define yfmacro_preamble try {} @catch(...) {}
#define yfmacro_epilogue do {} while (0)
#define yfmacro_pragma(A) _Pragma (#A)
#define yfmacro_concat(A,B) A ## B
#define yfmacro_strongify_(INDEX, VAR) \
__strong __typeof__(VAR) metamacro_concat(strong_, VAR) = metamacro_concat(VAR, _weak_);
#endif // Pods_YFObjCUtils_h
//
// YFObjCUtils.c
//
// Created by Adam Kaplan on 7/31/15.
// Licensed under the MIT license.
//
@interface ProhibitedReference : NSObject
+ (instancetype)sharedInstance;
@end
static ProhibitedReference *sharedInstance;
@implementation ProhibitedReference
+ (void)load {
sharedInstance = [self new];
}
+ (instancetype)sharedInstance {
return sharedInstance;
}
- (void)doesNotRecognizeSelector:(SEL)aSelector {
NSAssert(false, @"Improper use of weakified self-reference sentinel.");
}
@end
id sharedSentinelObject(void) {
return [ProhibitedReference sharedInstance];
}
@adamkaplan
Copy link
Author

The underlying concept here is that being explicit about the your intentions while coding can help identify error cases. A sloppy merge conflict is an easy way for a strong reference to self sneak in :-/

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment