Objective-C Memory Management For Lazy People

原文地址:http://interfacelab.com/objective-c-memory-management-for-lazy-people/ 有空的时候把主要内容给翻译一下。


Memory Management in Objective-C is Easier Than James Gregory Thinks

I know the concept of memory management scares the shit out of script kids and the average java developers, but it really isn’t that scary.  But before we get into it, let’s see what James Gregory has to say about it:

iPhone doesn’t have it. Seriously, Apple: What The? Various luminarieshave waxed geeky on why garbage collection is so great, so I’ll keep this brief. Basically there’s two reasons it’s awesome: I really don’t have time to learn the rules of memory ownership and when to free, and all that stuff for yet another language. Secondly, I just can’t think of another language feature that accelerates development as much as garbage collection. Android has it, and for anything not pushing the envelope in terms of what the hardware can do (ie, almost everything), it works really well. I might have understood why Apple didn’t include it in the first couple of iPhones — it does come at a computational premium — but they really don’t have an excuse any more. It’s a big enough deal that I would advise the first-time mobile app developer to start on Android for this reason alone.

Jesus, there are so many things wrong with this, but mostly it reinforces wrong and lazy ideas about memory management.  His statement, “I really don’t have time to learn the rules of memory ownership and when to free, and all that stuff for yet another language” is about the most ignorant thing I’ve ever heard a developer say.  Basic object-oriented memory management is universal, no matter if you are doing C++, ObjectPascal or any other non-garbage collected language.  It’s some pretty basic computer science and the concepts, specifically how they apply to Objective-C, are nearly trivial.  If I were interviewing someone for a job and they said something similar, the interview would be concluded at that point.

I agree with him that the iPhone should have GC though, so don’t think for a second I don’t see its advantages.  That’s why you should listen when I tell you that manual memory management is not a significant effort, specifically in Objective-C.  I sometimes, though rarely, use it for Cocoa desktop apps that I build, and I build a lot of them.  The typical use case for me to use GC is when a quick app idea I’m sketching out in XCode becomes a fairly full featured thing.  So while I am in the process of exploration, GC is on because I’m not typically writing code I’ll reuse.  Once my idea is proven, however, the GC goes off and I refactor all of that code.  The other case where I have it on is when I discover a leak in cocoa itself, but even that is pretty rare.

So if I use GC sometimes, why don’t I use it all of the time?  Because it’s so easy to do memory management that I don’t need it.  I also tend to write a lot of private frameworks that I re-use across all sorts of projects, which may or may not have GC turned on.

The thing is, the retain/release model in Objective-C is roughly a middle ground from no memory management to a basic GC so you are already ahead of the curve.  Unlike C++ or ObjectPascal, you actually never technically “free” objects you are using, you simply are saying “I’m done with you” and then let the runtime take care of the rest.

The Four Basic Rules

There are only 4 basic rules when it comes to memory management in Objective-C:

  • If you own it, release it.
  • If you don’t own it, don’t release it.
  • Override dealloc in your classes to release the fields that you own.
  • Never call dealloc directly.

That’s it.  The first two are the most important and we’ll get into the basics of object ownership next.

Rule #1.  When You Own It.

You own an object in Objective-C when you alloc it, copy it or new it.  For example:

      
      
- ( void ) someMethod
{
   // I own this!
   SomeObject * iOwnThis = [[ SomeObject alloc ] init ];
   [ iOwnThis doYourThing ];
   // I release this!
   [ iOwnThis release ];
}
- ( void ) someOtherMethod: ( SomeObject * ) someThing
{
   // I own this too!
   SomeObject * aCopyOfSomeThing = [ someThing copy ];
   [ aCopyOfSomeThing doSomething ];
   // I release this!
   [ aCopyOfSomeThing release ];
}
- ( void ) yetAnotherMethod
{
   // I own this too!
   SomeObject * anotherThingIOwn = [ SomeObject new ];
   [ anotherThingIOwn doSomething ];
   // I release this!
   [ anotherThingIOwn release ];
}
 
view raw gistfile1.m This Gist brought to you by  GitHub.

How easy is that?  I can’t even think of anything more to write about this, it’s that simple.  But to reiterate:

  • You own it if you alloc it.
  • You own it if you copy it.
  • You own it if you new it.  (New is simply a shortcut for alloc/init).

James if you are still reading this, you are 1/4th the way to learning it buddy.  Just stick with me for the next couple of bits and you’ll be writing software I trust in no time.

Rule #2.  When You Don’t Own It.

This is the tricky part.  You don’t own it when you don’t own it.  So if you did not alloc it, or you did not copy it, or you did not new it – you don’t own it.

What about the following?

      
      
- ( void ) sayWhat
{
    NSString * doIOwnThisIWonder = [ NSString stringWithFormat: @"%@" , @"Nope" ];
    NSImage * iOwnThisImage = [[ NSImage alloc ] initWithContentsOfFile: @"/tmp/youownthis.jpg" ];
    NSData * perhapsThisData = [ iOwnThisImage TIFFRepresentation ];
}
view raw gistfile1.m This Gist brought to you by  GitHub.

Do you own that NSString?  Nope, you didn’t alloc/copy/new it.

The NSImage?  Yes you own this, you alloc‘d that bad boy.

The NSData?  Nope, again, you didn’t alloc/copy/new it.

Here is how that method should really look, btw:

      
      
- ( void ) sayWhat
{
    NSString * doIOwnThisIWonder = [ NSString stringWithFormat: @"%@" , @"Nope" ];
    NSImage * iOwnThisImage = [[ NSImage alloc ] initWithContentsOfFile: @"/tmp/youownthis.jpg" ];
    NSData * perhapsThisData = [ iOwnThisImage TIFFRepresentation ];
    ... do my thing ...
   [ iOwnThisImage release ];
}
view raw gistfile1.m This Gist brought to you by  GitHub.

Yes, it’s so much harder than GC.  How do us Cocoa devs ever get anything done?

Rules #3 and #4.  Dealloc.

Ok, so here’s the hardest part of the whole thing.  If you have any class with object properties that you retain, you’ll have to release them when your object has been deallocated via the [dealloc] message.  An example to demonstrate:

      
      
//
// SomeObject.h
//
#import <Cocoa/Cocoa.h>
@interface SomeObject : NSObject {
     NSMutableArray * things ;
     NSMutableDictionary * someOtherThings ;
}
@end
view raw SomeObject.h This Gist brought to you by  GitHub.
      
      
//
// SomeObject.m
//
#import "SomeObject.h"
@implementation SomeObject
- ( id ) init {
if ( self = [ super init ]) {
things = [[ NSMutableArray arrayWithObjects: @"one" , @"two" , @"three" , nil ] retain ];
someOtherThings = [[ NSDictionary alloc ] init ];
}
return self ;
}
- ( void ) dealloc {
[ things release ];
[ someOtherThings release ];
[ super dealloc ];
}
@end
view raw SomeObject.m This Gist brought to you by  GitHub.

In the above example, our SomeObject has two fields: things and someOtherThings.  You can see in our init method that we create the objects and assign them to our properties.  For things, because we are using [NSMutableArray arrayWithObjects:], we have to call retain.  Remember, we didn’t alloccopyor new, instead we called a convenience method that returns an autoreleased object that we must explicitly retain.  What is autorelease you might be asking?  I’ll explain in the next part, but for now all you have to remember is the basics of object ownership described above.

In the [dealloc] method you’ll see that we are releasing the objects we created.  Nothing more, nothing less.

James if you have made it this far, you’ve essentially learned all you really need to know.  Everything below here is optional, but recommended to know.  That wasn’t so hard was it.

Properties

Objective-C 2.0 includes properties, and while I won’t get into the particulars of them, there is some confusion around the retain/release cycle with regards to them.  There is really only two rules:

  • If you retain or copy your property, you need to set it to nil in your dealloc.
  • If you initialize the property during initautorelease it.

Let’s look at some properties:

      
      
//
// SomeObject.h
//
#import <Cocoa/Cocoa.h>
@interface SomeObject {
     NSString * title ;
     NSString * subtitle ;
}
@property ( retain ) NSString * title ;
@property ( retain ) NSString * subtitle ;
@end
view raw SomeObject.h This Gist brought to you by  GitHub.
      
      
//
// SomeObject.m
//
#import "SomeObject.h"
@implementation SomeObject
@synthesize title ;
@synthesize subtitle ;
- ( id ) init {
     if ( self = [ super init ])
     {
         self . title = [ NSString stringWithFormat: @"allyouneed" ];
         self . subtitle = [[[ NSString alloc ] init ] autorelease ];
     }
     return self ;
}
- ( void ) dealloc {
     self . title = nil ;
     self . subtitle = nil ;
     [ super dealloc ];
}
@end
view raw SomeObject.m This Gist brought to you by  GitHub.

The title property we are calling a convenience method on NSString that returns an autoreleased object, so we don’t have to do a thing.  For subtitle, we are allocating a new NSString ourselves, therefore we need to autorelease it.  The reason being is that when the value is assigned to the property, the retain count is incremented by 1, so without the autorelease, that object will maintain a retain count of 2 (one for the alloc/init and another for the assignment to the property).  With a retain count of 2, the object will never be released and you’ll leak memory.

Autorelease?

Ok, I totally lied as there is one more complicated aspect to this whole thing, and that complexity comes in the form of Autorelease Pools.  The heck is that?  Autorelease pools maintain a list of objects that will be sent release messages when those pools are destroyed.  You really needn’t worry about when all of that happens, but rest assured that your autorelease objects are, if everything is done correctly, being released without your explicit telling it to do so.

Following our rules of object ownership, one has to wonder what happens to objects that are returned from methods.  Who owns those?  This is where autorelease and autorelease pools come in to play.  For example:

      
      
- ( NSImage * ) getAnImage {
 
   return [[ NSImage alloc ] initWithContentsOfFile: @"/tmp/youownthis.jpg" ];
}
view raw gistfile1.m This Gist brought to you by  GitHub.

Who owns that NSImage?  Well, in this example, the caller of this method is the owner and it is, therefore, the caller’s responsibility to release it.  However, this method is bad form.  The correct form:

      
      
- ( NSImage * ) getAnImage {
 
   return [[[ NSImage alloc ] initWithContentsOfFile: @"/tmp/youownthis.jpg" ] autorelease ];
}
view raw gistfile1.m This Gist brought to you by  GitHub.

Notice the autorelease message at the end.  What happens when autorelease is called, the object in question is added to an autorelease pool so that it will receive a release message when the pool is destroyed.  The caller needn’t have to worry about it, nor does the callee.

Autorelease pools can be nested, too.  This is great for situations where you are creating thousands upon thousands of temporary objects and want to keep your maximum memory footprint slim and tight.  Also, if you are doing threaded work, anytime you create a new thread, you’ll have to create an autorelease pool for that thread.

Responding to Hacker News

Below are some responses to some of the more incorrect responses on HN.  From orangecat:

And don’t get me started on those lazy kids and their assemblers. I mean, how hard is it to remember a few dozen opcode hex values?Less snarkily: Developer resources are not infinite. Time spent futzing with memory management in non-performance-critical areas is time not spent improving performance where it actually matters, adding features, or improving the user interface.

For example, Angry Birds on the Galaxy Tab versus Angry Birds on the iPad are no where near the same experiences. The Galaxy Tab is jerky and slow, while the iPad is smooth.

The Android code for Angry Birds is primarily in native code, so garbage collection is unlikely to be the cause of your observations. And it’s perfectly smooth on my Nexus One.

He is somehow brilliantly equating the difference between garbage collection and manual memory management in objective-c to the difference between writing raw machine code and writing assembly.  BravoThe last line, he tells me Angry Birds is written in native code, which means no garbage collection (the NDK doesn’t do GC) and doing C++ memory management, which renders the whole point of James Gregory’s post moot. Slow clap.

binaryfinery says:

I’ve written memory managers used in console games for the N64 (4Mb), PS2 (32Mb) and a complete memory tracking system for Unreal Engine 3 on XBox360 (OMG 512Mb).I use MonoTouch for iOS development. Why? Because I can build complex object graphs without having to maintain, in my head or in external documentation, the acyclic version of the graph. In a GC language I can create graphs that are happily cyclic, and have every reason to be, and yet have them deallocated when the application nulls the last reference.

In a reference counted system that desires cyclic pointers, one or more of those pointers have to be chosen as non-reference-incrementing pointers – and likewise one must remember not to release them either.

Memory management in Objective-C (without GC) is no effort provided one is making applications with simple object interactions, such as the plethora of tree-based hierarchies. Reference counting is great for that.

So it could be that the OP is lazy, or it could be that they have experience with problems that you do not. If you believe that OP actually requires a lesson on the basic rules of reference counted memory management (since thats what you provided) then I suggest that you are underestimating the experience of your detractors, which in turn leads me to believe that you are overestimating yours.

Cyclic object graphs in Objective-C are not a problem.  At all.  In fact the retain/release facilitate them pretty easily, and some very basic comp sci 101 OOP theory will have you moving around them just fine.  Again, it boils down on what you own and what you don’t own.  Also, if you’re shit is too complicated to document, you are doing it wrong.  Straight up.  I don’t care if you wrote the internet or not, btw.

And finally, the one that made me the most irate, from the blog poster himself:

It’s totally about laziness! But that’s what computers are all about — I could store printed versions of all of my documents in a filing cabinet, and go and manually sort them every time I needed a different ordering, but I’m lazy! I use a database!I just don’t see why laziness should be restricted to users. Developers are lazy too.

You’re right that there are only 4 rules (or more or less depending on your formulation), but I don’t care. I’d rather take the time to have another martini. Or, y’know, implement features that make my users happy.

And it definitely gets harder when there are more moving parts. You’re right that the rules are simple, but the execution of those rules gets more complex as you add more components, more threads, remoting, etc. I never said it was impossible, or up there with Fermat’s Last Theorem or anything like that. Just that this is work the computer could be doing for me. I want to be lazy, but Apple won’t let me.

Brother, I am all about laziness, but I am also all about doing it right.  So, go ahead and drink up friend, because while you are wallowing in your own ignorance, people are out there doing it better than you, smarter than you.  I’m not sure about you, but in my profession and my career, I am constantly striving to do the best job that I can, and a large part of that goes towards ways to allow myself to be lazier, but to get it right at the same time.  Also, it’s not a matter of what youwant, it’s a matter of making these damn computer things do what you want.  The trip, the technical aspects of it, have to be done right or your ship is going to sink.

In Summary

As you can see, this whole thing is not as complicated or scary as it seems.  Sure, GC is a lot easier, but even if you had it, there are so many instances where manual memory management is preferred: games and media apps.  Memory management is such a crucial aspect of the performance of these types of apps that not knowing how to do it wouldn’t work.  Thankfully, Cocoa and Objective-C make it pretty painless.

For More Info

Really, the only document you need to read is the one apple provides here.  But, in case you need some more help, the following links are fantastic:

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值