0

我从 Matt Gallagher 那里找到了一些非常棒的代码,用于使 Undo 与 NSInvocation 一起工作。尽管现在管理层希望我们对所有代码都使用 ARC,但我们现在正在将所有代码转换为 ARC。这最后一块就是剩下的了。马特,没有时间做转换,所以我希望我能在这里得到一些帮助。

任何援助将不胜感激!

谢谢!

NSInvocation(ForwardedConstruction).h

        //
        //  NSInvocation(ForwardedConstruction).h
        //
        //  Created by Matt Gallagher on 19/03/07.
        //  Copyright 2007 Matt Gallagher. All rights reserved.
        //
        //  Permission is given to use this source code file without charge in any
        //  project, commercial or otherwise, entirely at your risk, with the condition
        //  that any redistribution (in part or whole) of source code must retain
        //  this copyright and permission notice. Attribution in compiled projects is
        //  appreciated but not required.
        //


        @interface NSInvocation (ForwardedConstruction)

        + (id)invocationWithTarget:(id)target
            invocationOut:(NSInvocation **)invocationOut;
        + (id)retainedInvocationWithTarget:(id)target
            invocationOut:(NSInvocation **)invocationOut;

        @end

NSInvocation(ForwardedConstruction).m

        //
        //  NSInvocation(ForwardedConstuction).m
        //
        //  Created by Matt Gallagher on 19/03/07.
        //  Copyright 2007 Matt Gallagher. All rights reserved.
        //
        //  Permission is given to use this source code file without charge in any
        //  project, commercial or otherwise, entirely at your risk, with the condition
        //  that any redistribution (in part or whole) of source code must retain
        //  this copyright and permission notice. Attribution in compiled projects is
        //  appreciated but not required.
        //

    #import "NSInvocation(ForwardedConstruction).h"
    #import <objc/runtime.h>
    #import <objc/message.h>


        //
        // InvocationProxy is a private class for receiving invocations via the
        // forwarding mechanism and saving the received invocation to an external
        // invocation pointer.
        //
        // To avoid as many instance methods as possible, InvocationProxy is a base
        // class (not a subclass of NSObject) and does not implement the NSObject
        // protocol (and so is *not* a first-class object).
        //
    @interface InvocationProxy
    {
        Class isa;
        NSInvocation **invocation;
        id target;
        BOOL retainArguments;
        NSUInteger forwardingAddress;
    }

    /*+ (id)alloc;*/
    + (void)setValuesForInstance:(InvocationProxy *)instance
                          target:(id)target
           destinationInvocation:(NSInvocation **)destinationInvocation
                 retainArguments:(BOOL)retain;
    - (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector;
    - (void)forwardInvocation:(NSInvocation *)forwardedInvocation;

    @end

    #ifndef __OBJC_GC__

        //
        // DeallocatorHelper is a basic object which takes an id
        // and deallocates it when the DeallocatorHelper is deallocated.
        //
        // Not used in a garbage collected environment (which should deallocate the
        // id automatically).
        //
    @interface DeallocatorHelper : NSObject
    {
        id object;
    }

    - (id)initWithObject:(id)newObject;
    - (void)dealloc;

    @end

    @implementation DeallocatorHelper

        //
        // initWithObject
        //
        // Init method for objects which sets the id to be autoreleased.
        //
    - (id)initWithObject:(id)newObject
    {
        self = [super init];
        if (self != nil)
        {
            object = newObject;
        }
        return self;
    }

        //
        // dealloc
        //
        // Deallocates the id.
        //
    - (void)dealloc
    {
        NSDeallocateObject(object); 
        [super dealloc];
    }

    @end

    #endif

    @implementation InvocationProxy

        //
        // initialize
        //
        // This empty method is required because the runtime tries to invoke it when
        // the first message is sent to the Class. If it doesn't exist, the runtime
        // gets mad.
        //
    + (void)initialize
    {
    }


        //
        // alloc
        //
        // Allocator for the class. Also sets the 
        //
    + (id)alloc
    {
            //
            // Allocate the object using the default allocator.
            //
        InvocationProxy *newObject =
    #ifdef __OBJC_GC__
        objc_allocate_object(self, 0);
    #else
        NSAllocateObject(self, 0, nil);
    #endif
        return newObject;
    }


    - (id)init {  
        return self;  
    }

        //
        // setValuesForInstance:target:destinationInvocation:retainArguments:
        //
        // Method to set the attributes on the instance passed in. We use a class
        // method instead of an instance method to avoid extra instance methods on
        // the class.
        //
    + (void)setValuesForInstance:(InvocationProxy *)instance
                          target:(id)destinationTarget
           destinationInvocation:(NSInvocation **)destinationInvocation
                 retainArguments:(BOOL)retain;
    {
        instance->target = destinationTarget;
        instance->invocation = destinationInvocation;
        instance->retainArguments = retain;
    }

        //
        // methodSignatureForSelector:
        //
        // Invoked by the runtime whenever a message is sent for a method that doesn't
        // exist.
        //
    - (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector
    {
            //
            // This method should be invoked once before attributes are set (as an
            // "init" invocation).
            //
        if (target == nil)
        {
                //
                // If the invocation is something other than "init", complain using
                // NSObject's standard doesNotRecognizeSelector:
                //
            if (aSelector != @selector(init))
            {
                SEL failSEL = @selector(doesNotRecognizeSelector:);
                Method failMethod =
                class_getInstanceMethod([NSObject class], failSEL);
                IMP failImp = method_getImplementation(failMethod);
                failImp(self, failSEL, aSelector);
            }

                //
                // Otherwise, we use the forwarded "init" to preserve the return
                // address of the forwarding code (which we can use later to determine
                // if this is a forwarded or direct invocation).
                //
            forwardingAddress = (NSUInteger)__builtin_return_address(0);

                //
                // Return the NSMethodSignature from NSObject's init method (just
                // so we have something to return).
                //
            return [NSObject instanceMethodSignatureForSelector:aSelector];
        }

            //
            // On subsequent invocations, check if we are a forwarded invocation or
            // a direct invocation.
            //
        NSUInteger returnAddress = (NSUInteger)__builtin_return_address(0);
        if (returnAddress != forwardingAddress)
        {
                //
                // Handle the case where methodSignatureForSelector: is the message sent
                // directly to the proxy.
                //
                // There is a chance that we have guessed wrong (i.e. if this is sent
                // from __forward__ but from a different code branch) but that won't
                // cause a fatal problem, just a redundant autoreleased NSInvocation
                // that will get safely autoreleased and ignored.
                //
                // Create an NSInvocation for methodSignatureForSelector: 
                //
            NSMethodSignature *signature =
            [target methodSignatureForSelector:_cmd];
            *invocation =
            [NSInvocation invocationWithMethodSignature:signature];
            [*invocation setTarget:target];
            [*invocation setSelector:_cmd];
            [*invocation setArgument:&aSelector atIndex:2];
            if (retainArguments)
            {
                [*invocation retainArguments];
            }

                //
                // Deliberately fall through and still return the target's
                // methodSignatureForSelector: result (in case we guessed wrong).
                //
        }

            //
            // This is the "normal" case: after initialization, we have been correctly
            // invoked from the forwarding code. Return the target's
            // methodSignatureForSelector: for the given selector.
            //
        NSMethodSignature *signature =
        [target methodSignatureForSelector:aSelector];

        NSAssert3(signature != nil,
                  @"NSInvocation(ForwardedConstruction) error: object 0x%@ of class '%@' does not implement %s",
                  target, [target className], sel_getName(aSelector));

        return signature;
    }

        //
        // forwardInvocation:
        //
        // This method is invoked by message forwarding.
        //
    - (void)forwardInvocation:(NSInvocation *)forwardedInvocation
    {
            //
            // This method will be invoked once on initialization (before target is set).
            // Do nothing.
            //
        if (target == nil)
        {
                //
                // This branch will be followed when "init" is invoked on the newly
                // allocated object. Since "init" returns "self" we need to set that
                // on the forwardedInvocation.
                //
            [forwardedInvocation setReturnValue:&self];
            return;
        }

            //
            // Check if the target of the forwardedInvocation is equal to self. If
            // it is, then this is a genuine forwardedInvocation. If it isn't, then
            // forwardInvocation: was directly the message sent to this proxy.
            //
        if ([forwardedInvocation target] == self)
        {
            [forwardedInvocation setTarget:target];
            *invocation = forwardedInvocation;
            if (retainArguments)
            {
                [*invocation retainArguments];
            }
            return;
        }

            //
            // Handle the case where forwardedInvocation is the message sent directly
            // to the proxy. We create an NSInvocation representing a forwardInvocation:
            // sent to the target instead.
            //
        NSMethodSignature *signature =
        [target methodSignatureForSelector:_cmd];
        *invocation =
        [NSInvocation invocationWithMethodSignature:signature];
        [*invocation setTarget:target];
        [*invocation setSelector:_cmd];
        [*invocation setArgument:&forwardedInvocation atIndex:2];
        if (retainArguments)
        {
            [*invocation retainArguments];
        }
    }

    @end

    @implementation NSInvocation (ForwardedConstruction)

        //
        // invocationWithTarget:invocationOut:
        //
        // Basic constructor for NSIncoation using forwarded construction.
        //
    + (id)invocationWithTarget:(id)target
                 invocationOut:(NSInvocation **)invocationOut
    {
            //
            // Check that invocationOut isn't nil.
            //
        NSAssert2(target != nil && invocationOut != nil,
                  @"%@ method %s requires target that isn't nil and a valid NSInvocation** for the second parameter",
                  [self className], sel_getName(_cmd));

            //
            // Alloc and init the proxy
            //
        InvocationProxy *invocationProxy = [[InvocationProxy alloc] init];

            //
            // Set the instance attributes on the proxy
            //
        [InvocationProxy
         setValuesForInstance:invocationProxy
         target:target
         destinationInvocation:invocationOut
         retainArguments:NO];

            //
            // Create the DeallocatorHelper if needed
            //
    #ifndef __OBJC_GC__
            [[[DeallocatorHelper alloc]
                    initWithObject:invocationProxy]
                autorelease];
    #endif

        return invocationProxy;
    }

        //
        // retainedInvocationWithTarget:invocationOut:
        //
        // Same as above but sends retainArguments to the NSInvocation created.
        //
    + (id)retainedInvocationWithTarget:(id)target
                         invocationOut:(NSInvocation **)invocationOut
    {
            //
            // Check that invocationOut isn't nil.
            //
        NSAssert2(target != nil && invocationOut != nil,
                  @"%@ method %s requires target that isn't nil and a valid NSInvocation** for the second parameter",
                  [self className], sel_getName(_cmd));

            //
            // Alloc and init the proxy
            //
        InvocationProxy *invocationProxy = [[InvocationProxy alloc] init];

            //
            // Set the instance attributes on the proxy
            //
        [InvocationProxy
         setValuesForInstance:invocationProxy
         target:target
         destinationInvocation:invocationOut
         retainArguments:YES];

            //
            // Create the DeallocatorHelper if needed
            //
    #ifndef __OBJC_GC__
        [[[DeallocatorHelper alloc]
          initWithObject:invocationProxy]
         autorelease];
    #endif

        return invocationProxy;
    }

    @end
4

2 回答 2

2

您可以完全避免这种情况:

在 Xcode 中的 Target > Build Phases > Compile Sources 中选择此文件。

按回车。

键入 -fno-objc-arc。

按完成。

于 2012-06-22T23:02:45.063 回答
0

正如 Matt 提到的,您应该在编译文件时添加“-fno-objc-arc”标志,但这还不够。您需要做两件事来确保您的调用按预期被调用。

首先总是在变量的声明中添加“__autoreleasing”;你应该有类似的东西:

NSInvocation *__autoreleasing invocation;
[[NSInvocation retainedInvocationWithTarget:self invocationOut:&invocation] myMethod];

如果你没有这样做,ARC 会在你有时间使用它之前将“invocation”设置为 nil。

然后你需要修改 forwardInvocation: 在 NSInvocation 覆盖。这是必需的,因为 ARC 将向代理对象发送“保留”和“释放”,而您不希望它们干扰(这意味着您将无法使用代码来创建“保留”和“释放”的调用消息,但你很可能不是)。

- (void)forwardInvocation:(NSInvocation *)forwardedInvocation
{
    //
    // This method will be invoked once on initialization (before target is set).
    // Do nothing.
    //
    if (target == nil)
    {
        //
        // This branch will be followed when "init" is invoked on the newly
        // allocated object. Since "init" returns "self" we need to set that
        // on the forwardedInvocation.
        //
        [forwardedInvocation setReturnValue:&self];
        return;
    }
    if (forwardedInvocation.selector == @selector(retain))
    {
        // This branch will be followed when "retain" is invoked on the newly
        // allocated object. ARC will call retain before the selector we want
        // the signature of is invoked, so we need to return "self" to track
        // the next forward invocation.
        [forwardedInvocation setReturnValue:&self];
        return;
    }
    if (forwardedInvocation.selector == @selector(release))
    {
        // This branch will be followed when "release" is invoked on the newly
        // allocated object. ARC will call release after the selector we want
        // the signature of is invoked.
        return;
    }

    //
    // Check if the target of the forwardedInvocation is equal to self. If
    // it is, then this is a genuine forwardedInvocation. If it isn't, then
    // forwardInvocation: was directly the message sent to this proxy.
    //
    if ([forwardedInvocation target] == self)
    {
        [forwardedInvocation setTarget:target];
        *invocation = forwardedInvocation;
        if (retainArguments)
        {
            [*invocation retainArguments];
            //[forwardedInvocation retainArguments]; // added
        }
        // [forwardedInvocation setReturnValue:forwardedInvocation]; // added
        return;
    }

    //
    // Handle the case where forwardedInvocation is the message sent directly
    // to the proxy. We create an NSInvocation representing a forwardInvocation:
    // sent to the target instead.
    //
    NSMethodSignature *signature =
    [target methodSignatureForSelector:_cmd];
    *invocation =
    [NSInvocation invocationWithMethodSignature:signature];
    [*invocation setTarget:target];
    [*invocation setSelector:_cmd];
    [*invocation setArgument:&forwardedInvocation atIndex:2];
    if (retainArguments)
    {
        [*invocation retainArguments];
    }
}   
于 2013-07-23T16:57:41.530 回答