zoukankan      html  css  js  c++  java
  • Objectivec的@property 详解

    转自http://www.cnblogs.com/andyque/archive/2011/08/03/2125728.html

    之前很多网友对我翻译的教程中的Property的使用感到有些迷惑不解,搞不清楚什么时候要release,什么时候要self.xxx = nil;同时对于Objective-c的内存管理以及cocos2d的内存管理规则不够清楚。本文主要讲解objc里面@property,它是什么,它有什么用,atomic,nonatomic,readonly,readwrite,assign,retain,copy,getter,setter这些关键字有什么用,什么时候使用它们。至于Objc的内存管理和cocos2d的内存管理部分,接下来,我会翻译Ray的3篇教程,那里面再和大家详细讨论。今天我们的主要任务是搞定@property。

      学过c/c++的朋友都知道,我们定义struct/class的时候,如果把访问限定符(public,protected,private)设置为public的话,那么我们是可以直接用.号来访问它内部的数据成员的。比如

    复制代码
    //in Test.h
    class Test
    {
    public:
    int i;
    float f;
    };
    复制代码

      我在main函数里面是可以通过下面的方式来使用这个类的:(注意,如果在main函数里面使用此类,除了要包含头文件以外,最重要的是记得把main.m改成main.mm,否则会报一些奇怪的错误。所以,任何时候我们使用c++,如果报奇怪的错误,那就要提醒自己是不是把相应的源文件改成.mm后缀了。其它引用此类的文件有时候也要改成.mm文件)

      //in main.mm
        Test test;
    test.i
    =1;
    test.f
    =2.4f;

    NSLog(
    @"Test.i = %d, Test.f = %f",test.i, test.f);

      但是,在objc里面,我们能不能这样做呢?请看下面的代码:(新建一个objc类,命名为BaseClass)

    //in BaseClass.h
    @interface BaseClass : NSObject{
    @public
    NSString
    *_name;
    }

     接下来,我们在main.mm里面:

        BaseClass *base= [[BaseClass alloc] init];
    base.name =@"set base name";
    NSLog(
    @"base class's name = %@", base.name);

      不用等你编译,xcode4马上提示错误,请看截图:

      请大家注意看出错提示“Property 'nam' not found on object of type BaseClass*",意思是,BaseClass这类没有一个名为name的属性。即使我们在头文件中声明了@public,我们仍然无法在使用BaseClass的时候用.号来直接访问其数据成员。而@public,@protected和@private只会影响继承它的类的访问权限,如果你使用@private声明数据成员,那么在子类中是无法直接使用父类的私有成员的,这和c++,java是一样的。

      既然有错误,那么我们就来想法解决啦,编译器说没有@property,那好,我们就定义property,请看代码:

    复制代码
    //in BaseClass.h
    @interface BaseClass : NSObject{
    @public
    NSString
    *_name;
    }
    @property(nonatomic,copy) NSString
    *name;
    //in BaseClass.m
    @synthesize name = _name;
    复制代码

      现在,编译并运行,ok,很好。那你可能会问了@prperty是不是就是让”."号合法了呀?只要定义了@property就可以使用.号来访问类的数据成员了?先让我们来看下面的例子:

    复制代码
    @interface BaseClass : NSObject{
    @public
    NSString
    *_name;
    }
    //@property(nonatomic,copy) NSString *name;

    -(NSString*) name;
    -(void) setName:(NSString*)newName;
    复制代码

      我把@property的定义注释掉了,另外定义了两个函数,name和setName,下面请看实现文件:

    复制代码
    //@synthesize name = _name;

    -(NSString*) name{
    return _name;
    }

    -(void) setName:(NSString *)name{
    if (_name != name) {
    [_name release];
    _name
    = [name copy];
    }
    }
    复制代码

      现在,你再编译运行,一样工作的很好。why?因为我刚刚做的工作和先前声明@property所做的工作完全一样。@prperty只不过是给编译器看的一种指令,它可以编译之后为你生成相应的getter和setter方法。而且,注意看到面property(nonatomic,copy)括号里面这copy参数了吗?它所做的事就是

    _name = [name copy];

      如果你指定retain,或者assign,那么相应的代码分别是:

    //property(retain)NSString* name;
    _name = [name retain];

    //property(assign)NSString* name;
    _name = name;

      其它讲到这里,大家也可以看出来,@property并不只是可以生成getter和setter方法,它还可以做内存管理。不过这里我暂不讨论。现在,@property大概做了件什么事,想必大家已经知道了。但是,我们程序员都有一个坎,就是自己没有完全吃透的东西,心里用起来不踏实,特别是我自己。所以,接下来,我们要详细深挖@property的每一个细节。

      首先,我们看atomic 与nonatomic的区别与用法,讲之前,我们先看下面这段代码:

    复制代码
    @property(nonatomic, retain) UITextField *userName;    //1

    @property(nonatomic, retain,readwrite) UITextField
    *userName; //2

    @property(atomic, retain) UITextField
    *userName; //3

    @property(retain) UITextField
    *userName; //4

    @property(atomic,assign)
    int i; // 5

    @property(atomic)
    int i; //6
    @property int i;               //7
    复制代码

      请读者先停下来想一想,它们有什么区别呢?

      上面的代码1和2是等价的,3和4是等价的,5,6,7是等价的。也就是说atomic是默认行为,assign是默认行为,readwrite是默认行为。但是,如果你写上@property(nontomic)NSString *name;那么将会报一个警告,如下图:

      因为是非gc的对象,所以默认的assign修饰符是不行的。那么什么时候用assign、什么时候用retain和copy呢?推荐做法是NSString用copy,delegate用assign(且一定要用assign,不要问为什么,只管去用就是了,以后你会明白的),非objc数据类型,比如int,float等基本数据类型用assign(默认就是assign),而其它objc类型,比如NSArray,NSDate用retain。

      在继续之前,我还想补充几个问题,就是如果我们自己定义某些变量的setter方法,但是想让编译器为我们生成getter方法,这样子可以吗?答案是当然可以。如果你自己在.m文件里面实现了setter/getter方法的话,那以翻译器就不会为你再生成相应的getter/setter了。请看下面代码:

      

    复制代码
    //代码一:
    @interface BaseClass : NSObject{
    @public
    NSString
    *_name;
    }
    @property(nonatomic,copy,
    readonly) NSString *name; //这里使用的是readonly,所有会声明geter方法

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



    //代码二:
    @interface BaseClass : NSObject{
    @public
    NSString
    *_name;
    }
    @property(nonatomic,copy,
    readonly) NSString *name; //这里虽然声明了readonly,但是不会生成getter方法,因为你下面自己定义了getter方法。

    -(NSString*) name; //getter方法是不是只能是name呢?不一定,你打开Foundation.framework,找到UIView.h,看看里面的property就明白了)
    -(void) setName:(NSString*)newName;

    //代码三:
    @interface BaseClass : NSObject{
    @public
    NSString
    *_name;
    }
    @property(nonatomic,copy,readwrite) NSString
    *name; //这里编译器会我们生成了getter和setter

    //代码四:
    @interface BaseClass : NSObject{
    @public
    NSString
    *_name;
    }
    @property(nonatomic,copy) NSString
    *name; //因为readwrite是默认行为,所以同代码三
    复制代码

      上面四段代码是等价的,接下来,请看下面四段代码:  

    复制代码
    //代码一:
    @synthesize name = _name; //这句话,编译器发现你没有定义任何getter和setter,所以会同时会你生成getter和setter

    //代码二:
    @synthesize name = _name; //因为你定义了name,也就是getter方法,所以编译器只会为生成setter方法,也就是setName方法。

    -(NSString*) name{
    NSLog(
    @"name");
    return _name;
    }

    //代码三:
    @synthesize name = _name; //这里因为你定义了setter方法,所以编译器只会为你生成getter方法

    -(void) setName:(NSString *)name{
    NSLog(
    @"setName");
    if (_name != name) {
    [_name release];
    _name
    = [name copy];
    }
    }

    //代码四:
    @synthesize name = _name; //这里你自己定义了getter和setter,这句话没用了,你可以注释掉。

    -(NSString*) name{
    NSLog(
    @"name");
    return _name;
    }

    -(void) setName:(NSString *)name{
    NSLog(
    @"setName");
    if (_name != name) {
    [_name release];
    _name
    = [name copy];
    }
    }
    复制代码

      上面这四段代码也是等价的。看到这里,大家对Property的作用相信会有更加进一步的理解了吧。但是,你必须小心,你如果使用了Property,而且你自己又重写了setter/getter的话,你需要清楚的明白,你究竟干了些什么事。别写出下面的代码,虽然是合法的,但是会误导别人:

    复制代码
    //BaseClass.h
    @interface BaseClass : NSObject{
    @public
    NSArray
    *_names;
    }
    @property(nonatomic,assgin,
    readonly) NSArray *names; //注意这里是assign

    -(void) setNames:(NSArray*)names;

    //BaseClass.m
    @implementation BaseClass

    @synthesize names
    = _names;

    -(NSArray*) names{
    NSLog(@"names");
    return _names;
    }

    -(void) setNames:(NSArray*)names{
    NSLog(@"setNames");
    if (_name != name) {
    [_name release];
    _name
    = [name retain]; //你retain,但是你不覆盖这个方法,那么编译器会生成setNames方法,里面肯定是用的assign
    }
    }
     
    复制代码

      当别人使用@property来做内存管理的时候就会有问题了。总结一下,如果你自己实现了getter和setter的话,atomic/nonatomic/retain/assign/copy这些只是给编译的建议,编译会首先会到你的代码里面去找,如果你定义了相应的getter和setter的话,那么好,用你的。如果没有,编译器就会根据atomic/nonatomic/retain/assign/copy这其中你指定的某几个规则去生成相应的getter和setter。

      好了,说了这么多,回到我们的正题吧。atomic和nonatomic的作用与区别:

      如果你用@synthesize去让编译器生成代码,那么atomic和nonatomic生成的代码是不一样的。如果使用atomic,如其名,它会保证每次getter和setter的操作都会正确的执行完毕,而不用担心其它线程在你get的时候set,可以说保证了某种程度上的线程安全。但是,我上网查了资料,仅仅靠atomic来保证线程安全是很天真的。要写出线程安全的代码,还需要有同步和互斥机制。

      而nonatomic就没有类似的“线程安全”(我这里加引号是指某种程度的线程安全)保证了。因此,很明显,nonatomic比atomic速度要快。这也是为什么,我们基本上所有用property的地方,都用的是nonatomic了。

      还有一点,可能有读者经常看到,在我的教程的dealloc函数里面有这样的代码:self.xxx = nil;看到这里,现在你们明白这样写有什么用了吧?它等价于[xxx release];  xxx = [nil retain];(---如果你的property(nonatomic,retian)xxx,那么就会这样,如果不是,就对号入座吧)。

      因为nil可以给它发送任何消息,而不会出错。为什么release掉了还要赋值为nil呢?大家用c的时候,都有这样的编码习惯吧。

      int* arr = new int[10];    然后不用的时候,delete arr; arr = NULL;  在objc里面可以用一句话self.arr = nil;搞定。

    注意:

      这个说法好像不太好,应该用 “当前runLoop" 这个说法会合适一点,因为用mainLoop说的话,似乎会有所有autorelease的对象只有在程序退出的时候才release这样的误解,那这样的话,和内存泄露无异了吧。(我已经修改了说法)
    对于每一个Runloop, 系统会隐式创建一个Autorelease pool, 这样所有的release pool会构成一个象CallStack一样的一个栈式结构,在每一个Runloop结束时,当前栈顶的Autorelease pool会被销毁,这样这个pool里的每个Object会被release。
    那什么是一个Runloop呢? 一个UI事件,Timer call, delegate call, 都会是一个新的Runloop。
    引自:http://www.cnblogs.com/MobileDevelop/archive/2010/07/19/1779138.html
     

      

      讲了这么多,,如果大家还有什么问题,欢迎在下方留言,我有时间一定会忙回复你的。一直看我文章的朋友们,如果知道答案也帮忙回答一下哈,先谢谢你们啦!

      接下来,我会翻译Ray写的关于objc内存管理和使用property作内存管理的文章,敬请期待!

  • 相关阅读:
    Elasticsearch Query DSL 整理总结(三)—— Match Phrase Query 和 Match Phrase Prefix Query
    Elasticsearch Query DSL 整理总结(二)—— 要搞懂 Match Query,看这篇就够了
    Elasticsearch Query DSL 整理总结(一)—— Query DSL 概要,MatchAllQuery,全文查询简述
    Elasticsearch Java Rest Client API 整理总结 (三)——Building Queries
    Elasticsearch date 类型详解
    python 历险记(五)— python 中的模块
    python 历险记(四)— python 中常用的 json 操作
    python 历险记(三)— python 的常用文件操作
    Elasticsearch Java Rest Client API 整理总结 (二) —— SearchAPI
    Elasticsearch Java Rest Client API 整理总结 (一)——Document API
  • 原文地址:https://www.cnblogs.com/linyawen/p/2492150.html
Copyright © 2011-2022 走看看