0

Is it possible to use the AppName-Prefix.pch file to import a given header file in all source files except one?

Problem: I have followed the approach described here: https://stackoverflow.com/a/617559/1062572 to overwrite a C function call, namely the GCD dispatch_async function.

Now I need to import the header file intercept.h in all my source files and for that I tried to use the AppName-Prefix.pch file. However this also imports the header file in my implementation file intercept.m. This cause an endless call loop because I try to call the original dispatch_async in there.

Heres my header file intercept.h:

#ifdef INTERCEPT
#define dispatch_async(queue, block) my_dispatch_async(queue, block)
#endif

And heres is my implementation file intercept.m:

void my_dispatch_async(dispatch_queue_t queue, dispatch_block_t block) {
NSLog(@"\nBlock is enqueued!\n");
dispatch_async(queue, ^{
    NSLog(@"\nBlock is dequeued!\n");
    block();
    NSLog(@"\nBlock has executed!\n");
});
}

Here is my Prefix.pch file:

#ifdef INTERCEPT
#import "Intercept.h"
#endif

How can I import the header file in all my sources, with the implementation file as the only exception? I hope it can be done without manually having to insert an import statement in every source file. And without writing a script to do it. ;)

One thing that confuses me even more is: Actually I have the implementation file in a compiled library (Testing.a), so why is the header file imported in it?

Even more information: I am writing a test framework that waits until all async tasks has completed before checking the results. That why I override dispatch_async. Any other suggestion is welcome. :)

Also I have noticed this answer: https://stackoverflow.com/a/617606/1062572 However it seems that this will not work on OSX, hence not iOS which is my target.

All these approaches will only overwrite the function call in my own source code. Actually I want it to overwrite it everywhere. However for this question I am satisfied if it works for my own source code.

Community
  • 1
  • 1
dynamokaj
  • 461
  • 7
  • 18
  • What is the value in **INTERCEPT**? – nomann Oct 20 '13 at 18:48
  • @nomannasim I am not sure if I understand what you mean, but I compile the code with `-DINTERCEPT`, when I need it to use my own implementation of dispatch_async. – dynamokaj Oct 20 '13 at 18:54
  • Supposing you assigned 1 in INTERCEPT because if not, `#define dispatch_async(queue, block) my_dispatch_async(queue, block)` will never get called. – nomann Oct 20 '13 at 18:54
  • @nomannasim I can confirm that it is called. The problem is that; it is also called from the implementation file, because the header is also imported here with prefix headers. The implementation file `intercept.m` is the only file from where I do not want the `intercept.h`file to be imported, since this gives me an endless call stack. – dynamokaj Oct 20 '13 at 18:58

2 Answers2

1

Unfortunately, its not possible. Prefix headers are compiled, cached and included in every file during compilation. You can't tell which files to ignore. However you can ignore it if you already included the Intercept.h. Here's how:

1- Remove ifdef INTERCEPT condition around #import "Intercept.h" from Prefix.pch. You don't need it there.

2- Update your Intercept.h to:

#ifndef INTERCEPT_H
#define INTERCEPT_H
#define dispatch_async(queue, block) my_dispatch_async(queue, block)
#endif

What happens here is that you first checked whether INTERCEPT_H is already included/defined in current definition, if not, you defined it in the next line and then defined your macro. Now, the #ifndef INTERCEPT_H condition will return false if it has already included its content in the same context.

Hope it helps.

nomann
  • 2,269
  • 2
  • 20
  • 24
  • Thanks. This solved it out. Can you explain more detailed what the _H does? And why it works? – dynamokaj Oct 20 '13 at 20:32
  • 1
    I'm glad it helped. I just appended _H to make it unique (so that it won't collide with INTERCEPT if you have defined INTERCEPT macro somewhere else). I have added details to my answer. – nomann Oct 20 '13 at 20:57
  • The details gives me a bit better understanding. However I do still not understand it completely. Does this only works because I have me implementation file in a library (Testing.a) or would it also work even if I had both the header and the implementation file in the application were I use the header in the Prefix.pch? If the header is still imported into the implementation file, then I do not see how your solution resolved it? – dynamokaj Oct 20 '13 at 21:28
1

Even more information: I am writing a test framework that waits until all async tasks has completed before checking the results. That why I override dispatch_async. Any other suggestion is welcome. :)

Depending on your situation, this is likely solvable in better ways. As long as you have access to which queues are being used, it's pretty simple. Consider this API, where you are passing the queue to be used:

[object doSomethingAsyncWithCompletion:block1 queue:myQueue];
[object doSomethingElseAsyncWithCompletion:block2 queue:myQueue];
[object doMoreAsyncWithCompletion:block3 queue:myQueue];

Now, you want to wait until all those finish. Assuming this is a custom concurrent queue (not one of the global queues), just use a barrier:

dispatch_barrier_sync(myQueue, ^{
  NSLog(@"This will not run until everything else before it on the queue finishes.");
}

But what if you don't know what queue is being used? Well, as long as you control the completion blocks, that's fine, too. (See Waiting on Groups of Queued Tasks.)

dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
dispatch_group_t group = dispatch_group_create();

dispatch_block_t doneBlock = ^{
  dispatch_group_leave(group);
}

dispatch_group_enter(group);
[object doSomethingAsyncWithCompletion:doneBlock queue:myQueue];
dispatch_group_enter(group);
[object doSomethingElseAsyncWithCompletion:doneBlock queue:myQueue];
dispatch_group_enter(group);
[object doMoreAsyncWithCompletion:doneBlock queue:myQueue];

// Wait for all the doneBlocks to fire
dispatch_group_wait(group, DISPATCH_TIME_FOREVER);
dispatch_release(group);

Of course you could also do this with a semaphore. That's sometimes easier if you just want to convert a single operation from asynchronous to synchronous.

I'd recommend these kinds of approaches rather than trying to hijack dispatch_async itself.

Community
  • 1
  • 1
Rob Napier
  • 250,948
  • 34
  • 393
  • 528
  • Thanks for your suggestions. Actually I did think of `dispatch_barrier_sync` to begin with. The reason why I didn't use it was; 1.- I don't no all the queues. 2.- Even if I searched the source for queues. I still have a problem with multiple queues using this approach, because I need to do `dispatch_barrier_sync` on all of them, but what if one of them returns and after a task/block from another queue adds a block to the one I just did `dispatch_barrier_sync` on? I believe that hijacking dispatch_async gives me more control, since it tells me how many blocks is enqueued, running and finished. – dynamokaj Oct 20 '13 at 20:22
  • The second approach you mention that uses groups requires me to change the source code of the application I am testing quite a bit? My hope was to change as little as possible in the original application. – dynamokaj Oct 20 '13 at 20:31
  • You can use the dispatch groups from test code, as long as the test code has some control over completion blocks, or can be the delegate to be informed when the operations end (there has to be some way to know when the operation ends, or else that indicates a design problem in your objects). Note that hijacking dispatch_async assumes of course that you know that dispatch_async is the only function used to enqueue blocks (and it isn't in the general case). If at all possible, I would focus on making your code more testable rather than coming up with more clever ways to hijack things. – Rob Napier Oct 20 '13 at 21:13
  • I am creating a generic as possible test framework, were you just embed it into the application to test. And when you send a HTTP request to simulate an event (Tap, Swipe, ect.) Then it will block until all the tasks triggered by this has completed, and then return a response. Thats what I am trying to do. In the beginning I try to minimum the amount of code that should be change in the application to test. But in the future I might define some guidelines for the developer to follow in order to make it easier for the test framework to detect which tasks to wait for completion before returning. – dynamokaj Oct 20 '13 at 21:36
  • I suspect that this will not be a solvable problem in any practical way. There is no way to generally define "all the tasks triggered." There could be timers put on the run loop, or operations put onto NSOpertationQueues, or hand-rolled producer/consumer threading queues, or libevent queues, or async activity that you can't hijack (such as calls to NSURLSession; this is very common). I suspect that even for fairly simple programs, there will be so many corner cases that a hijack strategy is going to be a dead-end. – Rob Napier Oct 20 '13 at 21:55