Calling block from Objective C collection in Swift - ios

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()
}

Related

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.

Closure cannot implicitly capture a mutating self parameter

I am using Firebase to observe event and then setting an image inside completion handler
FirebaseRef.observeSingleEvent(of: .value, with: { (snapshot) in
if let _ = snapshot.value as? NSNull {
self.img = UIImage(named:"Some-image")!
} else {
self.img = UIImage(named: "some-other-image")!
}
})
However I am getting this error
Closure cannot implicitly capture a mutating self parameter
I am not sure what this error is about and searching for solutions hasn't helped
The short version
The type owning your call to FirebaseRef.observeSingleEvent(of:with:) is most likely a value type (a struct?), in which case a mutating context may not explicitly capture self in an #escaping closure.
The simple solution is to update your owning type to a reference once (class).
The longer version
The observeSingleEvent(of:with:) method of Firebase is declared as follows
func observeSingleEvent(of eventType: FIRDataEventType,
with block: #escaping (FIRDataSnapshot) -> Void)
The block closure is marked with the #escaping parameter attribute, which means it may escape the body of its function, and even the lifetime of self (in your context). Using this knowledge, we construct a more minimal example which we may analyze:
struct Foo {
private func bar(with block: #escaping () -> ()) { block() }
mutating func bax() {
bar { print(self) } // this closure may outlive 'self'
/* error: closure cannot implicitly capture a
mutating self parameter */
}
}
Now, the error message becomes more telling, and we turn to the following evolution proposal was implemented in Swift 3:
SE-0035: Limiting inout capture to #noescape contexts
Stating [emphasis mine]:
Capturing an inout parameter, including self in a mutating
method, becomes an error in an escapable closure literal, unless the
capture is made explicit (and thereby immutable).
Now, this is a key point. For a value type (e.g. struct), which I believe is also the case for the type that owns the call to observeSingleEvent(...) in your example, such an explicit capture is not possible, afaik (since we are working with a value type, and not a reference one).
The simplest solution to this issue would be making the type owning the observeSingleEvent(...) a reference type, e.g. a class, rather than a struct:
class Foo {
init() {}
private func bar(with block: #escaping () -> ()) { block() }
func bax() {
bar { print(self) }
}
}
Just beware that this will capture self by a strong reference; depending on your context (I haven't used Firebase myself, so I wouldn't know), you might want to explicitly capture self weakly, e.g.
FirebaseRef.observeSingleEvent(of: .value, with: { [weak self] (snapshot) in ...
Sync Solution
If you need to mutate a value type (struct) in a closure, that may only work synchronously, but not for async calls, if you write it like this:
struct Banana {
var isPeeled = false
mutating func peel() {
var result = self
SomeService.synchronousClosure { foo in
result.isPeeled = foo.peelingSuccess
}
self = result
}
}
You cannot otherwise capture a "mutating self" with value types except by providing a mutable (hence var) copy.
Why not Async?
The reason this does not work in async contexts is: you can still mutate result without compiler error, but you cannot assign the mutated result back to self. Still, there'll be no error, but self will never change because the method (peel()) exits before the closure is even dispatched.
To circumvent this, you may try to change your code to change the async call to synchronous execution by waiting for it to finish. While technically possible, this probably defeats the purpose of the async API you're interacting with, and you'd be better off changing your approach.
Changing struct to class is a technically sound option, but doesn't address the real problem. In our example, now being a class Banana, its property can be changed asynchronously who-knows-when. That will cause trouble because it's hard to understand. You're better off writing an API handler outside the model itself and upon finished execution fetch and change the model object. Without more context, it is hard to give a fitting example. (I assume this is model code because self.img is mutated in the OP's code.)
Adding "async anti-corruption" objects may help
I'm thinking about something among the lines of this:
a BananaNetworkRequestHandler executes requests asynchronously and then reports the resulting BananaPeelingResult back to a BananaStore
The BananaStore then takes the appropriate Banana from its inside by looking for peelingResult.bananaID
Having found an object with banana.bananaID == peelingResult.bananaID, it then sets banana.isPeeled = peelingResult.isPeeled,
finally replacing the original object with the mutated instance.
You see, from the quest to find a simple fix it can become quite involved easily, especially if the necessary changes include changing the architecture of the app.
If someone is stumbling upon this page (from search) and you are defining a protocol / protocol extension, then it might help if you declare your protocol as class bound. Like this:
protocol MyProtocol: class {
...
}
You can try this! I hope to help you.
struct Mutating {
var name = "Sen Wang"
mutating func changeName(com : #escaping () -> Void) {
var muating = self {
didSet {
print("didSet")
self = muating
}
}
execute {
DispatchQueue.global(qos: .background).asyncAfter(deadline: .now() + 15, execute: {
muating.name = "Wang Sen"
com()
})
}
}
func execute(with closure: #escaping () -> ()) { closure() }
}
var m = Mutating()
print(m.name) /// Sen Wang
m.changeName {
print(m.name) /// Wang Sen
}
Another solution is to explicitly capture self (since in my case, I was in a mutating function of a protocol extension so I couldn't easily specify that this was a reference type).
So instead of this:
functionWithClosure(completion: { _ in
self.property = newValue
})
I have this:
var closureSelf = self
functionWithClosure(completion: { _ in
closureSelf.property = newValue
})
Which seems to have silenced the warning.
Note this does not work for value types so if self is a value type you need to be using a reference type wrapper in order for this solution to work.

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)
}
}

How to declare blocks using Swift in iOS?

I am trying to convert my Objective C source code to swift, I have searched alot to change syntax for Blocks.
Here is my Objective C code that I want to switch to Swift :
class.h file :
#property (nonatomic, copy) void (^tapBlock)(CGFlagsCell *);
+ (NSString *)cellIdentifier;
Class.m file :
self.tapGesture = [[UITapGestureRecognizer alloc] initWithTarget:self action:#selector(didTapCell)];
- (void)didTapCell {
self.tapBlock(self);
}
In Swift, blocks, functions and closures are the same thing, they have the same signature and are interchangeable.
So this would give you something like this
var tapBlock: CGFlagsCell -> Void
You can add parenthesis around the parameter (optional if there is only one, recommended if there are multiple input parameters) and around the return type (optional):
var tapBlock: (CGFlagsCell) -> (Void)
Define a closure a variable of your class :
var tapBlock: (parameterTypes) -> (returnType)
Swift Closures are self-contained blocks of functionality that can be passed around and used in your code. Closures in Swift are similar to blocks Objective-C

Translating ObjC-Blocks to Swift Closures

I am trying to translate some objective-C Code into Swift. I added the Cocoapod "Masonry" for Autolayout to my project and added a Bridging-Header in order to able to use Objective-C Methods in Swift.
This ObjC Method:
[_tableView mas_makeConstraints:^(MASConstraintMaker *make) {
make.edges.equalTo(self.view);
}];
should be something like the following Closure:
tableView.mas_makeConstraints({ (make : MASConstraintMaker!) -> Void? in
make.edges.equalTo(self.view)
})
But I am getting an "Could not find member 'mas_makeConstraints'" which is not the error, as the method is indexed and autocomletion gives me the following:
tableView.mas_makeConstraints(block: ((MASConstraintMaker!) -> Void)?)
?!
Am I doing something wrong here?
Just my 2 cents if anyone encounter this case:
This objc
[productView mas_makeConstraints:^(MASConstraintMaker *make) {
make.left.equalTo(self.view.mas_left).with.offset(15);
make.right.equalTo(self.view.mas_right).with.offset(15);
make.top.equalTo(self.view.mas_top).with.offset(15);
make.height.equalTo(productView.mas_width);
}];
will turn into
productView.mas_makeConstraints{ make in
make.left.equalTo()(self.view.mas_left).with().offset()(15)
make.right.equalTo()(self.view.mas_right).with().offset()(-15)
make.top.equalTo()(self.view.mas_top).with().offset()(15)
make.height.equalTo()(productView.mas_width)
return ()
}
This piece here in the method signature:
(block: ((MASConstraintMaker!) -> Void)?)
...is telling you that the block argument is Optional, not the return value, which should be Void (not Void?, where you write (make: MASConstraintMaker!) -> Void?)
also: because Swift Type Inference you don't need to put the types in the block
also also: because Swift Trailing Closures you don't need to put a closure that is the final argument to a method inside of the argument list in parens (and since here it's the only argument, you can leave off the parens entirely)
so really your entire method call with block argument could be re-written as:
tableView.mas_makeConstraints { make in
make.edges.equalTo(self.view)
}
finally: it looks like the instance method you are calling on make.edges returns a block, and thanks to the Swift convenience feature of 'implicit return of single expression blocks' it's may be the case that your block is implicitly returning the value of that expression when it is expecting Void - so in the end, if the above doesn't work you may still need to explicitly return Void by writing your method call as:
tableView.mas_makeConstraints { make in
make.edges.equalTo(self.view)
return ()
}

Resources