Как отправить не объявленный селектор без 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: