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 ?
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
Related
I am trying to port an old obj-c application to swift, and in the process restructure and reprogramm everything. Some things need to be portet at a later point and I have to use old obj-c in swift, which isn't a problem, but I ran into a serious issue which seems like i cannot solve.
I have a obj-c "connection" class which is called from a swift wrapper. The problem is, i cannot pass the delegate object to obj-c or at least i dont know how.
Here is my code:
//swift protocol
#objc protocol ConnectionDelegate
{
#objc func connected() -> Void
}
//swift class
#objc class ConnectionManager : NSObject, ConnectionDelegate
{
var connectionThread : ConnectionThread
init(){
connectionThread.inti()
connectionThread.registerDelegate(self) //Value of type 'ConnectionThread' has no member of 'registerDelegate'
connectionThread.testFunc() //all ok
}
#objc func connected(){
}
}
//obj-c header ConnectionThread.h
#class ConnectionDelegate;
#property (nonatomic, weak) ConnectionDelegate* delegate;
-(void) registerDelegate: (ConnectionDelegate*) delegate;
-(void) testFunc;
//obj-c class ConnectionThread.h
#import ".....Swift.h"
#synthesize delegate;
-(void) registerDelegate:(ConnectionDelegate*) delegate
{
self.delegate = delegate;
}
-(void) testFunc
{
}
In the future, please copy and paste your actual code into your question. The code in your question is full of errors, which means it isn't your real code, and those errors might make it impossible to answer your question correctly.
So, assuming you haven't made too many errors in the code you posted, the problem is that you are lying to the compiler. Specifically, your Objective-C header file ConnectionThread.h says this:
#class ConnectionDelegate;
But ConnectionDelegate is not a class. It is a protocol, so you need to declare it as a protocol. Then you will also have to use the proper Objective-C syntax for a type that conforms to the protocol, which is id<ConnectionDelegate>.
// ConnectionThread.h
#import <Foundation/Foundation.h>
NS_ASSUME_NONNULL_BEGIN
#protocol ConnectionDelegate;
#interface ConnectionThread : NSObject
#property (nonatomic, weak) id<ConnectionDelegate> delegate;
- (void)registerDelegate:(id<ConnectionDelegate>)delegate;
#end
NS_ASSUME_NONNULL_END
// ConnectionThread.m
#import "ConnectionThread.h"
#implementation ConnectionThread
- (void)registerDelegate:(id<ConnectionDelegate>)delegate {
self.delegate = delegate;
}
#end
My project is combined with one viewcontroller written in objective C (objCViewController) and the other is written by swift (SwiftViewCOntroller).
I have several variable NSString. I would like to update the string in objCViewController and access it in SwiftViewController by using delegate because I need to change between these two viewcontroller continuously and keep updating the string.
Here is the code:
objCViewController.h
#import <UIKit/UIKit.h>
#protocol MyDelegate <NSObject>
#end
#interface objCViewController : UIViewController{
NSString * stringbeingpassed;
}
#property (nonatomic, weak) id<MyDelegate> delegate;
#property (nonatomic, retain) NSString * stringbeingpassed;
#end
objCViewController.m
#implementation objCViewController
#synthesize delegate;
#synthesize stringbeingpassed;
- (void)updatestring {
//update string in this method
NSString * newstring = #"testing";
if (delegate != nil && [delegate respondsToSelector:#selector(stringUpdated:)]) {
[delegate stringUpdated: newstring];
}
}
bridging header.h:
#import "objCViewController.h"
SwiftViewController.swift:
protocol MyDelegate {
func stringUpdated(newMessage: String)
}
import UIKit
#objc class SwiftViewController: UIViewController, MyDelegate{
override func viewDidLoad() {
super.viewDidLoad()
}
func stringUpdated(newMessage:String) {
let newMessage = "sent string"
}
I have tried to use delegate but I have no idea how to use it. I'm completely new to swift and objective C
Q1. I would like to ask how can I assign my newstring to delegate in objCViewController and then pass it to SwiftViewController.
Q2. Another question is that how can I retrieve the data in delegate in SwiftViewController. What should I add?
Q3. Anything else that I have missed in defining delegate? Do I need to define it in both viewcontroller?
Thank you.
It's hard to say whether a delegate is the right tool for the job due to the minimal information provided. In fact I might suggest researching better ways to architect your app.
That being said, it would be helpful to see the code for your protocol MyDelegate. If this protocol doesn't already it will need a 'func' that accepts a 'String' as an argument/parameter. For now let's call this function stringUpdated. With that code that's provided you're going to need to set an instance of SwiftViewController to your property delegate within objCViewController. That way when updatestring is called you can do something like this:
- (void)updatestring {
//update string in this method
NSString * newstring = #"testing";
if (delegate != nil && [delegate respondsToSelector:#selector(stringUpdated:)]) {
[delegate stringUpdated: newstring]
}
}
In SwiftViewController you'll have to adopt the protocol like so:
#objc class SwiftViewController: UIViewController, MyDelegate {
and then adhere to the protocol by implementing the function stringUpdated.
Update
Your protocol is still missing the method. It should look like this:
#protocol MyDelegate
- (void) stringUpdated:(NSString *)updatedString;
#end
let me answer Q3 first
Q3:Anything else that I have missed in defining delegate? Do I need to define it in both viewcontroller?
No you haven't to define MyDelegate in both viewControllers, just add it to the objCViewControlle
Now here under I had modified you code
objCViewController.h
#protocol MyDelegate <NSObject>
- (void) stringUpdated:(NSString *)updatedString;
- (NSString *) getString;
#end
#interface objCViewController : UIViewController{
NSString * stringbeingpassed;
}
#property (nonatomic, weak) id<MyDelegate> delegate;
#property (nonatomic, retain) NSString * stringbeingpassed;
#end
bridging header.h:
#import "objCViewController.h"
SwiftViewController.swift:
import UIKit
#objc class SwiftViewController: UIViewController, MyDelegate{
var thisIsObjectiveVCString:String?
override func viewDidLoad() {
super.viewDidLoad()
}
func stringUpdated(newMessage:String) {
thisIsObjectiveVCString = newMessage
}
func getString() ->String {
return thisIsObjectiveVCString
}
}
Q2: how can I retrieve the data in delegate in SwiftViewController. What should I add?
on objCViewControlle
if (delegate != nil && [delegate respondsToSelector:#selector(getString)]) {
NSString * theString = [delegate getString];
}
Q1: how can I assign my newstring to delegate in objCViewController and then pass it to SwiftViewController
actually on objCViewController if you call updatestring then you will update SwiftViewController
I'm writing some Swift classes that build upon functionality in our objective-c app. I have a objective-c class with a delegate that conforms to a protocol. I'm trying to call a method on that delegate from inside of a Swift class I'm simplified it down to this.
FredTestProtocol.h:
#protocol FredTestProtocol
- (void) dumbMethod;
#end
FredTestClass.h:
#import <Foundation/Foundation.h>
#import "FredTestProtocol.h"
#interface FredTestClass : NSObject <FredTestProtocol>
#property (nonatomic, weak) NSObject <FredTestProtocol> *delegate;
#end
FredTestClass.m:
#import "FredTestClass.h"
#implementation FredTestClass
- (void) dumbMethod
{
NSLog(#"Boy, this is a dumb method");
}
#end
FredSwiftClass.swift
import Foundation
class FredSwiftClass {
func test()
{
let ocObject = FredTestClass()
ocObject.delegate.dumbMethod() // Error occurs here.
}
}
The indicated line produces the error "'NSObject' does not have a method named 'dumbMethod'" I've tried a lot of ways to eliminate the error, to no avail. I'm sure I'm missing something really fundamental. Can someone tell me how I should go about calling the delegate method from Swift?
When Swift examines the property delegate it simply sees that is is an NSObject and the fact that you have noted that it implements a protocol is ignored. I can't find any specific documentation as to why this is the case.
You can address this in a couple of ways.
First, you can redefine your delegate property to use class anonymity, then Swift will just see it as some object that implements the protocol -
FredTestClass.h
#import <Foundation/Foundation.h>
#import "FredTestProtocol.h"
#interface FredTestClass : NSObject <FredTestProtocol>
#property id<FredTestProtocol> delegate;
#end
Then your Swift code will compile as written.
or you can leave your delegate definition as is and tell Swift that you want to access the delegate as an instance of an object that implements the protocol via downcast -
FredTestSwift.swift
import Foundation
class FredSwiftClass {
func test()
{
let ocObject = FredTestClass()
let theDelegate=ocObject.delegate as! FredTestProtocol
theDelegate.dumbMethod()
}
}
Pretty sure I've got it.
func test()
{
let ocObject = FredTestClass()
if let myDelegate = ocObject.delegate as? FredTestProtocol
{
myDelegate.dumbMethod()
}
}
I have an Objective-C controller called linkedinlogincontroller. Inside that controller I have an NSMutableDictionary called result.
Simply, all I need to know is what is the best practice way of having access to that dictionary in a Swift controller?
Look at this link:
Using Objective C & Swift together.
Expose the ObjectiveC class in <YourProjectName>-Bridging-Header.h.
#import "linkedinlogincontroller.h"
ObjectiveC Header
// linkedinlogincontroller.h
#import <UIKit/UIKit.h>
#interface linkedinlogincontroller : UIViewController
#property (nonatomic, strong) NSMutableDictionary * result;
#end
ObjectiveC Implementation
// linkedinlogincontroller.m
#import "linkedinlogincontroller.h"
#import "<YourProjectName>-Swift.h"
#implementation linkedinlogincontroller
...
self.result = [NSMutableDictionary dictionaryWithDictionary:#{#"key":#"value"}];
NSLog(#"Print from Objc %#", self.result);
YourSwiftClass * so = [[YourSwiftClass alloc] init];
[so printFromSwift:self];
...
Swift Implementation
// YourSwiftClass.swift
import Foundation
#objc class YourSwiftClass : NSObject {
func printFromSwift(vc:linkedinlogincontroller) {
println ("Print from Swift \(vc.result)")
}
}
You should just be able to use it like a swift dictionary. To access an element in it:
linkedinlogincontroller.result[key]
should work fine.
I'm trying to migrate some code from Objective-c to Swift, but i have problems at the beggining when i want to conform a Swift class with a Objective-c protocol and access this class from a objetive-c class. I'm doing something wrong, but i don't see it.
Objective-c protocol and obj-c class for testing (test_class.h)
#import <Foundation/Foundation.h>
#protocol test_delegate
-(void)returnData:(NSString*)data InMethod:(NSString*)method;
#end
#interface test_class : NSObject
#property (weak, nonatomic) id<test_delegate> delegate;
-(void)sendData:(NSString * )data;
#end
Objective-c implementation
#import "test_class.h"
#implementation test_class
-(id) init{
self = [super init];
if (self != nil){
self.delegate= nil;
}
return self;
}
-(id) initWithDelegate:(id<test_delegate>) delegate{
self = [super init];
if (self != nil){
self.delegate = delegate;
}
return self;
}
-(void)sendData:(NSString *)data{
[self.delegate returnData:data InMethod:#"method test"];
}
#end
Objetive-c brigde file
//
// Use this file to import your target's public headers that you would like to expose to Swift.
//
#import "test_class.h"
Swift file (FirstViewController.swift)
import UIKit
class FirstViewController: UIViewController, test_delegate {
var test : test_class!
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view, typically from a nib.
test = test_class()
test.delegate=self
test.sendData("show in log")
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
func returnData(data: String!, inMethod method: String!) {
NSLog(data)
}
}
Finally the error compile gives me
2015-01-27 08:21:05.787 Test[4566:51164] -[Test.FirstViewController returnData:InMethod:]: unrecognized selector sent to instance 0x7fa6abd0aa20
2015-01-27 08:21:05.792 Test[4566:51164] *** Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: '-[Test.FirstViewController returnData:InMethod:]: unrecognized selector sent to instance 0x7fa6abd0aa20'
This code, using only Obj-c classes works fine. What I'm doing wrong?
Using Xcode 6.1.1 and deployment target iOS 8.0
Thank you in advance.
UPDATE:
Swift has a weird behaviour when implement some method from a objective-c protocol: param's name must start with a tiny letter at the the begginig, else, compiler give you a compile-time error, and if not, give you a runtime error.
The unique solution I've found, is modify objective-c protocol method definition this way:
#protocol test_delegate
-(void)returnData:(NSString*)data InMethod:(NSString*)method;
#end
to:
#protocol test_delegate
-(void)returnData:(NSString*)data inMethod:(NSString*)method;
#end
Doing this change, it runs perfectly.
If anyone has an answer for this behaviour, is welcome to post and explain why this happens.
UPDATE 2:
Thank you to #Adam Freeman to point out another weird issue of Swift with classes names and variables names. Copied (with his permission) the code here:
Another thing to watch out for that is if your protocol delegate method takes its class as one of its parameters. Using this example:
#import <Foundation/Foundation.h>
#class TestClass;
#protocol TestDelegate
-(void)TestClass:(TestClass*)TestClass returnData:(NSString*)data inMethod:(NSString*)method;
#end
#interface TestClass : NSObject
#property (weak, nonatomic) id<TestDelegate> delegate;
-(void)sendData:(NSString * )data;
#end
This will cause problems with TestClass being found in your Swift code. The fix is:
#import <Foundation/Foundation.h>
#class TestClass;
#protocol TestDelegate
-(void)testClass:(TestClass*)testClass returnData:(NSString*)data inMethod:(NSString*)method;
#end
#interface TestClass : NSObject
#property (weak, nonatomic) id<TestDelegate> delegate;
-(void)sendData:(NSString * )data;
#end
Incidentally the Swift spec states that such things as classes and delegates should start with an upper case letter and methods and parameter names should start with a lower case letter.
Change:
func returnData(data: String!, inMethod method: String!) {
to
func returnData(data: String!, InMethod method: String!) {
and it should work. You use a capital letter in inMethod.
Another thing to watch out for that I ran into and this post helped me fix (thanX!) and that others may run into is if your protcol delegate method takes its class as one of its parameters. Such as ==>
#import <Foundation/Foundation.h>
#class TestClass;
#protocol TestDelegate
-(void)TestClass:(TestClass*)TestClass returnData:(NSString*)data inMethod:(NSString*)method;
#end
#interface TestClass : NSObject
#property (weak, nonatomic) id<TestDelegate> delegate;
-(void)sendData:(NSString * )data;
#end
This will cause problems with TestClass being found in your Swift code. The fix is:
#import <Foundation/Foundation.h>
#class TestClass;
#protocol TestDelegate
-(void)testClass:(TestClass*)testClass returnData:(NSString*)data inMethod:(NSString*)method;
#end
#interface TestClass : NSObject
#property (weak, nonatomic) id<TestDelegate> delegate;
-(void)sendData:(NSString * )data;
#end
Incidentally the Swift spec states that such things as classes and delegates should start with an upper case letter and methods and parameter names should start with a lower case letter. I know this is nit-picky but test_class really should be TestClass according to Apple's spec.