ARC的一些基础概念

先回顾一下Objective-C类的定义格式:

MyClass.h
#import <Foundation/Foundation.h>

@interface MyClass : NSObject
{
    //成员变量定义,没有修饰符的情况下,默认为@protected
    int i;
    
    @private
    float f;
    char *c;
    
    @protected
    NSString *str;

    @public
    id obj;
    struct {
            unsigned int lineBreakMode:3;
            unsigned int highlighted:1;
            unsigned int baselineAdjustment:2;
        } _textLabelFlags;
}

//属性定义
@property (nonatomic,copy) NSString *name;
@property (strong, nonatomic) IBOutletUILabel *lblName;

//方法定义
//Objective-C的编译器会给没有写显式的返回值函数加上一个默认的返回值,它的类型是id
- promoteTo:newPosition; 
- (void)setTitle:(NSString *)string;
+ (id) getMyName:(NSString *)string;

@end
MyClass.m
#import "MyClass.h"

@implementation MyClass

@synthesize name;
@synthesize lblName;

- (id)promoteTo:(id)newPosition
{
    returnnil;
}

- (void)setTitle:(NSString *)string
{
}

+(id)getMyName:(NSString *)string
{
    return@"myName";
}

@end

定义成员变量作用域

(1) @protected -- 作用范围在自身类和继承自己的子类,默认。

(2) @private -- 作用范围只在自身类。

(3) @public -- 范围最广,可被任何类访问

实例化一个类的一般做法是:[[Class alloc] init]。这个操作其实做了两件事:alloc给对象分配内存空间,init对对象进行初始化。 

上面的这个类,当外部实例化它时:

MyClass *tmpClass = [[MyClass alloc] init];

NSLog(@"%s",tmpClass->c); //error,"Instance variable 'c' is private

NSLog(@"%d",tmpClass->i); //error,"Instance variable 'i' is protected

id tmp = tmpClass->obj; // no-warning

UILabel *tmpLabel = tmpClass.lblName; // no-warning

可以看到成员变量只有声明为@public时,才能以"->"的形式访问。而要以"."操作符来访问变量,则必须声明为@property。

@property的一个主要作用就是生成getter和setter方法,这样做简化了大部分代码,看下面的例子:

@interface Worker : NSObject
{
    NSString *_name;
}

- (NSString*)name;

- (void)setName:(NSString*)strName;

//等价于
//@property (nonatomic,copy) NSString *name;

在实现文件(Worker.m)里:

- (NSString*)name
{
    return _name;
}

- (void)setName:(NSString*)strName
{
    if(_name!=strName)
    {
        [_name release];
        _name = [strName copy];
    }
}

//等价于
//@synthesize name;

上面的代码解释了@property和@synthesize背后的实际操作,同时也能看出来@property(copy)代表_name = [strName copy]; 如果你指定为retain,代表_name = [strName retain]; 指定为assign,代表_name = strName;

所以,@property并不仅仅是做了一个简化代码的getter/setter操作,同时它还做内存管理。

声明@property的语法为:@property(参数1, 参数2) 类型 名字。其中参数主要分为三类:

读写属性:readwrite(默认)/readonly

setter语意:assign(默认)/retain/copy

原子性:atomic(默认)/nonatomic

各参数意义如下:

readwrite:产生setter/getter方法

readonly:只产生getter,不产生setter

assign:默认类型,直接赋值而不进行retain操作

retain:先release旧值,再retain新值

copy:进行copy操作,与retain一样

atomic:开启多线程变量保护,会消耗一定的资源

nonatomic:禁止多线程变量保护,提高性能

先来研究一下readwrite和readonly:

【示例一】

#import <Foundation/Foundation.h>

@interface NewClass : NSObject

//这里声明为readonly,所以只会生成getter方法
@property (nonatomic,readonly,copy) NSString *nickname;

@end

基于上面的头文件,当你synthesize name以后,尝试做如下操作:

NewClass *myClass = [[NewClass alloc] init];

myClass.nickname = @"abc";

这个时候编译器会报错:Assigning to property with 'readonly' attribute not allowed. 就是提示你对设置为readonly的属性赋值是被禁止的。

【示例二】

#import <Foundation/Foundation.h>

@interface NewClass : NSObject

//这里声明为readonly,所以只会生成getter方法
@property (nonatomic,readonly,copy) NSString *nickname;

//提供了一个自定义的setter方法
- (void)setNickname:(NSString *)nickname;

@end

虽然在@property定义为readonly,但是随后提供了一个自定义的setter方法,那么实际作用等同于readwrite了。当尝试对其进行赋值,编译器将通过。

【示例三】

#import <Foundation/Foundation.h>

@interface NewClass : NSObject

@property (nonatomic,copy) NSString *nickname;

//自定义getter方法
- (NSString*)nickname;

@end
#import "NewClass.h"

@implementation NewClass

@synthesize nickname;

- (NSString*)nickname
{
    NSMutableString *str = [[NSMutableStringalloc] initWithString:nickname];

    [str appendString:@"_suffix"];

    return str;
}

@end
- (void)viewDidLoad
{
    [superviewDidLoad];

    NewClass *myClass = [[NewClass alloc] init];

    myClass.nickname = @"abc";

    NSLog(@"%@",myClass.nickname);

    //将输出abc_suffix
}

上面的例子里声明了一个属性nickname,默认为readwrite。但随后又提供了一个自定义的getter方法,并且在实现文件里将赋值后的值加上了一个后缀字符串。最终运行后,会发现自定义的getter方法生效了,它覆盖了@property自动生成的getter。

通过上面的例子同时可以得到一个结论:假如声明了一个@property,名称为nickname。那么会生成一个名为nickname的成员变量、一个名为nickname的getter方法、一个setNickname的setter方法。

再来看看atomic/nonatomic的区别。如果你用@synthesize去让编译器生成代码,那么atomic和nonatomic生成的代码是不一样的。如果使用atomic,它会保证每次getter和setter的操作都会正确的执行完毕,而不用担心其它线程在你get的时候set,可以说保证了某种程度上的线程安全。但是,仅仅靠atomic来保证线程安全是不够的。要写出线程安全的代码,还需要有同步和互斥机制。而nonatomic就没有类似的"线程安全"保证了。因此,很明显,nonatomic比atomic速度要快。这也是为什么,我们基本上所有用@property的地方,都用的是nonatomic了。

最后着重了解一下assign、retain与copy:

assign指定setter方法采用简单的赋值操作,而不更改索引计数。这也是没有明确指定setter语意的默认操作。基础数据类型(NSInteger、CGPoint)和C语言数据类型(int, float, double, char等)一般会采用assign。

- (void)setNickname:(NSString *)strName
{
    nickname = strName;
}

指定为retain后,先释放旧的(指针)对象,再设置为新的对象,并且索引计数加1。(指针拷贝)

- (void)setNickname:(NSString *)strName
{
    if(nickname!=strName)
    {
        [nickname release];
        nickname = [strName retain];
    }
}

指定为copy后,先释放旧的(指针)对象,再拷贝一份新的对象,不对(拷贝的)旧对象进行操作。(内容拷贝)

- (void)setNickname:(NSString *)strName
{
    if(nickname!=strName)
    {
        [nickname release];
        nickname = [strName copy];
    }
}

下面举例分析一下assign、retain和copy的一些不同之处:

NSString *str = [[NSString alloc] initWithString:@"test"];

这句话实际上产生了两个操作:

1. 申请了一段内存用来存储"test",这里假设内存地址为0x1111
2. 用一个名为str的指针,指向"test",假设指针的内存地址为0xaaaa

先来看assign在这种情况下的示例:

NSString *str2 = str;

此时str2和str完全相同,地址都是0xaaaa,都指向地址为0x1111的内容"test"。所以assign操作相当于是一个"别名"的概念

再来看retain的情况:

NSString *str3 = [str retain];

此时str3的地址不再是0xaaaa,可能是0xbbbb,但是仍然指向同一个对象(地址为0x1111,内容为"test")。retainCount(索引计数)增加1。

最后看看copy的情况:

NSString *str4 = [str copy];

此时会新开辟一段内存来存放内容"test",假设地址为0x2222。同时会为指针str4也分配一段新内存(假设为0xcccc),然后将str4指向地址为0x2222的"test"。操作结束后,原来地址为0x1111的"test"以及地址为"0xaaaa"的指针均不受影响;地址为0xcccc的指针指向地址为0x2222的"test"。且retainCount为1。

所以可以得出结论,retain是指针拷贝,copy是内容拷贝

copy也分为深拷贝和浅拷贝,类似于C++

(1) 深拷贝,就是新拷贝一块内存交给对象使用
(2) 浅拷贝,就是觉得拷贝内存太浪费,直接给你我的地址吧,相当于retain

在Objective-C里只有一种情况是浅拷贝,那就是不可变对象的copy,其它的都是深拷贝(包括不可变对象mutableCopy、可变对象的copy和mutableCopy)。

适用场合:

非对象的数据类型,比如int、float等基本数据类型用assign。

对象数据类型,例如NSObject用retain。

当类拥有mutable子类时,应该使用copy,而不是retain。例如:NSArray、NSSet、NSDictionary、NSData、NSCharacterSet、NSIndexSet、NSString。

虽然规范上NSString做属性都是写成copy,其实在不存在NSMutableString赋值给NSString时,是可以采用retain来避免字符串拷贝带来的消耗的。

MyClass.h
#import <Foundation/Foundation.h>

@interface NewClass : NSObject

@property (nonatomic,retain) NSString *strRetain;
@property (nonatomic,copy) NSString *strCopy;

@end
MyClass.m
#import "NewClass.h"

@implementation NewClass

@synthesize strRetain;
@synthesize strCopy;

@end
- (void)viewDidLoad
{
    [superviewDidLoad];

    NSMutableString *str = [[NSMutableStringalloc] initWithCapacity:100];

    [str setString:@"dog"];

    NewClass *myClass = [[NewClass alloc] init];

    myClass.strRetain = str;

    myClass.strCopy = str;

    NSLog(@"retain string is %@",myClass.strRetain);

    NSLog(@"copy string is %@",myClass.strCopy);

    [str setString:@"cat"];

    NSLog(@"retain string is %@",myClass.strRetain);

    NSLog(@"copy string is %@",myClass.strCopy);
}

将依次输出"dog, dog, cat, dog"。这样就可以看出,当使用retain方式的时候,NSMutableString的内容变化时,语义上应该不可变的NSString也变化了,而用copy则是始终保持赋值时的内容。在实际开发中,如果此NSString不存在NSMutableString赋值的情况,就可以采用retain来代替copy。

最后说一下iOS5加入ARC机制后新加入的一些关键词:

strong -- 强引用,关键字为__strong,用该属性声明的变量将成为对象的持有者。(默认)

weak -- 弱引用,关键字为__weak,用该属性声明的变量并没有对象的所有权,并且当对象失去持有者之后,变量会被自动设置为nil。

unsafe_unretained,关键字为__unsafe__unretained,iOS5之前的版本用这个关键字来代替__weak,于_weak的区别在于是否执行nil赋值。如果所指向的对象被释放了,这个指针就是一个野指针了。

autoreleasing,关键字为__autoreleasing,用来修饰一个函数的参数,这个参数会在函数返回的时候被自动释放。换个说法:该关键字使对象延迟释放。比如你想传一个未初始化的对象引用到一个方法当中,并在此方法中实例化该对象,那么这种情况可以使用__autoreleasing。它被经常用于函数有值参数返回时的处理,比如下面的例子:

- (void)testSomething:(NSObject * __autoreleasing *)objParam
{
    *objParam = [[NSObject alloc] init];
}

- (void)viewDidLoad
{
    [superviewDidLoad];

    NSObject *obj = nil;

    [self testSomething:&obj];

    NSLog(@"%p",obj);
}

另外,下面的写法是等效的:

id *obj == id __autoreleasing * obj;

NSObject **obj == NSObject * __autoreleasing * obj;

使用strong, weak, autoreleasing限定的变量会被隐式初始化为nil。

 

posted @ 2013-01-05 11:03  CoderWayne  阅读(1967)  评论(0编辑  收藏  举报