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
Related
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()
}
Hi guys Im kinda new to Objective-C and I've got a little code that I would like to convert it from Swift -> Objective-C. I've got a variable which is a closures but not sure how doing it in Objective-C
Here's the variable:
var didTimerFire: ((UICollectionViewCell) -> Void)?
also is there's any "self" in objective-C? sorry for being a noob but again kinda new to Objective-C :)
In Objective-C there are Blocks:
If you want to use them as property it goes like:
#property (nonatomic, copy, nullability) returnType (^blockName)(parameterTypes);
Or as method parameters:
- (void)method:(returnType (^nullability)(parameterTypes))blockName;
So for you example it will go like:
#property (nonatomic, copy, nullable) void (^didTimerFire)(UICollectionViewCell);
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)
}
}
I have problems to understand, why some member functions from an imported (and complicated) set of Objective-C interface are not available in Swift.
I have a Bridging-Header File:
#import "EvernoteSDK.h"
and I can't use some member functions in my ViewController
let userStore = EvernoteUserStore()
userStore.initWithSession(session)
initWithSession is not available for the swift code, but why?
The objective-C header shows:
#interface EvernoteUserStore : ENAPI
+ (instancetype)userStore;
- (id)initWithSession:(EvernoteSession *)session;
If I could view the exposed Objective-C header, I may understand, how the mangling works
In Swift the initializer call is combined with the constructor. In other words, Objective-C's
EvernoteUserStore *userStore = [[EvernoteUserStore alloc] initWithSession:session];
becomes
let userStore = EvernoteUserStore(session:session);
The tool recognizes the initWithSomething: name of Objective-C, and converts it to
init(something something : SomeType)
In case of EvernoteUserStore the corresponding init method looks like this:
init(session session: EvernoteSession!)
I need to use something like a C array:
MyStruct theArray[18][18];
but I cannot define it as a property:
#property (nonatomic) MyStruct theArray[18][18];
then I have to:
#implementation MyClass
{
MyStruct theArray[18][18];
}
But is this good in term of modern Objective C guideline?
Thanks
Update:
I know I can define the struct as class and use NSMutableArray to handle it, but it is more convenient to use the C array in my case, the main concern is coding guideline and memory issue, as I do not allocate or release the theArray[18][18], not sure what its life cycle is, and I'm using ARC.
Properties cannot be of array type, while public instance variables do not provide sufficient encapsulation. A more Objective C - like approach would be defining a private 2D array, and a pair of methods or a method returning a pointer to access it - something along these lines:
// For small structs you can use a pair of methods:
-(MyStruct)getElementAtIndexes:(NSUInteger)i and:(NSUInteger)j;
-(void)setElementAtIndexes:(NSUInteger)i and:(NSUInteger)j to:(MyStruct)val;
// For larger structs you should use a single method that returns a pointer
// to avoid copying too much data:
-(MyStruct*)elementAtIndexes:(NSUInteger)i and:(NSUInteger)j;
How about use pointers instead?
#property (nonatomic) MyStruct **theArray;
The answers so far are great. . . here's two more options:
1. A bit hacky
(I'm not sure if this requires Objective-C++)
You can create the array as a public property like so:
#interface MyClass
{
#public:
MyStruct theArray[18][18];
}
#end
And then access it as follows:
myClass->theArray
2. Return a Struct
While you can't return a C-style array, you can return a struct:
typedef struct
{
CGPoint textureCoordinates[kMaxHillVertices];
CGPoint borderVertices[kMaxBorderVertices];
} HillsDrawData;
#interface Hills : NSObject
{
HillsDrawData _drawData;
}
- (HillsDrawData)drawData; //This will get cleaned up when the class that owns it does.
#end