Objective-C:需要在init方法中设置实例变量的建议

时间:2022-09-02 08:46:59

I am using ARC.

我正在使用ARC。

This is my .h file

这是我的.h文件

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

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

This is my .m file

这是我的.m文件

....
@synthesize coordinate, title;

- (id)initWithCoordinate:(CLLocationCoordinate2D)c title:(NSString *)t
{
    self = [super init];
    if (self) {
        coordinate = c;
        [self setTitle:t];
     }
    return self;
}
....
  1. Is setting coordinate this way, the right way to do it? Given that I declare it as readonly, it seems like it is the only way to do it. What if I just use the default (i.e. readwrite), in this case, should I use the setter method [self setCoordinate] instead?

    是这样设置坐标,正确的方法吗?鉴于我将其声明为只读,似乎这是唯一的方法。如果我只使用默认值(即readwrite),在这种情况下,我应该使用setter方法[self setCoordinate]吗?

  2. I could set the title by doing title = t as well. Compare to using the setter method, the result is the same, but what is the difference ?

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


Thanks! Wish I could accept all of your answers.

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

5 个解决方案

#1


2  

1: As your code is now, yes, that is the right way to do it. If you weren't using ARC (assuming you are currently), you'd also want to retain the value to assert ownership. This will be done automatically under ARC. Keep in mind that that is not the only way of doing it; you could redeclare the property as readwrite in the class extension in the implementation file. This is a common practice which allows you to have the benefits of a readwrite property while having the property still be readonly to users of the class. Ex.

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

//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

At the end of the day, I'd say it's a good habit to use setters whenever you can to keep things KVO compliant and so that you always know when values change. For instance, if you have a custom UIView with a property that is reflected in its appearance, chances are you'd want to redisplay yourself when it changes. The easiest way to do this is to implement the setter yourself and call setNeedsDisplay after setting the value. You couldn't do that if you set the instance value backing the property directly; the user of the class would have to remember to call setneedsDisplay every time they set it, manually.

在一天结束的时候,我会说,只要你能够保持符合KVO标准,并且你总能知道价值何时发生变化,那么使用setter是一个好习惯。例如,如果您的自定义UIView具有反映在其外观中的属性,则您可能希望在更改时重新显示自己。最简单的方法是自己实现setter并在设置值后调用setNeedsDisplay。如果设置直接支持属性的实例值,则无法执行此操作;每次手动设置时,类的用户都必须记住调用setneedsDisplay。

2: One goes through the setter method, giving you a way to know when a value is going to be set, while one sets a value to the instance variable backing the property. The setter method will always handle memory management in the way it was told to, while it's up to you to do things such as copying values for a copy setter if you assign directly to an instance variable, so that you maintain some consistent scheme. Going through setters sometimes, and not others can lead to some nasty bugs if you don't be careful. Never going through setters makes it hard to know when values change, making it near impossible to weed out invalid values. For instance, if you had an int property you wanted to limit to values in some range and someone passed in a value under the minimum limit, you'd probably want to set the property to the lowest possible value in the range. You can't do that without the value going through the setter first.

2:一个人通过setter方法,给你一个知道何时设置一个值的方法,同时为一个支持该属性的实例变量设置一个值。 setter方法将始终以告知的方式处理内存管理,而如果您直接分配给实例变量,则由您自己执行诸如复制set setter的值之类的操作,以便您保持一些一致的方案。如果你不小心的话,有时会经历二传手,而不是其他人会导致一些讨厌的错误。从不通过setter会很难知道值何时发生变化,因此几乎无法清除无效值。例如,如果您有一个int属性,您希望限制在某个范围内的值,并且有人传入一个低于最小限制的值,您可能希望将该属性设置为该范围内的最低可能值。如果没有首先通过setter的值,你就不能这样做。

#2


3  

You're actually supposed to set ivars directly in an initializer method all the time. This is true whether or not you have a readonly or readwrite property. The documentation here even says so.

实际上你应该一直在初始化方法中直接设置ivars。无论您是否具有readonly或readwrite属性,都是如此。这里的文档甚至都这么说。

The reasoning behind this has to do with inheritance. If someone were to subclass your class and overwrite the setters for your properties such that they bypass the ivars you created (or do some other wacky thing), then suddenly your original implementation of your initializer method now no longer does what it is written to do. In particular, your initializer could end up creating an object with a weird state due to the subclass overriding your accessors. In the pre-ARC days, you could also end up with tricky (or just straight-up broken) memory situations when this sort of thing happens. The take-away message is: you should write initializers so that they will always create an object with a known valid state.

这背后的原因与继承有关。如果有人要为你的类创建子类并覆盖你的属性的setter,以便它们绕过你创建的ivars(或做一些其他古怪的东西),那么你的初始化方法的原始实现现在突然不再按照它所写的那样做。特别是,由于子类覆盖了访问器,初始化程序最终可能会创建一个具有奇怪状态的对象。在ARC之前的日子里,当这种事情发生时,你也可能会遇到棘手的(或者只是直接破碎的)内存情况。收集消息是:您应该编写初始化程序,以便它们始终创建具有已知有效状态的对象。

So (assuming you're using ARC) your initializer should actually be:

所以(假设您正在使用ARC)您的初始化程序应该是:

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

Personally, I prefer to synthesize ivars with a starting underscore to clarify when I'm using the property and when I'm accessing the ivar directly (LLVM 4.0 now does this to automatically synthesized properties as well).

就个人而言,我更喜欢使用起始下划线合成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;
}

#3


1  

Yes, it is fine to set it like that. If you prefer to use a property all the time you can override the property to be read/write rather than read-only in a class extension. In Foo.m:

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

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

@implementation Foo {
    // ...
    self.coordinate = c;
}

#4


1  

  1. Setting the coordinate that way is correct, and is the only way to do it if you have declared the property readonly.

    设置该方式的坐标是正确的,并且如果您已声明该属性为readonly,则是唯一的方法。

  2. Setting the title using title = t is different than setting the title using [self setTitle:t]. If you directly assign to the instance variable, you will just retain the NSString instance that was passed as argument t. But if you using the accessor method, the accessor will ask the string to copy itself (because you declared the property copy). If the string you were given as argument t is actually an NSMutableString, then you will get an immutable copy of it. If the string you were given as argument t is already an immutable string, it will just return itself when asked for a copy.

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

#5


0  

self.coordinate = c;

is essentially compiled to be the same as calling

基本上编译为与调用相同

[self setCoordinate:c];

The difference between coordinate = c and [self setCoordinate:c]; is that the first is just setting a variable directly where as the second is calling a method.

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

The reason to be wary is that methods could potentially have side effects depending on how the implementation is written e.g. (stupid example)

警惕的原因是方法可能具有副作用,这取决于如何编写实现,例如(愚蠢的例子)

- (void)setCoordinate:(CLLocationCoordinate2D)coordinate;
{
    _coordinate = coordinate;
    [self doSomethingCrazy];
}

#1


2  

1: As your code is now, yes, that is the right way to do it. If you weren't using ARC (assuming you are currently), you'd also want to retain the value to assert ownership. This will be done automatically under ARC. Keep in mind that that is not the only way of doing it; you could redeclare the property as readwrite in the class extension in the implementation file. This is a common practice which allows you to have the benefits of a readwrite property while having the property still be readonly to users of the class. Ex.

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

//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

At the end of the day, I'd say it's a good habit to use setters whenever you can to keep things KVO compliant and so that you always know when values change. For instance, if you have a custom UIView with a property that is reflected in its appearance, chances are you'd want to redisplay yourself when it changes. The easiest way to do this is to implement the setter yourself and call setNeedsDisplay after setting the value. You couldn't do that if you set the instance value backing the property directly; the user of the class would have to remember to call setneedsDisplay every time they set it, manually.

在一天结束的时候,我会说,只要你能够保持符合KVO标准,并且你总能知道价值何时发生变化,那么使用setter是一个好习惯。例如,如果您的自定义UIView具有反映在其外观中的属性,则您可能希望在更改时重新显示自己。最简单的方法是自己实现setter并在设置值后调用setNeedsDisplay。如果设置直接支持属性的实例值,则无法执行此操作;每次手动设置时,类的用户都必须记住调用setneedsDisplay。

2: One goes through the setter method, giving you a way to know when a value is going to be set, while one sets a value to the instance variable backing the property. The setter method will always handle memory management in the way it was told to, while it's up to you to do things such as copying values for a copy setter if you assign directly to an instance variable, so that you maintain some consistent scheme. Going through setters sometimes, and not others can lead to some nasty bugs if you don't be careful. Never going through setters makes it hard to know when values change, making it near impossible to weed out invalid values. For instance, if you had an int property you wanted to limit to values in some range and someone passed in a value under the minimum limit, you'd probably want to set the property to the lowest possible value in the range. You can't do that without the value going through the setter first.

2:一个人通过setter方法,给你一个知道何时设置一个值的方法,同时为一个支持该属性的实例变量设置一个值。 setter方法将始终以告知的方式处理内存管理,而如果您直接分配给实例变量,则由您自己执行诸如复制set setter的值之类的操作,以便您保持一些一致的方案。如果你不小心的话,有时会经历二传手,而不是其他人会导致一些讨厌的错误。从不通过setter会很难知道值何时发生变化,因此几乎无法清除无效值。例如,如果您有一个int属性,您希望限制在某个范围内的值,并且有人传入一个低于最小限制的值,您可能希望将该属性设置为该范围内的最低可能值。如果没有首先通过setter的值,你就不能这样做。

#2


3  

You're actually supposed to set ivars directly in an initializer method all the time. This is true whether or not you have a readonly or readwrite property. The documentation here even says so.

实际上你应该一直在初始化方法中直接设置ivars。无论您是否具有readonly或readwrite属性,都是如此。这里的文档甚至都这么说。

The reasoning behind this has to do with inheritance. If someone were to subclass your class and overwrite the setters for your properties such that they bypass the ivars you created (or do some other wacky thing), then suddenly your original implementation of your initializer method now no longer does what it is written to do. In particular, your initializer could end up creating an object with a weird state due to the subclass overriding your accessors. In the pre-ARC days, you could also end up with tricky (or just straight-up broken) memory situations when this sort of thing happens. The take-away message is: you should write initializers so that they will always create an object with a known valid state.

这背后的原因与继承有关。如果有人要为你的类创建子类并覆盖你的属性的setter,以便它们绕过你创建的ivars(或做一些其他古怪的东西),那么你的初始化方法的原始实现现在突然不再按照它所写的那样做。特别是,由于子类覆盖了访问器,初始化程序最终可能会创建一个具有奇怪状态的对象。在ARC之前的日子里,当这种事情发生时,你也可能会遇到棘手的(或者只是直接破碎的)内存情况。收集消息是:您应该编写初始化程序,以便它们始终创建具有已知有效状态的对象。

So (assuming you're using ARC) your initializer should actually be:

所以(假设您正在使用ARC)您的初始化程序应该是:

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

Personally, I prefer to synthesize ivars with a starting underscore to clarify when I'm using the property and when I'm accessing the ivar directly (LLVM 4.0 now does this to automatically synthesized properties as well).

就个人而言,我更喜欢使用起始下划线合成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;
}

#3


1  

Yes, it is fine to set it like that. If you prefer to use a property all the time you can override the property to be read/write rather than read-only in a class extension. In Foo.m:

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

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

@implementation Foo {
    // ...
    self.coordinate = c;
}

#4


1  

  1. Setting the coordinate that way is correct, and is the only way to do it if you have declared the property readonly.

    设置该方式的坐标是正确的,并且如果您已声明该属性为readonly,则是唯一的方法。

  2. Setting the title using title = t is different than setting the title using [self setTitle:t]. If you directly assign to the instance variable, you will just retain the NSString instance that was passed as argument t. But if you using the accessor method, the accessor will ask the string to copy itself (because you declared the property copy). If the string you were given as argument t is actually an NSMutableString, then you will get an immutable copy of it. If the string you were given as argument t is already an immutable string, it will just return itself when asked for a copy.

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

#5


0  

self.coordinate = c;

is essentially compiled to be the same as calling

基本上编译为与调用相同

[self setCoordinate:c];

The difference between coordinate = c and [self setCoordinate:c]; is that the first is just setting a variable directly where as the second is calling a method.

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

The reason to be wary is that methods could potentially have side effects depending on how the implementation is written e.g. (stupid example)

警惕的原因是方法可能具有副作用,这取决于如何编写实现,例如(愚蠢的例子)

- (void)setCoordinate:(CLLocationCoordinate2D)coordinate;
{
    _coordinate = coordinate;
    [self doSomethingCrazy];
}