Creating a Dynamic Proxy

发布时间:2014-10-23 23:22:38

还没完全掌握 Creating a Dynamic Proxy The final program demonstrates dynamic message forwarding using the Foundation FrameworkNSInvocation and NSProxy classes. In Chapter 3, you learned that Objective-C provides several types of message forwarding options: fast forwarding using the NSObject forwardingTargetForSelector: method and normal (full) forwarding using the NSObject forwardInvocation: method. One of the benefits of normal forwarding is that it enables you to perform additional processing on an object message, its arguments, and return value. Used in conjunction with NSProxy, it provides an excellent mechanism for implementing aspect-oriented programming (AOP) in Objective-C. AOP is a programming paradigm that aims to increase program modularity by separating cross-cuttingfunctionality (that rely on or affect many parts of a program) from its other parts. NSProxy is a Foundation Framework class specifically designed for proxying. It functions as the interface to the real class. Here, you create a subclass of NSProxy and implement the forwardInvocation: method, thereby enabling messages sent to the real (i.e., subject) class to be decorated with the desired cross-cutting functionality. An overall diagram of the components that you’ll implement in this program and their interrelationships are depicted in Figure 9-13. Figure 9-13. AspectProxy class diagram Don’t worry. You’ll take it one step at a time. Let’s begin with the Invoker protocol. Creating the Invoker Protocol In Xcode, create a new project by selecting New  Project … from the Xcode File menu. In the New Project Assistant pane, create a command-line application. In the Project Options window, specifyAspectProxy for the Product Name, choose Foundation for the Project Type, and select ARC memory management by checking the Use Automatic Reference Counting check box. Specify the location in your file system where you want the project to be created (if necessary, select New Folder and enter the name and location for the folder), uncheck the Source Control check box, and then click the Create button. Now you’re going to create a protocol and a class that conforms to the protocol. Select New  File …from the Xcode File menu, select the Objective-C protocol template, name the protocol Invoker, select the AspectProxy folder for the files location and the AspectProxy project as the target, and then click the Create button. A header file named Invoker.h has been added to the Xcode navigator pane. In the editor pane, update the Greeter.h file, as shown in Listing 9-10. Listing 9-10.  Invoker Protocol #import <Foundation/Foundation.h> @protocol Invoker <NSObject> // Required methods (must be implemented) @required - (void) preInvoke:(NSInvocation *)inv withTarget:(id) target; // Optional methods @optional - (void) postInvoke:(NSInvocation *)inv withTarget:(id) target; @end This protocol declares two methods. preInvoke:withTarget: is a required method (it must be implemented by any class that conforms to this protocol) that implements cross-cutting functionality performed immediately prior to invoking the method on the real object. The method namedpreInvoke:withTarget: is an optional method that implements cross-cutting functionality performed immediately after invoking the method on the real object. Now you’ll implement a class that conforms to this protocol, implementing the desired cross-cutting functionality. Select New  File … from the Xcode File menu, select the Objective-C class template,name the class AuditingInvoker (select NSObject in the Subclass of drop-down list), select theAspectProxy folder for the files location and the AspectProxy project as the target, and then click the Create button. In the Xcode navigator pane, select the resulting header file namedAspectProxy.h and update the interface as shown in Listing 9-11. Listing 9-11.  AuditingInvoker Interface #import <Foundation/Foundation.h> #import "Invoker.h" @interface AuditingInvoker : NSObject <Invoker> @end The template AuditingInvoker interface has been updated to adopt the Invoker protocol. Now select the AuditingInvoker.m file and update the implementation as shown in Listing 9-12. Listing 9-12.  AuditingInvoker Implementation #import "AuditingInvoker.h" @implementation AuditingInvoker - (void) preInvoke:(NSInvocation *)inv withTarget:(id)target {   NSLog(@"Creating audit log before sending message with selector %@ to %@ object",         NSStringFromSelector([inv selector]), [target className]); } - (void) postInvoke:(NSInvocation *)inv withTarget:(id)target {   NSLog(@"Creating audit log after sending message with selector %@ to %@ object",         NSStringFromSelector([inv selector]), [target className]); } @end The AuditingInvoker implementation defines the preInvoke:withTarget: and thepostInvoke:withTarget: methods, and thus it conforms to the Invoker protocol. The methods merely log appropriate messages to the Xcode output pane. OK, now that you’re done implementing the cross-cutting functionality, let’s tackle the NSProxy subclass. Coding the Proxy Class Now you’re going to create the proxy class. It will subclass NSProxy and implement the message forwarding methods forwardInvocation: and methodSignatureForSelector:. Select New  File …from the Xcode File menu, select the Objective-C class template, name the class AspectProxy(select NSObject in the Subclass of drop-down list), select the AspectProxy folder for the files location and the AspectProxy project as the target, and then click the Create button. In the Xcode navigator pane, select the resulting header file named AspectProxy.h and update the interface as shown in Listing 9-13. Listing 9-13.  AspectProxy Interface #import <Foundation/Foundation.h> #import "Invoker.h" @interface AspectProxy : NSProxy @property (strong) id proxyTarget; @property (strong) id<Invoker> invoker; @property (readonly) NSMutableArray *selectors; - (id)initWithObject:(id)object andInvoker:(id<Invoker>)invoker; - (id)initWithObject:(id)object selectors:(NSArray *)selectors           andInvoker:(id<Invoker>)invoker; - (void)registerSelector:(SEL)selector; @end The interface adds three properties and three methods. The proxyTarget property is the real object to which messages are forwarded through the NSProxy instance. The invoker property is an instance of a class (which conforms to the Invoker protocol) that implements the cross-cutting functionality desired. The selectors property is a collection of selectors that define the messages on which the cross-cutting functionality will be invoked. Two of the methods are initialization methods for anAspectProxy class instance, and the third method, registerSelector:, is used to add a selector to the current list. Now select the AspectProxy.m file and update the implementation as shown inListing 9-14. Listing 9-14.  AspectProxy Implementation #import "AspectProxy.h" @implementation AspectProxy - (id)initWithObject:(id)object selectors:(NSArray *)selectors           andInvoker:(id<Invoker>)invoker {   _proxyTarget = object;   _invoker = invoker;   _selectors = [selectors mutableCopy];   return self; } - (id)initWithObject:(id)object andInvoker:(id<Invoker>)invoker {   return [self initWithObject:object selectors:nil andInvoker:invoker]; } - (NSMethodSignature *)methodSignatureForSelector:(SEL)sel {   return [self.proxyTarget methodSignatureForSelector:sel]; } - (void)forwardInvocation:(NSInvocation *)inv {   // Perform functionality before invoking method on target   if ([self.invoker respondsToSelector:@selector(preInvoke:withTarget:)])   {     if (self.selectors != nil)     {       SEL methodSel = [inv selector];       for (NSValue *selValue in self.selectors)       {         if (methodSel == [selValue pointerValue])         {           [[self invoker] preInvoke:inv withTarget:self.proxyTarget];           break;         }       }     }     else     {       [[self invoker] preInvoke:inv withTarget:self.proxyTarget];     }   }   // Invoke method on target   [inv invokeWithTarget:self.proxyTarget];      // Perform functionality after invoking method on target   if ([self.invoker respondsToSelector:@selector(postInvoke:withTarget:)])   {     if (self.selectors != nil)     {       SEL methodSel = [inv selector];       for (NSValue *selValue in self.selectors)       {         if (methodSel == [selValue pointerValue])         {           [[self invoker] postInvoke:inv withTarget:self.proxyTarget];           break;         }       }     }     else     {       [[self invoker] postInvoke:inv withTarget:self.proxyTarget];     }   } } - (void)registerSelector:(SEL)selector {   NSValue* selValue = [NSValue valueWithPointer:selector];   [self.selectors addObject:selValue]; } @end The initialization methods initialize an AspectProxy object instance accordingly. Note that as NSProxyis a base class, there is no call to [super init] at the beginning of these methods. TheregisterSelector: method adds another selector to the collection, as mentioned earlier. The implementation of methodSignatureForSelector: returns an NSMethodSignature instance for the method to be invoked on the target object. The runtime system requires that this method be implemented when performing normal forwarding. The implementation of forwardInvocation:invokes the method on the target object and conditionally invokes the AOP functionality if the selector for the method invoked on the target object matches one of the selectors registered in theAspectProxy object. Whew. When you’ve had enough time to digest this, let’s move on to the main.mfile and test out your program! Testing the AspectProxy Let’s test the AspectProxy, but first let’s add a target class to the proxy. You’ll use the Calculatorclass that you implemented in Chapter 7. It declares the following methods: - (NSNumber *) sumAddend1:(NSNumber *)adder1 addend2:(NSNumber *)adder2; - (NSNumber *) sumAddend1:(NSNumber *)adder1 :(NSNumber *)adder2; As you learned earlier in this chapter, Xcode provides a simple mechanism for importing files into a project. Hold down the Ctrl key and (from the Xcode project navigator pane) select the AspectProxy project. A drop-down window displays a set of options. Select the Add Files to “AspectProxy” …option. Navigate over to the Calculator project folder, select the Calculator.h and Calculator.mfiles, and click the Add button to add them to your AspectProxy project. OK, now with that done, let’s update the main()function to create a calculator proxy and invoke its methods, causing the AOPAuditingInvoker methods to be invoked as well. In the project navigator pane, select the main.mfile and update it as shown in Listing 9-15. Listing 9-15.  AspectProxy main() Function #import <Foundation/Foundation.h> #import "AspectProxy.h" #import "AuditingInvoker.h" #import "Calculator.h" int main(int argc, const char * argv[]) {   @autoreleasepool   {     // Create Calculator object     id calculator = [[Calculator alloc] init];     NSNumber *addend1 = [NSNumber numberWithInteger:-25];     NSNumber *addend2 = [NSNumber numberWithInteger:10];     NSNumber *addend3 = [NSNumber numberWithInteger:15];     // Create proxy for object     NSValue* selValue1 = [NSValue valueWithPointer:@selector(sumAddend1:addend2:)];     NSArray *selValues = @[selValue1];     AuditingInvoker *invoker = [[AuditingInvoker alloc] init];     id calculatorProxy = [[AspectProxy alloc] initWithObject:calculator                                                    selectors:selValues                                                   andInvoker:invoker];          // Send message to proxy with given selector     [calculatorProxy sumAddend1:addend1 addend2:addend2];     // Now send message to proxy with different selector, no special processing!     [calculatorProxy sumAddend1:addend2 :addend3];          // Register another selector for proxy and repeat message     [calculatorProxy registerSelector:@selector(sumAddend1::)];     [calculatorProxy sumAddend1:addend1 :addend3];        }   return 0; } First, the code creates a Calculator object and some data values. Next, it creates a proxy for theCalculator object, initializing it with a selector for the sumAddend1:addend2: method and anAuditingInvoker instance. Notice the following statement: NSArray *selValues = @[selValue1]; The notation @[] is used to create an array literal. In Part 4 of this book, you’ll examine Objective-C literals in depth. The sumAddend1:addend2: message is sent to the proxy. This causes the proxy to perform the AOP functionality of the AuditingInvoker instance in addition to the method on theCalculator object. Next, the sumAddend1:: message is sent to the proxy; because this method is not yet registered on the proxy when it is invoked, the AOP functionality is not performed. Finally, thesumAddend1:: selector is added to the proxy and the message is resent to the proxy; because the selector is now registered, the AOP functionality is now performed. When you compile and run the AspectProxy program, you should observe the messages in the output pane shown in Figure 9-14. Figure 9-14. AspectProxy program output Observe from the messages in the output pane that invoking the sumAddend1:addend2: method on the calculator proxy results in the proxy calling the preInvoke: method on the AuditorInvokerobject, followed by the target method sumAddend1:addend2: method on the real subject (Calculator object), and finally the postInvoke: method on the AuditorInvoker object. Because the sumAddend1:addend2: method was registered with the calculator proxy, this matches the expected behavior. Next, the sumAddend1:: method is invoked on the calculator proxy. Because this method is not registered with the proxy, it only forwards this method call to the Calculator object, and does not call the AuditorInvoker methods. Finally, the sumAddend1:: method is registered with the proxy and the method is called again. This time, it calls the AOP methods on the AuditorInvokerobject and the sumAddend1:: method on the real subject, as expected.