PDA

Просмотр полной версии : DisplayList: удаление одного экземпляра посредством другого


Fogflasher
16.10.2013, 11:26
Всем привет.

Есть документ-класс Main, который отображает экземпляр класса Spritekeeper.

Класс Spritekeeper отображает спрайт (создан в библиотеке и пролинкован) и текстфилд-кнопку (добавленную кодом).
На "кнопку" добавлен слушатель, с помощью которого этот спрайт нужно удалить.
Но удалять его нужно не напрямую, а посредством экземпляра отдельного класса Spritecloser.

30103

И вот, например, я пытаюсь сделать это так:

Класс: Main.as
package
{

import flash.display.*;
import flash.events.*;


public class Main extends Sprite
{

public var spriteKeeper:Spritekeeper;

public function Main()
{

spriteKeeper = new Spritekeeper();
this.addChild(spriteKeeper);

}


}


}


Класс: Spritekeeper.as
package
{

import flash.display.*;
import flash.events.*;
import flash.text.*;


public class Spritekeeper extends Sprite
{

public var sprite1:Sprite1;
public var spriteCloser:Spritecloser;

private var controlObject:TextField;

public function Spritekeeper():void
{

//--- отобразим sprite1 ---//
sprite1 = new Sprite1();
sprite1.x = 100, sprite1.y = 100;
this.addChild(sprite1);

//--- отобразим кнопку ---//
controlObject = new TextField();
controlObject.x = 100, controlObject.y = 300;
controlObject.text = " REMOVE SPRITE ";
controlObject.autoSize = TextFieldAutoSize.LEFT;
controlObject.background = true;
controlObject.backgroundColor = 0xFEC792;
controlObject.border = true;
controlObject.borderColor = 0xA05001;
controlObject.selectable = false;
this.addChild(controlObject);
controlObject.addEventListener(MouseEvent.CLICK, removeSprite);

//--- экземпляр очищающего класса ---//
spriteCloser = new Spritecloser();

}

private function removeSprite(e:Event)
{

spriteCloser.removeContent(sprite1);

}


}


}



Класс: Spritecloser.as
package
{

import flash.display.*;
import flash.events.*;

public class Spritecloser extends Main
{

public function Spritecloser():void
{
//--- empty constructor
}

public function removeContent(mc:Sprite):void
{
trace("removeContent() started...", mc);
spriteKeeper.removeChild(mc);
}

}

}

Однако, такой вариант приводит к ошибке:
Error: Error #2136: The SWF file file:///С|/Classes%5Ftests/Main.swf contains invalid data.
at Spritekeeper$iinit()
at Main$iinit()

Два вопроса:
1. Почему происходит ошибка? Как ее исправить?
2. Насколько крив такой подход, и можно ли сделать как-то проще?

belv
16.10.2013, 12:34
Ошибка у вас в классе Spritecloser, Вы расширяете главный класс Main, что и приводит к ошибке.
Вот так работает.

package
{

import flash.display.*;
import flash.events.*;

public class Spritecloser extends Sprite
{

public function Spritecloser():void
{
//--- empty constructor
}

public function removeContent(mc:Sprite):void
{
if(mc.stage != null)
{
mc.parent.removeChild(mc);
trace("removeContent() started...", mc);
}
}
}
}

Fogflasher
16.10.2013, 14:09
belv, большое спасибо, всё работает.

Хотелось бы еще вот такие моменты прояснить:

1. Свойство stage у спрайтов, насколько я понимаю, служит индикатором того, что данный объект находится в Display List, то есть виден на экране. Это правильно? Или оно имеет более широкий смысл.

2. Почему всё-таки нельзя наследоваться от Main? Могу лишь догадываться, что возникает какая-то логическая рекурсия, когда по кольцу все друг на друга ссылаются.

3. Если бы нужно было удалить не sprite1, а экземпляр spriteKeeper (который мы добавляем в Main) то как это можно было бы реализовать?
Вот такой вариант:
private function removeSprite(e:Event)
{

//spriteCloser.removeContent(sprite1);
spriteCloser.removeContent(spriteKeeper);

}

... не работает, выдает ошибку: 1120: Access of undefined property spriteKeeper., видимо потому, что переменная spriteKeeper этому классу не видна (но ведь она же public, почему нет?).

in4core
16.10.2013, 16:27
1. Более глобальный смысл :) Это стейдж, он общий для всего.
2. Тут вопрос не почему, а зачем? У вас есть точка входа - это мейн, все вы вошли через него назад пути нет, зачем возвращаться? Да даже объяснять не надо - просто так нельзя и не нужно делать вот и все.
3. И по теме : какие то вы мутагенные связи придумываете, упрощайте себе жизнь - а не усложняйте , ведь в программировании все предельно просто, по сравнению с математикой :)

Fogflasher
17.10.2013, 11:20
1. Хм, как-то это смущает. Не понимаю, как можно нечто такое крутое, глобальное и сакральное, как Stage, превратить в какое-то мелкое свойство класса. Это типа ссылка на Stage, ссылка на экземпляр Stage, или какие-то трюки со статическими переменными?

2. Тут вот какое дело. Я так понимаю, что для того, чтобы публичная переменная класса Main была видна в одном из связанных с ним классов, нужно чтобы этот связанный класс был наследником Main, ведь верно? А в моем примере получается (если не ошибаюсь) - композиция. То есть один класс имеет внутри экземпляр другого. А при таком раскладе, как я понимаю, нельзя увидеть переменную основного класса, пусть даже она public.

3. Это да, но это скорее по причине смутного представления о том, как нужно разбивать один класс на ряд других.
Эту же задачу я могу легко реализовать в одном классе Main, не вопрос. Но я хочу понять, как сделать ее же посредством набора классов, так чтобы лучше изучить принципы наследования, композиции, видимости переменных, и т.п. И код разбитый на классы легче воспринимается.
Я вот написал тут одну тестовую хрень чисто в Main классе, юзая много ифов, кейзов, циклов, листенеров... но потом смотрю на нее, и думаю: омайнготт, этож месиво какое-то, сложновоспринимаемое даже с комментариями, лол.

belv
17.10.2013, 12:11
По первому пункту Вы верно мыслите, по-поводу рекурсии могу предположить, что да.Я никогда не задумывался над тем, что произойдет если расширить класс Main, не хочу писать свои мысли на этот счет, так как они могут быть ошибочны.Для того чтобы воспользоваться переменными другого объекта нужно иметь ссылку на этот объект, а так компилятор не знает где эта переменная или метод могут быть, он же не должен самостоятельно заходит в каждый класс и проверять, а нет ли тут той переменной? Даже более того, в любом другом классе может быть переменная с таким именем и как он должен будет решить, какую ему переменную использовать?
По 3 пункту не стоит разбивать классы так, чтобы каждый метод был в новом классе.Если Вам уже хочется, чтобы у Вас был в вооружении метод, который в любом месте можно использовать для удаления объекта со сцены, тогда задайте его как статический.
Вот так:

package
{
import flash.display.Sprite;
public class Spritecloser
{

public function Spritecloser():void
{
//--- empty constructor
}

public static function removeContent(mc:Sprite):void
{
if(mc.stage != null)
{
mc.parent.removeChild(mc);
trace("removeContent() started...", mc);
}
}
}
}

Его вызов

package
{

import flash.display.*;
import flash.events.*;
import flash.text.*;


public class Spritekeeper extends Sprite
{

public var sprite1:Sprite1;
//public var spriteCloser:Spritecloser;

private var controlObject:TextField;

public function Spritekeeper():void
{

//--- отобразим sprite1 ---//
sprite1 = new Sprite1();
sprite1.x = 100, sprite1.y = 100;
this.addChild(sprite1);

//--- отобразим кнопку ---//
controlObject = new TextField();
controlObject.x = 100, controlObject.y = 300;
controlObject.text = " REMOVE SPRITE ";
controlObject.autoSize = TextFieldAutoSize.LEFT;
controlObject.background = true;
controlObject.backgroundColor = 0xFEC792;
controlObject.border = true;
controlObject.borderColor = 0xA05001;
controlObject.selectable = false;
this.addChild(controlObject);
controlObject.addEventListener(MouseEvent.CLICK, removeSprite);

//--- экземпляр очищающего класса ---//
//spriteCloser = new Spritecloser();

}

private function removeSprite(e:Event)
{

Spritecloser.removeContent(sprite1);

}
}
}

А так Вы молодец, уже гораздо больше понимаете.Вы на верном пути.Удачи.

Fogflasher
17.10.2013, 17:05
Спасибо на добром слове ^_^
Хотя конешно практические навыки только-только проклевываются, и вопросов на понимание очень много.

Я никогда не задумывался над тем, что произойдет если расширить класс Main, не хочу писать свои мысли на этот счет, так как они могут быть ошибочны.
Ну вот, зато хотя бы понятно, что расширение Main это дурной тон, и практически не приветствуется.

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

mc.parent.removeChild(mc);

Не хотелось перегружать тему излишними вопросами, но есть еще один.
Что если на этот mc повешен слушатель, например так:

sprite1.addEventListener(MouseEvent.CLICK, clickHandler)


По идее, его тоже нужно удалить в этой статической функции.
Ну, при корректном подходе по крайней мере (хотя наверное GC его удалит сам, но не знаю точно).

И вот как тогда передать статическому методу имя этой функции?
Прямой вариант не прокатывает:
public static function removeContent(mc:Sprite):void
{
if(mc.stage != null)
{

mc.parent.removeEventListener(MouseEvent.CLICK, clickHandler);
mc.parent.removeChild(mc);

trace("removeContent() started...", mc);
}
}

А если попробовать название функции передавать по типу строки:
Spritecloser.removeContent(sprite1, "clickHandler");
... то возникают ошибки преобразования типов...
И я не уверен можно ли вообще имя функции как параметер передать?
Пробовал преобразовать в Object, но тоже implicit coercion ошибка идёт.

belv
17.10.2013, 21:07
Наверное более правильно, в том классе где Вы его добавляете на сцену повесить на него слушатель события REMOVED_FROM_STAGE

Объект.addEventListener(Event.REMOVED_FROM_STAGE , _handlerRemovedFromStage);
private function _handlerRemovedFromStage(e:Event):void
{
Объект.removeEventListener(Event.REMOVED_FROM_STAGE , _handlerRemovedFromStage);
Объект.removeEventListener(MouseEvent.CLICK, clickHandler);

}

Wolsh
17.10.2013, 23:12
1. Свойство stage у спрайтов, насколько я понимаю, служит индикатором того, что данный объект находится в Display List, то есть виден на экране.Свойство stage дисплейных объектов указывает на единственный экземпляр Stage, представляющий область отображения окна флэшплеера. Это ссылка на объект, а не "индикатор". Будь это индикатор, он имел бы тип Boolean (то есть true или false) и назывался, например, hasStage. И это свойство не равно null только тогда, когда список отображения, в котором находится экземпляр DisplayObject, реально добавлен на стейдж. То есть, команда container.addChild(mc) не гарантирует, что у mc появится ссылка на стейдж. Это случится только тогда, когда ссылка на стейдж будет у container. А на него точно так же действует это правило, и так вплоть до экземпляра Main // Документ-класса.
2. Тут вот какое дело.Всё не так. Кроме определения композиции. Оно верное))
Ну вот, зато хотя бы понятно, что расширение Main это дурной тон, и практически не приветствуется.Если Вы расширите мейн, ваша флэшка просто не запустится, даже если удастся ее скомпилировать. Так что да, "не приветствуется". Попробуйте почитать хоть что-нибудь о наследовании. Это не такая уж мозговзрывательная тема. Отбросьте страх, что это сложно, и все станет очень простым. Вкратце — если Вы расширяете Мейн, то при создании экземпляра такого класса Мейн "внутри него" тоже будет создан. Не экземпляр Мейн, как при композиции, но сам этот экземпляр класса и есть экземпляр Мейн + что-то еще. Но создание этого экземпляра прописано в классе Мейн, и значит он будет создан опять. А в нем — еще один Мейн. А в нем — еще один экземпляр, который снова содержит Мейн, и т.д. Это бесконечный процесс. Плеер вывалится через 15 секунд или раньше, если забъет всю память.
По идее, его тоже нужно удалить в этой статической функции.
Ну, при корректном подходе по крайней мере (хотя наверное GC его удалит сам, но не знаю точно).Нет смысла.
Если речь о мышиных событиях, то их не сможет посылать и принимать объект, не находящийся на стейдж. Да просто как вы по нему кликнете, если его нет на экране?))) // на самом деле конечно все технически серьезней, но не важно). При чем здесь GC вообще не понятно. Вы часом не путаете removeChild() с удалением объекта из памяти? Убрав объект со стейджа, Вы же его не удаляете. Он продолжает существовать в памяти, Вы можете добавить его в отображение снова в любой момент. Но даже если Вы собираетесь удалить этот объект совсем, нет никакого смысла убирать с него слушатели, пустота не посылает событий, знаете ли. То что Вы где-то краем уха слышали и не поняли про листенер и GC:
когда Вы в контексте this добавляете объекту obj слушатель на события от него
obj.addEventListener(eventType, handler);
то Вы не сохраняете этим какую-то чудо-ссылку на obj, которая помешает GC этот obj удалить.
Ровно наоборот. Вы отдаете в obj ссылку на handler, на функцию, описанную в том объекте, в котором написан этот код (this). Таким образом Вы передаете объекту obj ссылку на this в виде this.handler. Объект obj не может хранить ссылку на какой то handler в вакууме. Он запоминает ссылку на объект this и ЕГО метод handler. И пока в obj существует слушатель от this, Вы не можете удалить this. Удалить obj это никак не мешает. А после удаления obj, естественно, и ссылка в нем на this тоже удалится. Так что не надо выдумывать никакой охоты на ведьм.

Fogflasher
18.10.2013, 11:12
belv, Наверное более правильно, в том классе где Вы его добавляете на сцену повесить на него слушатель события REMOVED_FROM_STAGE

Попробовал, всё работает, благодарю.
И закрадывается подозрение, что передача функции как параметра это нечто редкое, и не благославляемое по канону.


Wolsh, не скрою, рад что вы отclickнулись.

Свойство stage дисплейных объектов указывает на единственный экземпляр Stage, представляющий область отображения окна флэшплеера. Это ссылка на объект, а не "индикатор".
Ок, в целом понятно.

когда список отображения, в котором находится экземпляр DisplayObject, реально добавлен на стейдж.
Имею сомнения в правильности понимания этой фразы. Как список отображения можно реально добавить на стэйдж?
Думаю, вы имеете ввиду акт прописывания класса Main как документ класса. И тогда его экземпляр автоматически попадает в список отображения (то есть на стэйдж). А дальше уже по цепочке addChild'ов, тут понятно.

Всё не так
Okay.jpg. Но как именно не так? То есть всё прямо-противоположно, тому что я там выше написал (а значит: публичные переменные и методы видны всем *.as-файлам в проекте, независимо от)


Попробуйте почитать хоть что-нибудь о наследовании. Это не такая уж мозговзрывательная тема.
Буду признателен если что-нибудь такое нуб-доступное посоветуете. Я кстати даже не в курсе, является ли тема наследования универсальной и независимой от языка программирования, или всегда есть специфика в реализации для AS3.0, например.

А в нем — еще один Мейн. А в нем — еще один экземпляр, который снова содержит Мейн, и т.д. Это бесконечный процесс.
Всё понял, прикольно : )

При чем здесь GC вообще не понятно. Вы часом не путаете removeChild() с удалением объекта из памяти? Убрав объект со стейджа, Вы же его не удаляете.
Да, попутал, действительно. Потому что исходил "из общих соображений", которые были у Мука и в некоторых камментах этого форума, согласно которым: "Если ты сделал где-то addListener, то ему должен соответствовать парный removeListener, а если это не так, то жди проблем и побочных эффектов".

...Таким образом Вы передаете объекту obj ссылку на this в виде this.handler. Объект obj не может хранить ссылку на какой то handler в вакууме. Он запоминает ссылку на объект this и ЕГО метод handler. И пока в obj существует слушатель от this, Вы не можете удалить this. Удалить obj это никак не мешает...
Не уверен что полностью понимаю практическое следствие из вышесказанного, в применении к целесообразности функции removeListener.
Я понял так: removeListener нужен только когда мы удаляем this-экземпляр. Но если даже мы не внедрили этот метод, то GC удалит слушатель сам, хотя возможно с какими-то побочными эффектами. В случае же с obj-экземпляром: GC либо вообще не удалит этот слушатель (так как это не нужно), либо удалит - но без проблем.

Wolsh
18.10.2013, 12:45
Имею сомнения в правильности понимания этой фразы. Как список отображения можно реально добавить на стэйдж?Да, тут есть некоторая путаница. Список отображения на самом деле не один, он есть у каждого экземпляра DisplayObjectContainer (т.е. Sprite, Loader, Stage, MovieClip). С помощью addChild() Вы добавляете объект в список отображения контейнера. Но на экране он естественно появится только в том случае, если вся цепочка будет в списке отображения стейджа, то есть области отображения плеера. Мейн, конечно, добавляется на стейдж автоматически (и конечно, мало кто додумается его удалить со стейджа зачем-то), однако это не значит что все контейнеры всегда добавлены в список отображения Мейна. Это как раз в наших руках.
Но как именно не так?Снова не так.
Публичные свойства и методы (говорят еще "члены класса") экземпляра видны всем экземплярам, имеющим ссылку на данный экземпляр. Например, в классе А создается экземпляр класса В. На него есть ссылка, и его публичные члены доступны через экземплярВ.метод(). Вобщем, везде где Вы можете написать экземплярВ, Вы можете и вызвать его метод() или обратиться к public свойству. А к private не можете. А к internal можете только если классы А и В лежат в одном пакете, это public "для своих".
Я кстати даже не в курсе, является ли тема наследования универсальной и независимой от языка программированияНюансы конечно есть, но в целом законы одни. В AS3 нет абстрактных классов, как во многих других ЯП, соответственно нет виртуальных функций и прочего, связанного с темой. Кроме того, понятия Класса и его роли не во всех языках схожи. Но в целом в плане наследования нет никаких противоречивых концепций, все в духе ООП.
Я понял так: removeListener нужен только когда мы удаляем this-экземпляр. Но если даже мы не внедрили этот метод, то GC удалит слушатель самНет, не удалит. Вы не можете вообще "удалить" никакой экземпляр. Это может только GC. Вы можете только "забыть" экземпляр, то есть удалить все ссылки на него. Если на экземпляр нет ни одной ссылки, значит все о нем забыли и никто не сможет к нему обратиться. Только в этом случае GC может его удалить из памяти насовсем, как никому не нужный. Но если Вы отдали кому-то ссылку на него в виде ссылки на его метод-handler, то его "помнят" и он кому-то "нужен". Вы можете обнулить все переменные, в которых хранились ссылки на этот экземпляр, но источники событий, в которые он передал ссылки на свои методы-хэндлеры, будут его помнить, и это не позволит GC удалить этот экземпляр. Он должен сначала отписаться от всех событий, которые слушает. Поэтому removeEventListener() конечно же важен, только не надо путать, чей именно.

Akopalipsis
18.10.2013, 23:31
У меня тоже вопрос про удаление, есть переменная с типом int, когда я пытаюсь её занулить,
то FD предупреждает, что инт не может ровняться нулю. И я пришёл в замешательство, потому что не знаю
как нужно нулить переменные с числовыми типами. Ведь правильно же null, а не NaN?)
_counter = null;

Wolsh
19.10.2013, 01:07
Ведь правильно же null, а не NaN?Правильно NaN.

Akopalipsis
19.10.2013, 01:13
Спасибо! я почему то думал, что удалять всё через null.

Wolsh
19.10.2013, 01:55
"удалять"?
Что, _counter куда-то удалится?))

Akopalipsis
19.10.2013, 02:10
Честно сказать, я не могу ответить на Ваш вопрос))
Наверное я понял свою глупость. я вот как рассуждал - переменная написана символами и значит, уже имеет вес в памяти. И тут дальше продолжение - что если её не занулить, то и место она будет по прежнему занимать)) GC он же обьекты удаляет.. не код же он из классов выкорчёвывает))

Wolsh
19.10.2013, 02:56
Переменная типа int. Она хранит значение-число, а не ссылку на область памяти, по той простой причине, что ссылка на область памяти займет столько же памяти (если не больше), сколько и само значение переменной. Нет смысла помнить два числа вместо одного.

КорДум
19.10.2013, 10:43
Стоит добавить, что занулять стоит ссылки на не простые типы. String и вариации численных типов являются простыми типами, с ними никаких манипуляций проводить не требуется. Они перестали быть нужны — GC придет с косой и заберет их всех, никому не оставит шанса. А вот сложные типы — Object, Array, Sprite, MyCoolClass и тому подобное — требуют внимательного к себе отношения. Именно их и стоит заNULLять, чтобы ссылок на них не осталось и коса GC коснулась и их.

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

Я даже больше скажу. Отписываться от событий тоже не обязательно (хотя является хорошим тоном и грамотной реализацией). Отпиской вы только мгновенно убьете постоянно отрабатывающие хэндлеры (например таймер или enterFrame), чтобы они зря на фоне не работали до тех пор, пока GC соизволит явиться.

Изучите такую штуку как профайлер. Он есть в FD, FB, FDT вроде тоже наделен. В IDEA кажется тоже имеется. MonsterDebugger тот же. В нем видно, какие объекты есть в памяти и можно посмотреть, что случается с ними, если принудительно позвать сборщик мусора. И учтите, что профайлеры не совершенны, они по какой-то нелепой случайности имеют свойство некоторые ссылки оставлять для себя и не отдавать никому.

Akopalipsis
19.10.2013, 14:33
КорДум Спасибо! Уже не когда не забуду.