Obj-C内存管理的规则
Objective-C 2.0增加了一些新的东西,包括属性和垃圾回收。那么,我们在学习Objective-C 2.0之前,最好应该先了解,从前是什么样的,为什么Objective-C 2.0要增加这些支持。
这一切都跟Cocoa内存的管理规则有关系,我们知道,Objective-C中所有变量都定义为指针。指针是一个特殊的变量,它里面存储的数值被解释成为内存里的一个地址,如果使用不当,就会出错或者造成内存的泄露。要了解这些,就需要看看其内存管理的规则到底是什么样的。
这篇文章也应该做为苹果开发工具中提供的性能调试工具Instruments使用前必读知识进行阅读。Cocoa China将在稍后提供Instruments工具的使用方法,以及Objective-C 2.0的详细介绍。
要知道,如果你使用Objective-C 2.0,那么本文描述的大部分工作你都不需要自己去处理了。但是这并不意味着你可以不了解它,相反,只有你对内存管理规则更加了解,你才能更好地使用Objective-C 2.0带来的便利。
本文原文作者是Mmalcolm Crawford,原文地址 这篇文章翻译起来比较晦涩,希望您能看得懂。
当Cocoa新手在进行内存管理时,他们看上去总是把事情变得更为复杂。遵循几个简单的规则就可以把生活变得更简单。而不遵循这些规则,他们几乎一定会造成诸如内存泄露或者将消息发送给释放掉的对象而出现的的运行错误。
Cocoa不使用垃圾回收(当然,Objective-C 2.0之后开始就使用了),你必须通过计算reference的数量进行自己的内存管理,使用-retain, -release和-autorelease。
方法描述
-retain
将一个对象的reference数量增加1。
-release
将一个对象的reference数量减少1。
-autorelease
在未来某些时候将reference数量减少1.
-alloc
为一个对象分配内存,并设置保留值数量(retain count)为1。
-copy
复制一个对象,并将其做为返回值。同时设置保留值数量(retain count)为1。
保留值数量规则
1 在一定的代码段中,使用-copy,-alloc和-retain的次数应该和-release,-autorelease保持一致。
2 使用便利构造方法创建的对象(比如NSString的stringWithString)可以被认为会被自动释放。(autoreleased)
3 在使用你自己的参数实例时,需要实现-dealloc方法来释放。
例子
-alloc / -release
– (void)printHello
{
NSString *string;
string = [[NSString alloc] initWithString:@"Hello"];
NSLog(string);
// 我们用 alloc 创建了NSString,那么需要释放它
[string release];
}
便利构造方法
– (void)printHello
{
NSString *string;
string = [NSString stringWithFormat:@"Hello"];
NSLog(string);
// 我们用便利构造方法创建的NSString
//我们可以认为它会被自动释放
}
永远使用存取方法
虽然有时候你可能会认为这很麻烦,但是如果你始终使用了存取方法,造成内存管理问题的麻烦将会降低很多。
如果你在代码实例的参数中频繁使用-retain和-release,几乎可以肯定你做了错误的事情。
例子
假设我们希望设置一个Counter对象的数量值。
@interface Counter : NSObject
{
NSNumber *count;
}
为了获取和设置count值,我们定义两个存取方法:
– (NSNumber *)count
{
return count;
// 无需retain或者release,
// 仅仅传递数值
}
– (void)setCount:(NSNumber *)newCount
{
// newCount值会被自动释放,那么我们希望保留这个newCount
// 所以需要在这里retain。
[newCount retain];
// 由于我们在这个方法中仅仅改变了计算数量的对象,我们可以在这里先释放它。因为[nil release]在objective-c中也是允许的,所以即使count值没有被指定,也可以这样调用。
//我们必须在[newCount retain]之后再释放count,因为有可能这两个对象的指针是同一个。我们不希望不小心释放它。
[count release];
// 重新指定
count = newCount;
}
命名约定
注意存取方法的命名约定遵循一个模式: -参数名 和 -set参数名。
遵循这一约定,会使你的代码可读性更强,而且,更重要地是你可以在后面使用key-value编码。(参阅NSKeyValueCoding协议)。
由于我们有一个对象实例参数,我们必须实现一个释放方法:
– (void)dealloc
{
[self setCount:nil];
[super dealloc];
}
假设我们希望实现一个方法重置计数器,我们会有很多选择。在最开始,我们使用了一个 便利构造方法,所以我们假设新的数值是自动释放的。我们不需要发送任何retain或者release消息。
– (void)reset
{
NSNumber *zero = [NSNumber numberWithInt:0];
[self setCount:zero];
}
然而,如果我们使用-alloc方法建立的NSNumber实例,那我们必须同时使用一个-release。
– (void)reset
{
NSNumber *zero = [[NSNumber alloc] initWithInt:0];
[self setCount:zero];
[zero release];
}
常见错误
在简单的情况下,以下代码几乎一定可以正常运行,但是由于可能没有使用存取方法,下面的代码在某些情况下几乎一定会出问题。
错误-没有使用存取方法
– (void)reset
{
NSNumber *zero = [[NSNumber alloc] initWithInt:0];
[count release]
count = zero;
}
错误-实例泄露
– (void)reset
{
NSNumber *zero = [[NSNumber alloc] initWithInt:0];
[self setCount:zero];
}
新建的NSNumber数值数量是1(通过alloc),而我们在这个方法里没有发出-release消息。那么这个NSNumber就永远不会被释放了,这样就会造成内存泄露。
错误-对已经释放的实例发送-release消息
– (void)reset
{
NSNumber *zero = [NSNumber numberWithInt:0];
[self setCount:zero];
[zero release];
}
你随后在存取count的时候在这里就会出错。这个简便构造方法会返回一个自动释放的对象,你无需发送其他释放消息。
这样写代码意味着,由于对象已经被自动释放,那么当你释放时,retain count将被减至0,对象已经不存在了。当你下次希望获取count值时,你的消息会发到一个不存在的对象(通常这样你会得到一个SIGBUS 10的错误提示)。
经常造成混淆的情况
数组和其他集合类
当对象被加入到数组、字典或者集合中,集合类会将其保留。当集合被释放的同时,对象也会收到一个释放消息。如果你希望写一个建立数字数组的例子,你可能会这么写:
NSMutableArray *array;
int i;
// …
for (i = 0; i < 10; i++)
{
NSNumber *n = [NSNumber numberWithInt: i];
[array addObject: n];
}
在这个例子里,你无需保留新建的数值,因为数组会帮你保留。
NSMutableArray *array;
int i;
// …
for (i = 0; i < 10; i++)
{
NSNumber *n = [[NSNumber alloc] initWithInt: i];
[array addObject: n];
[n release];
}
本例中,在for循环里你需要给n发送一个-release消息,因为你需要始终在-alloc之后将n的数量保持为1。这么做的原因是当其通过-addObject:方法被添加至数组中时,数组已经将其保存起来。即使你释放了n,但是这个数字由于已经保存在数组里,所以不会被释放。
为了了解这些,假设你自己就是编写数组类的人。你不希望接收的对象未经你同意就消失,所以你会在对象传递进来时,对其发送一个-retain消息。如果他们被删除,你同时也要对应地发送一个-release消息。在你自己-dealloc时,你也要给你收到的所有对象发送一个-release。
Mac 的启动键组合
启动时按住 C 键 ―― 从可启动 CD 或 DVD 光盘启动,如随机附带的 Mac OS X 安装光盘。
启动时按住 D 键 ―― 如果插入随机附带的 Mac OS X 安装光盘 1 ,则启动为
AppleHardware Test (AHT) 。
按住 Option-Command-P-R 键直至听到两声嘀嘀声 ―― 重置 NVRAM 。
启动时按住 Option 键 ―― 启动进入 Startup Manager ,您可以从列出的有效启动宗卷中选择一个来启动。注意:按住 N 键可显示出第一个可 启动网络宗卷。
按住 Eject 、 F12 键,或者按住鼠标键/触控板 ―― 推出所有移动介质,如光盘。
启动时按住 N 键 ―― 试图从兼容的网络服务器(NetBoot)启动。
启动时按住 T 键 ―― 启动为 FireWire 目标磁盘模式,相当于把本机变成一个火线接口的移动硬盘。
启动时按住 Shift 键 ―― 启动为安全模式并且暂时关闭登录项。
启动时按住 Command-V 键 ―― 启动为 Verbose 模式。
启动时按住 Command-S 键 ―― 启动为单用户模式。
启动时按住 Option-N 键 ―― 使用默认启动镜像从 NetBoot 服务器启动。
一、如何取出卡死在吸取式光驱中的光盘:
1.首先关机,拔掉所有连接苹果电脑的线(电源线、键盘鼠标线等),过1分钟后按住主机源开关5秒。
2.然后过1分钟后接上电源、键盘鼠标线仅这三根线,开机后迅速同时按住四个键:苹果键+option键+P键+R键,电脑不间断响4声后,松开手进入苹果系统。
3.最后在桌面上出现光盘图标,把光盘图标拖入到垃圾桶就自动弹出卡死在光驱中的光盘了。
NSDate and NSString
+
(NSDate *)NSStringDateToNSDate:(NSString *)string {NSDateFormatter *formatter = [[NSDateFormatter alloc] init];
[formatter setTimeZone:[NSTimeZone timeZoneWithAbbreviation:@"UTC"]];
//#define kDEFAULT_DATE_TIME_FORMAT (@"yyyy-MM-dd’T’HH:mm:ss’Z’")
[formatter setDateFormat:kDEFAULT_DATE_TIME_FORMAT];
NSDate *date = [formatter dateFromString:string];
[formatter release];
return date;
}
使用定制单元格创建与众不同的表格 2
我创建视图,增加背景图像并定义tableview。我将表格的行高设定为相应图像的高度,然后设定表格背景为透明。
另一段代码说明了我们是怎样提供构成表格的单元格的。代码如下:
1
2 3 4 5 6 7 8 9 10 11 12 13 14 |
– (UITableViewCell *)tableView:(UITableView *)tableView
cellForRowAtIndexPath:(NSIndexPath *)indexPath { CustomCell *cell= [[[CustomCell alloc] initWithFrame:CGRectZero reuseIdentifier:nil] autorelease]; // Default to no selected style and not selected cell.selectionStyle = UITableViewCellSelectionStyleNone; // Set the image for the cell [cell setTheImage:[UIImage imageNamed: [NSString stringWithFormat:@"Arrows%d.png",indexPath.row + 1]]]; return cell; } |
注意我没有使用UITableViewCell 而是使用了CustomCell 类(我下面会说明)。另外需注意的是第9行,我将要显示的图像设置到单元格中(下面会详细说明)。
创建定制单元格
我们看看定制单元格的代码,它也并不复杂,只不过是一个UIImageView和在单元格中设置图像的方法。根据你用户界面的需要,你还可以包括一个标签或其他UI元件。
1
2 3 4 5 6 7 8 9 10 |
#import <UIKit/UIKit.h>
@interface CustomCell : UITableViewCell { UIImageView *image; } – (void) setTheImage:(UIImage *)icon; @end |
CustomCell的实现只包括3个方法:初始化,为单元格设定图像以及设定所选行的alpha值:
1
2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 |
#import "CustomCell.h"
@implementation CustomCell /*———————————————————————– * *————————————————————————*/ – (id)initWithFrame:(CGRect)frame reuseIdentifier:(NSString *)reuseIdentifier { if (self = [super initWithFrame:frame reuseIdentifier:reuseIdentifier]) { // Cells are transparent [self.contentView setBackgroundColor:[UIColor clearColor]]; } return self; } /*———————————————————————–* *————————————————————————*/ – (void) setTheImage:(UIImage *) icon { // Alloc and set the frame image = [[UIImageView alloc] initWithImage:icon]; image.frame = CGRectMake(0, 0, 286, 68); // Add subview [self.contentView addSubview:image]; } /*————————————————————————* *————————————————————————*/ – (void)setSelected:(BOOL)selected animated:(BOOL)animated { [super setSelected:selected animated:animated]; if (selected == YES) image.alpha = .5; else image.alpha = 1; } /*————————————————————————* *————————————————————————*/ – (void)dealloc { [image release]; [super dealloc]; } @end |
这样我们就创建了一个简单的定制tableview。
下载Xcode项目 在这里下载。
原文来自:Creating Unique Looking Tables with Custom Cells 翻译:bagusflyer
使用定制单元格创建与众不同的表格 1
创建定制表格
这一切实际上很简单,只不过是让UITableView透明,所以可以看到背景。而每个单元格UITableViewCell允许定制。
在浏览代码之前,我们看一下我们将创建的示例程序的截图:
包含了tableview的主视图控制的类定义如下:
1
2 3 4 |
@interface MainViewController : UIViewController <UITableViewDelegate, UITableViewDataSource>
{ UITableView *theTableView; } |
请注意在这里我没有使用UITableViewController,而是使用了带有UITableView的UIViewController。另外请注意代码中的UITableViewDelegate和UITableViewDataSource。
先看看我是怎样对viewconroller进行初始化的:
1
2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 |
– (id)init
{ if (self = [super init]) { self.view = [[[UIView alloc] initWithFrame: [[UIScreen mainScreen] applicationFrame]] autorelease]; // Setup the background UIImageView *background = [[UIImageView alloc] initWithImage:[UIImage imageNamed:@"background.png"]]; [self.view addSubview:background]; [background release]; // Create table view theTableView = [[UITableView alloc] initWithFrame:CGRectMake(0, 11, 320, 460) style: UITableViewStylePlain]; [theTableView setDelegate:self]; [theTableView setDataSource:self]; // This should be set to work with the image height [theTableView setRowHeight:68]; // Transparent, so we can see the background [theTableView setBackgroundColor:[UIColor clearColor]]; [theTableView setSeparatorStyle:UITableViewCellSeparatorStyleNone]; [theTableView setIndicatorStyle:UIScrollViewIndicatorStyleWhite]; [self.view addSubview:theTableView]; } return self; } |
用代码手工创建NSTableView
某些情况下,我们可能需要使用手工编写代码而不是使用Interface Builder建立NSTableView。尤其在我们需要修改NSTableView其中的内容的时候,这变得更加重要,而开发文档中写的不是很清楚。
以下这段简单的代码可以手工创建一个NSTableView,可以帮你节省一点查资料的时间。
-
NSWindow *window; // 也可以是你自己的窗口
-
NSView *superView = [window contentView];
-
NSTableView *tableView = [[NSTableView alloc] initWithFrame:[superView frame]/* CGRect */];
-
[tableView addColumn:[[NSTableColumn alloc] initWithIdentifier:@"field1"]];
-
NSScrollView *scrollView = [[NSScrollView alloc] initWithFrame:[superView frame]];
-
[scrollView setDocumentView:tableView];
-
[superView addSubview:scrollView];-(void)loadView
16 {
17 // create and configure the view
18 CGRect cgRct = CGRectMake(0, 10, 320, 400); //define size and position of view
19 myView = [[UIView alloc] initWithFrame:cgRct]; //initilize the view
20 myView.autoresizesSubviews = YES; //allow it to tweak size of elements in view
21 self.view = myView; //set view property of controller to the newly created view
22 UITableView * tableView = [[UITableView alloc] initWithFrame:cgRct style:UITableViewStylePlain];
23 tableView.editing = YES; //This allows user of progrm to add or remove elements from list
24 tableView.dataSource = self;
25 tableView.delegate = self; //make the current object the event handler for view
26
27 [self.view addSubview:tableView];
28 }