Xamarin.iOS11.10からWeakAttributeが追加されました。
これによって以前まではちょっとめんどくさいことをしなければいけなかった循環参照回避のためのフィールドの弱参照が凄い楽に書けるようになりました。
循環参照とは?
循環参照はオブジェクト同士がお互いに参照を持っている状態のことで、Objective-CやSwiftなどの場合オブジェクトが使われなくなったあとも適切に開放されず、メモリリークが起きます。
参考: メモリー管理とパフォーマンスの最適化 | Xamarin : XLsoft エクセルソフト
C#では基本循環参照によるメモリリークは起きないのですが、Xamarin.iOSでObjective-CのAPIを呼び出すことで発生する場合があります。
public partial class ViewController : UIViewController
{
protected ViewController(IntPtr handle) : base(handle)
{
}
public override void ViewDidLoad()
{
base.ViewDidLoad();
var strongView = new StrongView(this);
// ここでObjective-CのAPI(UIView.AddSubview)に
// ViewControllerの参照を持ったStrongViewを渡したことで
// 循環参照発生
this.View.AddSubview(strongView);
}
partial void tapped(NSObject sender)
{
var viewController = UIStoryboard.FromName("Main", null).InstantiateInitialViewController();
NavigationController.PushViewController(viewController, true);
}
protected override void Dispose(bool disposing)
{
base.Dispose(disposing);
Console.WriteLine($"{nameof(ViewController)} is disposed.");
}
}
class StrongView: UIView
{
private ViewController viewController;
public StrongView(ViewController viewController)
{
this.viewController = viewController;
}
protected override void Dispose(bool disposing)
{
base.Dispose(disposing);
Console.WriteLine($"{nameof(StrongView)} is disposed.");
}
}
上記の例のようなコードを書くと本来参照が開放されるときに呼ばれるDispose
メソッドが(ViewController・StrongViewともに)いつまでも呼ばれず、メモリを圧迫します。
今までの解決策(WeakReferenceクラス)
上のコードではStrongView
がViewController
の参照を直接持っているのが問題でした。
なのでWeakReference
クラスを間に挟むことによって強参照を避けます。
class WeakReferenceView : UIView
{
private WeakReference<ViewController> weakViewController;
public WeakReferenceView(ViewController viewController)
{
this.weakViewController = new WeakReference<ViewController>(viewController);
}
}
上記のようにすることで弱参照となり、ViewControllerが使われなくなったときにちゃんとDisposeメソッドが呼ばれオブジェクトは破棄されるようになります。
ただし、weakViewController
からViewController
を取り出すには以下のようにするしかなく、(C#7.0で少し簡潔になりましたが)手間になります。
if(!weakViewController.TryGetTarget(out var strongThis))
{
// ViewControllerの参照が開放されていた場合
return;
}
// ViewControllerの参照が開放されていない場合
新しい解決策(WeakAttribute)
そこで今回新しく追加されたWeakAttribute
の出番です。
WeakAttribute
を使うと以下のように書けます。
class WeakView : UIView
{
[Weak]
private ViewController viewController;
public WeakView(ViewController viewController)
{
this.viewController = viewController;
}
}
とても簡潔になりました!
WeakAttributeをつけることでコードを汚さず、今まで通りの書き方で弱参照にすることができます。
もちろん、WeakAttribute
を付けたフィールドは参照先が消えるとnullになるので扱いには気をつけたほうがいいです。
まとめ
Xamarin.iOS11.10から追加されたWeakAttribute
を使うことでめんどくさかったフィールドの弱参照が簡潔に書けるようになりました。
ただし、もう1つのよくある循環参照であるクロージャでthisをキャプチャすることによる循環参照は今の所従来通りのWeakRreference
クラスを用いたやり方しかありません。
こちらのほうが頻繁に起きる(しかも分かりづらい)ので今後に期待したいところです。