1

我正在使用 ARC。

这是我的 .h 文件

...
- (id)initWithCoordinate:(CLLocationCoordinate2D)c title:(NSString *)t;

@property (nonatomic, readonly) CLLocationCoordinate2D coordinate;
@property (nonatomic, copy) NSString *title;
...

这是我的 .m 文件

....
@synthesize coordinate, title;

- (id)initWithCoordinate:(CLLocationCoordinate2D)c title:(NSString *)t
{
    self = [super init];
    if (self) {
        coordinate = c;
        [self setTitle:t];
     }
    return self;
}
....
  1. 以这种方式设置坐标,正确的方法吗?鉴于我将其声明为readonly,这似乎是唯一的方法。如果我只使用默认值(即readwrite)怎么办,在这种情况下,我应该使用 setter 方法 [self setCoordinate] 来代替吗?

  2. 我也可以通过这样做来设置标题title = t。与使用setter方法相比,结果是一样的,但有什么区别呢?


谢谢!希望我能接受你所有的答案。

4

5 回答 5

3

您实际上应该一直直接在初始化方法中设置ivars。无论您是否拥有readonlyreadwrite财产,这都是正确的。这里的文档甚至是这样说的。

这背后的原因与继承有关。如果有人要对您的类进行子类化并覆盖您的属性的设置器,以便他们绕过您创建的 ivars(或做一些其他古怪的事情),那么突然之间,您的初始化方法的原始实现现在不再执行其编写的操作. 特别是,由于子类覆盖了您的访问器,您的初始化程序最终可能会创建一个具有奇怪状态的对象。在 ARC 之前的日子里,当这种事情发生时,你也可能会遇到棘手的(或者只是直接坏掉的)记忆情况。要点是:您应该编写初始化程序,以便它们始终创建具有已知有效状态的对象。

因此(假设您使用的是 ARC)您的初始化程序实际上应该是:

- (id)initWithCoordinate:(CLLocationCoordinate2D)c title:(NSString *)t
{
    self = [super init];
    if (self) {
        coordinate = c;
        title = [t copy];
     }
    return self;
}

就个人而言,我更喜欢用下划线开头的 ivars 来说明我何时使用该属性以及何时直接访问该 ivar(LLVM 4.0 现在也这样做以自动合成属性)。

@synthesize coordinate = _coordinate;
@synthesize title = _title;

- (id)initWithCoordinate:(CLLocationCoordinate2D)c title:(NSString *)t
{
    self = [super init];
    if (self) {
        _coordinate = c;
        _title = [t copy];
     }
    return self;
}
于 2012-10-28T03:34:58.133 回答
2

1:就像您现在的代码一样,是的,这是正确的方法。如果您没有使用 ARC(假设您目前正在使用),您还希望保留该值以声明所有权。这将在 ARC 下自动完成。请记住,这不是唯一的方法;readwrite您可以在实现文件中的类扩展中重新声明该属性。这是一种常见的做法,它允许您在拥有该readwrite属性的同时仍为readonly该类的用户提供该属性的好处。前任。

//MyClass.h

@interface MyClass : NSObject
@property (nonatomic, strong, readonly) NSNumber* number;
- (void) initWithNumber:(NSNumber*)number;
@end

//MyClass.m

@interface MyClass ()
@property (nonatomic, strong, readwrite) NSNumber* number;
@end

@implementation MyClass
//this changes the instance variable backing the property to _number.
@synthesize number = _number;

- (void) initWithNumber:(NSNumber*)number{
    self = [super init];
    if (self) {
        self.number = number;
    }
    return self;
}
@end

归根结底,我想说尽可能使用 setter 来保持 KVO 兼容是一个好习惯,这样你就可以随时知道值何时发生变化。例如,如果您有一个自定义 UIView,其属性反映在其外观中,您可能希望在它更改时重新显示自己。最简单的方法是自己实现 setter 并setNeedsDisplay在设置值后调用。如果您直接设置支持属性的实例值,则无法做到这一点;该类的用户必须记住setneedsDisplay每次手动设置时都要调用。

2:一个是通过setter方法,让你知道什么时候要设置一个值,而一个是为支持属性的实例变量设置一个值。setter 方法将始终以它被告知的方式处理内存管理,而如果您直接分配给实例变量,则由您来执行诸如为setter 设置copy值之类的事情copy,以便您保持一些一致的方案。如果您不小心,有时会通过 setter 而不是其他人会导致一些令人讨厌的错误。从不通过 setter 很难知道值何时发生变化,从而几乎不可能清除无效值。例如,如果你有一个int如果您想将属性限制为某个范围内的值,并且有人传入了低于最小限制的值,则您可能希望将该属性设置为该范围内可能的最低值。如果没有首先通过设置器的值,您将无法做到这一点。

于 2012-10-28T03:06:32.010 回答
1

是的,可以这样设置。如果您更喜欢始终使用属性,则可以在类扩展中将属性覆盖为读/写而不是只读。在 Foo.m 中:

@interface Foo ()
@property (nonatomic) CLLocationCoordinate2D coordinate;
@end

@implementation Foo {
    // ...
    self.coordinate = c;
}
于 2012-10-27T21:08:22.790 回答
1
  1. 以这种方式设置坐标是正确的,如果您声明了 property ,这是唯一的方法readonly

  2. 使用 设置标题与使用title = t设置标题不同[self setTitle:t]。如果您直接分配给实例变量,您将只保留NSString作为参数传递的实例t。但是如果您使用访问器方法,访问器将要求字符串复制自身(因为您声明了属性copy)。如果您作为参数给出的字符串t实际上是一个NSMutableString,那么您将获得它的不可变副本。如果您作为参数给出的字符串t已经是一个不可变的字符串,它只会在被要求复制时返回自己。

于 2012-10-27T22:06:58.823 回答
0
self.coordinate = c;

本质上编译为与调用相同

[self setCoordinate:c];

coordinate = c和之间的区别在于[self setCoordinate:c];,第一个只是直接设置一个变量,而第二个是调用方法。

需要警惕的原因是方法可能会产生副作用,具体取决于实现的编写方式,例如(愚蠢的例子)

- (void)setCoordinate:(CLLocationCoordinate2D)coordinate;
{
    _coordinate = coordinate;
    [self doSomethingCrazy];
}
于 2012-10-27T22:00:22.103 回答