ARC的一些基础概念
先回顾一下Objective-C类的定义格式:
 MyClass.h
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
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
MyClass.h
#import <Foundation/Foundation.h> @interface NewClass : NSObject @property (nonatomic,retain) NSString *strRetain; @property (nonatomic,copy) NSString *strCopy; @end
 MyClass.m
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。

 MyClass.h
MyClass.h
 
                
            
         
         浙公网安备 33010602011771号
浙公网安备 33010602011771号