Pass argument in class method (+) of Objective-C class from Swift class - ios

I am working with hybrid project of combination of Obj-C classes and Swift classes. I am little confuse at some point.
The scenario is like this, i have one Obj-C class call Util.h which have some class methods like bellow code
+ (id)dict_TO_Object:(NSDictionary *)dbData withObject:(Class)myObj {
//Some code
return obj;
}
Now, i have one Swift class called LoginVC.Swift. In this call i want to call dict_TO_Object method bellow is the code
let tmpDict:NSMutableDictionary
someVar = Util.dict_TO_Object(tmpDict as [NSObject : AnyObject], withObject: User.self())
//Above line give me an error
Error : Cannot invoke 'dict_TO_Object' with an argument list of type '([NSObject : AnyObject], withObject: User)'
in above code i am passing tmpDict and User as a class which is User.h in Obj-c
Please suggest me a solution or modification.
Edit:
Tried bellow codes but get same error
someVar = Util.dict_TO_Object(tmpDict as NSDictionary, withObject: User().self)
Edit Solution:
Actually this method returns me an object of User.h class, but my variable did not understand that. See solution bellow
someVar = Util.dict_TO_Object(responseDict as [NSObject : AnyObject], withObject: User.self) as! User

The second parameter has type Class, which bridges to AnyClass in Swift.
You need to pass User.self. Not User.self() (which is the same as User()), and not User().self (which doesn't make sense).

Related

Calling block from Objective C collection in Swift

I have a Swift object that takes a dictionary of blocks (keyed by Strings), stores it and runs block under given key later at some point depending on external circumstances (think different behaviours depending on the backend response):
#objc func register(behaviors: [String: #convention(block) () -> Void] {
// ...
}
It's used in a mixed-language project, so it needs to be accessible from both Swift and Objective-C. That's why there's #convention(block), otherwise compiler would complain about not being able to represent this function in Objective-C.
It works fine in Swift. But when I try to invoke it from Objective-C like that:
[behaviorManager register:#{
#"default": ^{
// ...
}
}];
The code crashes and I get following error:
Could not cast value of type '__NSGlobalBlock__' (0x...) to '#convention(block) () -> ()' (0x...).
Why is that, what's going on? I thought #convention(block) is to specifically tell the compiler that Objective C blocks are going to be passed, and that's exactly what gets passed to the function in the call.
That's why there's #convention(block), otherwise compiler would
complain about not being able to represent this function in
Objective-C
For the sake of consistency: commonly you use #convention attribute the other way around - when there is an interface which takes a C-pointer (and implemented in C) or an Objective-C block (and implemented in Objective-C), and you pass a Swift closure with a corresponding #convention as an argument instead (so the compiler actually can generate appropriate memory layout out of the Swift closure for the C/Objective-C implementation). So it should work perfectly fine if it's Objective-C side where the Swift-created closures are called like blocks:
#interface TDWObject : NSObject
- (void)passArguments:(NSDictionary<NSString *, void(^)()> *)params;
#end
If the class is exposed to Swift the compiler then generates corresponding signature that takes a dictionary of #convention(block) values:
func passArguments(_ params: [String : #convention(block) () -> Void])
This, however, doesn't cancel the fact that closures with #convention attribute should still work in Swift, but the things get complicated when it comes to collections, and I assume it has something with value-type vs reference-type optimisation of Swift collections. To get it round, I'd propose to make it apparent that this collection holds a reference type, by promoting it to the [String: AnyObject] and casting later on to a corresponding block type:
#objc func takeClosures(_ closures: [String: AnyObject]) {
guard let block = closures["One"] else {
return // the block is missing
}
let closure = unsafeBitCast(block, to: ObjCBlock.self)
closure()
}
Alternatively, you may want to wrap your blocks inside of an Objective-C object, so Swift is well aware of that it's a reference type:
typedef void(^Block)();
#interface TDWBlockWrapper: NSObject
#property(nonatomic, readonly) Block block;
#end
#interface TDWBlockWrapper ()
- (instancetype)initWithBlock:(Block)block;
#end
#implementation TDWBlockWrapper
- (instancetype)initWithBlock:(Block)block {
if (self = [super init]) {
_block = block;
}
return self;
}
#end
Then for Swift it will work as simple as that:
#objc func takeBlockWrappers(_ wrappers: [String: TDWBlockWrapper]) {
guard let wrapper = wrappers["One"] else {
return // the block is missing
}
wrapper.block()
}

Swift init from unknown class which conforms to protocol

I'm currently working on updating a large project from Objective-C to Swift and I'm stumped on how to mimic some logic. Basically we have a class with a protocol which defines a couple functions to turn any class into a JSON representation of itself.
That protocol looks like this:
#define kJsonSupport #"_jsonCompatible"
#define kJsonClass #"_jsonClass"
#protocol JsonProtocol <NSObject>
- (NSDictionary*)convertToJSON;
- (id)initWithJSON:(NSDictionary* json);
#end
I've adapted that to Swift like this
let JSON_SUPPORT = "_jsonCompatible"
let JSON_CLASS = "_jsonClass"
protocol JsonProtocol
{
func convertToJSON() -> NSDictionary
init(json: NSDictionary)
}
One of the functions in the ObjC class runs the convertToJSON function for each object in an NSDictionary which conforms to the protocol, and another does the reverse, creating an instance of the object with the init function. The output dictionary also contains two keys, one denoting that the dictionary in question supports this protocol (kJsonSupport: BOOL), and another containing the NSString representation of the class the object was converted from (kJsonClass: NSString). The reverse function then uses both of these to determine what class the object was converted from to init a new instance from the given dictionary.
All of the classes are anonymous to the function itself. All we know is each class conforms to the protocol, so we can call our custom init function on it.
Here's what it looks like in ObjC:
Class rootClass = NSClassFromString(obj[kJsonClass]);
if([rootClass conformsToProtocol:#protocol(JsonProtocol)])
{
Class<JsonProtocol> jsonableClass = (Class<JsonProtocol>)rootClass;
[arr addObject:[[((Class)jsonableClass) alloc] initWithJSON:obj]];
}
However, I'm not sure how to make this behavior in Swift.
Here's my best attempt. I used Swiftify to try and help me get there, but the compiler isn't happy with it either:
let rootClass : AnyClass? = NSClassFromString(obj[JSON_CLASS] as! String)
if let _rootJsonClass = rootClass as? JsonProtocol
{
weak var jsonClass = _rootJsonClass as? AnyClass & JsonProtocol
arr.add(jsonClass.init(json: obj))
}
I get several errors on both the weak var line and the arr.add line, such as:
Non-protocol, non-class type 'AnyClass' (aka 'AnyObject.Type') cannot be used within a protocol-constrained type
'init' is a member of the type; use 'type(of: ...)' to initialize a new object of the same dynamic type
Argument type 'NSDictionary' does not conform to expected type 'JsonProtocol'
Extraneous argument label 'json:' in call
Is there any way for me to instantiate from an unknown class which conforms to a protocol using a custom protocol init function?
You will likely want to rethink this code in the future, to follow more Swift-like patterns, but it's not that complicated to convert, and I'm sure you have a lot of existing code that relies on behaving the same way.
The most important thing is that all the objects must be #objc classes. They can't be structs, and they must subclass from NSObject. This is the major reason you'd want to change this to a more Swifty solution based on Codable.
You also need to explicitly name you types. Swift adds the module name to its type names, which tends to break this kind of dynamic system. If you had a type Person, you would want to declare it:
#objc(Person) // <=== This is the important part
class Person: NSObject {
required init(json: NSDictionary) { ... }
}
extension Person: JsonProtocol {
func convertToJSON() -> NSDictionary { ... }
}
This makes sure the name of the class is Person (like it would be in ObjC) and not MyGreatApp.Person (which is what it normally would be in Swift).
With that, in Swift, this code would be written this way:
if let className = obj[JSON_CLASS] as? String,
let jsonClass = NSClassFromString(className) as? JsonProtocol.Type {
arr.add(jsonClass.init(json: obj))
}
The key piece you were missing is as? JsonProtocol.Type. That's serving a similar function to +conformsToProtocol: plus the cast. The .Type indicates that this is a metatype check on Person.self rather than a normal type check on Person. For more on that see Metatype Type in the Swift Language Reference.
Note that the original ObjC code is a bit dangerous. The -initWithJSON must return an object. It cannot return nil, or this code will crash at the addObject call. That means that implementing JsonProtocol requires that the object construct something even if the JSON it is passed is invalid. Swift will enforce this, but ObjC does not, so you should think carefully about what should happen if the input is corrupted. I would be very tempted to change the init to an failable or throwing initializer if you can make that work with your current code.
I also suggest replacing NSDictionary and NSArray with Dictionary and Array. That should be fairly straightforward without redesigning your code.

Swift class in Objective-C project (Compiler error)

I created an Swift class in my Objective-C project. There are no problems with the generated bridging interfaces between objective-c and swift.
In my objective-c project, I have an class named Task, its Task.h looks like following:
#interface Task: NSObject
…
- (id) initWithHomeworks:(NSDictionary *)homeworks
#end
The Task.m looks like this:
#implementation Task
- (id) initWithHomeworks:(NSDictionary *)settings {
self = [super init];
//Do something with self...
...
return self
}
My swift code inherits the above class:
import Foundation
class SubTask : Task {
//Compiler Error 1
func initWithHomeworks(homeworks: Dictionary<String, String>) -> AnyObject{
//Compiler Error 2
return super.initWithSettings(settings)
}
}
I get two compiler errors in the place commented above. The error messages are below:
Compiler Error 1:
Method ‘initWithHomeworks’ with Objective-C selector ‘initWithHomeworks:’ conflicts with initializer ‘init(homework:)’ from superclass ‘Task’ with the same Objective-C selector
(I haven't declared any method init(homework:))
Compiler Error 2:
‘Task’ does not have a member named ‘initWithHomeworks’
Why I get these two errors in my Swift class? How to fix them?
The fundamental reason for that you are seeing is that Swift converts Objective-C methods names intelligently. From the docs:
To instantiate an Objective-C class in Swift, you call one of its
initializers with Swift syntax. When Objective-C init methods come
over to Swift, they take on native Swift initializer syntax. The
“init” prefix gets sliced off and becomes a keyword to indicate that
the method is an initializer. For init methods that begin with
“initWith,” the “With” also gets sliced off. The first letter of the
selector piece that had “init” or “initWith” split off from it becomes
lowercase, and that selector piece is treated as the name of the first
argument. The rest of the selector pieces also correspond to argument
names. Each selector piece goes inside the parentheses and is required
at the call site.
In your case the Objective-C initWithHomeworks:(NSDictionary *)homeworks method is converted to Swift as init(homeworks: [NSObject : AnyObject]!). You can override it as follows:
Given an Objective-C header:
#interface Task : NSObject
- (id)initWithHomeworks:(NSDictionary *)homeworks;
#end
Swift subclass:
class SubTask : Task {
override init!(homeworks: [NSObject : AnyObject]!) {
super.init(homeworks: homeworks)
}
}
This answer was completed using Swift 1.2 in Xcode 6.4
Update based on comments
You have defined your SubTask's initialiser as a regular function:
func initWithHomeworks(homeworks: Dictionary<String, String>) -> AnyObject{
return super.initWithSettings(settings)
}
This is incorrect for the following reasons:-
func is not allowed on init methods
init methods shouldn't declare a return type, remove -> AnyObject
init methods don't return anything, remove the return keyword from the super.init...
Swift initialisers do not return a value however you can still assign the result to a variable:
Swift:
var mySubTask = SubTask(homeworks: someDictionary)
Objective-C:
SubTask *mySubTask = [[SubTask alloc] initWithHomeworks:someDictionary];
If you want to override init method,you should write it like this
class SubTask:Task{
override init!(homeworks: [NSObject : AnyObject]!) {
super.init(homeworks: homeworks)
}
}

Passing dictionary objects to Objective C protocol in Swift

I'm trying to pass a dictionary object to an Objective C protocol using swift.
the protocol code snippet is as follows:
#protocol MessageDelegate
- (void)handleNewMessageArrived:(NSDictionary *)messageContent;
#end
and this is the swift class the implements the protocol:
class ViewController: UIViewController, MessageDelegate
{
...
func handleNewMessageArrived(messageContent : NSDictionary!)
{
...
}
}
But the build fails, and the error I get is:
"the type 'ViewController' does not conform to protocol 'MessageDelegate"
I looked at this SO Question but it deals with a specific object type.
is there an error in the way I declare\implement the delegate method? or in the way I assume the arguments are mapped in swift?
I'm new to Swift so any Help will be much appreciated.
Try implementing the method in your Swift class like this:
func handleNewMessageArrived(messageContent: [NSObject : AnyObject]!) {
// Handle the message
}
In case of Swift 3, this is what you will need
func handleNewMessageArrived(messageContent: [AnyHashable : Any]!) {
// Handle the message
}

How to access an Objective-C class method from Swift language

In my Swift app, I need to access a class method called weibo() as below from Objective-C
#interface Weibo : NSObject
+ (Weibo*)weibo;
#end
I have configured the bridging header and tried the following statement
let w = Weibo.weibo() as Weibo
It doesn't work.
UPDATE:
I have forked the repo and fixed this issue as below.
let w = Weibo.getWeibo() as Weibo // the method has been changed.
The reason why it didn't work because Swift treats + (Weibo*)weibo; as a convenience constructor. Since weibo is same as the Class name Weibo although the case is different. I need to change the name to getWeibo to fix this issue to support Swift.
Thanks for every one contributing to this answer. Special thanks to #Anil and #David
Jake
+ (Weibo*)weibo; is the class method of your class Weibo. You could access it in swift like
let w:Weibo = Weibo.weibo()
But it gives me error when i tried('weibo' is unavailable: use object construction 'Weibo()') may be because of method name and class name are same. When i change the method name error goes
let w:Weibo = Weibo.getWeibo() // works: method name changed
That's a class method not a property.
So you would access it like...
let w = Weibo.weibo()
... I think.
Type would be inferred but you could do it as...
let w:Weibo = Weibo.weibo() as Weibo
I believe, and it would still work.
I have the same problem by change code
+(instancetype _Nonnull)manager;
you should change it like this
#property(nonatomic, class, strong, readonly) CKKNetManager *_Nonnull manager;
then you can call in Swift like this
CKKNetManager.manager.get(kGetCompanyInfo, success: {[unowned self] (obj) in
self.hideEmptyView()
self.info = CKKCorpInfo.init(dictionary: obj as! [String : AnyObject])
}) {[unowned self] (errorString) in
self.showEmptyView(in: self.view, with: EmptyViewType.pageLoadFailed)
}
In my situation, my Obj-c class method returned a pointer to a new class not added to my bridging header so the compiler did not make that class method accessible.
You need both the class with the class method you want to call and any class in the class method's description to be included in your bridging header.
In case anyone has the same problems as I had in the past, the reason why I could not access the function from swift is because it was missing the function declaration in the .h (header) file.
So it was only in the .m file and not accessible from my swift file.

Resources