Впровадження 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. Це означає, що мені також потрібно реалізовувати це на класах, які проводяться в 2 масивах, що належать доCar об'єкту.

Я був би дуже вдячний, якби міг отримати деякі вказівки щодо впровадження NSCopyingпротоколу Vendor, я ніде не можу знайти підручників з цього.


Чи читали ви документацію NSCopying? Я знайшов це цілком зрозумілим, коли це було потрібно.
jv42

4
Так, прочитайте, і воно перечитає. Я рідко виявляю, що в яблучних документах легко дізнатися, хоча вони чудово підходять для пошуку методів тощо під час програмування. Дякую -Код

Відповіді:


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і друзі) просто встановити змінні рівними. Отже, для вашого покупець-постачальника це виглядатиме так:

- (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), вам не потрібно турбуватися про випуск та автоматичний випуск.
rishabh

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
    }
}
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.