1

Background: I have an object (let's call it BackendClient) that represents connection with server. Its methods are generated to single @protocol and they are all synchronous, so I want to create proxy object that will call them in background. The main problem is return value, which I obviously can't return from async method, so I need to pass a callback. The "easy" way will be copy all BackendClient's methods and add callback argument. But that's not very dynamic way of solving that problem, while ObjectiveC nature is dynamic. That's where performSelector: appears. It solves problem entirely, but it almost kills proxy object transparency.

Problem: I want to be able to send not declared selector to proxy (subclass of NSProxy) object as if it was already declared. For example, I have method:

-(AuthResponse)authByRequest:(AuthRequest*)request

in BackendClient protocol. And I want proxy call look like this:

[proxyClient authByRequest:myRequest withCallback:myCallback];

But this wouldn't compile because

No visible @interface for 'BackendClientProxy' declares the selector 'authByRequest:withCallBack:'

OK. Let's calm down compiler a bit:

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

Awww. Another error:

No known instance method for selector 'authByRequest:withCallBack:'

The only thing that comes to my mind and this point is somehow construct new @protocol with needed methods at runtime, but I have no idea how to do that.

Conclusion: I need to suppress this compilation error. Any idea how to do that?

folex
  • 5,122
  • 1
  • 28
  • 48
  • 1
    Sounds like you are deep down the mats-programming / dynamic runtime rabbit hole. While ObjC does have roots in SmallTalk and *can* be bent to that will, it really isn't a natural fit at all. In general, heavy use of `performSelector:` or proxies is considered a *code smell* in that it is often difficult to maintain over time. – bbum Sep 01 '13 at 01:04
  • So, what will be the right way ask achieve async wrap around client methods? Just copy-past all methods and add callback argument to them? – folex Sep 01 '13 at 14:23

2 Answers2

1

If I understand it, you have a synchronous, non-threaded, API that you want to be asynchronous for purposes of not blocking, say, the main event loop, etc...

I would add a serial queue to BackgroundClient:

@property(strong) dispatch_queue_t serialQueue;

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

Then:

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

That can be used like:

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

That is the easiest way to move the BackgroundClient to an asynchronous model without rewriting it or heavily refactoring it.

If you want to harden the API, then create a class wrapper for BackendClient that holds an instance of the client and the serial queue. Make it such that said class instantiates the client and the rest of your code only retrieves instances from that wrapper. That'll allow you to still have the same dispatchOperation: model, but not require mirroring all the methods.


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

Caller:

 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];
    }
 }];
 ....
bbum
  • 162,346
  • 23
  • 271
  • 359
  • I don't get last 2 sentences. OK, let's assume I have such wrapper, let's call it `BackendClientWrapper`, there is `client` property in it. Now, how do I call backend methods? – folex Sep 01 '13 at 18:09
  • Now I get it. You can keep this queue wherever you want. Well, thanks for option! Sad I can't do it transparently. – folex Sep 02 '13 at 14:32
0

To look up a selector at runtime, you can use NSSelectorFromString(), but in this case you should just go ahead and import whatever header you need to get the declaration of -authByRequest:

NSResponder
  • 16,861
  • 7
  • 32
  • 46
  • Creating selector from string in this case would be the same as `performSelector:`. And yes, I can get `authByRequest:` selector, but I need `authByRequest:withCallback:`. – folex Aug 31 '13 at 23:59