モノトーンの伝説日記

OBS Studio と Blackmagic Design が大好き。

<mini> Xamarin.iOS で Delegate の待ちを Awaitable に変換したかった。

 タイトルの通り。

 以下が基本ヘルパークラス。

// MIT license
// Copyright (C) 2018 mntone. All right reserved.

using System;
using System.Runtime.CompilerServices;
using System.Threading;

namespace Mntone.YourProduct.Core.Extensions
{
    public interface IAwaitable
    {
        bool IsCompleted { get; }
        Action OnCompleted { get; }

        void ContinueWith(Action action);
    }

    public interface IAwaitable<T> : IAwaitable
    {
        T Result { get; }
    }

    public sealed class AwaitableHelper
    {
        private readonly IAwaitable _awaitable;

        public bool IsCompleted { get; private set; }
        public Action OnCompleted { get; private set; }

        public AwaitableHelper(IAwaitable awaitable)
        {
            _awaitable = awaitable;
        }

        public void SetCompleted()
        {
            IsCompleted = true;
            OnCompleted?.Invoke();
        }

        public void ContinueWith(Action action)
        {
            OnCompleted = action;
            if (_awaitable.IsCompleted && action != null)
                action();
        }
    }

    public static class IAwaitableExtensions
    {
        public class Awaiter<T> : INotifyCompletion
        {
            private static readonly SendOrPostCallback _postCallback = state => ((Action)state)();

            private readonly IAwaitable<T> _target = null;

            public Awaiter(IAwaitable<T> target) => _target = target;

            public void OnCompleted(Action continuation)
            {
                var context = SynchronizationContext.Current;
                _target.ContinueWith(() =>
                {
                    context.Post(_postCallback, continuation);
                });
            }

            public bool IsCompleted => _target.IsCompleted;
            public T GetResult() => _target.Result;
        }

        public static Awaiter<T> GetAwaiter<T>(this IAwaitable<T> awaitable)
        {
            return new Awaiter<T>(awaitable);
        }
    }
}

 使い方: iOS の Delegate ベースクラスを継承したものに IAwaitable をつけ,Helper Methods にあるものをコピペ。コンストラクターに Helper 初期化をしておく。そして Delegate を await する。

 まずは Delegate 側のサンプル

       private class YourDelegate : YourDelegateBase, IAwaitable<bool>
        {
            private readonly AwaitableHelper _awaitableHelper;

            public bool Result { get; private set; }

            public YourDelegate()
                : base()
            {
                _awaitableHelper = new AwaitableHelper(this);
            }

            public override void OverrideMethods(...)
            {
                // 処理を入れる。終わったら Result に変数を格納し,Helper の Completed を set。
                Result = true;
                _awaitableHelper.SetCompleted();
            }

           #region Helper Methods
            public bool IsCompleted => _awaitableHelper.IsCompleted;
            public Action OnCompleted => _awaitableHelper.OnCompleted;
            public void ContinueWith(Action action) => _awaitableHelper.ContinueWith(action);
           #endregion
        }
       public async Task<bool> SampleAsync()
        {
            var requestTask = new YourDelegate();
            request.Delegate = requestTask;
            var result = await requestTask;
            return result;
        }

 これにより,delegate を awaitable にすることにより外部実装は簡便化可能となる。もちろん,EAP パターンや Rx での実装可能だし,選択肢はこれだけじゃないが。