355 500 произведений, 25 200 авторов.

Электронная библиотека книг » Стивен Кочан » Программирование на Objective-C 2.0 » Текст книги (страница 24)
Программирование на Objective-C 2.0
  • Текст добавлен: 19 сентября 2016, 13:02

Текст книги "Программирование на Objective-C 2.0"


Автор книги: Стивен Кочан



сообщить о нарушении

Текущая страница: 24 (всего у книги 32 страниц)

Глава 18. Копирование объектов

В этой главе рассматриваются некоторые особенности копирования объектов. Мы рассмотрим понятия поверхностного (shallow) и глубокого (deep) копирования и опишем, как создавать копии в Foundation framework.

В главе 8 было показазно, что происходит, если мы присваиваем один объект другому с помощью простого оператора присваивания, например origin = pt;

В этом примере origin и pt – это объекты класса XYPoint, которые определены следующим образом. @interface XYPoint: NSObject { intx; int у; @end

Напомним, что присваивание – это просто копирование адреса объекта pt в origin. После операции присваивания обе переменные указывают одно и то же место в памяти. Внесение изменений в переменные экземпляра с помощью сообщения [origin setX: 100 andY: 200];

приводит к изменению координат х,у объекта класса XYPoint. на который ссыла-ются обе переменные (origin и pt), поскольку они указывают на один и тот же объект в памяти.

То же самое относится к объектам Foundation: присваивание одной перемен-ной другой вызывает создание еще одной ссылки на объект (но приводит к нара-щиванию счетчика ссылок, см. главу 17). Например, если dataArray и dataArray2 – объекты класса NSMutableArray, то следующие операторы удаляют первый элемент из одного и того же массива, на который ссылаются обе эти переменные. dataArray2 = dataArray; [dataArray2 removeObjectAtlndex: 0]; 18.1. Методы copy и mutableCopy

В классах Foundation реализованы методы сору и mutableCopy, предназначенные для создания копии объекта. Для создания копий методы реализуются в соот-ветствии с протоколом . Если для вашего класса требуется различать создание мутабельных и немутабельных копий объекта, то методы реализуются согласно протоколу .

Вернемся к методам копирования в классах Foundation для двух объектов dataArray2 и dataArray класса NSMutableArray. С помощью оператора dataArray2 = [dataArray mutableCopy];

создается новая копия массива dataArray в памяти с дублированием всех его эле-ментов. Поэтому оператор [dataArray2 removeObjectAtlndex: 0];

удаляет первый элемент из массива dataArray2, но не из массива dataArray.

Это показано в программе 18.1. #import #import #import ffimport int main {int arge, char *argv[]) { NSAutoreleasePool * pool = [[NSAutoreleasePool alloc] init]; NSMutableArray *dataArray = [NSMutableArray arrayWithObjects: @none'', @"two", @"three", @"four", nil]; NSMutableArray *dataArray2; // простое присваивание dataArray2 = dataArray; [dataArray2 removeObjectAtlndex: 0]; NSLog (@"dataArray:"); for ( NSString *elem in dataArray ) NSLog (@" elem); NSLog (@"dataArray2:"); for ( NSString *elem in dataArray2 ) NSLog (@n elem); // копирование, затем удаление первого элемента из копии dataArray2 = [dataArray mutableCopy]; [dataArray2 removeObjectAtlndex: 0]; NSLog {@"dataArray: for ( NSString *elem in dataArray ) NSLog (@n %@ elem); NSLog (@"dataArray2: "); for ( NSString *elem in dataArray2 ) NSLog (@" %@", elem); [dataArray2 release]; [pool drain]; return 0; }

Вывод программы 18.1 dataArray: two three four dataArray2: two three four dataArray: two three four dataArray2: three four `` В программе определяется объект мутабельного массива dataArray, и его эле-ментам присваиваются строковые объекты @"one", @"two", @"three", @"four" соответственно. Как говорилось выше, оператор присваивания

dataArray2 = dataArray; просто создает еще одну ссылку на тот же объект массива в памяти. Поэтому после удаления первого объекта из dataArray2 и вывода элементов объектов-мас-сивов первый элемент (строка @''опе") исчезает из обеих ссылок на этот объект-массив. Затем мы создаем мутабельную копию dataArray и присваиваем полученную копию массиву dataArray2. В результате получаются два отдельных мутабельных массива в памяти, каждый из которых содержит три элемента. Теперь удаление первого элемента из dataArray2 не оказывает влияния на содержимое массива dataArray, что подтверждается последними двумя строками вывода программы. Отметим, что для создания мутабельной копии объекта копируемый объект не обязан бытьмутабельным. То же относится и кнемутабельным копиям: можно сделать немутабельную копию мутабельного объекта. При создании копии массива операция копирования автоматически наращивает счетчик ссылок (удержаний) для каждого элемента массива. Поэтому после создания копии массива и последующего высвобождения (release) исходного массива элементы копии продолжают действовать. Но поскольку копия массива dataArray была создана в этой программе с по-мощью метода mutableCopy, вы обязаны сами освободить его память. Как расска-зывалось в предыдущей главе, вы обязаны сами высвобождать объекты, которые создали с помощью одного из методов копирования, поэтому в конце программы 18.1 вставлена строка

[dataArray2 release]; ## 18.2. Поверхностное и глубокое копирование В программе 18.1 элементы массива dataArray заполняются немутабельными стро-ками (напомним, что константные строковые объекты яатяютси немутабельными). В программе 18.2 мы будем заполнять его мутабельными строками, чтобы можно было изменить одну из арок в этом массиве. Просмотрите программу 18.2 и постарайтесь понять ее вывод. import ffimport import import

int main (int arge, char argv[]) { NSAutoreleasePool pool = [[NSAutoreleasePool alloc] init]; NSMutableArray dataArray = [NSMutableArray arrayWithObjects: [NSMutableString stringWithString: @"one"], [NSMutableString stringWithString: @"two"], [NSMutableString stringWithString: @"three"], nil ]; NSMutableArray dataArray2; NSMutableString mStr; NSLog (@"dataArray:"); for ( NSString elem in dataArray ) NSLog {@" %@", elem); // создание копии, затем изменение одной из строк dataArray2 = [dataArray mutableCopy]; mStr = [dataArray objectAtlndex: 0]; [mStr appendString: @"ONE"]; NSLog (@"dataArray: for ( NSString elem in dataArray ) NSLog (@" %@", elem); NSLog (@"dataArray2: "); for ( NSString e!em in dataArray2 ) NSLog (@" %@", elem); [dataArray2 release]; [pool drain]; return 0; } Вывод программы 18.2

dataArray: one two three dataArray: oneONE two three dataArray2: oneONE two three `` Мы считываем первый элемент массива dataArray с помощью оператора mStr = [dataArray objectAtlndex: 0]; и добавляем в него строку' @"ONE" с помощью оператора [mStr appendString: @"ONE"];

Обратите внимание на значение первого элемента исходного массива и его копии: в обоих массивах оно было изменено. Понятно, почему был изменен первый элемент dataArray, но почему была изменена и его копия? Получая элемент из коллекции, мы получаем новую ссылку на этот элемент, но не новую копию. Поэтому при вызове метода objectAtlndex: для dataArray возвращаемый объект указывает на тот же объект в памяти, что и первый элемент в dataArray. Последующее изменение строкового объекта mStr также сопровождается изменением первого элемента массива dataArray, что подтверждается результатами вывода.

Но почему изменился первый элемент созданной копии? По умолчанию копии ивляклси поверхностными (shallow) копиями. Когда массив был скопирован с помощью метода mutableCopy, в памяти было выделено пространство для нового объекта-массива, и элементы были скопированы в новый массив. Но копирование каждого элемента массива из исходного места в новое означает только копирование ссылки из одного элемента массива в другой. В результате элементы обоих массивов ссылаются на одни и те же строки в памяти, что не отличается от присваивания одного объекта другому, о котором мы говорили в начале этой главы.

Чтобы создать другие копии каждого элемента массива, необходимо выпол-нить глубокое (deep) копирование, при котором создаются копии содержимого каждого объекта в массиве, а не копируются ссылки на объекты (подумайте, что это означает, если элемент массива сам является объектом-массивом). Но глубокое копирование не выполняется по умолчанию, если мы используем методы сору или mutableCopy с классами Foundation. В главе 19 мы покажем возможности архивации Foundation для создания глубокой копии объекта.

Копируя массив, словарь или набор, мы получаем новую копию этих кол-лекций. Создание копий отдельных элементов может потребоваться, например, если нужно внести изменения в коллекцию, но не в ее копию. Например, если в программе 18.2 нужно было бы изменить первый элемент массива dataAnay2, но не dataArray, вы могли бы создать новую строку (например, с помощью ме тода stringWithString:) и сохранить ее в первом элементе dataArray2 с помощью оператора mStr = [NSMutableString stringWithString: [dataArray2 objectAtlndex: 0]];

Затем можно было бы внести изменения в переменную mStr и добавить ее в этот массив с помощью метода replaceObject: at I ndex:withObject: [mStr appendString @"ONE"]; [dataArray2 replaceObjectAtlndex: 0 withObject: mStr];

Даже после замены объекта mStr и первый элемент dataArray2 ссылаются на один и тот же объект в памяти. Поэтому последующее изменение в mStr вызовет также изменение первого элемента э того массива. Чтиобы избежать этого, выс-вободите (release) mStr и выделите память (alloc) для нового экземпляра, поскольку метод replaceObject:allndex:withObject: автоматически удерживает объект. 18.3. Реализация протокола

Если применить метод сору к одному из ваших собственных классов, например, к вашей адресной книге (address book), как в строке NewBook = [myBook mutableCopy];

то будет выдано сообщение об ошибке: *** -[AddressBook copyWithZone:]: selector not recognized (селектор не распознан) *** Uncaught exception: (Невыявленное исключение) *** -[AddressBook copyWithZone:]: selector not recognized

Как уже говорилось, для реализации копирования с вашими собственными классами необходимо реализовать один или два метода согласно протоколу .

Теперь покажем, как добавить метод сору в класс Fraction, который много ис-пользовался в части I. Эти способы вполне применимы для ваших собственных классов. Если эти классы являются подклассами любого из классов Foundation, то потребуется реализация более сложной стратегии копирования, поскольку' в суперклассе может быть уже реализована его собственная стратегия копирования.

Напомним, что класс Fraction содержит две целые переменные экземпляра: numerator (числитель) и denominator (знаменатель). Чтобы создать копию одного из этих объектов, необходимо выделить пространство для новой дроби (fraction) и затем скопировать значения этих переменных в эту новую дробь.

При реализации протокола ваш класс должен реализовать метод copyWithZone:, чтобы реагировать на сообщение сору. (Сообщение сору просто передает сообщение copyWithZone: в наш класс с аргументом nil.) Если вам нужно отличать мутабельные и немутабельные копии, то потребуется также реализовать метод mutaЫеСоpyWithZone: согласно протоколу . Если вы реализуете оба метода, то copyWithZone: будет возвращать немугабельную копию, a mutableCopyWithZone: будет возвращать мутабельную копию. Создание мутабель– кой копии объекта не требует, чтобы копируемый объект был тоже мутабель– ным, и наоборот; вполне возможно, что может требоваться мутабельная копия немутабельного объекта (например, строкового объекта).

Директива @interface должна выглядеть следующим образом. @interface Fraction: NSObject

Fraction – это подкласс NSObject, подчиняющийся протоколу NSCopying.

В файле секции implementation Fraction.m добавьте следующее определение для нового метода. -(id) copyWithZone: (NSZone *) zone { Fraction *newFract = [(Fraction allocWithZone: zone] init]; [newFract setTo: numerator over: denominator]; return newFract; }

Аргумент zone применяется для разных зон памяти, которые вы можете вы-делить для работы с профаммой. Это требуется только в том случае, если ваши приложения занимают много памяти, и вы хотите оптимизировать выделение памяти, группируя ее по зонам. Вы можете брать значение, передаваемое методу copyWithZone:, и передавать его методу выделения памяти allocWithZone:. Этот метод выделяет память в указанной зоне.

После выделения памяти для нового объекта класса Fraction в него копиру-ются переменные получателя numerator и denominator. Предполагается, что метод copyWithZone: будет возвращать новую копию объекта, которую вы создаете в своем методе.

Этот новый метод проверяется в профамме 18.3. // Копирование дробей #import "Fraction.h" Jfimport int main (int arge, char *argv[]) { NSAutoreleasePool * pool = [[NSAutoreleasePool alloc] init]; Fraction *11 = [[Fraction alloc] init]; Fraction *f2; [ft setTo: 2 over: 5]; f2 = [fl copy]; [!2 setTo: 1 over: 3]; [fl print]; [12 print]; [fl release]; [f2 release]; [pool drain]; return 0; }

Вывод программы 18.3 2/5 1/3

Эта программа создает объект класса Fraction с именем fl и присваивает ему значение 2/5. Затем вызывается метод сору для создания копии, который передает сообщение copyWrthZone: этому объекту. Этот метод создает новый объект класса Fraction, копирует в него значения из 11 и возвращает результат. После воз-врата в main этот результат присваивается 12. Последующее присваивание 12 дро-би 1/3 подтверждает, что это не оказывает влияния на исходную дробь 11. Изме-ните строку программы 12 = [И сору];

на 12 = 11;

и удалите высвобождение (release) 12 в конце программы, после чего увидите другие результаты.

Если ваш класс может быть подклассом, то метод copyWithZone: будет насле-доваться. В таком случае вы должны изменить строку этого метода Fraction *newFract = [[Fraction allocWithZone: zone] init]; на строку Fraction *newFract = [[[self class] allocWithZone: zone] init];

Это позволяет выделить память для нового объекта изданного класса, кото-рый является получателем копии. (Например, если это подкласс с именем NewFraction, то нужно выделить в наследуемом методе память для нового объекта NewFraction вместо объекта Fraction.)

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

Вы должны решить, какое копирование нужно реализовать в вашем классе: поверхностное или глубокое. Задокументируйте это для других пользователей вашего класса. 18.4. Копирование объектов в методах-установщиках и методах-получателях

Каждый раз, реализуя метод-установщик (setter) или метод-получатель (getter), вы должны продумать, что будет сохраняться в переменных экземпляра, что будет считываться и нужно ли защитить эти значения. Рассмотрим оператор, в котором мы задаем имя одного из объектов AddressCard с помощью метода setName: [newCard setName: newName];

Предположим, что newName – это строковый объект, содержащий имя новой карточки. Предположим также, что внутри процедуры установщика мы просто присваиваем параметр соответствующей переменной экземпляра. -(void) setName: (NSString *) theName { name = theName; }

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

Чтобы исключить этот побочный эффект, следует создать копию объекта в процедуре установщика. С помощью метода alloc мы создали новый строковый объект и затем с помощью метода initWithString: присвоили ему значение пара-метра, передаваемого методу.

Можно также написать версию метода setName:, чтобы использовать сору, например -(void) setName: (NSString *) theName { name = [theName copy];}

Чтобы управлять памятью в процедуре установщика было удобно, необхо-димо сначала автоматически высвободить (autorelease) старое значение, как показано ниже. -(void) setName: (NSString *) theName { [name autorelease]; name = [theName copy]; }

Если задать атрибут copy в объявлении свойств (property) для переменной экземпляра, то в синтезируемом методе будет использоваться метод класса сору (написанный вами или унаследованный). Поэтому объявление @property (nonatomic, copy) NSString *name;

приведет к созданию синтезируемого метода, который действует следующим образом. -(void) setName: (NSString *) theName { if (theName != name) { [name release] name = [theName copy]; } }

Атрибут nonatomic указывает системе, что в данном случае не нужно защи-щать методы доступа с помощью блокировки mutex (mutually exclusive – взаимоисключение). При написании кода с защитой потоков используются блокировки mutex, чтобы исключить одновременное выполнение одного кода двумя потоками (ситуация, которая часто вызывает ужасные проблемы). Но эти блокировки могут замедлять выполнение программ, поэтому вы можете отказаться от них, если знаете, что этот код будет всегда выполняться только в одном потоке.

Если атрибут nonatomic не указан или вместо него указан атрибут atomic (это атрибут по умолчанию), то переменная экземпляра будет защищена блокировкой mutex. Кроме того, синтезируемый метод-получатель будет удерживать (retain) и автоматически высвобождать (autorelease) переменную экземпляра перед тем, как будет возвращено ее значение. В среде без сборки мусора это защищает переменную экземпляра от возможной перезаписи методом-установщиком, который высвобождает старое значение переменной экземпляра, прежде чем установить новое значение. Использование retain в методе-получателе гарантирует, чтобы память для старого значения не будет освобождена.

Примечание. Проблема высвобождения и автоматического высвобождения (retain/autorelease) не актуальна в среде со сборкой мусора, в которой вызовы этих методов игнорируются, но это не относится к блокировке mutex. Если ваш код будет выполняться в многопотоковой среде, предусмотрите использование методов доступа с атрибутом atomic,.

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

Вернемся к реализации метода сору. Если мы копируем переменные экземп-ляра, которые содержатнемутабельные объекты (например, немутабельные строковые объекты), то создание новой копии содержимого объекта, возможно, не потребуется. Достаточно создать новую ссылку на объект путем его удержания (retain). Например, если мы реализуем метод сору для класса AddressCard с члена-ми name и email, то достаточно написать следующую реализацию для copyWithZone:. -(AddresssCard *) copyWithZone: (NSZone *) zone { AddressCard *newCard = [[AddressCard allocWiihZone: zone] init]; [newCard retainName: name andEmail: email]; return newCard; -(void) retainName: (NSString *) theName andEmail: (NSString *) IheEmail { name = [theName retain]; email = [theEmail retain]; }

Здесь для копирования переменных экземпляра не используется метод setName:andEmail:, поскольку он создает новые копии своих аргументов. Вместо этого мы просто удерживаем (retain) две переменные с помощью нового метода retainName:andEmail:. (Мы могли бы задавать эти две переменные экземпляра в newCard непосредственно внутри метода copyWithZone:, но для этого нужны операции с указателями, без которых мы обходились до настоящего момента. Операции с указателями были бы более эффективны и не показывали бы пользователю этого класса метод [retainNameiandEmail;], не предназначенный для общего пользования, поэтому в какой-то момент вам, возможно, придется этому научиться – но не сейчас!)

В данном случае достаточно использовать удержание (retain) для переменных экземпляра вместо создания их полных копий, поскольку владелец скопированной карточки не может повлиять на компоненты пате и email исходной карточки. Убедитесь сами, что это действительно так (подсказка: он должен работать с методами-установщиками). Упражнения

Реализуйте метод сору для класса AddressBook согласно протоколу NSCopying. Имеет ли смысл реализовать также метод mutableCopy? Почему?

Внесите изменения в классы Rectangle и XYPoint, определенные в главе 8, чтобы они подчинялись протоколу . Добавьте в оба класса метод copyWithZone;. Сделайте так, чтобы в Rectangle выполнялось копирование его члена XYPoint origin с помощью метода XYPoint сору. Имеет ли смысл реализовать как мутабельную, так и немутабельную копию для этих классов? Объясните.

Создайте объект-словарь NSDictionary и заполните его несколькими парами ключ/объект. Затем создайте мутабельную и немутабельную копии. Это глу-бокие или поверхностные копии? Проверьте свой ответ.

Кто обязан освобождать память, выделяемую для новой адресной карточки (AddressCard) в методе copyWithZone:, если выполнена реализация, как в этой главе? Почему?


    Ваша оценка произведения:

Популярные книги за неделю