Как отправить не объявленный селектор без executeSelector:?

Фон: у меня есть объект (давайте назовем его BackendClient), который представляет соединение с сервером. Его методы генерируются в один @protocol и все они синхронны, поэтому я хочу создать прокси-объект, который будет вызывать их в фоновом режиме. Основная проблема - это возвращаемое значение, которое я, очевидно, не могу вернуть из асинхронного метода, поэтому мне нужно передать обратный вызов. "Легким" способом будет копирование всех BackendClientметоды и добавить аргумент обратного вызова. Но это не очень динамичный способ решения этой проблемы, в то время как природа ObjectiveC динамична. Это где performSelector: появляется. Это полностью решает проблему, но почти убивает прозрачность прокси-объекта.

Проблема: я хочу иметь возможность отправить не объявленный селектор на прокси (подкласс NSProxy) объект, как если бы он был уже объявлен. Например, у меня есть метод:

-(AuthResponse)authByRequest:(AuthRequest*)request

в BackendClient протокол. И я хочу, чтобы вызов прокси выглядел так:

[proxyClient authByRequest:myRequest withCallback:myCallback];

Но это не скомпилируется, потому что

Нет видимого @interface для 'BackendClientProxy' объявляет селектор 'authByRequest:withCallBack:'

ХОРОШО. Давайте немного успокоим компилятор:

[(id)proxyClient authByRequest:myRequest withCallback:myCallback];

Awww. Еще одна ошибка:

Не известен метод экземпляра для селектора 'authByRequest:withCallBack:'

Единственное, что приходит мне в голову и этот момент так или иначе конструируют новые @protocol с необходимыми методами во время выполнения, но я понятия не имею, как это сделать.

Вывод: мне нужно подавить эту ошибку компиляции. Есть идеи, как это сделать?

2 ответа

Если я понимаю, у вас есть синхронный, непоточный API, который вы хотите использовать в асинхронном режиме, чтобы не блокировать, скажем, основной цикл событий и т. Д.

Я бы добавил последовательную очередь в BackgroundClient:

@property(strong) dispatch_queue_t serialQueue;

 ... somewhere in your -init ...
 _serialQueue = dispatch_queue_create(..., serial constant);

Затем:

 - (void)dispatchOperation:(dispatch_block_t)anOperation
 {
      dispatch_async(_serialQueue, anOperation);
 }

Это можно использовать как:

 [myClient dispatchOperation:^{
       [myClient doSynchronousA];
       id result = [myClient doSynchronousB];
       dispatch_async(dispatch_get_main_queue(), ^{
            [someone updateUIWithResult:result];
       }
 }];

Это самый простой способ переместить BackgroundClient в асинхронную модель, не переписывая его и не подвергая его резкому рефакторингу.

Если вы хотите укрепить API, то создайте оболочку класса для BackendClient, которая содержит экземпляр клиента и последовательную очередь. Сделайте так, чтобы указанный класс создавал экземпляр клиента, а остальная часть вашего кода извлекала только экземпляры из этой оболочки. Это позволит вам иметь то же самое dispatchOperation: модель, но не требует зеркального отражения всех методов.


typedef void (^ AsyncBackendBlock (BackendClient * bc); @interface AsyncBackend + (instancetype) asyncBackendWithBackend: (BackendClient *) bc;

 @property .... serialQueue;

 - (void) dispatchAsync:(AsyncBackendBlock) backBlock;
 @end

.m:

 @interface AsyncBackend()
 @property... BackendClient *client;
 @end

 @implementation AsyncBackend

 - (void) dispatchAsync:(AsyncBackendBlock) backBlock
 {
     dispatch_async(_serialQueue, ^{
         backBlock(_client);
     });
 }
 @end

Абонент:

 AsyncBackend *b = [AsyncBackend asyncBackendWithBackend:[BackendClient new]];
 [b dispatchAsync:^(BackendClient *bc) {
    [bc doSomething];
    id result = [bc retrieveSomething];
    dispatch_async(dispatch_get_main_queue(), ^{
         [uiThingy updateWithResult:result];
    }
 }];
 ....

Чтобы посмотреть селектор во время выполнения, вы можете использовать NSSelectorFromString(), но в этом случае вам нужно просто продолжить и импортировать любой заголовок, который вам нужен, чтобы получить объявление -authByRequest:

Другие вопросы по тегам