Каким же образом происходит управление памятью при вызове метода и получении в качестве результата экземпляра, названного готовым в главе 5? Рассмотрим, например, следующий фрагмент кода:

NSArray* myArray = [NSArray array];

Согласно золотому правилу управления памятью объект, на который теперь указывает переменная myArray, не требует управления памятью. Для его получения не была выдана команда alloc, а следовательно, не были затребованы права на его владение, и поэтому нет никакой нужды его освобождать. Но как такое возможно? Каким образом класс NSArray способен предоставить массив, который не нужно освобождать без опасения утечки этого объекта?

Для того чтобы развеять таинственность происходящего, воспользуйтесь явным управлением памятью, т.е. кодом без поддержки механизма ARC, чтобы все команды сохранения и освобождения из памяти стали видимыми, а также попробуйте поставить себя на место класса NSArray. Как реализовать метод array, чтобы сформировать массив, управление памятью для которого недоступно в вызывающем коде? Не думайте, что для этого достаточно вызвать какой-нибудь другой метод из класса NSArray, поставляющий готовый экземпляр. Это лишь приведет к отсрочке решения вопроса на один шаг назад. Выполняя роль класса NSArray, вы рано или поздно вынуждены будете сформировать экземпляр заново и возвратить его, как показано ниже.

-(NSArray*) array ( NSArray* arr = [[NSArray alloc] init]; return arr; // пожалуй, не так быстро... }

По-видимому, такой подход не годится. Значение переменной arr было сформировано по команде alloc. В соответствии с золотым правилом управления памятью это означает, что из памяти нужно также освободить объект, на который указывает переменная arr. Но когда это можно сделать? Если сделать это до возврата переменной arr, она будет указывать на “мусор”, который и будет в конечном итоге поставлен вызывающему коду. Но этого нельзя сделать и после возврата переменной arr, поскольку при выдаче команды return метод все еще существует!

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

 -(NSArray*) array { NSArray* arr = [[NSArray alloc] init]; [arr autorelease]; } 

Поскольку метод autorelease возвращает объект, которому он посылается, то код автоматического освобождения из памяти можно сделать еще короче следующим образом:

 - (NSArray*) array { NSArray* arr = [[NSArray alloc] init]; return [arr autorelease]; } 

Метод autorelease действует следующим образом. Прикладной код выполняется в присутствии так называемого автоматически освобождаемого пупа. (Заглянув в файл main.m, можно обнаружить в нем фактически созданный автоматически освобождаемый пул.) Когда метод autorelease посылается объекту, этот объект размещается в автоматически освобождаемом пуле, при этом ведется автоматический подсчет количества размещений объекта в данном пуле. Время от времени, когда ничего другого не происходит, этот пул автоматически освобождается. Это означает, что автоматически освобождаемый пул посылает команду release каждому хранящемуся в нем объекту и тем самым освобождается от всех этих объектов. Если это приводит к обнулению подсчета сохраняемых ссылок на объект, то объект уничтожается обычным способом. Таким образом, автоматическое освобождение методом autorelease похоже на обычное освобождение из памяти методом release и, по существу, является формой последнего, но при условии, что это делается не сию секунду, а позднее.

Знать, когда именно пул освободится автоматически, не нужно, да и невозможно, если только не заставить освободиться его принудительно, как будет показано ниже. Следует, однако, иметь в виду, что при вызове такого метода, как array, вызывающему коду придется приостановить на значительное врем свое выполнение в ожидании поставляемого объекта, а при желании — сохранить этот объект.

Объект, поставляемый такими методами, как array, называется автоматически освобождаемым. Объект, выполняющий поставку, на самом деле завершает свое управление памятью для поставляемого объекта. Следовательно, поставляемый объект имеет потенциально, но не окончательно нулевой подсчет сохраняемых ссылок. Поставляемый объект не исчезнет сразу же после вызова [NSArray array], поскольку код все еще выполняется, а следовательно, и пул не освободится автоматически сию же секунду. Поэтому получателю такого объекта следует иметь в виду, что он может быть автоматически освобождаемым. Такой объект не исчезнет до тех пор, пока выполняется код, из которого был вызван метод, поставляющий этот объект. Но если получающему объекту требуется сохранить поставляемый объект для последующего применения, то он должен это сделать непременно.

Именно поэтому после приема готового экземпляра вы как программист не несете никакой ответственности за управление памятью, как, например, при вызове [NSArray array]. Экземпляр, получаемый вами другим способом, а не согласно золотому правилу управления памятью, вам уже не принадлежит. Им владеет какой-нибудь другой объект, или же он освобождается автоматически. Если же этот экземпляр принадлежит какому-нибудь другому объекту, то он не будет уничтожен до тех пор, пока не перестанет принадлежать данному объекту, как было показано на примере массива типа NSMutableArray в предыдущем разделе. Если вас беспокоит, что нечто подобное может произойти, то вы должны завладеть готовым экземпляром, сохранив его, а затем освободив по своему усмотрению. И наконец, если экземпляр освобождается автоматически, то он, безусловно, будет храниться достаточно долго, чтобы прикладной код завершил свое выполнение, поскольку пул не освободится автоматически до тех пор, пока выполнение прикладного кода не завершится. Опять же, если экземпляр требуется сохранить еще дольше, для этого придется завладеть им.

Как и следовало ожидать, если действует механизм ARC, то все операции управления памятью происходят согласованно. Для этого совсем не нужно, да и нельзя на самом деле выдавать команду autorelease. Напротив, механизм ARC сделает это автоматически по описанным ранее правилам именования методов. Например, метод, вызывающий метод array, не должен начинаться со слова new, init, alloc, сору или mutableCopy, если он именуется в смешанном написании. Следовательно, он должен возвращать объект, управление памятью для которого уравновешивается, используя метод autorelease для окончательного освобождения из памяти. Механизм ARC проверит, что это именно так и есть. С другой стороны, в методе, вызывающем метод array и принимающем возвращаемый из него объект, должно предполагаться, что этот объект автоматически освобождается и может прекратить свое существование, если только не сохранить его. Именно это и предполагается в механизме ARC.

Иногда пул требуется немедленно освободить автоматически. Рассмотрим следующий фрагмент кода:

 for (NSString* aWord in myArray) { NSString* lowerAndShorter = [[aWord lowercaseString] substringFromlndex:1]; [myMutableArray addObject: lowerAndShorter]; } 

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

В качестве выхода из подобного затруднения можно вмешаться в механизм действия автоматически освобождаемого пула, предоставив собственный аналогичный пул. Это вполне допустимо, поскольку автоматически освобождаемый пул служит для хранения автоматически освобождаемого объекта в самом последнем из созданных пулов. Для этого достаточно создать автоматически освобождаемый пул в начале цикла и освободить его в конце цикла на каждом его шаге. В современной версии языка Objective-C для этой цели код, который выполняется под действием собственного автоматически освобождаемого пула, заключается в фигурные скобки директивы @autoreleasepool { }, как показано ниже.

 for (NSString* aWord in myArray) { Qautoreleasepool { NSString* lowerAndShorter = [[aWord lowercaseString] substringFromlndex:1]; [myMutableArray addObject: lowerAndShorter]; } } 

 

Многие классы представляют программистам два равнозначных способа получения объекта: в виде автоматически сохраняемого объекта (готового экземпляра) или же объекта, самостоятельно создаваемого методом alloc и определенной формой метода init (т.е. инициализации с самого начала). Например, в классе NSMutableArray для этой цели предоставляется метод класса array и метод экземпляра init. Какой же из этих методов следует использовать? В общем, метод alloc или определенную форму метода init следует применять там, где лучше всего сформировать с их помощью объект. Такая стратегия препятствует появлению задержек, возникающих в автоматически освобождаемом пуле, а также как можно более экономному расходованию памяти.


 

 

 

Добавить комментарий