Почему я не могу инициализировать класс через сеттер?

Если у меня есть собственный класс под названием Tires:

#import <Foundation/Foundation.h>

@interface Tires : NSObject {
@private
     NSString *brand;
     int size;
}

@property (nonatomic,copy) NSString *brand;
@property int size;

- (id)init;
- (void)dealloc;

@end
=============================================

#import "Tires.h"

@implementation Tires

@synthesize brand, size;

- (id)init {
     if (self = [super init]) {
          [self setBrand:[[NSString alloc] initWithString:@""]];
          [self setSize:0];
     }
     return self;
}

- (void)dealloc {
     [super dealloc];
     [brand release];
}

@end

И я синтезирую сеттер и получатель в моем контроллере представления:

#import <UIKit/UIKit.h>
#import "Tires.h"

@interface testViewController : UIViewController {
     Tires *frontLeft, *frontRight, *backleft, *backRight;
}

@property (nonatomic,copy) Tires *frontLeft, *frontRight, *backleft, *backRight;

@end

====================================

#import "testViewController.h"

@implementation testViewController

@synthesize frontLeft, frontRight, backleft, backRight;

- (void)viewDidLoad {
     [super viewDidLoad];
     [self setFrontLeft:[[Tires alloc] init]];
}
- (void)dealloc {
    [super dealloc];
}

@end

Он умирает после возврата [self setFrontLeft: [[Tires alloc] init]]. Он компилируется отлично, и когда я запускаю отладчик, он фактически полностью проходит через метод init на шинах, но как только он возвращается, он просто умирает, и представление никогда не появляется. Однако, если я изменю метод viewDidLoad на:

- (void)viewDidLoad {
     [super viewDidLoad];
     frontLeft = [[Tires alloc] init];
}

Работает отлично. Я мог бы просто отказаться от сеттера и напрямую получить доступ к переменной frontLeft, но у меня создалось впечатление, что я должен использовать сеттеры и геттеры как можно чаще, и логически кажется, что setFrontLeft метод должен работать.

Это поднимает дополнительный вопрос, который мои коллеги продолжают задавать по этому поводу (все мы новички в Objective-C); зачем вообще использовать сеттеры и геттеры, если вы принадлежите к тому же классу, что и эти сеттеры и геттеры.


person Rob    schedule 02.05.2010    source источник
comment
Зачем вообще использовать сеттер / геттер в одном классе? Потому что ваш сеттер (и гораздо реже геттер) может делать что-то, кроме простого присвоения значения, например, управлять ресурсами или обновлять другие переменные экземпляра. Доступ к переменной непосредственно внутри класса означает, что вам нужно продублировать эту дополнительную функциональность, иначе вы рискуете потерять внутреннюю согласованность. Это нарушает инкапсуляцию. Я считаю, что существует принцип отказа от использования методов синтетических свойств в конструкторах, потому что они могут быть не полностью настроены, но я недостаточно разбираюсь во внутреннем устройстве, чтобы понять, как они могут выйти из строя.   -  person walkytalky    schedule 03.05.2010


Ответы (2)


Вы объявили frontLeft как свойство copy:

@property (nonatomic,copy) Tires *frontLeft, *frontRight, *backleft, *backRight;

Когда вы назначаете это свойство, копия создается путем вызова метода copy объекта. Это работает только для объектов, которые поддерживают протокол NSCopying (т. Е. Реализуют метод copyWithZone:). Поскольку ваш класс Tires не реализует этот метод, вы получите исключение.

Вероятно, вы захотите изменить это свойство на «сохранить»:

@property (nonatomic,retain) Tires *frontLeft, *frontRight, *backleft, *backRight;

См. документацию Objective C по объявленным свойствам, чтобы узнать больше об объявлениях свойств.

person Damien Neil    schedule 02.05.2010
comment
Это сработало отлично! Я забыл, что сеттеры вызывают метод copy. Отметив это как ответ. Распространена ли практика использовать сеттеры и геттеры для переменных экземпляра, даже если вы принадлежите к тому же классу, что и они? Это обсуждение продолжается между мной и моими соавторами. - person Rob; 03.05.2010
comment
Я думаю, что используете ли вы геттеры / сеттеры - это в основном вопрос предпочтений. В этом случае я бы использовал сеттер, поскольку он автоматически сохранит переменную для вас. Я бы не стал использовать геттер из класса. - person Damien Neil; 03.05.2010
comment
Использование сеттеров дает вам бесплатное управление памятью (при условии, что вы правильно настроили свои свойства), что в большинстве ситуаций является большим преимуществом. Я обычно использую геттеры, если использую сеттеры (внутри класса), чтобы сделать код более согласованным (и, следовательно, более читаемым). - person Nick Forge; 03.05.2010
comment
Часто в книгах и примерах я вижу, что они используют alloc в отдельной строке, а затем освобождают его. Поэтому вместо [self setFrontLeft: [[Tyres alloc] init]] они сначала будут выполнять Tyres * myTires = [[Tyres alloc] init], а затем выполнять [self setFrontLeft: myTires], а затем отпустите myTires. Способ, которым я пытаюсь это сделать, вызывает утечку памяти? - person Rob; 03.05.2010

Одна проблема, которую я вижу, здесь:

- (void)viewDidLoad {
    [super viewDidLoad];
    [self setFrontLeft:[[Tires alloc] init]];
}

Когда вы вызываете [Tyres alloc], вы возвращаете объект со счетчиком удержания, равным 1. Затем вы используете синтезированный вами метод set, который увеличивает счетчик удержания до 2. Когда ваш объект завершает работу с объектом Tire, он будет уменьшите счетчик удержания до 1, но шина никогда не освободится. Я думаю, вам стоит использовать:

[self setFrontLeft:[[[Tires alloc] init] autorelease]];
person D.C.    schedule 02.05.2010
comment
Пробовал [self setFrontLeft: [[[Tires alloc] init] autorelease]];, и он по-прежнему делает то же самое. В отладчике написано TERMINATING_DUE_TO_UNCAUGHT_EXCEPTION, и если я запускаю приложение, оно просто вылетает, как только оно запускается. - person Rob; 03.05.2010