YIFEIYANG 易飞扬的博客

206 views
283月/120

iPhone开发之深入浅出 (7) — ARC总结

iPhone开发之深入浅出 (7) — ARC总结

通过前面几篇文章的介绍,我想大家应该对ARC有了一个比较完整的理解。最后,我们来对ARC做一个总结,并把一些未涉及到的细节部分再深入讨论一下。

内存管理基本原则

内存管理的依循下面的基本原则
  • 自己生成的对象,那么既是其持有者
  • 不是自己生成的对象,也可成为其持有者(一个对象可以被多个人持有)
  • 如果不想持有对象的时候,必须释放其所有权
  • 不能释放已不再持有所有权的对象

    不管ARC有没有效,该原则始终存在。

所有权关键字

从代码上看,有ARC的代码和没有ARC的代码区别就在下面的几个关键字。

类似 NSObject* 的对象类型,或者 id 类型1,当ARC有效的时候,根据具体情况,这些关键字必须要使用2

  • __strong
  • __weak
  • __unsafe_unretained
  • __autoreleasing

    __strong是默认的修饰符。

    __weak修饰了一个自动nil的weak引用。

    __unsafe_unretained声明了一个不会自动nil的weak引用。当变量被释放,那么它就变成了一个野指针了。

    __autoreleasing 用来修饰一个声明为 (id *) 的函数的参数,当函数返回值时被释放。

接下来,我们结合下面ARC的使用准则,来看看一些使用ARC后的技术细节。

ARC使用准则

为了比秒程序秒退的尴尬,ARC有效时,我们的代码必须遵循下面的准则。

  • 不能使用 retain/release/retainCount/autorelease
  • 不能使用 NSAllocateObject/NSDeallocateObject
  • 不能使用 NSZone
  • 不能明示调用dealloc
  • 内存管理相关的函数必须遵循命名规则
  • 使用@autoreleasepool代替NSAutoreleasePool
  • Objective-C 对象不能作为C语言结构体(struct/union)的成员
  • 【id】与【void*】之间需要明示cast

建议使用Objective-C的class来管理数据格式,来代替C语言的struct。不能隐式转换 id 和 void *。

让我们一个一个来分析

不能使用 retain/release/retainCount/autorelease

内存管理完全交给编译器去做,所以之前内存相关的函数(retain/release/retainCount/autorelease)不能出现在程序中。Apple的ARC文档中也有下面的说明。

ARC 有效后,不需要再次使用retain 和 release

如果我们在程序中使用这些函数,经得到类似下面的编译错误信息。

    error: ARC forbids explicit message send of ’release’
         [o release];
          ^ ~~~~~~~

不能使用 NSAllocateObject/NSDeallocateObject

生成并持有一个Objective-C对象的时候,往往像下面一样使用NSObject的alloc接口函数。

    id obj = [NSObject alloc];

实际上,如果我们看了GNUstep 中关于 alloc 的代码就会明白,实际他是使用 NSAllocateObject 来生成并持有对象实例的。换言之,ARC有效的时候,NSAllocateObject函数的调用也是禁止的。如果使用,也会遇到下面的编译错误。

    error: ’NSAllocateObject’ is unavailable:
        not available in automatic reference counting mode

同样,对象释放时使用的 NSDeallocateObject 函数也不能使用。

不能使用 NSZone

NSZone 是什么?NSZone 是为了防止内存碎片而导入的一项措施。Zone 是内存管理的基本单元,系统中管理复数的Zone。系统根据对象的使用目的,尺寸,分配其所属的Zone区域。以提高对象的访问效率,避免不必要的内存碎片。但是,现在的运行时系统(用编译开关 __OBJC2__ 指定的情况下)是不支持Zone概念的。所以,不管ARC是否有效,都不能使用 NSZone。

不能明示调用dealloc

不管是否使用ARC,当对象被释放的时候,对象的dealloc函数被调用(就像是C++中对象的析构函数)。在该函数中,需要做一些内存释放的动作。比如,当对象中使用了malloc分配的C语言内存空间,那么dealloc中就需要像下面一样处理内存的释放。

1
2
3
4
- (void) dealloc
{
    free(buffer_);
}

又或者是注册的delegate对象,观察者对象需要被删除的时候,也是在dealloc函数中动作。

1
2
3
4
- (void) dealloc
{
    [[NSNotificationCenter defaultCenter] removeObserver:self];
}

如果在ARC无效的时候,我们还要像下面一样,调用父类对象的dealloc函数。

1
2
3
4
- (void) dealloc
{
    [super dealloc];
}

但是当ARC有效的时候,[super dealloc];的调用已经被编译器自动执行,已经不需要我们明示调用了。如果你在代码中还这样写,难免遇到下面的错误。

    error: ARC forbids explicit message send of ’dealloc’
         [super dealloc];
          ^ ~~~~~~~

内存管理相关的函数必须遵循命名规则

iPhone开发之深入浅出 (3) — ARC之前世今生中,我们知道如果是 alloc/new/copy/mutableCopy/init 开头的函数,需要将对象所有权返回给调用端。这条规则不管ARC是否有效都应该被遵守。只是 init 开头的函数比较特殊,他只在ARC下有要求,而且异常苛刻。

init 开始的函数只能返回id型,或者是该函数所属的类/父类的对象类型。基本上来说,init函数是针对alloc函数的返回值,做一些初始化处理,然后再将该对象返回。比如:

    id obj = [[NSObject alloc] init];

再比如下面定义的函数就是不对的:

    - (void) initThisObject;

需要是下面这样:

    - (id) initWithObject:(id)obj;

另外,下面名为 initialize 的函数比较特殊,编译器将把它过滤掉,不按上面的规则处理。

使用@autoreleasepool代替NSAutoreleasePool

在ARC之下,已经不能在代码中使用 NSAutoreleasePool,我们之前写 main.m 文件的时候,往往像下面这样写。

1
2
3
4
5
6
    int main(int argc, char *argv[]) {
    NSAutoreleasePool * pool = [[NSAutoreleasePool alloc] init];
    int retVal = UIApplicationMain(argc, argv, nil, nil);
    [pool release];
    return retVal;
}

而当ARC有效后,我们需要用@autoreleasepool代替NSAutoreleasePool。

1
2
3
4
5
6
int main(int argc, char *argv[])
{
    @autoreleasepool {
        return UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class]));
    }
}

当编译器看到 @autoreleasepool 定义的块后会自动生成 NSAutoreleasePool 对象,并将需要的对象放入 AutoReleasePool 中,当出方块的定义范围时,pool 中的对象将被释放。

Objective-C 对象不能作为C语言结构体(struct/union)的成员

当我们设置ARC有效,并在C语言的结构体中定义Objective-C的对象时,将出现类似下面的编译错误。

1
2
3
struct Data {
    NSMutableArray *array;
};
    error: ARC forbids Objective-C objs in structs or unions
         NSMutableArray *array;
                         ^

由于 ARC 是将内存管理的细节委托给编译器来做,所以说编译器必须要管理对象的生命周期。而LLVM 3.0中不存在对单纯C语言构造体成员的内存管理方法。如果单纯是栈对象,利用进出栈原理,可以简单地维护对象的生命周期;而结构体是不行的,简单地理解,结构体没有析构函数,编译器自身不能自动释放其内部的 Objective-C 对象。

当我们必须在C语言的结构体中放入 Objective-C 对象的时候,可以使用 void* 转型,或者使用 __unsafe_unretained 关键字。比如下面:

1
2
3
struct Data {
    NSMutableArray __unsafe_unretained *array;
};

这样一来,该内存信息不在编译器内存管理对象内,仅仅是使用而已,没有对象的持有权。当然,对象所有权的持有者需要明确的管理他与该结构体的交互,不要引起不必要的错误3

【id】与【void*】之间需要明示cast

ARC 有效的时候,由于编译器帮我们做了内存管理的工作,所以我们不需要太担心。但是当与 ARC 管理以外的对象类型交互的时候,就需要特殊的转型关键字,来决定所有权的归属问题。

主要的转型关键字是:

关键字 解释
__bridge 单纯的类型转换,没有进行所有权的转移
__bridge_retained 类型转换是伴随所有权传递,转换前后变量都持有对象的所有权
__bridge_transfer 类型转换伴随所有权转移,被转换变量将失去对象的所有权

当我们在 Core Foundation 对象类型与 Objective-C 对象类型之间切换的时候,需要把握下面的因素:

  • 明确被转换类型是否是 ARC 管理的对象
    • Core Foundation 对象类型不在 ARC 管理范畴内
    • Cocoa Framework::Foundation 对象类型(即一般使用到的Objectie-C对象类型)在 ARC 的管理范畴内
  • 如果不在 ARC 管理范畴内的对象,那么要清楚 release 的责任应该是谁
  • 各种对象的生命周期是怎样的

题外话

Xcode 4.3带来的变化

最近随着 iOS 5.1 的推出,Xcode也推出了4.3版本。在该版本下,ARC 有效时的属性(@property) 定义的时候,如果不明确指定所有权关键字,那么缺省的就是 strong。而在 Xcode4.2 中,即使 strong 也要显示指定。

在 Xcode4.2 的时候,针对下面的代码,

1
2
3
4
5
6
7
// ARC 无效
@property (nonatomic, retain) NSString *string;

// --->

// ARC 有效
@property (nonatomic, strong) NSString *string;

而在 Xcode 4.3 中,我们可以这么做,

1
2
3
4
5
6
7
// ARC 无效
@property (nonatomic, retain) NSString *string;

// --->

// ARC 有效
@property (nonatomic) NSString *string;

ARC 代码自动变换

另外,Xcode 4.2开始,增加了旧代码向 ARC 代码自动转换的功能。有兴趣的朋友可以试试。位置是:

Edit->Refactor->Convert to Objective-C ARC…

为什么iOS中没有GC

我们已经知道ARC并不是GC(垃圾回收)了,那么,为什么iOS中不支持该机能呢?还特意搞出个ARC来。以下是我的分析:

  • 消耗CPU时间的处理尽量避免,以节约电池电量
  • GC执行的后,会停掉运行时库;这是最大的心结
  • 嵌入式设备本身内存就不是很大,如果GC不停的在后台运行,执行的频率会很高,严重影响性能
    • UI动画处理是iOS的一大卖点,而有了GC后可能会引起不必要的性能损失

1. 关于Objective-C对象的解释,可以参考iPhone开发入门(7)— 从C/C++语言到Objective-C语言

2. 当然,如果你不写,编译器会用缺省的值代替。具体见iPhone开发之深入浅出 (3) — ARC之前世今生中的描述。

3. 关于这一点,可以参考iPhone开发之深入浅出 (1) — ARC是什么 一文,明白为什么 __unsafe_unretained 是危险的。

248 views
243月/120

iPhone开发之深入浅出 (6) — ARC之对象转型

iPhone开发之深入浅出 (6) — ARC之对象转型

自 Xcode4.2 开始导入ARC机制后,为了支持对象间的转型,Apple又增加了许多转型用的关键字。这一讲我们就来了解其用法,以及产生的理由。

引子

我们先来看一下ARC无效的时候,我们写id类型转void*类型的写法:

1
2
id obj = [[NSObject alloc] init];
void *p = obj;

反过来,当把void*对象变回id类型时,只是简单地如下来写,

1
2
id obj = p;
[obj release];

但是上面的代码在ARC有效时,就有了下面的错误:

    error: implicit conversion of an Objective-C pointer
        to ’void *’ is disallowed with ARC
        void *p = obj;
                  ^

    error: implicit conversion of a non-Objective-C pointer
        type ’void *’ to ’id’ is disallowed with ARC
        id o = p;
                ^

__bridge

为了解决这一问题,我们使用 __bridge 关键字来实现id类型与void*类型的相互转换。看下面的例子。

1
2
3
4
5
id obj = [[NSObject alloc] init];

void *p = (__bridge void *)obj;

id o = (__bridge id)p;

将Objective-C的对象类型用 __bridge 转换为 void* 类型和使用 __unsafe_unretained 关键字修饰的变量是一样的。被代入对象的所有者需要明确对象生命周期的管理,不要出现异常访问的问题。

除过 __bridge 以外,还有两个 __bridge 相关的类型转换关键字:

  • __bridge_retained
  • __bridge_transfer

接下来,我们将看看这两个关键字的区别。

__bridge_retained

先来看使用 __bridge_retained 关键字的例子程序:

1
2
3
id obj = [[NSObject alloc] init];

void *p = (__bridge_retained void *)obj;

从名字上我们应该能理解其意义:类型被转换时,其对象的所有权也将被变换后变量所持有。如果不是ARC代码,类似下面的实现:

1
2
3
4
id obj = [[NSObject alloc] init];

void *p = obj;
[(id)p retain];

可以用一个实际的例子验证,对象所有权是否被持有。

1
2
3
4
5
6
7
8
void *p = 0;

{
    id obj = [[NSObject alloc] init];
    p = (__bridge_retained void *)obj;
}

NSLog(@"class=%@", [(__bridge id)p class]);

出了大括号的范围后,p 仍然指向一个有效的实体。说明他拥有该对象的所有权,该对象没有因为出其定义范围而被销毁。

__bridge_transfer

相反,当想把本来拥有对象所有权的变量,在类型转换后,让其释放原先所有权的时候,需要使用 __bridge_transfer 关键字。文字有点绕口,我们还是来看一段代码吧。

如果ARC无效的时候,我们可能需要写下面的代码。

1
2
3
4
// p 变量原先持有对象的所有权
id obj = (id)p;
[obj retain];
[(id)p release];

那么ARC有效后,我们可以用下面的代码来替换:

1
2
// p 变量原先持有对象的所有权
id obj = (__bridge_transfer id)p;

可以看出来,__bridge_retained 是编译器替我们做了 retain 操作,而 __bridge_transfer 是替我们做了 release1

Toll-Free bridged

在iOS世界,主要有两种对象:Objective-C 对象和 Core Foundation 对象0。Core Foundation 对象主要是有C语言实现的 Core Foundation Framework 的对象,其中也有对象引用计数的概念,只是不是 Cocoa Framework::Foundation Framework 的 retain/release,而是自身的 CFRetain/CFRelease 接口。

这两种对象间可以互相转换和操作,不使用ARC的时候,单纯的用C原因的类型转换,不需要消耗CPU的资源,所以叫做 Toll-Free bridged。比如 NSArray和CFArrayRef, NSString和CFStringRef,他们虽然属于不同的 Framework,但是具有相同的对象结构,所以可以用标准C的类型转换。

比如不使用ARC时,我们用下面的代码:

1
2
NSString *string = [NSString stringWithFormat:...];
CFStringRef cfString = (CFStringRef)string;

同样,Core Foundation类型向Objective-C类型转换时,也是简单地用标准C的类型转换即可。

但是在ARC有效的情况下,将出现类似下面的编译错误:

    Cast of Objective-C pointer type ‘NSString *’ to C pointer type ‘CFStringRef’ (aka ‘const struct __CFString *’) requires a bridged cast
    Use __bridge to convert directly (no change in ownership)
    Use __bridge_retained to make an ARC object available as a +1 ‘CFStringRef’ (aka ‘const struct __CFString *’)

错误中已经提示了我们需要怎样做:用 __bridge 或者 __bridge_retained 来转型,其差别就是变更对象的所有权。

正因为Objective-C是ARC管理的对象,而Core Foundation不是ARC管理的对象,所以才要特意这样转换,这与id类型向void*转换是一个概念。也就是说,当这两种类型(有ARC管理,没有ARC管理)在转换时,需要告诉编译器怎样处理对象的所有权。

上面的例子,使用 __bridge/__bridge_retained 后的代码如下:

__bridge
1
2
NSString *string = [NSString stringWithFormat:...];
CFStringRef cfString = (__bridge CFStringRef)string;

只是单纯地执行了类型转换,没有进行所有权的转移,也就是说,当string对象被释放的时候,cfString也不能被使用了。

__bridge_retained
1
2
3
4
NSString *string = [NSString stringWithFormat:...];
CFStringRef cfString = (__bridge_retained CFStringRef)string;
...
CFRelease(cfString); // 由于Core Foundation的对象不属于ARC的管理范畴,所以需要自己release

使用 __bridge_retained 可以通过转换目标处(cfString)的 retain 处理,来使所有权转移。即使 string 变量被释放,cfString 还是可以使用具体的对象。只是有一点,由于Core Foundation的对象不属于ARC的管理范畴,所以需要自己release。

实际上,Core Foundation 内部,为了实现Core Foundation对象类型与Objective-C对象类型的相互转换,提供了下面的函数。

1
2
3
4
5
6
7
CFTypeRef  CFBridgingRetain(id  X)  {
    return  (__bridge_retained  CFTypeRef)X;
}

id  CFBridgingRelease(CFTypeRef  X)  {
    return  (__bridge_transfer  id)X;
}

所以,可以用 CFBridgingRetain 替代 __bridge_retained 关键字:

1
2
3
4
NSString *string = [NSString stringWithFormat:...];
CFStringRef cfString = CFBridgingRetain(string);
...
CFRelease(cfString); // Core FoundationはARC対象外なので自分でreleaseする。
__bridge_transfer

所有权被转移的同时,被转换变量将失去对象的所有权。当Core Foundation对象类型向Objective-C对象类型转换的时候,会经常用到 __bridge_transfer 关键字。

1
2
3
4
CFStringRef cfString = CFStringCreate...();
NSString *string = (__bridge_transfer NSString *)cfString;

// CFRelease(cfString); 因为已经用 __bridge_transfer 转移了对象的所有权,所以不需要调用 release

同样,我们可以使用 CFBridgingRelease() 来代替 __bridge_transfer 关键字。

1
2
CFStringRef cfString = CFStringCreate...();
NSString *string = CFBridgingRelease(cfString);

总结

由上面的学习我们了解到 ARC 中类型转换的用法,那么我们实际使用中按照怎样的原则或者方法来区分使用呢,下面我总结了几点关键要素。

  • 明确被转换类型是否是 ARC 管理的对象
    • Core Foundation 对象类型不在 ARC 管理范畴内
    • Cocoa Framework::Foundation 对象类型(即一般使用到的Objectie-C对象类型)在 ARC 的管理范畴内
  • 如果不在 ARC 管理范畴内的对象,那么要清楚 release 的责任应该是谁
  • 各种对象的生命周期是怎样的

1. 声明 id obj 的时候,其实是缺省的申明了一个 __strong 修饰的变量,所以编译器自动地加入了 retain 的处理,所以说 __bridge_transfer 关键字只为我们做了 release 处理。

467 views
83月/120

iPhone开发之深入浅出 (5) — ARC之Outlet与弱引用

iPhone开发之深入浅出 (5) — ARC之Outlet与弱引用

使用weak property声明Outlet

当我们使用 Interface Builder 生成Outlet对象的时候,一般都是作为 subview 来使用的。比如 UIViewController 的view。所以说Outlet的持有者就是superview对象,即有“父子”关系。由上一回 iPhone开发之深入浅出 (4) — ARC之循环参照 我们知道,当对象间有“父子”关系时,需要使用弱参照,以避免“循环参照”。

ViewController 本身是不会作为Outlet的所有者的,所以使用weak property声明。

简化viewDidUnload

Outlet都使用weak property声明的时候,还有一个好处,就是简化viewDidUnload的处理。

iOS在系统内存不足的时候,UIViewController会将没有表示的所有view做unload处理,即调用viewDidUnload接口。

所以,如果是强参照的情况下,需要释放所有权,

1
@property (nonatomic, strong) IBOutlet UILabel *label;
1
2
3
4
(void)viewDidUnload {
    self.label = nil; // 取消强参照,释放所有权
    [super viewDidUnload];
}

如果没有 self.label = nil 的处理,那么 UIViewController 将不会释放 label 的所有权;结果,系统是调用了unload,但是subview对象始终留在内存中。随着界面上控件的增多,内存泄露会越来越大。

如果使用的是weak property声明的话,会是怎样的呢?

1
@property (nonatomic, weak) IBOutlet UILabel *label;

这时,系统在unload时,由于label没有被强参照,更加ARC的规则,这时,label的对象即被释放。并在释放的同时,变量自动指向nil。

1
2
3
4
- (void)viewDidUnload {
    // 这里什么也不用管
    [super viewDidUnload];
}

其实,如果我们的viewDidUnload只是用来释放Outlet用的话,那么该函数也可以不被重载的。

什么时候要用strong property

由上我们也可以看到,并不是所有的Outlet都用弱参照来声明都是正确的;当使用Interface Builder生成的第一层的view或
者windows被作为Outlet来使用的话,那么不是不能声明为弱参照property的。(比如,Storyboard的各个scene)

理由很简单,没有被任何人强参照的对象,生成之后就会立刻被释放。

综上,当我们使用Outlet的时候,注意不同的情况来使用strong或者是weak。

525 views
13月/120

iPhone开发之深入浅出 (4) — ARC之循环参照

iPhone开发之深入浅出 (4) — ARC之循环参照

概念

当我们使用强参照(Strong reference)时,往往需要留意 循环参照 的问题。循环参照指的是两个对象被互相强参照,以至于任一对象都不能释放。

一般情况下,当对象之间有“父子关系”时,强参照的情况发生的比较多。比如通讯薄对象AddrBook和每个通讯录Entry的关系如下。

这种情况下,由于Entry对象被AddrBook强参照,所以不能释放。另一方面,如果Entry被释放了,AddrBook对象的强参照也就没有了,其对象也应被释放。

解决方式

像上面的例子,当多个对象间有“父子关系”时,需要在一侧用“弱参照”来解决循环参照问题。一般情况下,“父亲”作为“孩子”的拥有者,对“孩子”是强参照,而“孩子”对父亲是弱参照。

如图所示,当强参照AddrBook对象的变量被释放的时候,AddrBook对象将被自动释放,同时将失去Entry成员对象的强参照。另外,当AddrBook对象被释放的时候,Entry对象中的AddrBook变量也将由Zeroing机制,自动带入nil。我们不需要担心释放对象的再访问问题。

下面,我们将看看有几种情况下,需要注意循环参照问题。

Delegate模式

iOS程序中经常用到delegate模式,比如ViewController中,用ModalView打开/关闭DetailViewController时,需要delegate的设定。

这里,ViewController对象中强参照detailViewController,如果DetailViewController的delegate不是弱参照ViewController话,将引起循环参照。

另外,当类中使用weak @property声明的delegate变量时,如果参照对象被释放,该变量将被自动设为nil,不需要程序代码设置。

Blocks

Blocks是iOS 4开始导入的,可以理解为python或者lisp中的Lambda,C++11也已导入了该概念;类似概念ruby/smalltalk/JSP语言中也有定义。具体讲解见以后的文章,本节我们主要看看在Block中的循环参照问题。

比如,block对象用copy的属性定义时候,

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
typedef void(^MyBlock)(void);

@interface MyObject : NSObject
@property (nonatomic, copy) MyBlock block;
@property (nonatomic, strong) NSString *str;

- (void)performBlock;
@end

@implementation MyObject
@synthesize block, str;

- (void)performBlock {
    if (self.block) {
        self.block();
    }
}
@end

调用端如下:

1
2
3
4
5
6
7
MyObject *object = [[MyObject alloc] init];
object.str = @"hoge";

object.block = ^{
    NSLog(@"block: str=%@", object.str);
};
[object performBlock];

我们看到,Block的构文中参照了object,同样object也强参照block。

为了解决该问题,我们可以有下面两种选择。

使用__block关键字修饰

使用__block关键字,让对象有读写权限,如果Block内的处理完毕就释放object。

1
2
3
4
5
6
7
8
__block MyObject *object = [[MyObject alloc] init];
object.str = @"hoge";

object.block = ^{
    NSLog(@"block: str=%@", object.str);
    object = nil;
};
[object performBlock];

该关键字的意思就是让block取消对object的强参照,以避免循环参照。但是,有一个问题就是,object的释放动作是在Block内部执行,如果Block没有被执行的话,循环参照一直存在。比如上面的代码,如果第8行 [object performBlock]; 没有执行的话,那么一直还是循环参照状态。

使用__weak关键字修饰

另一种方案就是让Block的参照变为弱参照。

1
2
3
4
5
6
7
8
MyObject *object = [[MyObject alloc] init];
object.str = @"hoge";

__weak MyObject *weakObject = object;
object.block = ^{
    NSLog(@"block: str=%@", weakObject.str);
};
[object performBlock];

考虑到异步通信时Blocks的使用情况,weak变量weakObject有可能随时变为nil,所以类似于下面先变为strong变量,并检查是否为nil的处理方式应该更安全。

1
2
3
4
5
6
7
8
9
10
11
MyObject *object = [[MyObject alloc] init];
object.str = @"hoge";

__weak MyObject *weakObject = object;
object.block = ^{
    MyObject strongObject = weakObject;
    if (strongObject) {
        NSLog(@"block: str=%@", strongObject.str);
    }
};
[object performBlock];

总上,当我们使用Blocks时,也需要考虑Block中变量和实例的关系,不要引起不必要的循环参照问题。

834 views
192月/123

iPhone开发之深入浅出 (3) — ARC之前世今生

iPhone开发之深入浅出 (3) — ARC之前世今生

前两节我们对 ARC(Automatic Reference Counting) 有了一个基本的理解,但是 ARC 是怎么产生的,为什么苹果要在其最新的 iOS/Mac OS X 上导入该框架? 如果不理解其背后的基本原理,只是死记硬背那些规则/方法,是毫无意义的。就像我们从小接受的填鸭式教育,基本上到后来都还给老师了。

本节,我们先来看看 ARC 产生之前的 Objective-C 内存管理世界,然后再来看看导入 ARC 后,新的 LLVM 编译器在背后为我们做了什么。

Objective-C 内存管理

和许多面向对象语言一样,Objective-C 中内存管理的方式其实就是指 引用计数 (Reference Counting)的使用准则。如下图所示,对象生成的时候必定被某个持有者拿着,如果有多个持有者的话,其引用计数就会递增;相反失去一个持有者那么引用计数即会递减,直到失去所有的持有者,才真正地从内测中释放自己。

基本原则

内存管理的依循下面的基本原则
  • 自己生成的对象,那么既是其持有者
  • 不是自己生成的对象,也可成为其持有者(一个对象可以被多个人持有)
  • 如果不想持有对象的时候,必须释放其所有权
  • 不能释放已不再持有所有权的对象

结合 Objective-C 语言中的方法,我们来看看基本的内存管理。

方法 动作
alloc/new/copy/mutableCopy 生成对象并拥有所有权
retain 拥有对象所有权
release 释放对象所有权
dealloc 释放对象资源

实际上这些函数并不能说是 Objective-C 语言所特有的,而是 OS X / iOS 系统库中包含的基类函数;具体说就是 Cocoa Framework::Foundation::NSObject 基类的成员函数。

Objective-C 语言内部严格遵守上面表格中的定义;首先是 alloc/new/copy/mutableCopy 这几个函数,并且是alloc/new/copy/mutableCopy 开头的函数,比如:allpcMyObject/newTheObject/copyThis/mutableCopyTheObject 等都必须遵循这个原则。

反而言之,如果不是 alloc/new/copy/mutableCopy 开头的函数,而且要返回对象的话,那么调用端只是生成对象,而不是其持有者。

1
2
3
4
5
6
7
8
9
10
11
-(id)allocObject {
    /*
     * 生成对象并拥有所有权
     */
    id obj = [[NSObject alloc] init];

    /*
     * 自己一直是持有对象状态
     */
    return obj;
}

如上面的例子,alloc 生成的对象,其所有权会传递给函数的调用端;即满足了 alloc 开头函数的命名规则。

再看下面的例子
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
-(id)object {
    id obj = [[NSObject alloc] init];

    /*
     * 自己一直是持有对象状态
     */

    [obj autorelease];

    /*
     * 对象还存在,只是并不持有它的所有权
     */

    return obj;
}

这里我们用到了 autorelease 函数。它的作用既是将对象放入 NSAutoreleasePool 中,由其来维护其生命周期。换句话说对象的持有者是 NSAutoreleasePool;上面的例子中,object 返回后,调用者将不持有其所有权。(除非再调用 retain。)

用 autorelease 的一个理由既是让程序员来控制对象的存活周期,而不像 C/C++ 等语言中,出栈后,栈中数据都被自动废弃,或者用 { } 框住的自动变量,当出了范围就看不到了。在 Objective-C 中,只有当 [pool drain] 被调用的时候,才清空 pool 中所有登录的对象实体,在这之前,你可以像往常一样正常使用对象。

当然可以想象得到的,如果一个程序只有一个 NSAutoreleasePool,并在 main 中声明,程序结束时才 [pool drain]/[pool release] 的话,那么所有 autorelease 的对象都将塞满这个 pool,会耗掉系统大部分内存。所以,使用 NSAutoreleasePool 的时候也尽量建议局部使用,比如下面的循环。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
for (i=0; i < 100; i++) {
    NSAutoreleasePool* pool = [[NSAutoreleasePool alloc] init];

    // 下面的函数由于不属于 alloc/new/copy/mutableCopy 范畴的函数,所以都使用了 autorelease
    NSMutableArray* array = [NSMutableArray array];
    NSString *str = [NSString stringWithFormat:@"TestCode"];

    /*
     * 其他使用autorelease定义的对象
     */
    Test *test = [[[Test alloc] init] autorelease];

    // 通过下面的函数,可以随时监控pool中的对象
    // iOS以外的运行库的情况下,也可以使用 _objc_autoreleasePoolPrint() 私有函数,只是需要下面的声明
    // extern void _objc_autoreleasePoolPrint();
    [NSAutoreleasePool showPools];

    // 这里把所有pool中的对象都释放掉
    [pool release];
}

当然 NSAutoreleasePool 也可以嵌套,基本上都依存大括号规则。

编程准则

基于以上原则,在 ARC 诞生之前,我们往往用下面准则来写代码。

生成对象时,使用autorelease

一般情况下,我们这样生成对象并使用

1
2
3
MyController* controller = [[MyController alloc] init];
// ......
[controller release];

如果在 [controller release] 之前函数return了怎么样,内存泄露了不是;为了防患于未然,一般像下面一样 生成对象时,使用autorelease。这样一来,该对象就被自动加入到最近的那个 pool 中。

1
MyController* controller = [[[MyController alloc] init] autorelease];
对象代入时,先autorelease后再retain

对象代入的时候,如果之前不将变量所持有的对象释放,那么很可能引起内存泄露。比如下面的代码

1
2
3
4
5
6
7
8
9
10
11
12
{
  _member = [[TempValue alloc] init];
}

- (void)setValue:(TempValue *)value {
  _member = value;
  // 这时,之前持有的对象因为没有 release 而引起内存泄露
  // 当然,先 [_member release] 后再代入也是可以的,
  // 但是当与「对象在函数中返回时」的问题一同考虑时,
  // 如果没有 return [[object retain] autorelease] 的保证,这里即使 [_member release]也是百搭
  // 详细的解释见下
}

鉴于以上原因,我们将原先的对象先autorelease后再将新对象retain代入。

1
2
3
4
5
6
7
8
9
10
{
  _member = [[TempValue alloc] init];
  // 这里,即使使用【生成对象时,使用autorelease】的准则,也没有关系
  // 使用autorelease一次就将制定对象放入pool中,放几次[pool drain]的时候就释放几次
}

- (void)setValue:(TempValue *)value {
  [_member autorelease];
  _member = [value retain];
}

该原则遵循 Failed Self 的原则,虽然从性能上看有所损耗但是保证了代码质量。

对象在函数中返回时,使用return [[object retain] autorelease]

严格地说,是除 alloc/new/copy/mutableCopy 开头函数以外的函数中,有对象放回时,使用return [[object retain] autorelease]。

我们结合下面的例子来说明,并总结出该问题的几种解决方案
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
49
50
51
@implementation FooClass

- (void)setObject:(MyObject *)object;
 {
     // 这里故意没有使用 autorelease,以便说明问题
     [_object release];
     _object = [object retain];
 }

- (id)object;
 {
     return _object;
 }

- (void)dealloc;
 {
     [_object release];
     [super dealloc];
 }

@end

@implementation BarClass

- (void)doStuff;
 {
    FooClass * foo = [[FooClass alloc] init];

    // 创建第一个对象,引用计数 = 1
    MyObject * firstObject = [[MyObject alloc] init];
    // setObject中由于 [object retain] ,引用计数 = 2
    [foo setObject:firstObject];
    // 释放一次,引用计数 = 1;这之后对象有正确的所有权属性
    [firstObject release];

    // 通过非 alloc/new/copy/mutableCopy 开头函数得到对象
    // anObject 指向第一个对象,但是并没有其所有权,对象引用计数 = 1
    MyObject * anObject = [foo object];
    [anObject testMethod];

    // 创建第二个对象
    MyObject * secondObject = [[MyObject alloc] init];
    // setObject中由于 [_object release]; 第一个对象引用计数 = 0,内存被释放
    [foo setObject:secondObject];
    [secondObject release];

    // 程序在这里崩溃了,因为 anObject 指向了一个空地址
    [anObject testMethod];
}

@end

从结论我们来看看该问题的几种可行的解决方案;各种方案中没有列出的代码与原先代码一致。

生成对象时,使用autorelease
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
@implementation BarClass

- (void)doStuff;
 {
    FooClass * foo = [[FooClass alloc] init];

    MyObject * firstObject = [[[MyObject alloc] init] autorelease];
    [foo setObject:firstObject];

    MyObject * anObject = [foo object];
    [anObject testMethod];

    MyObject * secondObject = [[[MyObject alloc] init] autorelease];
    [foo setObject:secondObject];

    [anObject testMethod];
}

@end

对象生成时,即被放入最近的 pool 中,不需要人为特殊的维护,对象的生命周期将被延续,出 {} 范围之时即对象释放之际。

对象代入时,先autorelease后再retain
1
2
3
4
5
6
7
8
9
10
11
- (void)setObject:(MyObject *)object;
 {
     [_object autorelease];
     _object = [object retain];
 }

- (id)object;
 {
     // 遵循非 alloc/new/copy/mutableCopy 开头的函数,不赐予所有权原则
     return _object;
 }

同样的,对象被放入最近的 pool 中,第二次 setObject 后对象引用计数仍为1, pool 清空时才执行最后一次对象release,从而保证了代码的正确性。

对象在函数中返回时,使用return [[object retain] autorelease];
1
2
3
4
5
6
7
8
9
10
11
- (void)setObject:(MyObject *)object;
 {
     [_object release];
     _object = [object retain];
 }

- (id)object;
 {
     // 遵循非 alloc/new/copy/mutableCopy 开头的函数,不赐予所有权原则
     return [[_object retain] autorelease];
 }

好不容易回到了本小节要说明的方法;可以看到这是从另一个角度解决了该问题:[foo object] 的时候保证引用计数是2,并将对象放入pool中维护。

总结上面上面3中方法,虽说是从不同角度入手解决了这个问题,但是基本原则不变,利用 NSAutoreleasePool 机制帮程序员维护代码,管理内存。

如果你觉得3种编码原则怎么搭配使用,在什么样的场合下选择比较麻烦,不要紧,都用就得了。我们牺牲的只是 NSAutoreleasePool 中的一些内存,一小许性能损失罢了,这总比我们的程序崩溃了强。

ARC 诞生

ARC 是什么我不需要再解释,若有不明白,可以看iPhone开发之深入浅出 (1) — ARC是什么

ARC 严格遵守 Objective-C 内存管理的基本原则
  • 自己生成的对象,那么既是其持有者
  • 不是自己生成的对象,也可成为其持有者(一个对象可以被多个人持有)
  • 如果不想持有对象的时候,必须释放其所有权
  • 不能释放已不再持有所有权的对象

并从编译器角度维护了该原则,比如如果不是 alloc/new/copy/mutableCopy 开头的函数,编译器会将生成的对象自动放入 autoReleasePool 中。如果是 __strong 修饰的变量,编译器会自动给其加上所有权。等等,详细,我们根据不同的关键字来看看编译器为我们具体做了什么。并从中总结出 ARC 的使用规则。

__strong

我们先来看看用 __strong 修饰的变量,以及缺省隐藏的 __strong 情况。

1
2
3
4
5
6
7
8
9
10
11
12
13
{
    /*
     * 生成对象并拥有所有权
     */
    id __strong obj = [[NSObject alloc] init];

    /*
     * 自己一直是持有对象状态
     */
}
    /*
     * 变量出生命周期时,失去全部所有者,对象内存空间被释放
     */

这种情况毫无悬念,缺省使用 alloc/new/copy/mutableCopy 开头的函数也是这样的结果。并且在这里,编译器帮我们自动的调用了对象的 release 函数,不需要手工维护。再看看下面的情况。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
{
    /*
     * 生成对象但是并没有其所有权
     */
    id __strong obj = [NSMutableArray array];

    /*
     * 由于变量声明是强引用,自己一直是持有对象状态
     * 编译器根据函数名,再将该对象放入 autoreleasepool 中
     */
}
    /*
     * 变量出生命周期时,失去全部所有者,对象内存空间被释放
     */

由上,虽然不是用 alloc/new/copy/mutableCopy 开头的函数得到的对象,由于是强参照,我们任然成为对象的持有者。而这,正是编译器帮我们做到的。

具体做的是什么呢?其实就是【对象在函数中返回时,使用return [[object retain] autorelease]】所描述的;如果你反汇编一下ARC生成的代码,可以看到这时会自动调用名为 objc_retainAutoreleaseReturnValue 的函数,而其作用和 [[object retain] autorelease] 一致。编译器通过函数名分析,如果不是 alloc/new/copy/mutableCopy 开头的函数,自动加入了这段代码。

另外,缺省 __strong 修饰的变量,对象代入的时候也正确地保证对象所有者规则;代入新对象时,自动释放旧对象的参照,代入nil的时候,表示释放当前对象的强参照。

__weak

虽然大部分场合,大部分问题使用 __strong 来编码就足够了;但是为了解决循环参照的问题 __weak 关键字修饰【弱参照】变量就发挥了左右。关于循环参照的问题,准备在以后的博文中介绍;今天,主要看看编译器在背后怎么处理 __weak 变量的。

__weak 声明的变量其实是被放入一个weak表中,该表和引用计数的表格类似,是一个Hash表,都是以对象的内存地址做key,同时,针对一个对象地址的key,可以同时对应多个变量的地址。

当一个 __weak 所指对象被释放时,系统按下面步骤来处理
  • 从weak表中,通过对象地址(key)找到entry
  • 将entry中所有指向该对象的变量设为nil
  • 从weak表中删除该entry
  • 从对象引用计数表中删除对象entry(通过通过对象地址找到)

另外,当使用 __weak 修饰的变量的时候,变量将放入 autoreleasepool 中,并且用几次放几次。比如下面的简单例子。

1
2
3
4
5
6
7
8
{
    id __weak o = obj;
    NSLog(@"1 %@", o);
    NSLog(@"2 %@", o);
    NSLog(@"3 %@", o);
    NSLog(@"4 %@", o);
    NSLog(@"5 %@", o);
}

这里我们用了5次,那么pool中就被登录了5次;从效率上考虑这样当然不是很好,可以通过代入 __strong 修饰的强参照变量来避开这个问题。

1
2
3
4
5
6
7
8
9
{
    id __weak o = obj;
    id temp = o;
    NSLog(@"1 %@", temp);
    NSLog(@"2 %@", temp);
    NSLog(@"3 %@", temp);
    NSLog(@"4 %@", temp);
    NSLog(@"5 %@", temp);
}

另外,还有通过重载 allowsWeakReference/retainWeakReference 函数来限制 __weak 声明变量使用回数的方法,毕竟不在本次讨论范畴之内,就此省略。

话说回来,为什么使用弱参照变量的时候,要将其放入 autoreleasepool 中呢?想想弱参照的定义就应该明白了 —- 如果在访问弱参照对象时,该对象被释放了怎么办,程序不就崩溃了嘛;所以为了解决该问题,又再一次用到了 pool。

__autoreleasing

虽然上面还没有降到该关键字,但是编译器在很多时候已经用到了 autoreleasepool。比如非 alloc/new/copy/mutableCopy 开头的函数返回一个对象的时候,又比如使用一个 __weak 声明的变量的时候。

实际上,写ARC代码的时候,明示 __autoreleasing 声明变量和明示 __strong 声明变量一样基本上没有,因为编译器已经为我们做了很多,很智能了(前提是我们要按ARC的规则写代码)。

还有一种编译器缺省使用 __autoreleasing 关键字声明变量的时候:对象指针类型。比如下面的对应关系。

1
2
   id *obj == id __autoreleasing *obj
   NSObject **obj == NSObject * __autoreleasing *obj

所以,下面两个函数的是等价的。

1
2
3
-(BOOL)performOperationWithError:(NSError **)error;

-(BOOL)performOperationWithError:(NSError * __autoreleasing *)error;

像下面的函数调用,为什么是可行的呢?

1
2
NSError __strong *error = nil;
BOOL result = [obj performOperationWithError:&error];

其实,编译器是这样解释这段代码的。

1
2
3
4
NSError __strong *error = nil;
NSError __autoreleasing *tmp = error;
BOOL result = [obj performOperationWithError:&tmp];
error = tmp;

那么我们这样声明函数不就可以了吗?

1
-(BOOL)performOperationWithError:(NSError * __strong *)error;

答案是肯定的,你可以这样做,编译是可以通过,但你违反了非 alloc/new/copy/mutableCopy 开头的函数,不返回对象持有权的原则。这里是没有问题了,但也许影响到其他地方NG。

ARC 规则

结合上面的讲解,我想你也应该能够总结出来使用ARC时的规则

(这里只列出本讲中涉及的内容,其他的内容以后总结)

  • 代码中不能使用retain, release, retain, autorelease
  • 不能使用NSAllocateObject, NSDeallocateObject
  • 不能使用NSAutoReleasePool、而需要@autoreleasepool块
  • 严守内存管理相关函数命名规则

关于函数命名,伴随ARC的导入,还有一系列函数的定义也被严格定义了,那就是以 init 开头的函数。init 函数作为alloc生成对象的初期化函数,需要按原样直接传递对象给调用段,所以下面的声明是OK的。

1
-(id)initWithObject:(id)obj;

而下面的是NG的。

1
-(void)initWithObject;

不过声明为 -(void) initialize; 是没有问题的。

标签: , , 3 评论
755 views
201月/123

iPhone开发技巧之发布篇(7)— 制作自己的Cydia发布源

iPhone开发技巧之发布篇(7)— 制作自己的Cydia发布源

针对越狱的iPhone,大家肯定都知道用Cydia来安装各种免费应用的经历。Cydia中是否能指定我们自己的发布源,让我们自己随心所欲地发布程序给别人呢?答案是肯定的,我们只要创建Cydia的发布repository即可。

Linux Debian

首先,我们需要有Linux Debian或者Ubuntu系统来制作Cydia的发布源。如果你没有Linux Debian系统的机器,不要紧,可以随时用VMware Player或VirtualBox虚拟机来安装一个。

安装的时候用i386的iso即可,如果是64bit的OS用amd64的image来安装。安装的时候,选择Graphical Install,其他缺省安装。

脚本工具

下载下面两个脚本文件到本地,并加上可执行属性。

制作deb文件

以名称为MyProgram的程序微粒,首先在home目录下建立下面的目录结构
1
2
3
4
5
6
7
8
9
10
Cydia
   +- MyProgram
      +- DEBIAN
      |  +- control
      +- var
         +- mobile
            +- MyProgram.app
                +- Info.plist
                +- MyProgram
                +- icon.png

可以看出来,我们需要把程序MyProgram安装到/var/mobile/中去.

DEBIAN目录下面有一个名为control的文件,我们来看看它的内容。

1
2
3
4
5
6
7
8
9
Package: net.yifeiyang.MyProgram
Name: MyProgram
Version: 1.0.4-1
Architecture: iphoneos-arm
Description: test text.
Homepage: http://www.yifeiyang.net
Maintainer: YIFEIYANG <kane_yj@hotmail.com>
Author: YIFEIYANG <kane_yj@hotmail.com>
Section: Games
名称 说明
Package 唯一标示Package的名称,一般用「域名.Package名」
Name 程序的名称
Version 程序版本,不能使用字母
Architecture 固定为iphoneos-arm
Description 程序概要说明,将显示在Cydia的说明页内
Homepage 程序网页
Maintainer 维护者名称,邮箱
Author 作者,邮箱
Section 所属类型,设定了之后,程序名旁边将显示具体的icon
编译在制作DEB文件很简单,只需要
1
2
cd ~/Cydia
dpkg-deb –b MyProgram

这之后,我们就可以试试该deb文件,用iFile将文件上传到iPhone上并安装,查看/var/mobile/下的情况。这里我们可以使用iPhone开发技巧之发布篇(6)— 不需Developper认证的真机调试方法中的方法制作我们的程序,从而不需要验证信息。

制作Repository

每次追加新的deb文件,或者是deb文件有更新时,需要做下面两步
1
2
dpkg-scanpackages –m . /dev/null >Packages
bzip2 Packages

OK了,接下来我们就可以把Packages.bz2和MyProgram.deb这两个文件上传到你的web服务器中,然后把地址作为Source添加到Cydia中就搞定了。如果没有web服务器也没有关系,可以使用Dropbox等共享网盘,得到唯一的一个URL即可。

1,580 views
41月/124

iPhone开发之深入浅出 (2) — ARC之@property使用

所有者属性
读写相关的属性 (readwrite, readonly)

iPhone开发之深入浅出 (2) — ARC之@property使用

上一回我们学到了一些ARC的基本概念,这一次我们来看看ARC对@property的使用规则有何影响。

所有者属性

我们先来看看与所有权有关系的属性,关键字间的对应关系。

属性值 关键字 所有权
strong __strong
weak __weak
unsafe_unretained __unsafe_unretained
copy __strong
assign __unsafe_unretained
retain __strong
strong

该属性值对应 __strong 关键字,即该属性所声明的变量将成为对象的持有者。

weak

该属性对应 __weak 关键字,与 __weak 定义的变量一致,该属性所声明的变量将没有对象的所有权,并且当对象被破弃之后,对象将被自动赋值nil。

并且,delegate 和 Outlet 应该用 weak 属性来声明。同时,如上一回介绍的 iOS 5 之前的版本是没有 __weak 关键字的,所以 weak 属性是不能使用的。这种情况我们使用 unsafe_unretained。

unsafe_unretained

等效于__unsafe_unretaind关键字声明的变量;像上面说明的,iOS 5之前的系统用该属性代替 weak 来使用。

copy

与 strong 的区别是声明变量是拷贝对象的持有者。

assign

一般Scalar Varible用该属性声明,比如,int, BOOL。

retain

该属性与 strong 一致;只是可读性更强一些。

读写相关的属性 (readwrite, readonly)

读写相关的属性有 readwrite 和 readonly 两种,如果使用ARC之后,我么需要注意一下 readonly 属性的使用。

比如下面的变量声明。

1
@property (nonatomic, readonly) NSString *name;

一般声明为 readonly 的变量按理说应该不需要持有所有权了,但是在ARC有效的情况下,将出现下面的错误信息 :

“ARC forbids synthesizing a property of an Objective-C object with unspecified ownership or storage attribute”

如果定义了ARC有效,那么必须要有所有者属性的定义;所以我们的代码改成这样,就OK了

1
@property (nonatomic, strong, readonly) NSString *name;

不过有一点,Scalar Varible的变量缺省都有 assign 的属性定义,所以不需要给他们单独的明示声明了。

2,436 views
11月/129

iPhone开发之深入浅出 (1) — ARC是什么

iPhone开发之深入浅出 (1) — ARC是什么

新年伊始,万象更新。新一年开始,我们来更加深入了解一下iPhone开发的内部。作为开始,我们先来了解一下ARC。

ARC是什么

ARC是iOS 5推出的新功能,全称叫 ARC(Automatic Reference Counting)。简单地说,就是代码中自动加入了retain/release,原先需要手动添加的用来处理内存管理的引用计数的代码可以自动地由编译器完成了。

该机能在 iOS 5/ Mac OS X 10.7 开始导入,利用 Xcode4.2 可以使用该机能。简单地理解ARC,就是通过指定的语法,让编译器(LLVM 3.0)在编译代码时,自动生成实例的引用计数管理部分代码。有一点,ARC并不是GC,它只是一种代码静态分析(Static Analyzer)工具。

变化点

通过一小段代码,我们看看使用ARC前后的变化点。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
@interface NonARCObject : NSObject {
    NSString *name;
}
-(id)initWithName:(NSString *)name;
@end

@implementation NonARCObject
-(id)initWithName:(NSString *)newName {
    self = [super init];
    if (self) {
        name = [newName retain];
    }
    return self;
}

-(void)dealloc {
    [name release];
    [Super dealloc];
}
@end
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
@interface ARCObject : NSObject {
    NSString *name;
}
-(id)initWithName:(NSString *)name;
@end

@implementation ARCObject
-(id)initWithName:(NSString *)newName {
    self = [super init];
    if (self) {
        name = newName;
    }
    return self;
}
@end
我们之前使用Objective-C中内存管理规则时,往往采用下面的准则
  • 生成对象时,使用autorelease
  • 对象代入时,先autorelease后再retain
  • 对象在函数中返回时,使用return [[object retain] autorelease];

而使用ARC后,我们可以不需要这样做了,甚至连最基础的release都不需要了。

使用ARC的好处

使用ARC有什么好处呢?

  • 看到上面的例子,大家就知道了,以后写Objective-C的代码变得简单多了,因为我们不需要担心烦人的内存管理,担心内存泄露了
  • 代码的总量变少了,看上去清爽了不少,也节省了劳动力
  • 代码高速化,由于使用编译器管理引用计数,减少了低效代码的可能性

不好的地方

  • 记住一堆新的ARC规则 — 关键字及特性等需要一定的学习周期
  • 一些旧的代码,第三方代码使用的时候比较麻烦;修改代码需要工数,要么修改编译开关

关于第二点,由于 XCode4.2 中缺省ARC就是 ON 的状态,所以编译旧代码的时候往往有"Automatic Reference Counting Issue"的错误信息。

这个时候,可以将项目编译设置中的“Objectice-C Auto Reference Counteting”设为NO。如下所示。

如果只想对某个.m文件不适应ARC,可以只针对该类文件加上 -fno-objc-arc 编译FLAGS,如下图。

ARC基本规则

  • retain, release, autorelease, dealloc由编译器自动插入,不能在代码中调用
  • dealloc虽然可以被重载,但是不能调用[super dealloc]

由于ARC并不是GC,并需要一些规则让编译器支持代码插入,所以必须清楚清楚了这些规则后,才能写出健壮的代码。

Objective-C对象

ObjectiveC中的对象,有强参照(Strong reference)和弱参照(Weak reference)之分,当需要保持其他对象的时候,需要retain以确保对象引用计数加1。对象的持有者(owner)只要存在,那么该对象的强参照就一直存在。

对象处理的基本规则是
  • 只要对象的持有者存在(对象被强参照),那么就可以使用该对象
  • 对象失去了持有者后,即被破弃
强参照 (Strong reference)
(s1)

firstName作为”natsu”字符串对象的最初持有者,是该NSString类型对象的Strong reference。

(s2)

这里将firstName代入到aName中,即aName也成为了@”natsu”字符串对象的持有者,对于该对象,aName也是Strong reference。

(s3)

这里,改变firstName的内容。生成新的字符串对象”maki”。这时候firstName成为”maki”的持有者,而@”natsu”的持有者只有aName。每个字符串对象都有各自的持有者,所以它们都在内存中都存在。

(s4)

追加新的变量otherName, 它将成为@”maki”对象的另一个持有者。即NSString类型对象的Strong reference。

(s5)

将otherName代入到aName,这时,aName将成为@”maki”字符串对象的持有者。而对象@”natsu”已经没有持有者了,该对象将被破弃。

弱参照 (Weak reference)

接下来我们来看看弱参照 (Weak reference) 的使用方式。

(w1)

与强参照方式同样,firstName作为字符串对象@”natsu”的持有者存在。即是该NSString类型对象的Strong reference。

(w2)

使用关键字__weak,声明弱参照weakName变量,将firstName代入。这时weakName虽然参照@”natsu”,但仍是Weak reference。即weakName虽然能看到@”natsu”,但不是其持有者。

(w3)

firstName指向了新的对象@”maki”,成为其持有者,而对象@”natsu”因为没有了持有者,即被破弃。同时weakName变量将被自动代入nil。

引用关键字

ARC中关于对象的引用参照,主要有下面几关键字。使用strong, weak, autoreleasing限定的变量会被隐式初始化为nil。

__strong

变量声明缺省都带有__strong关键字,如果变量什么关键字都不写,那么缺省就是强参照。

__weak

上面已经看到了,这是弱参照的关键字。该概念是新特性,从 iOS 5/ Mac OS X 10.7 开始导入。由于该类型不影响对象的生命周期,所以如果对象之前就没有持有者,那么会出现刚创建就被破弃的问题,比如下面的代码。

1
2
NSString __weak *string = [[NSString alloc] initWithFormat:@"First Name: %@", [self firstName]];
NSLog(@"string: %@", string); //此时 string为空

如果编译设定OS版本 Deployment Target 设定为这比这低的版本,那么编译时将报错(The current deployment target does not support automated __weak references),这个时候,我们可以使用下面的__unsafe_unretained。

弱参照还有一个特征,即当参数对象失去所有者之后,变量会被自动付上nil (Zeroing)。

__unsafe_unretained

该关键字与__weak一样,也是弱参照,与__weak的区别只是是否执行nil赋值(Zeroing)。但是这样,需要注意变量所指的对象已经被破弃了,地址还还存在,但内存中对象已经没有了。如果还是访问该对象,将引起「BAD_ACCESS」错误。

__autoreleasing

该关键字使对像延迟释放。比如你想传一个未初始化的对像引用到一个方法当中,在此方法中实例化此对像,那么这种情况可以使用__autoreleasing。他被经常用于函数有值参数返回时的处理,比如下面的例子。

1
2
3
4
5
6
7
8
9
10
11
- (void) generateErrorInVariable:(__autoreleasing NSError **)paramError {
    ....
    *paramError = [[NSError alloc] initWithDomain:@"MyApp" code:1 userInfo:errorDictionary];
}

....
{
    NSError *error = nil;
    [self generateErrorInVariable:&error];
    NSLog(@"Error = %@", error);
}

又如函数的返回值是在函数中申请的,那么希望释放是在调用端时,往往有下面的代码。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
-(NSString *)stringTest
{
    NSString *retStr = [NSString stringWithString:@"test"];

    return [[retStr retain] autorelease];
}

// 使用ARC

-(NSString *)stringTest
{
    __autoreleasing NSString *retStr = [NSString alloc] initWithString:@"test"];

    return retStr;
}

即当方法的参数是id*,且希望方法返回时对象被autoreleased,那么使用该关键字。

总结

今天,我们看到了基本的ARC使用规则
  • 代码中不能使用retain, release, retain, autorelease
  • 不重载dealloc(如果是释放对象内存以外的处理,是可以重载该函数的,但是不能调用[super dealloc])
  • 不能使用NSAllocateObject, NSDeallocateObject
  • 不能在C结构体中使用对象指针
  • id与void *间的如果cast时需要用特定的方法(__bridge关键字)
  • 不能使用NSAutoReleasePool、而需要@autoreleasepool块
  • 不能使用“new”开始的属性名称 (如果使用会有下面的编译错误”Property’s synthesized getter follows Cocoa naming convention for returning ‘owned’ objects”)

今后,我们将更加深入ARC,学习其更多的特性。

1,329 views
2512月/114

iPhone开发技巧之发布篇(6)— 不需Developper认证的真机调试方法

iPhone开发技巧之发布篇(6)— 不需Developper认证的真机调试方法

以前,iPhone开发入门(10)--- 设备上运行程序中介绍了正式注册iPhone/iOS Developer Program后在设备运行程序的方法;在iPhone开发入门(3)—Linux上构筑iPhone OS3.1.2开发环境iPhone开发进阶(4) --- 使用Makefile自动编译iPhone程序 也简单介绍了在越狱机器上运行程序的方法。

今天给大家介绍另一种不需要注册iPhone/iOS Developer Program,也可以简单地在设备中运行程序的方法.

制作证明书

首先通过Keychain制作一个新的证明书 Keychain Access -> Certificate Assistant -> Create a Certification

证明书的名称任意(需要记住,下面会用到),固有类型选择self-signed root,证明书类型选择Code Signing,并选择“let me override defaults”。

程序设置

程序的Code Signing都设置为「Don’t Code Sign」

编辑SDKSettings.plist文件

/Developer/Platforms/iPhoneOS.platform/Developer/SDKs/iPhoneOS5.0.sdk/SDKSettings.plist

将 CODE_SIGNING_REQUIRED 设置为 NO。

设置之后编译程序 for iOS Device。

署名

在控制台用codesign给应用程序署名
1
codesign –fs “Fake Code Sign” ./Application.app/Application

验证

制作好Application.app之后,通过iPhoneExplorer,iFile等上传到iPhone的/Applications目录下。并添加执行属性。

OK, 可以运行一下我们的程序看看了。

标签: , 4 评论
578 views
2112月/110

敏捷开发实践(2) — 敏捷软件开发者的习惯

敏捷开发实践(2) — 敏捷软件开发者的习惯

敏捷开发的最小单位就是参与敏捷开发的个人。将这些敏捷开发者聚集起来,就形成了敏捷开发团队。

正如上回介绍的,敏捷开发是一种以人为核心、迭代、循序渐进的开发方法,它以最大可能地发挥团队的作用为目的。根据需要,随时改善,以降低软件开发中的风险。

敏捷开发者的态度

敏捷开发者首先需要有忠实,勤恳的态度,在此之上要有持续改善和迅速达成目标的紧迫感。如何让开发者养成敏捷的心态,如何磨练开发者敏捷的意志,让开发者了解敏捷的习惯很重要。

习惯来自于经验,习惯需要用实践来养成。我们来看看作为敏捷软件开发者必备的4种技能 :

  • 编故事(Creating Stories)

    这里不是让你去写一部小说,而是让开发者站在用户的视点,用用户能理解的词汇描述软件系统的机能,行为。只有理解了用户真正的需求,我们才能写出真正需要的软件。

  • 制定计划(Planning)

    敏捷开发并不是没有计划就投入编码。指定计划也是很重要的,但是与以往的开发计划不同,敏捷的计划是随着开发的实际情况改变而随时制定并改善的。所以说敏捷开发的计划的作成、修改的频度很高,需要尽可能高效地完成每次制定的计划。

  • 测试驱动开发(Test-Driven Development)

    它的基本思想就是在开发功能代码之前,先编写测试代码。也就是说在明确要开发某个功能后,首先思考如何对这个功能进行测试,并完成测试代码的编写,然后编写相关的代码满足这些测试用例。然后循环进行添加其他功能,直到完成全部功能的开发。

  • 重构(Refactoring)

    没有最好,只有更好。软件开发也不例外,任何时候我们都可以对软件代码做任何更动以增加可读性或者简化结构而不影响输出结果。

敏捷开发者的习惯

了解了开发者应该具有的态度,我们就可以从自己开始掌握敏捷开发者的习惯。然后在团队中展开,最终形成敏捷的团队。

构成敏捷习惯的要素
  • 敏捷的心态
  • 敏捷的实践
  • 持续改善
重视各种反馈

通过实践中的反馈,我们可以得到过程中的经验,并对今后的开发产生有益的作用。但是并不是一味的重视实践就好了,需要知道何时从实践和反馈中学习。

为了按阶段,周期性的完成实践及反馈的过程,差生了迭代的概念。

迭代开发

迭代开发方法中,整个开发工作被组织为一系列的短小的、固定长度(如2周)的小项目,被称为一系列的迭代。每一次迭代都包括了需求分析、设计、实现与测试。采用这种方法,开发工作可以在需求被完整地确定之前启动,并在一次迭代中完成系统的一部分功能或业务逻辑的开发工作。再通过客户的反馈来细化需求,并开始新一轮的迭代。

迭代的一个很重要思想就是将作业按时间单位来划分,管理。在一定的周期内完成开发的作业。不同的团队可以按照机能划分,并行开展,如下图 :

比如,一个项目的作业期间是3个月,按2周为单位来开展迭代开发,那么迭代的总数就是6个。并且该期限是严守的,一旦规定好了一般是不能更改的。

回顾改善

每个迭代结束之后,都需要回顾上个迭代中的内容,考虑是否有改善和坚持的地方,以提高接下来迭代中的开发效率。

该回顾需要定期的地实施,并在30分钟到1个小时内完成。一般情况下,在白板上使用KPT方法来总结课题。

  • Keep : 好的,需要今后坚持的
  • Problem : 问题点,需要注意并改善的
  • Try : 下次开始尝试改善的
进行KPT的步骤
  • 检查上次的Try课题
  • 回忆本次迭代中遇到的课题
  • 按Keep,Problem,Try的形式总结
  • Closing,总结,给白板拍照
第 1 页,共 12 页12345...10...最旧 »

分类目录

标签

free counters