Руководство по actionscript. часть 2, стр. 097

Если исключение генерируется в блоке try, вложенном в блок catch, и внутренний блок try не имеет блока catch, способного обработать данное исключение, поиск подходящего блока catch начинается за пределами внешней инструкции try/ catch/finally:

try {

// Здесь генерируется внешнее исключение, throw new Error(«Test error 1″); } catch (e;Error) { // Здесь обрабатывается внешнее исключение. trace(e. message); // Выводит: Test error 1 try {

// Здесь генерируется внутреннее исключение, throw new ErrorC’Test error 2″); } catch (e:SomeSpecificError) { // Внутреннее исключение здесь не обрабатывается. trace(e. message); // Инструкция никогда не будет выполнена, // поскольку типы не совпадают.

}

}

// Процесс поиска подходящего блока catch для внутреннего исключения // начинается здесь.

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

// Этот метод генерирует исключение в блоке finally, public function throwTwoExceptions ( ):void { try {

// Здесь генерируется внешнее исключение. Поскольку этот блок try // не имеет соответствующего блока catch, внешнее исключение начинает // свой подъем по иерархии объектов, throw new ErrorC’Test error 1″); } finally { try {

// Здесь возникает внутреннее исключение. Внутреннее исключение

// будет обработано до того, как внешнее исключение фактически начнет

// подъем по иерархии объектов, throw new ErrorC’Test error 2″); } catch (e:Error) { // Внутреннее исключение обрабатывается здесь, tracer Internal catch: » + e. message);

}

}

}

// Где-то в другом месте, внутри метода, // вызывающего предыдущий метод, try {

throwTwoExceptions( ); } catch (e:Error) { // Здесь обрабатывается внешнее исключение, // поднявшееся из метода throwTwoExceptions( ). trace(«External catch: » + e. message);

}

// Вывод (обратите внимание, что внутреннее исключение обработано первым): // Internal catch: Test error 2 // External catch: Test error 1

Если бы в предыдущем коде исключение, сгенерированное в блоке finally, не было обработано, среда Flash сообщила бы об ошибке на этапе отладки и прекратила бы выполнение оставшегося кода в стеке вызовов. В результате никакой код в стеке вызовов не обработал бы сгенерированное первым внешнее исключение. Следующий код демонстрирует описанный сценарий. Этот код генерирует необрабатываемое исключение в блоке finally. В итоге исключение, сгенерированное внешним блоком try, будет потеряно.

Руководство по actionscript. часть 2, стр. 098

Try {

// Здесь генерируется внешнее исключение, throw new ErrorC’Test error 1″); } finally { try {

// Здесь генерируется внутреннее исключение, throw new ErrorC’Test error 2″); } catch (e:SomeSpecificError) { // Внутреннее исключение здесь не обрабатывается. trace(«internal catch: » + e. message); // Инструкция никогда не будет

// выполнена, поскольку типы

// не совпадают.

}

}

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

Предыдущий код демонстрирует влияние необработанного исключения на ход выполнения программы в одном сценарии, однако хочется еще раз повторить, что

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

Изменение хода выполнения программы в инструкции try/catch/finally

На протяжении этой главы мы неоднократно убеждались в том, что инструкция throw изменяет ход выполнения программы. Когда среда Flash встречает инструкцию throw, она немедленно прекращает выполнение текущей задачи и передает управление программой подходящим блокам catch и finally. Тем не менее блоки catch и finally также могут влиять на ход выполнения программы с помощью оператора return (в случае метода или функции), break или continue (в случае цикла). Более того, операторы return, break и continue могут также использоваться в блоке try.

Чтобы познакомиться с правилами изменения хода выполнения программы в инструкции try/catch/finally, рассмотрим, как оператор return влияет на ход выполнения программы в блоках try, catch и finally. Следующий пример кода содержит функцию changeFlow ( ), которая демонстрирует ход выполнения программы в различных гипотетических ситуациях.

Руководство по actionscript. часть 2, стр. 099

В листинге 13.1 показан оператор return в блоке try, помещенный перед инструкцией, генерирующей ошибку. В данном случае метод выполняется нормально и никакая ошибка не будет сгенерирована или обработана. Однако перед возвратом из метода будет выполнен блок finally. Стоит отметить, что вы вряд ли увидите код, в точности повторяющий код из листинга 13.1, в реальной программе. В большинстве случаев оператор return используется в условных операторах и выполняется в ответ на некоторое определенное условие в программе.

Листинг 13.1. Использование оператора return в блоке try перед оператором throw

public function changeFlow ( ):void { try { return;

throw new ErrorC’Test error.»);

} catch (e:Error) {

trace(«Caught: » +- e. message); } finally {

trace(«Finally executed.»);

}

traceC’Last line of method.»);

}

// Вывод после вызова метода changeFlow( ): // Finally executed.

В листинге 13.2 показан оператор return в блоке try, помещенный после инструкции, генерирующей ошибку. В данном случае return не выполнится, поскольку ошибка будет сгенерирована до того, как программа дойдет до этого оператора. Как только

ошибка будет обработана и выполнение инструкции try/ cat ch/finally завершится, выполнение программы продолжится после данной инструкции try/catch/finally и выход из метода произойдет в конце его тела. Снова повторим, что листинг 13.2 всего лишь демонстрирует принцип, но не является типичным в реальном сценарии, поскольку ошибка обычно генерируется на основании некоторого условия.

Руководство по actionscript. часть 2, стр. 100

Листинг 13.2. Использование оператора return в блоке try после оператора throw

public function changeFlow ( ):void { try {

throw new ErrorC’Test error.»); return;

} catch (e:Error) {

trace(«Caught: » + e. message); } finally {

trace(«Finally executed.»);

}

traceC’Last line of method.»);

}

// Вывод после вызова метода changeFlow( ): // Caught: Test error. // Finally executed. // Last line of method.

Листинг 13.3 демонстрирует использование оператора return в блоке catch. В данном случае return будет выполнен после завершения процесса обработки ошибки. При этом код, размещенный после инструкции try/catch/finally, выполнен не будет. Однако, как обычно, перед возвратом из метода будет выполнен блок fin а 11 у. В отличие от листингов 13.1 и 13.2 данный код является типичным в реальном сценарии, когда выполнение метода прекращается при возникновении ошибки.

Листинг 13.3. Использование оператора return в блоке catch

public function changeFlow ( ):void { try {

throw new ErrorC’Test error.»); } catch (e:Error) {

trace(«Caught: » + e. message);

return; } finally {

traceC’Finally executed.»);

}

traceC’Last line of function.»);

}

// Вывод после вызова метода changeFlow( ): // Caught: Test error. // Finally executed. ,

Из-за известной ошибки в приложении Flash Player 9 при выполнении кода из листингов 13.2 и 13.3 происходит обращение к несуществующей области стека. Корпорация Adobe планирует исправить эту проблему в следующей версии приложения Flash Player.

Руководство по actionscript. часть 2, стр. 101

В листинге 13.4 показан оператор return в блоке finally. В данном случае он выполняется тогда, когда выполняется блок finally (как уже известно, выполнение блока finally происходит после завершения соответствующего блока try одним из следующих способов: без ошибок; с обработанной ошибкой; с необработанной ошибкой; в результате вызова операторов return, break или continue). Обратите внимание, что оператор return в листинге 13.4 предотвращает выполнение кода, размещенного после инструкции try/catch/ finally. Вы можете использовать подобную методику для завершения метода после выполнения блока кода независимо от того, было сгенерировано исключение или нет. При этом вся инструкция try/catch/finally обычно помещается внутрь условного оператора (иначе оставшаяся часть метода никогда не будет выполнена!).

Листинг 13.4. Использование оператора return в блоке finally

public function changeFlow ( ):void { try {

throw new ErrorC’Test error.»); } catch (e:Error) {

trace(«Caught: » + e. message); } finally {

trace(«Final! у executed.»);

return;

}

traceC’Last line of method.»); // He выполняется.

}

// Вывод после вызова метода changeFlow( ): Caught: Test error. Finally executed.

^ I Если оператор return в блоке finally вызывается после того, как был вызван оператор м$ 4 * return в соответствующем блоке try, вызов return в блоке finally отменяет предыдущий цу вызов return.

Обработка предопределенного исключения

В самом начале этой главы мы узнали, что с помощью инструкции try/catch/ finally можно обрабатывать как предопределенные, так и пользовательские ошибки. До настоящего момента все обрабатываемые нами ошибки были пользовательскими. Сейчас мы рассмотрим инструкцию try/catch/finally, обрабатывающую предопределенную ошибку.

Руководство по actionscript. часть 2, стр. 102

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

используя класс Socket. В некоторых случаях установить соединение не получится из-за ограничений безопасности. Чтобы показать, что причиной неудачной попытки соединения являются ограничения безопасности, среда выполнения Flash генерирует исключение SecurityError. Таким образом, при попытке создать соединение мы помещаем соответствующий код в блок try. Если установить соединение не получится по причинам безопасности, мы отобразим для пользователя сообщение об ошибке, обозначив проблему, из-за которой возникла данная ошибка.

var socket:Socket = new Socket( ); try {

// Пытаемся подключиться к указанному порту socket. connect(«example. com». userPort); } catch (e:SecurityError) { // Код. расположенный здесь, отображает сообщение для пользователя

}

I Список причин, которые могут привести к ошибкам соединения сокета, можно найти м?’ л * в описании метода connect() класса Socket в справочнике по языку ActionScript корпо-цу рации Adobe.

События об ошибках в случае проблемных ситуаций. В предыдущем разделе мы рассмотрели, как обработать исключение, вызванное недопустимой попыткой установить соединение сокета. Однако не все сбойные ситуации в языке ActionScript приводят к генерации исключений. Информация о проблемах, возникающих асинхронно (то есть спустя некоторое время), передается через события об ошибках, а не через исключения. Например, если мы попытаемся загрузить файл, среда выполнения Flash в асинхронном режиме сначала должна проверить, существует ли запрашиваемый файл. Если этот файл не существует, среда Flash выполнит диспетчеризацию события IOErrorEvent. IO_ERROR. Чтобы обработать возникшую проблему, код, инициировавший операцию загрузки, должен зарегистрировать обработчик для события IOErrorEvent. IO ERROR. Если этого не произойдет, возникнет ошибка на этапе выполнения. Пример обработчика события об ошибке можно найти среди примеров в подразд. «Два дополнительных примера регистрации приемников событий» разд. «Основы обработки событий в ActionScript» гл. 12.

Впереди еще одна скучная работа

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

ПИВА 14

Сборка мусора

Всякий раз, когда программа создает объект, среда выполнения Flash сохраняет его в системной памяти (например, ОЗУ). По мере того как программа создает сотни, тысячи или даже миллионы объектов, объем памяти, занимаемой этой программой, постепенно увеличивается. Чтобы предотвратить полное исчерпывание системной памяти, Flash автоматически удаляет из нее объекты, когда программа перестает в них нуждаться. Процесс автоматического удаления объектов из памяти называется сборкой мусора.

Доступность объектов для сборки мусора

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

? переменные, определенные на уровне пакета;

? локальные переменные в выполняющемся методе или функции;

? статические переменные;

? переменные экземпляра основного класса программы;

? переменные экземпляра объекта, находящегося в списке отображения среды выполнения Flash;

? переменные, находящиеся в цепочке областей видимости выполняющейся функции или метода.

Руководство по actionscript. часть 2, стр. 103

Например, рассмотрим следующую версию класса VirtualZoo из нашей программы «Зоопарк». Обратите внимание, что в этой версии объект VirtualPet, создаваемый в конструкторе класса VirtualZoo, не присваивается переменной.

package { import flash. display. Sprite; import zoo.*;

public class VirtualZoo extends Sprite { public function VirtualZoo ( ) { new VirtualPet(«Stan»);

}

При выполнении конструктора класса VirtualZoo из предыдущего кода выражение new VirtualPet («Stan») создает новый объект класса VirtualPet. Однако обратиться к объекту после его создания невозможно, поскольку он не был присвоен никакой переменной. Как результат, созданный объект считается недостижимым и сразу же становится доступным для сборки мусора.

Теперь рассмотрим следующую версию класса VirtualZoo. Особое внимание уделите телу метода-конструктора, выделенного полужирным шрифтом:

package { import flash. display. Sprite; import zoo.*;

public class VirtualZoo extends Sprite { public function VirtualZoo ( ) { var pet:Virtual Pet * new Virtual Pet(«Stan»);

}

}

}

Как и раньше, при выполнении конструктора класса VirtualZoo из предыдущего кода выражение new VirtualPet («Stan») создает новый объект класса VirtualPet. Однако на этот раз созданный объект класса VirtualPet присваивается локальной переменной pet. В конструкторе класса VirtualZoo к объекту Vi rtual Ре t можно обратиться через эту локальную переменную, поэтому данный объект недоступен для сборки мусора. Тем не менее, как только выполнение конструктора VirtualZoo будет завершено, время жизни переменной pet истечет и обратиться к объекту VirtualPet будет невозможно. Как результат, объект считается недостижимым и становится доступным для сборки мусора.

Далее рассмотрим следующую версию класса VirtualZoo:

package { import flash. display. Sprite; import zoo.*;

public class VirtualZoo extends Sprite { private var pet:Virtual Pet;

public function VirtualZoo ( ) { pet = new Virtual Pet(«Stan»);

}

}

}

При выполнении конструктора класса VirtualZoo из предыдущего кода выражение new VirtualPet («Stan») снова создает объект класса Virtual Pet. Однако на этот раз созданный объект Vi rtual Ре t присваивается переменной экземпляра основного класса программы. По существу, этот объект считается достижимым и поэтому недоступен для сборки мусора.

Руководство по actionscript. часть 2, стр. 104

Теперь рассмотрим следующую версию класса VirtualZoo (снова обратите внимание на фрагменты кода, выделенные полужирным шрифтом):

package { import flash. display. Sprite; import zoo.*;

public class VirtualZoo extends Sprite { private var pet:Virtual Pet;

public function VirtualZoo ( ) { pet = new VirtualPetC’Stan»); pet = new Virtual Pet(«Tim»);

}

}

}

Как и раньше, первая строка конструктора из предыдущего кода создает объект класса VirtualPet и присваивает его переменной экземпляра pet. Однако вторая строка метода-конструктора из предыдущего кода создает еще один объект класса VirtualPet и тоже присваивает его переменной экземпляра pet. Вторая операция присваивания заменяет первое значение переменной pet (то есть животное с именем Stan) новым значением (то есть животным с именем Tim). Как результат, объект VirtualPet с именем Stan считается недостижимым и становится доступным для сборки мусора. Стоит отметить, что, если бы мы присвоили переменной pet значение null (или любое другое допустимое значение), как показано в следующем коде, объект VirtualPet с именем Stan также оказался бы недостижимым: pet = null;

Наконец, рассмотрим следующую версию класса VirtualZoo, в которой определены две переменные экземпляра petl и pet2:

package { import flash. display. Sprite; import zoo.*;

public class VirtualZoo extends Sprite { private var petl:Virtual Pet; private var pet2:Virtual Pet;

public function VirtualZoo ( ) { petl = new VirtualPetC’Stan»); pet2 = petl; petl = nul1; pet2 — nul1;

}

}

}

Как и раньше, первая строка конструктора из предыдущего кода создает объект класса VirtualPet; для удобства назовем его «Stan». Кроме того, первая строка присваивает объект «Stan» переменной petl. Затем вторая строка присваивает тот же объект переменной экземпляра pet2. После выполнения второй строки конструктора и перед выполнением третьей строки программа может обратиться

к объекту «Stan» как через переменную petl, так и через переменную pet2, поэтому данный объект недоступен для сборки мусора. Третья строка заменяет значение переменной petl значением null, так что объект «Stan» оказывается недоступен через эту переменную. Тем не менее к объекту 11 Stan» по-прежнему можно обратиться через переменную pet2, поэтому данный объект все еще недоступен для сборки мусора. Наконец, четвертая строка заменяет значение переменной pet2 значением null. Как результат, к объекту «Stan» больше нельзя обратиться через какую-либо переменную. «Бедный» объект «Stan» оказывается недостижимым и официально становится доступным для сборки мусора.

Руководство по actionscript. часть 2, стр. 105

В языке ActionScript существует два особых случая, когда являющийся достижимым объект становится доступным для сборки мусора. Соответствующую информацию можно найти в разд. «Приемники событий и управление памятью» гл. 12 и в описании класса Dictionary справочника по языку ActionScript корпорации Adobe.

Алгоритм поэтапных пометок и вычищений

В предыдущем разделе рассказывалось, что объект становится доступным для сборки мусора (автоматического удаления из памяти) в тот момент, когда он оказывается недостижимым. Однако мы так и не выяснили, когда недостижимые объекты удаляются из памяти. В идеальном мире объекты должны удаляться из памяти сразу после того, как они оказались недостижимыми. Однако на практике незамедлительное удаление недостижимых объектов отнимает очень много времени и приводит к тому, что большинство нетривиальных программ выполняется медленно или вообще не отвечает на запросы. Соответственно среда выполнения Flash не удаляет недостижимые объекты из памяти незамедлительно. Вместо этого, она ищет и удаляет недостижимые объекты периодически, в процессе выполнения циклов сборки мусора.

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

После запуска среда Flash просит операционную систему оставить, или выделить, произвольный объем памяти, в которой будут храниться объекты выполняемой в настоящий момент программы. По мере выполнения программы объекты накапливаются в памяти. В любой момент времени некоторые объекть программы являются достижимыми, а другие могут оказаться недостижимыми Если программа создаст достаточное количество объектов, среда выполнение Flash в конечном счете решит выполнить цикл сборки мусора. В процессе выполнения цикла все объекты, находящиеся в памяти, проверяются на «достижимость». В результате все достижимые объекты помечаются и продолжают храниться в памяти, а все недостижимые объекты вычищаются (удаляются^

из памяти. Однако в большой программе проверка объектов на достижимость может оказаться достаточно длительной. Соответственно среда Flash разбивает циклы сборки мусора на небольшие части, или приращения, которые включаются в процесс выполнения программы. Кроме того, среда выполнения Flash использует механизм отложенного подсчета ссылок для повышения производительности процесса сборки мусора. Информацию о механизме отложенного подсчета ссылок можно найти в статье «The Memory Management Reference: Beginner’s Guide: Recycling» по адресу http://www. memorymanagement. org/articles/ recycle. html#reference. deferred.

В языке ActionScript циклы сборки мусора обычно запускаются тогда, когда объем памяти, необходимый для хранения объектов программы, достигает объема памяти, выделенного среде Flash. Однако ActionScript не дает никакой гарантии относительно времени запуска циклов сборки мусора. Более того, программист не может заставить среду Flash выполнить цикл сборки мусора.

Намеренное освобождение объектов

В языке ActionScript не существует прямого способа для удаления объекта из системной памяти. Удаление любых объектов программы осуществляется косвенно, через автоматическую систему сборки мусора.

Тем не менее, хотя программа и не может самостоятельно удалить объект из системной памяти, она по крайней мере может сделать этот объект доступным для удаления, устранив все ссылки на него. Чтобы устранить все ссылки на объект, мы должны вручную удалить этот объект из любых массивов, содержащих его, и присвоить значение null (или другое подходящее значение) всем переменным, ссылающимся на него.

Тот факт, что объект является доступным для сборки мусора, вовсе не означает, что он будет незамедлительно удален из памяти. Среда выполнения Flash просто получает разрешение на удаление этого объекта, когда (и если) запустится цикл сборки мусора.

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

^ I Когда это возможно, вы должны стараться повторно использовать существующие объ-м?; 4 „ екты, а не удалять их.

Руководство по actionscript. часть 2, стр. 106

Снова вернемся к программе по созданию виртуального зоопарка и рассмотрим следующий код. В нем описывается метод для кормления животного яблоками.

package { import flash. display. Sprite: import zoo.*;

public class VirtualZoo extends Sprite { private var pet:Virtual Pet;

public function VirtualZoo ( ) { pet = new VirtualPetC’Stan»): pet. eat(new Applet )); pet. eat(new Apple( )); pet. eat(new Apple( )); pet. eat(new Apple( )); pet. eat(new Apple( ));

}

}

}

Обратите внимание, что при каждом кормлении животного предыдущий код создает новый экземпляр класса Apple и передает этот экземпляр в метод eat ( ). Всякий раз, когда завершается выполнение метода eat ( ), все ссылки на переданный в этот метод экземпляр класса Apple теряются, поэтому данный экземпляр становится доступным для сборки мусора. Теперь представьте, что произойдет, если мы скормим животному 1000 объектов класса Apple. Затраты нашей программы на обработку данных будут включать не только затраты на создание объектов класса Apple, но и затраты на их удаление из памяти в процессе сборки мусора. Чтобы минимизировать подобные затраты, лучше создать один повторно используемый объект класса Apple и применять его при каждом кормлении животного яблоком. Следующий код демонстрирует процесс пятикратного кормления животного одним и тем же объектом класса Apple:

package { import flash. display. Sprite: import zoo.*:

public class VirtualZoo extends Sprite { private var pet-.Virtual Pet: private var apple:Apple;

public function VirtualZoo ( ) { pet = new VirtualPetC’Stan»): apple = new Apple( );

pet. eat(apple); pet. eat(apple); pet. eat(apple); pet. eat(apple); pet. eat(apple);

}

}

}

К затратам предыдущего кода можно отнести затраты на создание единственного объекта. При этом не будет вообще никаких затрат на сборку мусора. Этот подход гораздо более эффективен по сравнению с предыдущим подходом, заключавшимся в создании нового объекта класса Apple и его последующем удалении для каждого вызова метода eat ( )!

Деактивация объектов

Как уже известно, удаление всех ссылок на объект делает его доступным для сборки мусора. Тем не менее даже после этого объект продолжает существовать в памяти до тех пор, пока среда выполнения Flash не решит «смести» его в одном из циклов сборки мусора. С того момента, как объект становится доступным для сборки мусора, и до того, как он фактически будет удален из системной памяти, данный объект продолжает получать события и, в случае объектов класса Function, может по-прежнему вызываться функцией setlnterval ( ).



Полезные ссылки
Случайные записи
  • 17.06.2010">Самоучитель по креативному веб-дизайну. Книга 4, стр.1
  • 05.08.2011">МФУ от Hewlett-Packard – качество и надежность
  • 19.03.2011">Руководство по actionscript. часть 2, стр. 107
  • 14.03.2011">Руководство по actionscript. часть 3, стр. 072
  • 02.03.2011">Руководство по actionscript. часть 5, стр. 096
  • 16.06.2010">Самоучитель по креативному веб-дизайну. Книга 4, стр.30
  • 07.03.2011">Руководство по actionscript. часть 4, стр. 126
  • 18.03.2011">Руководство по actionscript. часть 2, стр. 121
  • 23.01.2011">Руководство по actionscript. часть 1, стр. 029
  • 19.05.2010">Самоучитель по креативному веб-дизайну. Книга 2, стр.10
  • 11.05.2010">Самоучитель по креативному веб-дизайну. Книга 1, стр.44
  • 14.03.2011">Руководство по actionscript. часть 3, стр. 082
  • 11.03.2011">Руководство по actionscript. часть 3, стр. 146
  • 19.05.2010">Самоучитель по креативному веб-дизайну. Книга 2, стр.29
  • 10.05.2010">Самоучитель по креативному веб-дизайну. Книга 1, стр.56
Опрос

Какие цвета вы предпочитаете?

View Results

Loading ... Loading ...