Search code examples
iosswiftswift2nsproxy

Proxy Pattern in iOS - Swift


I need to create proxy pattern in iOS using swift

I have tried it using Objective C and here is the code

MyProtocol.h

#import <Foundation/Foundation.h>
@protocol MyProtocol <NSObject>
@required
-(void)testMessage;    
@end

TestBO.h

#import <Foundation/Foundation.h>
#import "MyProtocol.h"

@interface TestBO : NSObject <MyProtocol>

@end

TestBO.m

#import "TestBO.h"

@implementation TestBO 

-(void)testMessage{
    NSLog(@"Test Message");
}

@end

TestProxyHandler.h

#import <Foundation/Foundation.h>

@interface TestProxyHandler : NSProxy

@property (nonatomic, strong) id object;

- (instancetype)initWithProtocol:(Protocol *)protocol andObject:(Class)clazz;

- (void)forwardInvocation:(NSInvocation *)invocation;

- (NSMethodSignature *)methodSignatureForSelector:(SEL)selector;

@end

TestProxyHandler.m

#import "TestProxyHandler.h"
#import "TestBO.h"

@implementation TestProxyHandler 

- (instancetype)initWithProtocol:(Protocol *)protocol andObject:(Class)clazz{
    if ([clazz conformsToProtocol:@protocol(MyProtocol)]) {
        self.object = [[clazz alloc] init];
    }else{
        NSLog(@"Error it does not conform to protocol");
    }
    return self;
}

- (void)forwardInvocation:(NSInvocation *)invocation{
    NSString *selString = NSStringFromSelector(invocation.selector);
    NSLog(@"Called %@",selString);
    [invocation invokeWithTarget:self.object];
}

- (NSMethodSignature *)methodSignatureForSelector:(SEL)selector {
    return [self.object methodSignatureForSelector:selector];
}


@end

I have invoked it using

id <MyProtocol> delegate = (TestBO *)[[TestProxyHandler alloc] initWithProtocol:@protocol(MyProtocol) andObject:[TestBO class]];

[delegate testMessage];

But I am not able to make it work in Swift even the initialzier is showing that the message

TestHandler.swift

import Foundation
class TestHandler: NSProxy {
    var object: AnyObject

    convenience override init(`protocol`: Protocol, andObject clazz: AnyClass) {
        if clazz.conformsToProtocol() {
            self.object = clazz()
        }
        else {
            NSLog("Error it does not conform to protocol")
        }
    }        
}

Does anyone have any clue to do this in swift ??

EDIT:

In java you can create runtime implementation of a method using the Proxy.newProxyInstance call but can this be achieved in iOS ? using swift ? Any clue ?


Solution

  • Comparing with Objective C and Swift, Swift offers extremely limited access to runtime language access . So based on my research till now it can’t be done :(

    I even tried subclassing the NSProxy class in swift but just couldn’t call the super.init and code never compiles but however same thing works in objective C

    So I ended up doing this approach

    I created a protocol using

    @objc protocol SomeProt {
        // Some method
    }
    

    Note the keyword @objc before protocol is essential else you would not be able to pass it as a variable, also adding @objc limits the usage of protocol to objective c runtime features so don’t expect to get full features of protocols in swift

    public func someMethod(`protocol` : Protocol, implementation : AnyClass) {
        let isImplemented : Bool = implementation.conformsToProtocol(`protocol`)
            // some code
    }
    

    If you need to use it in some dictionary or places where it should conform to NSCopying class then use

    NSStringFromProtocol

    and

    NSProtocolFromString

    methods

    Now I have wrote a objective c helper class to do the initialization

    ObjcHelper.h

    #import <Foundation/Foundation.h>
    
    @interface ObjcHelper : NSObject
    +(NSObject *)objectForClass:(Class)clazz;
    @end
    

    ObjcHelper.m

    #import "ObjcHelper.h"
    
    @implementation ObjcHelper
    + (NSObject *)objectForClass:(Class)clazz{
        return [[clazz alloc] init];
    }
    @end
    

    Now to use it

    let prot : SomeProt = ObjcHelper.objectForClass(NSClassFromString("PROT_HANDLER_CLASS_NAME")) as! SomeProt
    

    However in future if anyone can offer a better answer then please be sure to post it here