Внедрение NSCopying

84

Я прочитал NSCopyingдокументацию, но все еще не уверен, как реализовать то, что требуется.

Мой класс Vendor:

@interface Vendor : NSObject 
{
    NSString        *vendorID;
    NSMutableArray  *availableCars;
    BOOL            atAirport;
}

@property (nonatomic, copy) NSString *vendorID;
@property (nonatomic, retain) NSMutableArray *availableCars;
@property (nonatomic, assign) BOOL atAirport;

- (id)initFromVehVendorAvailsDictionary:(NSDictionary *)vehVendorAvails;

@end

У Vendorкласса есть массив вызываемых объектов Car.

Мой Carобъект:

@interface Car : NSObject 
{
    BOOL            isAvailable;
    NSString        *transmissionType;
    NSMutableArray  *vehicleCharges; 
    NSMutableArray  *fees; 
}

@property (nonatomic, assign) BOOL isAvailable;
@property (nonatomic, copy) NSString *transmissionType;
@property (nonatomic, retain) NSMutableArray *vehicleCharges;
@property (nonatomic, retain) NSMutableArray *fees;

- (id) initFromVehicleDictionary:(NSDictionary *)vehicleDictionary;

@end

Итак, Vendorсодержит массив Carобъектов. Carсодержит 2 массива других настраиваемых объектов.

Оба Vendorи Carинициализируются из словаря. Я добавлю один из этих методов, они могут иметь значение, а могут и не быть.

-(id)initFromVehVendorAvailsDictionary:(NSDictionary *)vehVendorAvails {

    self.vendorCode      = [[vehVendorAvails objectForKey:@"Vendor"] 
                           objectForKey:@"@Code"];

    self.vendorName      = [[vehVendorAvails objectForKey:@"Vendor"] 
                           objectForKey:@"@CompanyShortName"];

    self.vendorDivision  = [[vehVendorAvails objectForKey:@"Vendor"]   
                           objectForKey:@"@Division"];

    self.locationCode    = [[[vehVendorAvails objectForKey:@"Info"] 
                           objectForKey:@"LocationDetails"] 
                           objectForKey:@"@Code"];

    self.atAirport       = [[[[vehVendorAvails objectForKey:@"Info"] 
                           objectForKey:@"LocationDetails"] 
                           objectForKey:@"@AtAirport"] boolValue];

    self.venLocationName = [[[vehVendorAvails objectForKey:@"Info"] 
                           objectForKey:@"LocationDetails"] 
                           objectForKey:@"@Name"];

    self.venAddress      = [[[[vehVendorAvails objectForKey:@"Info"] 
                           objectForKey:@"LocationDetails"] 
                           objectForKey:@"Address"] 
                           objectForKey:@"AddressLine"];

    self.venCountryCode  = [[[[[vehVendorAvails objectForKey:@"Info"]  
                           objectForKey:@"LocationDetails"] 
                           objectForKey:@"Address"] 
                           objectForKey:@"CountryName"]
                           objectForKey:@"@Code"];

    self.venPhone        = [[[[vehVendorAvails objectForKey:@"Info"]  
                           objectForKey:@"LocationDetails"]        
                           objectForKey:@"Telephone"] 
                           objectForKey:@"@PhoneNumber"];

    availableCars        = [[NSMutableArray alloc] init];

    NSMutableArray *cars = (NSMutableArray *)[vehVendorAvails objectForKey:@"VehAvails"];

    for (int i = 0; i < [cars count]; i++) {

        Car *car = [[Car alloc] initFromVehicleDictionary:[cars objectAtIndex:i]];
        [availableCars addObject:car];
        [car release];
    }

    self.venLogo = [[[vehVendorAvails objectForKey:@"Info"] 
                   objectForKey:@"TPA_Extensions"] 
                   objectForKey:@"VendorPictureURL"];

    return self;
}

Итак, чтобы резюмировать страшную проблему.

Мне нужно скопировать массив Vendorобъектов. Я считаю, что мне нужно реализовать NSCopyingпротокол Vendor, что может означать, что мне нужно реализовать его также, Carпоскольку он Vendorсодержит массив Cars. Это означает, что мне также нужно реализовать его в классах, которые содержатся в двух массивах, принадлежащих Carобъекту.

Я был бы очень признателен, если бы мог получить некоторые рекомендации по реализации NSCopyingпротокола Vendor, я нигде не могу найти никаких руководств по этому поводу.

Миталкумар Гамит
источник
Вы читали документацию NSCopying? Я обнаружил, что это достаточно ясно, когда это необходимо.
jv42 03
4
Да, прочти и перечитай. Я редко нахожу, что документы Apple легко изучить, хотя они отлично подходят для поиска методов и т. Д. Во время программирования. Спасибо

Ответы:

186

Чтобы реализовать NSCopying , ваш объект должен реагировать на -copyWithZone:селектор. Вот как вы заявляете, что соблюдаете его:

@interface MyObject : NSObject <NSCopying> {

Затем в реализации вашего объекта (ваш .mфайл):

- (id)copyWithZone:(NSZone *)zone
{
    // Copying code here.
}

Что должен делать ваш код? Сначала создайте новый экземпляр объекта - вы можете вызвать его, [[[self class] alloc] init]чтобы получить инициализированный объект текущего класса, который хорошо работает для создания подклассов. Затем для любых переменных экземпляра, которые являются подклассом NSObject, поддерживающим копирование, вы можете вызвать [thatObject copyWithZone:zone]новый объект. Для примитивных типов ( int, char, BOOLи друзья) просто установить переменные равными. Итак, для вашего объекта Vendor это будет выглядеть так:

- (id)copyWithZone:(NSZone *)zone
{
    id copy = [[[self class] alloc] init];

    if (copy) {
        // Copy NSObject subclasses
        [copy setVendorID:[[self.vendorID copyWithZone:zone] autorelease]];
        [copy setAvailableCars:[[self.availableCars copyWithZone:zone] autorelease]];

        // Set primitives
        [copy setAtAirport:self.atAirport];
    }

    return copy;
}
Джефф Келли
источник
2
@Code: copyобычно реализуется как неглубокая копия, как показал Джефф. Это необычно - хотя и возможно - что вам нужна полная глубокая копия (где все копируется полностью). Глубокие копии тоже доставляют гораздо больше проблем, поэтому обычно нужно быть уверенным, что это действительно то, что вам нужно.
Чак
3
В вашем коде есть проблема, когда вы копируете свои подклассы, поскольку copyWithZone:возвращает объект со счетчиком ссылок 1 и без автоматического выпуска, это приведет к утечке. Нужно добавить как минимум автоспуск.
Мариус
22
Не следует [[self class] alloc]использовать allocWithZoneвместо этого? Извините за то, что поднял этот вопрос.
jweyrich
1
Ребята, я полагаю, что, используя ARC (поскольку минимальный поддерживаемый IOS для любого приложения - 4.3), вам не нужно беспокоиться о выпуске и автоматическом выпуске.
Ришаб 05
1
@GeneralMike: Вероятно, это должен быть отдельный вопрос, но в целом (посмотрите, что я там сделал?), Вы хотите убедиться, что копируете каждый объект из оригинала во время глубокой копии - и убедитесь, что их -copyметоды также делают глубокие копии .
Джефф Келли
6

Этот ответ аналогичен принятому, но использует allocWithZone:и обновляется для ARC. NSZone - это базовый класс для распределения памяти. Хотя игнорирование NSZoneможет работать в большинстве случаев, это все же неверно.

Для правильной реализации NSCopyingвы должны реализовать метод протокола, который выделяет новую копию объекта со свойствами, соответствующими значениям оригинала.

В объявлении интерфейса в заголовке укажите, что ваш класс реализует NSCopyingпротокол:

@interface Car : NSObject<NSCopying>
{
 ...
}

В реализации .m добавьте -(id)copyWithZoneметод, который выглядит примерно так:

- (id)copyWithZone:(NSZone*)zone
{
    Car* carCopy = [[[self class] allocWithZone:zone] init];

    if (carCopy)
    {
        carCopy.isAvailable = _isAvailable;
        carCopy.transmissionType = _transmissionType;
        ... // assign all other properties.
    }

    return carCopy;
}
Джастин Майнерс
источник
2

Быстрая версия

Просто позвоните, object.copy()чтобы создать копию.

Я не использовал copy()для типов значений, поскольку они копируются «автоматически». Но пришлось использовать copy()для classтипов.

Я проигнорировал NSZoneпараметр, потому что в документах говорится, что он устарел:

Этот параметр игнорируется. Зоны памяти больше не используются Objective-C.

Также обратите внимание, что это упрощенная реализация. Если у вас есть подклассы он получает немного Tricker и вы должны использовать динамический тип: type(of: self).init(transmissionType: transmissionType).

class Vendor {
    let vendorId: String
    var availableCars: [Car] = []

    init(vendorId: String) {
        self.vendorId = vendorId
    }
}

extension Vendor: NSCopying {
    func copy(with zone: NSZone? = nil) -> Any {
        let copy = Vendor(vendorId: vendorId)
        if let availableCarsCopy = availableCars.map({$0.copy()}) as? [Car] {
            copy.availableCars = availableCarsCopy
        }
        return copy
    }
}

class Car {
    let transmissionType: String
    var isAvailable: Bool = false
    var fees: [Double] = []

    init(transmissionType: String) {
        self.transmissionType = transmissionType
    }
}

extension Car: NSCopying {
    func copy(with zone: NSZone? = nil) -> Any {
        let copy = Car(transmissionType: transmissionType)
        copy.isAvailable = isAvailable
        copy.fees = fees
        return copy
    }
}
Кгаидис
источник