Неизменяемый объект в Objective-C: большой метод инициализации? - PullRequest
3 голосов
/ 25 июля 2010

Я хочу иметь Объект с неизменяемыми полями в Objective-C.

В C # я бы использовал Properties с частными установщиками и большим конструктором.

Что бы я использовал в Objective-C?

Использование @property, по-видимому, не позволяет мне объявить сеттер частным.

Использование

initWithData: (NSString*) something createDate: (NSDate*) date userID: (long) uid

кажется слишком многословным, если у меня более 4 свойств дляset.

Должен ли я объявлять получатели в файле .h, а установщики только в .m?

Мне нужно использовать retain или copy для чего-либо и даты (кстати: какой изэти два я должен использовать?), поэтому мне нужен код в сеттере.

Или есть что-то еще, например, неизменное ключевое слово?

Ответы [ 2 ]

12 голосов
/ 25 июля 2010

Вы можете иметь общедоступное свойство только для чтения и использовать частное свойство для чтения и записи, чтобы предоставить установщик для свойства в вашем классе, если оно вам действительно нужно.Однако вам следует подумать, нужно ли это вообще.

В качестве примера рассмотрим следующее объявление и определение неизменяемого класса Person:

// Person.h
#import <Foundation/Foundation.h>

@interface Person : NSObject {
@private
    NSString *name_;
    NSDate *dateOfBirth_;
}

@property (readonly, copy) NSString *name;
@property (readonly, copy) NSDate *dateOfBirth;

/*! Initializes a Person with copies of the given name and date of birth. */
- (id)initWithName:(NSString *)name dateOfBirth:(NSDate *)dateOfBirth;

@end

// Person.m
#import "Person.h"

@implementation Person

@synthesize name = name_;
@synthesize dateOfBirth = dateOfBirth_;

- (id)initWithName:(NSString *)name dateOfBirth:(NSDate *)dateOfBirth {
    self = [super init];
    if (self) {
        name_ = [name copy];
        dateOfBirth_ = [dateOfBirth copy];
    }

    return self;
}

- (void)dealloc {
    [name_ release];
    [dateOfBirth_ release];

    [super dealloc];
}

@end

Во-первых, обратите внимание, что я сделалне объявляет расширение класса в Person.m, которое повторно объявляет свойства name и dateOfBirth как readwrite.Это потому, что цель класса - быть неизменным;нет необходимости в установщиках, если переменные экземпляра будут устанавливаться только во время инициализации.

Также обратите внимание, что я объявил переменные экземпляра с именами, отличными от свойств.Это проясняет различие между свойствами как программным интерфейсом класса и переменными экземпляра как подробностью реализации класса.Я видел слишком много разработчиков (особенно новичков в Mac OS X и iOS, включая многих из C #), которые связывают свойства с переменными экземпляра, которые могут использоваться для их реализации.

Третье, на что стоит обратить вниманиеявляется то, что я объявил оба эти свойства как copy, хотя они только для чтения.Есть две причины.Во-первых, хотя прямые экземпляры этого класса неизменны, ничто не мешает созданию подкласса MutablePerson.На самом деле, это может быть даже желательно!Таким образом, copy четко определяет ожидания суперкласса - что значения самих свойств name и dateOfBirth не изменятся.Он также намекает, что -initWithName:dateOfBirth:, вероятно, также копирует;его документальный комментарий должен прояснить это.Во-вторых, и NSString, и NSDate являются классами значений;копии неизменяемых должны быть недорогими, и вы не хотите зависать от экземпляра изменяемого подкласса, который изменится из-за вашего собственного класса.(На самом деле нет никакого изменяемого подкласса NSDate, но это не значит, что кто-то не может создать свой собственный ...)

Наконец, не беспокойтесь о том, является ли указанный вами инициализатор многословным.Если экземпляр вашего объекта недействителен, если он не находится в каком-то определенном состоянии, то назначенный вами инициализатор должен перевести его в это состояние - и для этого ему нужно принять соответствующие параметры.

Есть еще одинВещи: если вы создаете класс неизменяемых значений, как этот, вам, вероятно, следует также реализовать собственные методы -isEqual: и -hash для быстрого сравнения и, вероятно, также соответствовать NSCopying.Например:

@interface Person (ImmutableValueClass) <NSCopying>
@end

@implementation Person (ImmutableValueClass)

- (NSUInteger)hash {
    return [name_ hash];
}

- (BOOL)isEqual:(id)other {
    Person *otherPerson = other;
    // Using [super isEqual:] to allow easier reparenting
    // -[NSObject isEqual:] is documented as just doing pointer comparison
    return ([super isEqual:otherPerson]
            || ([object isKindOfClass:[self class]]
                && [self.name isEqual:otherPerson.name]
                && [self.dateOfBirth isEqual:otherPerson.dateOfBirth]));
}

- (id)copyWithZone:(NSZone *)zone {
    return [self retain];
}

@end

Я объявил это в своей собственной категории, чтобы не повторять весь код, который я ранее показывал в качестве примера, но в реальном коде я, вероятно, поместил бы все это в основной файл @interface и @implementation.Обратите внимание, что я не объявлял -hash и -isEqual:, я только определил их, потому что они уже объявлены NSObject.И поскольку это класс неизменных значений, я могу реализовать -copyWithZone:, просто сохранив self, мне не нужно делать физическую копию объекта, поскольку он должен вести себя точно так же.

Однако если вы используете Core Data, не делайте этого;Базовые данные реализуют уникальные объекты для вас, поэтому вы не должны иметь собственную реализацию -hash или -isEqual:.И для хорошей меры вы не должны действительно соответствовать NSCopying в подклассах Core Data NSManagedObject;что означает «копировать» объекты, являющиеся частью графа объектов Core Data, требует тщательного обдумывания и, как правило, в большей степени относится к поведению на уровне контроллера.

1 голос
/ 25 июля 2010

Вы можете сделать что-то неизменное с ключевым словом @property;просто используйте readonly вместо copy или retain (копирование и сохранение описывают желаемое поведение при назначении, и поэтому они не будут иметь смысла для свойства, которое доступно только для чтения).Обратите внимание, что это приведет к генерации функции получения, когда вы используете @synthesize, но не к функции установки.Если ваши данные действительно неизменны, то было бы бессмысленно иметь функцию установки, хотя вы могли бы легко создать ее так же, как и любой другой метод в Objective-C.Что касается сохранения и копирования - это зависит от того, хотите ли вы поделиться объектом с вызывающей стороной или иметь полностью независимую копию объекта.Использование retain более эффективно, но оно также немного неуловимо и страшно, поскольку ваш объект может быть изменен откуда-то еще через этот общий объект.Это действительно зависит от гарантий, которые дает ваш класс, и от того, допустима ли такая внешняя модификация.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...