LoginSignup
0
0

【Unity】ネイティブプラグインにおける Dispose のタイミングについて

Last updated at Posted at 2023-12-23

本記事について

この記事は CoreBluetoothForUnity Advent Calendar 2023 の24日目の記事です。

ネイティブプラグインにおいて、アンマネージドなオブジェクトを作成したらそれは必ず明示的に破棄する必要があります。

その上で、「誰」が「いつ」破棄するのかについて開発する上でいくつかパターンがあったためそれをまとめます。

環境

  • CoreBluetoothForUnity 0.4.7

パターン1 使い終わったら破棄する

基本のパターンです。

SampleLightControl_Central.cs では CBCentralManager のインスタンスを Start で作って OnDestroy で破棄しています。

SampleLightControl_Central.cs
    public class SampleLightControl_Central : MonoBehaviour, ICBCentralManagerDelegate, ICBPeripheralDelegate
    {
...
        CBCentralManager _centralManager;
...
        void Start()
        {
            var initOptions = new CBCentralManagerInitOptions() { ShowPowerAlert = true };
            _centralManager = new CBCentralManager(this, initOptions);
...
        }
...
        void OnDestroy()
        {
            if (_centralManager != null)
            {
                _centralManager.Dispose();
                _centralManager = null;
            }
        }
    }

CoreBluetoothForUnity の README にも記載しています。

自分で IDisposable を実装したクラスをインスタンス化する場合にはこれになるかなと思います。

パターン2 大元のオブジェクトが破棄されたら破棄する

CBCentralManager が持つ、CBPeripheralCBCentralManager が破棄されたら自動的に破棄されます。

ライブラリがインスタンスするものはライブラリが破棄する役割を持つというパターンです。
以下で引数で渡る peripheral です。

リンク

SampleLightControl_Central.cs
void ICBCentralManagerDelegate.DidDiscoverPeripheral(CBCentralManager central, CBPeripheral peripheral, int rssi)
{
    _header.SetStateText("Connecting...");
    _peripheral = peripheral;
    peripheral.Delegate = this;
    central.StopScan();
    central.Connect(peripheral);
}

CBCentralManager.cs

CBCentralManager.cs
public void Dispose()
{
    if (_disposed) return;

...
    foreach (var peripheral in _peripherals.Values)
    {
        peripheral.Dispose();
    }

    _disposed = true;
}

作ったらキャッシュして全部破棄しています。

パターン3 閾値を設けて破棄する

CoreBluetooth では通信のたびに、CBATTRequestというクラスを使ってデータをやり取りします。

このクラスは通信ごとにインスタンス化されるため数が多くなってしまいますが、C# においては参照カウントが0になったら破棄するという実装をすることはできないという問題がありました。

そのため、閾値を設けて破棄するという実装をしています。

CBPeripheralManager.cs

CBPeripheralManager.cs
    public class CBPeripheralManager : CBManager, IPeripheralManagerData, INativePeripheralManagerDelegate, IDisposable
    {
...
        static readonly int s_maxATTRequests = 10;
        Queue<IDisposable> _attRequestDisposables = new Queue<IDisposable>();
...
        void AddATTRequestDisposable(IDisposable disposable)
        {
            _attRequestDisposables.Enqueue(disposable);
            while (_attRequestDisposables.Count > s_maxATTRequests)
            {
                _attRequestDisposables.Dequeue().Dispose();
            }
        }
...

        void INativePeripheralManagerDelegate.DidReceiveReadRequest(SafeNativeATTRequestHandle requestHandle)
        {
            CallbackContext.Post(_ =>
            {
                if (_disposed) return;
                var request = new CBATTRequest(requestHandle, new NativeATTRequestProxy(requestHandle, this));
                _delegate?.DidReceiveReadRequest(this, request);
                AddATTRequestDisposable(request);
            }, null);
        }
...
        public void Dispose()
        {
            if (_disposed) return;

...
            while (_attRequestDisposables.Count > 0)
            {
                _attRequestDisposables.Dequeue().Dispose();
            }

            _disposed = true;
        }
    }

Queue を使って閾値を超えたら古いものから破棄という実装です。
さらにパターン2のように大元のオブジェクトが破棄されたら同時に全部破棄します。

IDisposable をあえて実装しないという手

パターン2 と パターン3 ではライブラリ側が破棄する役割を持っています。
であるならば Dispose メソッドを公開する必要はないため、そもそも IDisposable を実装しないという手もありかなと思いました。

それによって、ユーザーがこのオブジェクトは破棄すべきなんだろうか..?と悩む必要がなくなるかもしれません。(需要があるかはわかりませんが..)

おわりに

本記事ではネイティブプラグインにおける Dispose の呼び出し方の種類についてまとめました。
本記事の内容は CoreBluetoothForUnity の開発で得た知見ですが、他のライブラリがどうやっているか等は正直わからない部分も多いです。他のパターンもご存知の方いたらコメントいただけると嬉しいです!

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0