4

I am developing an Objective-C application, and what I want to do, is something like the following:

+-----------------+              +---------------+
|   Some Object   | <----------  |  Synchronize  |
|(Not Thread Safe)|              |     Proxy     |
+-----------------+            / +---------------+
                              / 
                             /  Intercepts [someobject getCount]
                            /   @synchronize (someObject)
                           /               
    [someObject getCount] /
                +----------------------+
                |  Some Calling Object |
                +----------------------+

What I've asking is, how can I create an object in objective-c, that intercepts messages sent to another object, in order to perform code before the message is sent to that object.

Some things that I think will not work:

  • Categories (I need this to only happen for certain instances of a class)
  • Rewriting the object (I don't have access to the source of the object)
  • Method swizzling (once again, this need to only happen for certain instances of a class)
Charles
  • 50,943
  • 13
  • 104
  • 142
Richard J. Ross III
  • 55,009
  • 24
  • 135
  • 201
  • So, I get that you don't have control to neither 'Some Object' nor 'Some Calling Object'? – barley Jan 06 '12 at 19:08
  • @Barley, that is correct. Its a shame you don't have that much access to the foundation libraries on iOS. – Richard J. Ross III Jan 06 '12 at 19:08
  • In general, this kind of willy-nilly "all threads can call any method at any time" concurrency pattern leads to pathologically bad performance. – bbum Jan 06 '12 at 22:13

3 Answers3

3

You would implement an NSProxy that forwards messages to your non-thread-safe object.

Here is a nice writeup of message forwarding in Objective-C, and here is Apple's documentation.

To handle thread safety, it depends on what you need. If your non-thread-safe object must run on a specific thread then you can use a NSRunLoop on said thread to serialize messages to that object.

Here is an example of using NSInvocation in conjunction with NSRunLoop. In that example they're using performSelector:withObject:afterDelay: but to use it with performSelector:onThread:withObject:waitUntilDone: would be very similar.

Otherwise, just use a single NSRecursiveLock in your proxy.

Darren
  • 25,520
  • 5
  • 61
  • 71
2

If you know exactly what instances should have the behavior you are trying to achieve you can go with method swizzling and call the base implementation if the instance is not the one you are looking for. You can have a global shared object that lists the "interesting" instances and use it in the swizzling implementation whether you have to call the base one or your custom one.

alediaferia
  • 2,537
  • 19
  • 22
2

So, I bit the bullet, and decided to make my own proxy class. To subclass, you simply override the 'forwardInvocation:' message, and you call any code you need there, before calling [super forwardInvocation:]. Please not this will NOT work with vardic methods, as NSInvocation doesn't work with vardic methods.

#import <Foundation/Foundation.h>
#import <objc/runtime.h>
#import <objc/objc.h>
#import <objc/message.h>

@interface RJProxy : NSObject {
    @private
    NSObject *target;
}

@property(readwrite, retain) NSObject *target;

-(NSObject *) getTarget;

@end

@implementation RJProxy

@synthesize target;

-(NSMethodSignature *) methodSignatureForSelector:(SEL)aSelector
{ 
    if (objc_getAssociatedObject(self, "isProxy"))
    {
        IMP NSObjectImp = [NSObject instanceMethodForSelector:@selector(methodSignatureForSelector:)];

        NSMethodSignature *methodSignature = (NSMethodSignature *) NSObjectImp(self, @selector(methodSignatureForSelector:), aSelector);

        if (methodSignature)
            return methodSignature;

        return [target methodSignatureForSelector:aSelector];
    }
    else
    {
        Class subClass = self->isa;
        @try {
            self->isa = objc_getAssociatedObject(self, "realSuperclass");
            return [super methodSignatureForSelector:aSelector];
        }
        @finally {
            self->isa = subClass;
        }
    }
}

-(void) forwardInvocation:(NSInvocation *)anInvocation
{
    if (objc_getAssociatedObject(self, "isProxy"))
    {
        Class subClass = target->isa;
        target->isa = objc_getAssociatedObject(self, "realSuperclass");
        [anInvocation invokeWithTarget:target];
        target->isa = subClass;
    }
    else
    {
        Class realSuperclass = objc_getAssociatedObject(self, "realSuperclass");
        Class subclass = self->isa;

        self->isa = realSuperclass;

        if ([self respondsToSelector:[anInvocation selector]])
        {
            [anInvocation invokeWithTarget:self];
        }
        else
        {
            [self doesNotRecognizeSelector:[anInvocation selector]];
        }

        self->isa = subclass;
    }
}

-(NSObject *) getTarget
{
    if (objc_getAssociatedObject(self, "isProxy"))
    {
        return target;
    }

    return self;
}

@end

BOOL object_setProxy(NSObject *object, RJProxy *proxy);
BOOL object_setProxy(NSObject *object, RJProxy *proxy)
{
    proxy.target = object;

    Class objectClass = object_getClass(object);

    Class objectSub = objc_allocateClassPair(objectClass, [[NSString stringWithFormat:@"%s_sub%i", class_getName(objectClass), objc_getAssociatedObject(objectClass, "subclassTimes")] UTF8String], 0);
    objc_setAssociatedObject(objectClass, "subclassTimes", (id) ((int) objc_getAssociatedObject(objectClass, "subclassTimes") + 1), OBJC_ASSOCIATION_ASSIGN);
    objc_registerClassPair(objectSub);

    Class proxyClass = object_getClass(proxy);

    Class proxySub = objc_allocateClassPair(proxyClass, [[NSString stringWithFormat:@"%s_sub%i", class_getName(proxyClass), objc_getAssociatedObject(proxyClass, "subclassTimes")] UTF8String], 0);
    objc_setAssociatedObject(proxyClass, "subclassTimes", (id) ((int) objc_getAssociatedObject(proxyClass, "subclassTimes") + 1), OBJC_ASSOCIATION_ASSIGN);
    objc_registerClassPair(proxySub);

    object_setClass(object, proxySub);
    object_setClass(proxy,  proxySub);

    objc_setAssociatedObject(object, "isProxy", (id) NO, OBJC_ASSOCIATION_ASSIGN);
    objc_setAssociatedObject(proxy,  "isProxy", (id) YES, OBJC_ASSOCIATION_ASSIGN);

    objc_setAssociatedObject(object, "realSuperclass", objectClass, OBJC_ASSOCIATION_ASSIGN);
    objc_setAssociatedObject(proxy,  "realSuperclass", proxyClass, OBJC_ASSOCIATION_ASSIGN);

    return NO;
}

@interface SynchronizeProxy : RJProxy

@end

@implementation SynchronizeProxy

-(void) forwardInvocation:(NSInvocation *)anInvocation {
    @synchronized ([self getTarget])
    {
        [super forwardInvocation:anInvocation];
    }
}

@end

int main (int argc, const char * argv[])
{
    @autoreleasepool { 
        NSArray *arrayToSynchronize = [NSArray arrayWithObjects:@"This, is, a, test!", nil];
        SynchronizeProxy *myProxy = [SynchronizeProxy new];

        object_setProxy(arrayToSynchronize, myProxy);

        // now all calls will be synchronized!
        NSLog(@"Array at address 0x%X with count of %lu, and Objects %@ ", (unsigned) arrayToSynchronize, [arrayToSynchronize count], arrayToSynchronize);

        [myProxy release];
        [arrayToSynchronize release];
    }

    return 0;
}
Richard J. Ross III
  • 55,009
  • 24
  • 135
  • 201