At this point, I assume everyone knows what reference counting is and how to use it. If not, you should verse yourself with Apple's Memory Management Overview. Now since Apple has introduced Automatic Reference Counting (ARC), objective-c memory management has reached near trivial levels as that of managed memory languages like Java and C#. But for those of us that need to support older platforms, work in legacy code bases or develop public libraries that require non-ARC support, being elegant with reference counting is very important. And even those of us that are converted over to ARC, we still need to be aware of Core Foundation memory management which does not have ARC.
So that was a serious introduction to nothing it would seem! Well, what this post will actually cover is the pitfall of recursive (aka reentrant) code that can lead to crashing in a ref counted world. Basically, we'll deal with releasing an object that leads to it being released again.
The problem: reentrant releases!
WARNING: Contrived example coming up!! Don't just dismiss this topic due to the example being impractical, I know it's impractical - it's just an example.
First let's establish the problem with an example. Ignoring that iOS 6 has deprecated
viewWillUnload
and viewDidUnload
(a subject I tackle in this post), it is common practice to have member variables of UIViewController
instances that have a life time from viewDidLoad
to viewDidUnload
. Those member variables are often internal an not exposed as properties, so having self.property = nil;
in viewDidUnload
is not really necessary because creating a synthesized property is overkill. Instead we pair the alloc-init
or retain
that is done in viewDidLoad
with a release
and an assignment of nil
.- (void) viewDidLoad { [super viewDidLoad]; // load work ... _someObject = [[SomeObject alloc] init]; } - (void) viewDidUnload { [super viewDidUnload]; // unload work ... [_someObject release]; _someObject = nil; } - (void) dealloc { // remember to release in dealloc [_someObject release]; [super dealloc]; }
Seems reasonable right? In fact this has been my pattern for releasing and niling an object for over a decade. So much so, that I made it a best practice to use a helper macro. [NOTE: I love macros, the pre-processor is my best friend and I will have to do a post on macros sometime in the future]
#define NIL_RELEASE(obj) { [obj release]; obj = nil; }
So now we have:
- (void) viewDidUnload { [super viewDidUnload]; // unload work ... NIL_RELEASE(_someObject); }
Alright! Nice and clean, and everywhere throughout my code. Seems like we have great pattern for releasing an object's reference and then assigning the reference
nil
. Now enters the problem of reentrant code. Now first and foremost, a developer should do his diligence to ensure not to create retain cycles. But sometimes we can end up accepting a retain cycle if we know that we can break that cycle somehow. Let's say we've put ourselves in that position. Our _someObject
has a property to hold a block
for some use. Now let's say at some point we set that block
property with a block that uses self
(the UIViewController
we are dealing with). Since that block now retains self
we now have a retain cycle. But we have looked at the code and see that _someObject
will have a shorter lifecycle than the UIViewController
or we just will rely on a memory warning purging the view which will call viewDidUnload
.Here's the problem as it will unfold. [BTW: I apologize for using dealloc as a verb]
- Memory warning occurs
viewDidUnload
is called_someObject
hasrelease
called_someObject
now deallocs due to it's last reference being released_someObject
'sdealloc
releases it'sblock
property- the
block
deallocs and releases the reference toself
self
now has no more references and needs todealloc
_someObject
hasrelease
called!EXEC_BAD_ACCESS
-_someObject
has already started deallocing!
Oh man! We've just established that our "safe"
NIL_RELEASE
macro isn't safe at all! And we've already spread our NIL_RELEASE
throughout our codebase! Let's tackle this.So we've established that due to reentrancy, the reference
_someObject
can be compromised on release
. We also already know that objective-c will handle sending messages to (aka calling methods on) nil
references. So logically, we can note that we want to set the reference to nil
before we call release
. So let's update our macro!
#define NIL_RELEASE(obj) \ id obj_ = obj; \ obj = nil; \ [obj_ release];
Alright! Now we've solved the reentrancy! But wait, what if we have multiple objects we want to release? The
obj_
temporary variable will cause compiler warnings. No problem! We'll scope the NIL_RELEASE
(like I had previously). This is also valuable if we use NIL_RELEASE
in an if
, else
, while
, or for
statement that isn't scoped like this:
if (true) NIL_RELEASE(_someObject);
which the precompiler will expand into this (yikes!):
if (true) id obj_ = _someObject; _someObject = nil; [obj_ release];
It should really always be considered best practice to scope you macros that behave like functions, paren your macro variables, and paren your macro if it behaves like an expression.
#define SUM(x,y) ((x) + (y))
. I'll have to touch on macros again sometime. So scoped, we now have:
#define NIL_RELEASE(obj) \ { \ id obj_ = obj; \ obj = nil; \ [obj_ release]; \ }
That should be it, right? Right!?!? Oh so close! Here we come to the point where compilers can really be too smart for their own good! First let's break down what will happen in the
NIL_RELEASE
macro at a C level.
- (void) viewDidUnload { objc_msgSend(super, @selector(viewDidUnload)); // [super viewDidUnload]; id obj_ = _someObject; _someObject = nil; objc_msgSend(obj_, @selector(release)); // [obj_ release]; }
Now the compiler comes along and says, "Hey, this is a waste of operations! There's no need for a temporary variable obj_ since I can directly release _someObject and then set it to nil. Let's do that instead!" So, thanks to optimizations in the compiler to remove unnecessary instructions, we get:
- (void) viewDidUnload { objc_msgSend(super, @selector(viewDidUnload)); // [super viewDidUnload]; objc_msgSend(_someObject, @selector(release)); // [_someObject release]; _someObject = nil; }
Dang it! We now have exactly the same thing as the original
NIL_RELEASE
! What can be done! Never fear, memory barriers are here! So in order to have the compiler not over optimize the code into a bug, we want to separate the assignment of _someObject
to the temporary variable from the function call against the temp variable such that the compiler will treat them as independent. To do this we use the OSMemoryBarrier()
function. And with the content kept in the safety of a do/while statement, we now have:
#define SAFE_RELEASE(obj) \ do { \ id obj_ = obj; \ obj = nil; \ OSMemoryBarrier(); \ [obj_ release]; \ } while (0) // you'll need to include the header #include <libkern/OSAtomic.h>
There you have it; a complete mechanism for safely releasing an object reference and setting it to nil. In fact, I've renamed the macro to
SAFE_RELEASE
. I'll also provide a Core Foundation macro remembering that CFRelease
cannot be passed NULL
.
#define CFReleaseSafe(ref) \ if (ref) \ { \ CFTypeRef ref_ = ref; \ ref = NULL; \ OSMemoryBarrier(); \ CFRelease(ref_); \ } // you'll need to include the header #include <libkern/OSAtomic.h>
https://www.theconvehersation.com/2018/02/do-not-let-being-woman-hold-you-back.html
ReplyDelete
ReplyDeleteTo be able to earn profits
mygcu.edustudentportal
Watch cartoon online
movies from Solar Movies
https://saglamproxy.com
ReplyDeletemetin2 proxy
proxy satın al
knight online proxy
mobil proxy satın al
5VFPK7