zoukankan      html  css  js  c++  java
  • ObjectiveC 的属性与合成方法使用详解

    Objective-C 中的属性可以结合 Java 和 C# 的属性来理解,Java 的属性需要自己实现 getter/setter 方法,在 C# 中现在可方便些了,写上{set;get;}自动生成相应的存取器。

    Objective-C 中声明属性及使用时会涉及到 @property, @synthesize 和点号(.) 访问,@property 用来指定属性及某些特性,@synthesize 能为你用 @property 指定的属性自动生成 getter/setter 方法。下面最常规的例子:

    main.m 代码:本例在 Xcode 4.0.2 中编译运行的, 可能涉及到一些是 Objective-C 的新特性,Apple 总是推动大家用新版本的东西。

    01
    02
    03
    04
    05
    06
    07
    08
    09
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    #import <Foundation/Foundation.h
    @interface Unmi : NSObject {
     
         NSString* gender;  //1   -- 这行可以不用的
     }
     
    @property(nonatomic, assign) NSString* gender; //2
     @end
     
    @implementation Unmi
     
    @synthesize gender; //3
     
    @end
     
    int main (int argc, const char * argv[])
     {   
         NSAutoreleasePool * pool = [[NSAutoreleasePool alloc] init];
         
         Unmi* unmi = [[Unmi alloc] init];
         unmi.gender = @"Male"; //4
         NSLog(@"Unmi is %@!", unmi.gender); //5
         
         [pool drain];
         return 0;
     }

    上面代码的输出是:

    2011-07-01 01:19:09.344 TestObjC[5502:903] Unmi is Male!

    下面一些说明,分别对应到上面的几个代码行上的标号:

    //1. 一般属性会对应一个类的实例变量,用来保存状态的, 而实际上把该行代码注释掉也可以输出相同的结果,也就是这行代码可有可无,有它后面的 @property 就够了。

    //2. 指明属性变量,类型及一些其他特性,这里的特性就比较多了,你可以简单写为 @property NSString* gender; 会采用默认值,但编译时会出现警告,不建议这么做。
       这里如果没有显式的声明一个 NSString * gender 成员变量,它会帮你补上与这里的属性同名成员变量。
       即使是 IBOutlet 的属性也可以不用显式的声明这个成员变量,同样写在 @property 中就行的:
              如:@property(nonatomic, retain) IBOutlet UIButton *button_screen;
       关键是括号中的内容:当然,最好的教程莫过于 官方的权威 Declared Properties.
       @property 括号中的属性用逗号分隔来写, 对于对象 (atomic, assign) 是它的默认值, 基本类型默认为 (atomic, readwrite), 有三组值可以设置,互斥的就不要写在一起:
        1) atomic 和 nonatomic, 原子还是非原子性操作,前者为默认,表示属 性是原子的,支持多线程并发访问(实际就是 setter 的实现中加入了同步锁),后者是非原 子的,也就是适合在非多线程的环境提升效率(因为 setter 中没有同步锁的代码)。没有特别的多线程要求用 nonatomic 有助于提高性能。
        2) readonly, readwrite 表示属性的可读写特性;
           如果是对象类型,还有 retain, assign, copy, 这决定了 setter 方法内部实现时对传入的对象的持有方式。retain 会增加引用计数,强引用类型, assign 是给变量直接赋值,弱引用类型,也是默认值, copy 是把 setter 的参数复制一份再赋给成员变量。注意它们对引用计数产生的影响,如果外部不再使用的话,用了 retain 或 copy 赋值的可以
        release 掉那个对象。
        3) getter=getterName 和 setter=setterName, 显式设置 getter/setter 方法名, 未指定它们时 Objective-C 会为我们生成默认的 setter/getter 方法, 有一定的规则,
            比如上面的 NSString* gender 属性生成默认的
              setter 方法是: -(void) setGender:(NSString *);
              getter 方法是: -(NSString *) gender;
            想看看 Objective-C 为我们生成什么 getter/setter 方法, 不用点号来隐式调用 setter/getter 方法,而是显式的用 [unmi setGender] 或 [unmi gender], 输入式这两个方法会自动提示出来的。注意这里的 getter 方法名并非是像 Java 的 getGender, 而是和属性名同.
            假如你想要自己个性的 getter/setter 方法,比如写成 @property(getter=getGender, setter=setSex:) NSString* gender; 那么相应的就会生成:
              setter 方法是: -(void) setSex:(NSString *);
              getter 方法是: -(NSString *) getGender;
            在 Xcode 中 esc unmi 就能看到相应的 setter/getter 方法名的. 可以只用其中一个了,那另一个保持默认。这两个较少用,用途就是可用来生成自己个性的但要符合某个范围内规范的 setter/getter 方法。像 @property(getter = isOnline) BOOL online; 则会生成 -(BOOL) isOnline; 这样的 getter 方法,而不是 -(BOOL) online; 当然我们也很少且不推荐直接调用 getter/setter 方法,而是用点号的方式,但是有意思的去覆盖 getter/setter 方法时就较象明确了。
           
    //3. @synthesize 后跟上前面用 @property 声明的属性名列表,这样 Objective-C 就能自动按照 @property 规则生成相应的 setter/getter 方法。你也可以不对前面某个属性使用 @synthesize,那么它相应的 setter/getter 方法就得自己按照规则亲自实现了。
       所以,到这里我们可以理解到,@property 相当于声明 setter/getter 的方法原型,@synthesize 就是那些 setter/getter 相应实现。只是它们俩都自动完成了,连存储状态的变量也自动添加了。
       前面讲过,如果类中没有声明与 @property 相应成员变量,会自动加上一个与属性同名的成员变量,如果你不想要与属性同名的成员变量,这里可以自定义,方法是:
    @property gender=_gender;
    那就相当于在类中声明了一个 (NSString *) _gender 成员变量来存储 gender 属性的值, 而不再存在 (NSString *) gender 这个成员变量了。这样在类 Unmi 实例方法中可以直接访问 _gender 变量的. 另外,据我刚刚试验过的,用 @property gender=_gender; 自动生成的成员变量 (NSString *) _gender 同样可以在断点时光标停在某个 Unmi 实例上能显示出来的。
        接着,这里又会牵涉到 @dynamic 的用法,当 @property(getter=getGender) 只为 gender 指定了 getter 方法名时,而后不用 @synthesize 自动合成,而是自己实现的 -(NSString *) getGender; 方法,编译器会警告 setGender 未实现,这时就用 @dynamic gender, 此处不细究 @dynamic 的用法了。

    //4. 用点号(.) 来使用属性,这和 C# 中的属性较类似了,凡是对属性进行赋值,会调用相应的 setter 方法,这里调用 -(void) setGender:(NSString *);

    //5. 点号获取属性值时,实际调用了相应的 getter 方法,这里调用了 -(NSString *) gender;

    这里的例子是通过实例变量来使用属性,读写时分别会走 getter/setter 方法,然而在类的内部可以直接访问该成员变量,也可以用点号属性的方式, 在类内部怎么访问都无所谓的,来看下面的例子,变动了一下:

    main.m:

    01
    02
    03
    04
    05
    06
    07
    08
    09
    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
    52
    53
    #import <Foundation/Foundation.h>
     
    @interface Address : NSObject
     @end
     @implementation Address
     @end
     
    @interface Unmi : NSObject {
         //Address * address; //声明或不声明这个都一样  --1
     }
     
    @property(nonatomic, retain) Address * address; //有 retain 特性
     -(void) foo;
     @end
     
    @implementation Unmi
     
    @synthesize address;
     //@synthesize address=address; //通过这里来命名一个成员变量也无妨 --2
     
    -(void) foo{
         Address * newAddress = [[Address alloc] init];
         NSLog(@"1. newAddress retain count: %lu", [newAddress retainCount]);
         address = newAddress; //直接访问成员变量
         NSLog(@"2. newAddress retain count: %lu", [newAddress retainCount]);
         self.address = newAddress; //self. 的方式
         NSLog(@"3. newAddress retain count: %lu", [newAddress retainCount]);
         [self setAddress:newAddress]; //直接调用 setter 方法,与上面其实是一致的
         NSLog(@"4. newAddress retain count: %lu", [newAddress retainCount]);
     }
     @end
     
    int main (int argc, const char * argv[])
     {
     
        NSAutoreleasePool * pool = [[NSAutoreleasePool alloc] init];
     
        Unmi* unmi = [[Unmi alloc] init];
     
        [unmi foo];
         
         Address * newAddress = [[Address alloc] init];
         NSLog(@"5. newAddress retain count: %lu", [newAddress retainCount]);
         unmi.address = newAddress;
         NSLog(@"6. newAddress retain count: %lu", [newAddress retainCount]);
         unmi.address = newAddress;
         NSLog(@"7. newAddress retain count: %lu", [newAddress retainCount]);
         unmi.address = [[Address alloc] init];
         NSLog(@"8. newAddress retain count: %lu", [newAddress retainCount]);
     
        [pool drain];
         return 0;
     }

    执行结果是:

        2011-07-01 04:08:17.483 TestObjC[7532:903] 1. newAddress retain count: 1
        2011-07-01 04:08:17.492 TestObjC[7532:903] 2. newAddress retain count: 1
        2011-07-01 04:08:17.493 TestObjC[7532:903] 3. newAddress retain count: 1
        2011-07-01 04:08:17.497 TestObjC[7532:903] 4. newAddress retain count: 1
        2011-07-01 04:08:17.498 TestObjC[7532:903] 5. newAddress retain count: 1
        2011-07-01 04:08:17.502 TestObjC[7532:903] 6. newAddress retain count: 2
        2011-07-01 04:08:17.502 TestObjC[7532:903] 7. newAddress retain count: 2
        2011-07-01 04:08:17.504 TestObjC[7532:903] 8. newAddress retain count: 1

    address 属性用了 retain 来修饰,从上面的输出可看到在实例方法中无论通过什么方式访问 address 属性都不会增加参数的引用计数,所以在类内部想用 对实例变量直接赋值或是通过属性来赋值都无所谓,效果是一样的,只有在外部通过实例来调用方法时才会使引用计数加 1.

    启用这段代码中的 --1 或是 --2 也是一样的效果。

    也提一下 @property 中的 retain/assign/copy 对应 setter 方法的内部实现:

    01
    02
    03
    04
    05
    06
    07
    08
    09
    10
    11
    12
    13
    14
    15
    16
    //retain 时:
     -(void) setAddress: (Address *) address {
         [self.address release]; //self.address = nil;
         self.address = address;
     }
     
    //assign 时:
     -(void) setAddress: (Address *) address {
         self.address = address;
     }
     
    //copy 时:
     -(void) setAddress: (Address *) address {
         [self.address release];
         self.address = [address copyWithZone: zone];// Address 必须实现 NSCoping 协议
     }

    看到前面用 %lu 来输出对象的 retainCount 也知道是一个 Mac OS X 的程序,因为 GC 的因素,也许会怀疑在 iPhone/iPad 下会得到不同的结果,我试过了,改成 %u 来输出 retainCount,做成  iOS 程序的执行效果完全是一样的。

  • 相关阅读:
    网络七层参考模型(OSI)
    TCP/IP体系结构
    VC调用外部程序接口
    处事八条
    HFC网络
    sql server 2005中的分区函数用法(partition by 字段) 
    Java实现的几个常用排序算法详细解读
    Java中读取字符文件类FileReader
    如何在Java中进行图片剪裁
    从零开始构建HTML 5 Web页面
  • 原文地址:https://www.cnblogs.com/pinping/p/2126150.html
Copyright © 2011-2022 走看看