When Apple released iOS 6, they stated that developers were having "trouble" with properly using viewDidUnload
and viewWillUnload
and decided that ultimately the memory hit from releasing objects in these methods wasn't enough of a concern to keep those methods and they were deprecated. It was basically a nice way of saying that a lot of developers are so terrible at view lifecycle management that they were just going to remove the unload methods so that developers didn't continue to mess up their app by not know what they are doing. Now I personally applaud Apple for taking into account this problem and dealing with it in a way that doesn't hurt legacy apps. However, I do believe that all APIs are tools that when wielded appropriately can be useful for building something that works well. So when viewDidUnload
and viewWillUnload
were deprecated, one of the first things I set out to do was port them into the future so that I could continue to use these "tools" into the future. Now, if you don't want to use viewDidUnload
or viewWillUnload
and want to conform to Apple's recommended application life cycle design guidelines, then this article won't likely help you, and I must credit your wisdom. For the rest of you, let's jump into memory warnings!
viewWillUnload and viewDidUnload ported to iOS 6
So to implement the new viewDidUnload
and viewWillUnload
methods, we need to know when they are supposed to be executed. Pretty simply, when a UIViewController
gets a memoryWarning
and its view is not actively visible, it will call viewWillUnload
, release its view and then will call viewDidUnload
. So let's start by implementing our revised didReceiveMemoryWarning
method.
01 -(void) didReceiveMemoryWarningWithViewUnloading
02 {
03 if (_cmd == @selector(didReceiveMemoryWarning))
04 {
05 06 [self didReceiveMemoryWarningWithViewUnloading];
07 }
08 else
09 {
10 11 [self didReceiveMemoryWarning];
12 }
13
14 static BOOL s_portUnloading = NO;
15 static dispatch_once_t onceToken;
16 dispatch_once(&onceToken, ^{
17 NSArray* comps = [[[UIDevice currentDevice]
systemVersion] componentsSeparatedByString:@"."];
18 s_portUnloading = [[comps objectAtIndex:0] integerValue] >= 6;
19 });
20 if (s_portUnloading)
21 {
22 if (self.isViewLoaded && !self.view.window)
23 {
24 if ([self respondsToSelector:@selector(viewWillUnload)])
25 {
26 [self viewWillUnload];
27 }
28 self.view = nil; 29 NSAssert(!self.isViewLoaded);
30 if ([self respondsToSelector:@selector(viewDidUnload)])
31 {
32 [self viewDidUnload];
33 }
34 }
35 }
36}
First thing we do in our method to support unloading a view controller's view on a memory warning is ensure that the original memory warning method is called. This is as easy as doing a simple selector comparison to see if we are swizzled or not.
Next, we need to check that our OS version is iOS 6 or later to ensure we are extending the functionality only where it needs to be extended. We don't however want to take the overhead of making the OS version check over and over again, so we will use a static BOOL
to store whether we are iOS 6+ or not. Now as far as checking the OS, I'm just parsing the systemVersion
but honestly every app should have a strong mechanism for OS Version checking. I personally have a custom Version
object that is an object representation of a version with an class method for easily accessing the OS Version (as well as another for the Application Version). I should do a post on Version objects in the future as it's really something every application should have.
Once we've checked that we do want to support the unloading of the view, we just replicate the view unload logic that older iOS versions already support.
- Only unload if the view is loaded AND is not currently visible (I use
self.view.superview
as the check, however, it could be feasible to check if the view hierarchy exists in a window but we'd also need to remove the view before unloading it so that it's not a dangling view reference inside some other view). - Call
viewWillUnload
if available - Unload the view
- Call
viewDidUnload
if available
Ok, we've got our replacement method so let's implement the category class method we'll use to turn on view unloading by memory warnings.
01 @implementation UIViewController (ViewUnloadSupport)
02
03 + (void) portViewUnloadSupport
04 {
05 static dispatch_once_t onceToken;
06 dispatch_once(&onceToken, ^{
07 NSArray* comps = [[[UIDevice currentDevice] systemVersion]
componentsSeparatedByString:@"."];
08 if ([[comps objectAtIndex:0] integerValue] >= 6)
09 {
10 SwizzleInstanceMethods([UIViewController class],
@selector(didReceiveMemoryWarning),
@selector(didReceiveMemoryWarningWithViewUnloading));
11 }
12 });
13 }
14
15 @end
Alright, no problem! If the OS is greater than or equal to iOS 6, let's swizzle the didReceiveMemoryWarning
instance method. All we have to do is call [UIViewController portViewUnloadSupport];
from the application did load method and we have view unloading support once again in iOS 6.
NOTE: I did not provide the @interface
declaration of the category, but it just seemed like trivial detail.
CodeProject Read post ยป