Как я могу сделать каждое сообщение, которое объект получает потокобезопасным? - PullRequest
4 голосов
/ 06 января 2012

Я занимаюсь разработкой приложения Objective-C, и то, что я хочу сделать, выглядит примерно так:

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

Я спрашиваю, как я могу создать объект в target-c, который перехватывает сообщения, отправленные другому объекту, чтобы выполнить код перед отправкой сообщения этому объекту.

Некоторые вещи, которые, я думаю, не будут работать:

  • Категории (мне нужно, чтобы это происходило только для определенных экземпляров класса)
  • Перезапись объекта (у меня нет доступа к источнику объекта)
  • Метод swizzling (опять же, это должно произойти только для определенных экземпляров класса)

Ответы [ 3 ]

3 голосов
/ 06 января 2012

Вы бы реализовали NSProxy, который перенаправляет сообщения на ваш не поточно-безопасный объект.

Вот хорошая запись пересылки сообщений в Objective-C, а здесь - документация Apple .

Для обеспечения безопасности потоков это зависит отна то что тебе нужно.Если ваш не потокобезопасный объект должен выполняться в определенном потоке, вы можете использовать NSRunLoop в указанном потоке для сериализации сообщений этому объекту.

Вот пример использования NSInvocation в сочетании с NSRunLoop.В этом примере они используют performSelector:withObject:afterDelay:, но использовать его с performSelector:onThread:withObject:waitUntilDone: было бы очень похоже.

В противном случае, просто используйте один NSRecursiveLock в вашем прокси.

2 голосов
/ 08 января 2012

Итак, я укусил пулю и решил создать свой собственный прокси-класс. Для создания подкласса вы просто переопределяете сообщение '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;
}
2 голосов
/ 06 января 2012

Если вы точно знаете, какие экземпляры должны иметь поведение, которого вы пытаетесь достичь, вы можете воспользоваться методом swizzling и вызвать базовую реализацию, если экземпляр не тот, который вы ищете. Вы можете иметь глобальный общий объект, который перечисляет «интересные» экземпляры и использовать его в быстрой реализации, независимо от того, вызываете ли вы базовый или свой собственный.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...