如何使对象接收线程安全的每条消息?

时间:2012-01-06 19:03:47

标签: objective-c ios multithreading thread-safety

我正在开发一个Objective-C应用程序,我想做的事情如下:

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

我要问的是,如何在objective-c中创建一个拦截发送到另一个对象的消息的对象,以便在将消息发送到该对象之前执行代码。

我认为有些事情不起作用:

  • 类别(我需要这只发生在某个类的某些实例中)
  • 重写对象(我无权访问对象的源)
  • 方法调整(再一次,这只需要发生在类的某些实例上)

3 个答案:

答案 0 :(得分:3)

您将实现一个将消息转发到非线程安全对象的NSProxy。

Objective-C中的消息转发的

Here is a nice writeuphere is Apple's documentation

为了处理线程安全,它取决于您的需求。如果您的非线程安全对象必须在特定线程上运行,那么您可以在所述线程上使用NSRunLoop来序列化该对象的消息。

将NSInvocation与NSRunLoop结合使用的

Here is an example。在该示例中,他们使用的是performSelector:withObject:afterDelay:,但与performSelector:onThread:withObject:waitUntilDone:一起使用会非常相似。

否则,只需在代理中使用一个NSRecursiveLock

答案 1 :(得分:2)

如果您确切知道哪些实例应该具有您尝试实现的行为,则可以使用方法调配并在实例不是您要查找的实例时调用基本实现。 您可以拥有一个列出“有趣”实例的全局共享对象,并在调配实现中使用它,无论您是否需要调用基本实例或自定义实例。

答案 2 :(得分:2)

所以,我咬紧牙关,决定自己制作代理课程。要进行子类化,只需覆盖'forwardInvocation:'消息,然后在调用[super forwardInvocation:]之前调用所需的任何代码。请注意,这不适用于vardic方法,因为NSInvocation不适用于vardic方法。

#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;
}