Xamarin.iOSでWeakAttributeを使って循環参照を回避する

Xamarin.iOS11.10から[WeakAttribute](https://developer.xamarin.com/releases/ios/xamarin.ios_11/xamarin.ios_11.10/#WeakAttribute)が追加されました。

これによって以前まではちょっとめんどくさいことをしなければいけなかった循環参照回避のためのフィールドの弱参照が凄い楽に書けるようになりました。

## 循環参照とは?
循環参照はオブジェクト同士がお互いに参照を持っている状態のことで、Objective-CやSwiftなどの場合オブジェクトが使われなくなったあとも適切に開放されず、メモリリークが起きます。

参考: [メモリー管理とパフォーマンスの最適化 | Xamarin : XLsoft エクセルソフト](https://www.xlsoft.com/jp/products/xamarin/memory_perf_best_practices.html)

C#では基本循環参照によるメモリリークは起きないのですが、**Xamarin.iOSでObjective-CのAPIを呼び出す**ことで発生する場合があります。

```csharp
    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`クラスを間に挟むことによって強参照を避けます。

```csharp
    class WeakReferenceView : UIView
    {
        private WeakReference<ViewController> weakViewController;

        public WeakReferenceView(ViewController viewController)
        {
            this.weakViewController = new WeakReference<ViewController>(viewController);
        }
    }
```

上記のようにすることで弱参照となり、ViewControllerが使われなくなったときにちゃんとDisposeメソッドが呼ばれオブジェクトは破棄されるようになります。

ただし、`weakViewController`から`ViewController`を取り出すには以下のようにするしかなく、(C#7.0で少し簡潔になりましたが)手間になります。

```csharp
if(!weakViewController.TryGetTarget(out var strongThis))
{
    // ViewControllerの参照が開放されていた場合
    return;
}

// ViewControllerの参照が開放されていない場合
```

## 新しい解決策(WeakAttribute)
そこで今回新しく追加された`WeakAttribute`の出番です。

`WeakAttribute`を使うと以下のように書けます。

```csharp
    class WeakView : UIView
    {
        [Weak] 
        private ViewController viewController;

        public WeakView(ViewController viewController)
        {
            this.viewController = viewController;
        }
    }
```

とても簡潔になりました!

WeakAttributeをつけることでコードを汚さず、今まで通りの書き方で弱参照にすることができます。

もちろん、`WeakAttribute`を付けたフィールドは参照先が消えるとnullになるので扱いには気をつけたほうがいいです。

## まとめ
Xamarin.iOS11.10から追加された`WeakAttribute`を使うことでめんどくさかったフィールドの弱参照が簡潔に書けるようになりました。

ただし、もう1つのよくある循環参照であるクロージャでthisをキャプチャすることによる循環参照は今の所従来通りの`WeakRreference`クラスを用いたやり方しかありません。
こちらのほうが頻繁に起きる(しかも分かりづらい)ので今後に期待したいところです。