Копирование простых объектов
Чаще всего ByteArray используют для копирования объектов. AMF сериализатор и десериализатор (не люблю эти слова, но не нашёл подходящего перевода) доступен через ByteArray API. Для дублирования объектов с помощью ByteArray мы будем использовать методы writeObject и readObject:
// Создаём пустой ByteArray
var stream:ByteArray = new ByteArray();
// Создаём объект
var parameters:Object = { age : 25, name : "Bob" };
// «Переводим» объект в формат AMF и сохраняем его в ByteArray
stream.writeObject( parameters );
// Сбрасываем позицию
stream.position = 0;
// Считываем объект
var objectCopy:Object = stream.readObject();
На основе этого кода можно создать функцию, которую мы сможем использовать для копирования объектов:
function copyObject(objectToCopy:*):*
{
var stream:ByteArray = new ByteArray();
stream.writeObject( objectToCopy );
stream.position = 0;
return stream.readObject();
}
Чтобы удостовериться, что мы действительно создали отдельную копию объекта, мы можем модифицировать свойства исходного объекта:
// Создаём объект
var parameters:Object = { age : 25, name : "Bob" };
var parametersCopy:Object = copyObject ( parameters );
parameters.nom = "Stevie";
/* outputs :
name : Bob
age : 25
*/
for ( var p:String in parametersCopy )
{
trace( p, " : ", parametersCopy[p] );
}
function copyObject ( objectToCopy:* ):*
{
var stream:ByteArray = new ByteArray();
stream.writeObject( objectToCopy );
stream.position = 0;
return stream.readObject();
}
Чуть ниже мы узнаем, как можно сохранять и восстанавливать более сложные типы данных, с помощью ByteArray.
Копирование пользовательских классов
Код, описанный ранее, не будет работать для пользовательских классов, которые вы можете использовать в вашем приложении. Разберём очень простой пример, скажем, вам нужно использовать объекты класса User:
package
{
public class User
{
private var _firstName:String;
private var _lastName:String;
public function set firstName (firstName:String):void
{
_firstName = firstName;
}
public function set lastName (lastName:String):void
{
_lastName = lastName;
}
public function get firstName ():String
{
return _firstName;
}
public function get lastName ():String
{
return _lastName;
}
}
}
Вам может понадобиться сохранить объект класса User на сервере в виде бинарных данных или локально в SharedObject. Если вы попробуете сохранить объект класса User в ByteArray и извлечь его, то при чтении, Flash Player будет проверять, был ли зарегистрирован тип User ранее, если нет, то этот объект будет «разбираться» как обычный объект:
// Создаём экземпляр класса User
var myUser:User = new User ();
// Устанавливаем свойства
myUser.firstName = "Stevie";
myUser.lastName = "Wonder";
// outputs :[object User]
trace ( myUser );
// Создаём ByteArray для сохранения экземпляра
var bytes:ByteArray = new ByteArray();
// Сохраняем экземпляр
bytes.writeObject ( myUser );
// Сбрасываем позицию
bytes.position = 0;
// outputs : false
trace ( bytes.readObject() is User );
Чтобы зарегистрировать тип User в Flash Player можно использовать метод registerClassAlias. После этого объекты класса User можно будет «разбирать» с помощью ByteArray.
// Создаём экземпляр класса User
var myUser:User = new User ();
// Устанавливаем свойства
myUser.firstName = "Stevie";
myUser.lastName = "Wonder";
// outputs :[object User]
trace ( myUser );
// Регистрируем тип User для «разбора»
registerClassAlias ( "userTypeAlias", User );
// Создаём ByteArray для сохранения экземпляра
var bytes:ByteArray = new ByteArray();
// Сохраняем экземпляр
bytes.writeObject ( myUser );
// Сбрасываем позицию
bytes.position = 0;
// При «разборе» сохраненённого объекта, автоматически будет использоваться нужный тип
var storedUser:User = bytes.readObject() as User;
// outputs : true
trace ( storedUser is User );
// outputs : Stevie Wonder
trace ( storedUser.firstName, storedUser.lastName );
Используя эту технику мы можем сохранять и восстанавливать экземпляры любых пользовательских классов. Как вы можете увидеть, сейчас мы работаем с копией объекта пользовательского класса:
storedUser.firstName = "Bobby";
storedUser.lastName = "Womack";
// outputs : Stevie Wonder
trace ( myUser.firstName, myUser.lastName );
// outputs : Bobby Womack
trace ( storedUser.firstName, storedUser.lastName );
Нужно помнить, что некоторые нативные типы данных не могут быть сериализованы/десериализованы с помощью AMF. Это относится к таким визуальным объектам, например DisplayObject. Например, если вы попробуете «разобрать» экземпляр MovieClip (если бы это можно было сделать, то это было бы очень круто =), то у вас ничего не получится:
// Создаём ByteArray
var bytes:ByteArray = new ByteArray();
// Пытаемся сохранить объетк DisplayObject
bytes.writeObject ( new MovieClip() );
// Сбрасываем позицию
bytes.position = 0;
// outputs : undefined
trace ( bytes.readObject() );
Поддержка визуальных объектов в AMF3 была бы очень полезным дополнением для списка поддерживаемых в AMF типов.
От переводчика
Мне нравится «упаковывать» все часто используемые функции в отдельные классы со статическими функциями и данный случай с копированием объектов не стал исключением =) Для этого я создал небольшой класс и попытался сохранить описанным способом некоторый свой класс, в ходе небольшого эксперимента я наткнулся на небольшой нюанс, который не описан в исходной статье: если конструктор классов нужного вам типа требует передачи обязательных параметров, то при «восстановлении» объекта возникнет ошибка, связанная с тем, что в конструктор не были переданы нужные данные. Благо, данная проблема не возникает, если конструктор требует передачи параметров, но они не являются обязательными.
Кстати, у себя в примере, я попробовал скопировать Array и Vector., и у меня всё это получилось). Необходимость сохранения/чтения данных в SharedObject у меня возникает довольно часто, поэтому, думаю, что данный способ разбора и копирования сложных классов будет мне очень и очень полезен.
Исходник примера