经过一段长时间的iPhone开发,本人对iPhone的开发有了更进一步的了解,今天特意在此总结出一些可行性的实践,主要是针对在开发过程中也许会碰到的问题提出的一些解决方法以及需要注意的地方。下面说的都并非是最佳实践(本人还不够官方,呵呵),但确实是需要引起我们的注意,也值得作为参考。
1、要养成有借有还的习惯。
这里指的是内存管理方面的问题,我们在开发过程中是必须要对内存进行操作的,而且手机上可供给程序使用的内存也远远不如PC上那么充足,因此如果你是一个有借不还的人,那么国库(系统资源)将面临被你掏空的情况,最后导致国家(系统)无法养活你而白白饿死你了(程序崩溃)。所以,为了维持一个良好的生态循环,我们申请内存之后一定要对申请的内存进行释放。
在iPhone上其内存管理是使用引用次数机制进行内存管理维护的,简单地说引用次数为零的时候内存就被释放掉了,否则,系统将不回收资源。不过面对内存申请与释放的情况也分别有不同的情况。请看下面我为大家列举的:
(1)对于调用alloc、retain以及copy方法但尚未调用autorelease方法的对象,一定要进行释放。
其中alloc方法为对象构造时调用,如:Class *a=[[Class alloc] init];而retain方法则是对现有对象的引用次数自增一次。而copy方法则是克隆现有对象创建一个新对象,其中新对象的引用次数为1。对于调用了这三个方法的地方一定要记住进行对象释放。不过如果你在调用这些方法后紧接着调用了autorelease方法,那么你就不需要进行释放了,系统会自动帮忙回收,如:Class *a=[[[Class alloc] init] autorelease];
(2)类成员属性应该在init系列函数中申请内存,在dealloc中释放内存。
这不是必须遵守的规则,但是为了保证良好的编程风格本人建议这样做。因为一般作为成员变量的对象都是提供给整个类对象使用并不是作为某个方法的临时变量使用,因此,其生存周期也应该是在对象销毁时进行释放,这是理所当然的事情。如:
@interface ClassA { NSString *_text; } @end ; @implementation ClassA -(id)init{ If(self = [super init]){ _text=[[NSString alloc] initWithString:@”text”]; } Return self; } -(void)dealloc{ [_text release]; _text=nil; [super dealloc]; } @end ;
(3)在非init系列函数中使类成员变量引用新对象时,必须先把类成员之前的引用先释放掉。
这个规则也是必须要遵守的,但也是大多数人容易忘记的一个步骤,如果你定义的类成员变量是用于保存对象引用,那么要先把之前的引用进行释放,否则将会导致内存泄露。如:
-(void)demo:(NSString *)text{ [text retain]; [_text release]; _text=text; }
上面的代码其实也时类属性的默认实现方法。
2、不要轻易在viewDidLoad和loadView方法中初始化成员变量。
说到这点确实会令人迷糊,很多人会认为loadView就是加载完View之后触发的,它只会执行一次,那我在这里初始化成员变量应该是没有问题的。确实,在正常情况下loadView只会执行一次。但是当你的应用收到内存警告时,那么loadView就会被重新执行了。并且系统会把你加入到View中的子View全部remove掉。(也许有些人会说,我做的程序占用内存很少没有这方面的担忧,但问题不单止是看你程序占用的,还要考虑机器可用的内存。)
官方文档中也说明了这一点,而且还很明确地指出需要在收到内存警告时作出释放的操作。本人觉得收到内存警告后要看情况进行释放,暂时不用的临时数据可以释放,但是视图和显示用的数据最好能够保留,这样可以确保应用状态不被重置。因此在loadView中可以加入判断来初始化成员变量。如:
-(void)loadView{ if(_label==nil){ _label=[[UILabel alloc] initWithFrame:CGRectMake(0,0,60,18)]; _label.text=@”text”; } [self.view addSubview:_label]; }
3、如果您的ViewController中使用了NSTimer的话,那你要注意了。
对于某些ViewController我们可能会实现定时刷新的功能,这时候就要使用到NSTimer对象,那么你可要小心处理了,因为NSTimer有可能会导致你的ViewController被正常释放。我们先来下面的代码:
-(void)loadView{ if(_timer!=nil){ [_timer invalidate]; [_timer release]; _timer=nil; } _timer=[[NSTimer scheduledTimerWithTimeInterval:1.0 target:self selector:@selector(refreshView) userInfo:nil repeats:YES] retain]; } -(void)dealloc{ if(_timer!=nil){ [_timer invalidate]; [_timer release]; _timer=nil; } [super dealloc]; }
表面上来看着段代码最正常不过了,我在loadView的时候启动定时器,等待释放的时候就停止并释放定时器。但问题就来了,当你想把ViewController进行销毁时,系统是不会调用dealloc方法的,因为他认为还有引用未释放掉。也就是说,我们要想销毁ViewController时就必须把NSTimer对象给先释放掉,否则是不可能释放ViewController的。具体网上也出来了一种做法,那就是在viewWillAppear中启用定时器,在viewWillDisappear中停止和释放定时器。具体代码如下:
- (void)viewWillAppear:(BOOL)animated{ if(_timer!=nil){ [_timer invalidate]; [_timer release]; _timer=nil; } _timer=[[NSTimer scheduledTimerWithTimeInterval:1.0 target:self selector:@selector(refreshView) userInfo:nil repeats:YES] retain]; } - (void)viewWillDisappear:(BOOL)animated{ if(_timer!=nil){ [_timer invalidate]; [_timer release]; _timer=nil; } }
4、原来CLLocationManager并非可以同时生成多个实例。
我曾经使用CLLocationManager的多个实例来同时获取定位信息,但后来我发觉我这样做是很愚蠢的,因为其实他们取到的信息其实是一样的,其实可以共用一个CLLocationManager。后来在检测内存泄露时也发现同时生成多个CLLocationManager会造成内存泄露并弹出警告。所以我改了一种方式来实现多个模块获取定位信息。主要思路是新建一个定位模块,定位模块包含CLLocationManager并以单例存在,各个需要获取定位的模块需要先获取定位模块的工作状态,如果定位模块处于空闲状态则激活模块进行定位并监听定位的信息返回,如果定位模块处于工作状态则只需要监听定位信息的返回即可。其中监听机制可以通过NSNotificationCenter进行实现。具体代码就不在这里公布了。
5、要相信内存永远是不够用的,应尽量使用惰性原则。
我们知道手机的内存是非常宝贵的,除了有申请必须释放的规则外,对于那些不会立即使用到的对象,我们应尽量采用惰性原则进行申请。简单的说就是用的时候才去申请,不用的时候就释放掉。举例来说,我的程序中有一个按钮,点击这个按钮后要获取一个列表信息。那么这时候我们可能会想在类中定义一个NSMutableArray来装载这些列表信息。这时候,我们不必要把NSMutableArray在类对象初始化时进行初始化。因为用户未必会去点这个按钮,那么就没必要去申请这部分内存。因此,我们可以把初始化延迟到点击按钮时进行初始化操作。如:
-(void)buttonClick:(id)sender{ If(_array==nil){ _array=[[NSMutableArray alloc] init]; } //具体的操作 }
6、作为类中的委托属性应该尽量使用assign特性。
对于一个新手来说,可能会搞不懂属性中的retain、assign以及copy特性,其中retain和copy特性比较相近其都会增加引用次数,而且copy是会新建对象。而assign则是一种弱引用方式不会导致引用次数的增加或者对象的复制。以下代码说明了他们的工作方式:
//retain特性 -(void)setValue:(id)value{ [value retain]; [_value release]; _value=value; } //copy特性 -(void)setValue:(id)value{ [_value release]; _value=[value copy]; } //assign特性 -(void)setValue:(id)value{ _value=value; }
因此,为了避免作为委托对象遭到循环引用(循环引用是引用次数机制经常要面临的问题,就是委托对象引用源对象,而源对象又组合在委托对象中,此种情况就会导致释放委托对象时需要释放源对象,但释放源对象时又需要释放委托对象的窘态),一般作为委托属性的特性都应该设置为assign特性。同时,还有一个不是必须要做但最好还是要做的工作,那就是作为委托的对象要赋予delegate属性后,当委托对象需要释放时最好把delegate释放为nil。如:
-(void)loadView{ _tableView=[[UITableView alloc] initWithFrame:CGRectMake(0,0,320,640) style:UITableViewStylePlain]; _tableView.delegate=self; } -(void)dealloc{ _tableView.delegate=nil; [_tableView release]; [super dealloc]; }
当然这里的UITableView的delegate可以不置为空nil。因为其已在析构中释放掉。上面的代码可能看不出这种做法的必要性,但是如果你要释放的不是UITableView而是一个NSURLConnection对象,那么这时候就显得有必要了。一旦你的NSURLConection进行请求后但在您的对象析构时其还没返回,那么当你的对象析构后NSURLConnection返回了,那么你的程序则有可能面临崩溃的危险。所以如果你不想在析构时停止NSURLConnection的请求,最好的办法就是把委托重置为nil。