Я думал, что добавлю несколько конкретных примеров специально для контроллера представления. Многие из объяснений, не только здесь о переполнении стека, действительно хороши, но я лучше работаю с примерами из реального мира (у @drewag было хорошее начало для этого):
weak
, потому что они долгоживущие. Контроллер представления может закрыться до завершения запроса, поэтому self
больше не указывает на действительный объект при вызове замыкания. Если у вас есть замыкание, которое обрабатывает событие на кнопке. Это может быть unowned
, потому что как только контроллер вида уходит, кнопка и любые другие элементы, на которые он может ссылаться из self
, одновременно исчезают. Закрывающий блок также исчезнет одновременно.
class MyViewController: UIViewController {
@IBOutlet weak var myButton: UIButton!
let networkManager = NetworkManager()
let buttonPressClosure: () -> Void // closure must be held in this class.
override func viewDidLoad() {
// use unowned here
buttonPressClosure = { [unowned self] in
self.changeDisplayViewMode() // won't happen after vc closes.
}
// use weak here
networkManager.fetch(query: query) { [weak self] (results, error) in
self?.updateUI() // could be called any time after vc closes
}
}
@IBAction func buttonPress(self: Any) {
buttonPressClosure()
}
// rest of class below.
}
Я решил немного иначе, как я хотел для регистрации в одном событии и обработки всех добавлений и удалений в обработчике событий. Я начал с переопределения события изменения коллекции и перенаправления действий сброса на действия удаления со списком элементов. Все пошло не так, поскольку я использовал наблюдаемую коллекцию в качестве источника элементов для представления коллекции и получил сообщение «Действия диапазона не поддерживаются».
Наконец, я создал новое событие под названием CollectionChangedRange, которое действует так, как я ожидал от встроенной версии. act.
Я не могу себе представить, почему это ограничение было разрешено, и надеюсь, что этот пост, по крайней мере, остановит других от того, чтобы зайти в тупик, как я.
/// <summary>
/// An observable collection with support for addrange and clear
/// </summary>
/// <typeparam name="T"></typeparam>
[Serializable]
[TypeConverter(typeof(ExpandableObjectConverter))]
public class ObservableCollectionRange<T> : ObservableCollection<T>
{
private bool _addingRange;
[field: NonSerialized]
public event NotifyCollectionChangedEventHandler CollectionChangedRange;
protected virtual void OnCollectionChangedRange(NotifyCollectionChangedEventArgs e)
{
if ((CollectionChangedRange == null) || _addingRange) return;
using (BlockReentrancy())
{
CollectionChangedRange(this, e);
}
}
public void AddRange(IEnumerable<T> collection)
{
CheckReentrancy();
var newItems = new List<T>();
if ((collection == null) || (Items == null)) return;
using (var enumerator = collection.GetEnumerator())
{
while (enumerator.MoveNext())
{
_addingRange = true;
Add(enumerator.Current);
_addingRange = false;
newItems.Add(enumerator.Current);
}
}
OnCollectionChangedRange(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Add, newItems));
}
protected override void ClearItems()
{
CheckReentrancy();
var oldItems = new List<T>(this);
base.ClearItems();
OnCollectionChangedRange(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Remove, oldItems));
}
protected override void InsertItem(int index, T item)
{
CheckReentrancy();
base.InsertItem(index, item);
OnCollectionChangedRange(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Add, item, index));
}
protected override void MoveItem(int oldIndex, int newIndex)
{
CheckReentrancy();
var item = base[oldIndex];
base.MoveItem(oldIndex, newIndex);
OnCollectionChangedRange(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Move, item, newIndex, oldIndex));
}
protected override void RemoveItem(int index)
{
CheckReentrancy();
var item = base[index];
base.RemoveItem(index);
OnCollectionChangedRange(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Remove, item, index));
}
protected override void SetItem(int index, T item)
{
CheckReentrancy();
var oldItem = base[index];
base.SetItem(index, item);
OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Replace, oldItem, item, index));
}
}
/// <summary>
/// A read only observable collection with support for addrange and clear
/// </summary>
/// <typeparam name="T"></typeparam>
[Serializable]
[TypeConverter(typeof(ExpandableObjectConverter))]
public class ReadOnlyObservableCollectionRange<T> : ReadOnlyObservableCollection<T>
{
[field: NonSerialized]
public event NotifyCollectionChangedEventHandler CollectionChangedRange;
public ReadOnlyObservableCollectionRange(ObservableCollectionRange<T> list) : base(list)
{
list.CollectionChangedRange += HandleCollectionChangedRange;
}
private void HandleCollectionChangedRange(object sender, NotifyCollectionChangedEventArgs e)
{
OnCollectionChangedRange(e);
}
protected virtual void OnCollectionChangedRange(NotifyCollectionChangedEventArgs args)
{
if (CollectionChangedRange != null)
{
CollectionChangedRange(this, args);
}
}
}
Я просто просматривал код построения диаграмм в наборах инструментов Silverlight и WPF и заметил, что они также решили эту проблему (подобным образом) ... и я подумал, что смогу вперед и опубликуйте свое решение.
По сути, они также создали производную ObservableCollection и переопределили ClearItems, вызывая Remove для каждого очищаемого элемента.
Вот код:
/// <summary>
/// An observable collection that cannot be reset. When clear is called
/// items are removed individually, giving listeners the chance to detect
/// each remove event and perform operations such as unhooking event
/// handlers.
/// </summary>
/// <typeparam name="T">The type of item in the collection.</typeparam>
public class NoResetObservableCollection<T> : ObservableCollection<T>
{
public NoResetObservableCollection()
{
}
/// <summary>
/// Clears all items in the collection by removing them individually.
/// </summary>
protected override void ClearItems()
{
IList<T> items = new List<T>(this);
foreach (T item in items)
{
Remove(item);
}
}
}
Итак, я решил получить
Microsoft приложила ОЧЕНЬ много работы, чтобы всегда удостовериться, что NotifyCollectionChangedEventArgs не имеет данных при вызове сброса. Я предполагаю, что это было решение производительности / памяти. Если вы сбрасываете коллекцию из 100 000 элементов, я m при условии, что они не хотят дублировать все эти элементы.
Но, поскольку в моих коллекциях никогда не бывает более 100 элементов, я не вижу в этом проблемы.
В любом случае я создал унаследованный класс со следующим метод:
protected override void ClearItems()
{
CheckReentrancy();
List<TItem> oldItems = new List<TItem>(Items);
Items.Clear();
OnPropertyChanged(new PropertyChangedEventArgs("Count"));
OnPropertyChanged(new PropertyChangedEventArgs("Item[]"));
NotifyCollectionChangedEventArgs e =
new NotifyCollectionChangedEventArgs
(
NotifyCollectionChangedAction.Reset
);
FieldInfo field =
e.GetType().GetField
(
"_oldItems",
BindingFlags.Instance | BindingFlags.NonPublic
);
field.SetValue(e, oldItems);
OnCollectionChanged(e);
}
Для сценария присоединения и отсоединения обработчиков событий к элементам коллекции ObservableCollection также существует решение "на стороне клиента". В коде обработки события вы можете проверить, находится ли отправитель в ObservableCollection, используя метод Contains. Плюсы: вы можете работать с любой существующей ObservableCollection. Минусы: метод Contains работает за O(n), где n - количество элементов в ObservableCollection. Так что это решение для небольших ObservableCollections.
Еще одно решение "со стороны клиента" - использовать обработчик событий в середине. Просто регистрируйте все события в обработчике событий в середине. Этот обработчик событий в свою очередь уведомляет реальный обработчик событий через обратный вызов или событие. Если происходит действие Reset, удалите обратный вызов или событие, создайте новый обработчик событий в центре и забудьте о старом. Этот подход также работает для больших ObservableCollections. Я использовал это для события PropertyChanged (см. код ниже).
/// <summary>
/// Helper class that allows to "detach" all current Eventhandlers by setting
/// DelegateHandler to null.
/// </summary>
public class PropertyChangedDelegator
{
/// <summary>
/// Callback to the real event handling code.
/// </summary>
public PropertyChangedEventHandler DelegateHandler;
/// <summary>
/// Eventhandler that is registered by the elements.
/// </summary>
/// <param name="sender">the element that has been changed.</param>
/// <param name="e">the event arguments</param>
public void PropertyChangedHandler(Object sender, PropertyChangedEventArgs e)
{
if (DelegateHandler != null)
{
DelegateHandler(sender, e);
}
else
{
INotifyPropertyChanged s = sender as INotifyPropertyChanged;
if (s != null)
s.PropertyChanged -= PropertyChangedHandler;
}
}
}
Он не претендует на включение старых элементов, потому что Reset не означает, что список был очищен
Это означает, что произошла какая-то драматическая вещь. место, и стоимость работы над добавлением / удалением, скорее всего, превысит стоимость простого повторного сканирования списка с нуля ... так что это то, что вам следует делать.
MSDN предлагает пример повторной сортировки всей коллекции как кандидата на сброс.
Повторюсь. Сброс не означает очистку , это означает Ваши предположения о списке теперь неверны. Относитесь к нему как к совершенно новому списку . Clear является одним из примеров этого, но могут быть и другие.
Некоторые примеры:
У меня был такой список, в котором было много элементов, и он был привязан к WPF ListView
для отображения на экране.
Если вы очистите список и вызовете событие .Reset
, производительность будет практически мгновенной, но если вы вместо этого вызовете много отдельных событий .Remove
, производительность будет ужасной, поскольку WPF удаляет предметы один за другим.
Я также использовал .Reset
в моем собственном коде, чтобы указать, что список был повторно отсортирован, вместо того, чтобы выполнять тысячи отдельных операций Move
. Как и в случае с Clear, при возникновении множества отдельных событий производительность сильно падает.
ObservableCollection, а также интерфейс INotifyCollectionChanged явно написаны с учетом конкретного использования: Построение пользовательского интерфейса и его специфические характеристики производительности.
Когда вы хотите получать уведомления об изменениях коллекции, вас обычно интересуют только события Add и Remove.
Я использую следующий интерфейс:
using System;
using System.Collections.Generic;
/// <summary>
/// Notifies listeners of the following situations:
/// <list type="bullet">
/// <item>Elements have been added.</item>
/// <item>Elements are about to be removed.</item>
/// </list>
/// </summary>
/// <typeparam name="T">The type of elements in the collection.</typeparam>
interface INotifyCollection<T>
{
/// <summary>
/// Occurs when elements have been added.
/// </summary>
event EventHandler<NotifyCollectionEventArgs<T>> Added;
/// <summary>
/// Occurs when elements are about to be removed.
/// </summary>
event EventHandler<NotifyCollectionEventArgs<T>> Removing;
}
/// <summary>
/// Provides data for the NotifyCollection event.
/// </summary>
/// <typeparam name="T">The type of elements in the collection.</typeparam>
public class NotifyCollectionEventArgs<T> : EventArgs
{
/// <summary>
/// Gets or sets the elements.
/// </summary>
/// <value>The elements.</value>
public IEnumerable<T> Items
{
get;
set;
}
}
Я также написал свою собственную перегрузку Collection, где:
Конечно, можно добавить и AddRange.