I've just about completed the integration of a photo library based on Objective-C into my code, but I'm stuck when trying to re-write the Objective-C example code into Swift in one spot in particular. Here is the Objective-C code causing the problem.
__weak MHGalleryController *blockGallery = gallery;
gallery.finishedCallback = ^(NSInteger currentIndex,UIImage *image,MHTransitionDismissMHGallery *interactiveTransition,MHGalleryViewMode viewMode){
NSIndexPath *newIndex = [NSIndexPath indexPathForRow:currentIndex inSection:0];
[self.tableView scrollToRowAtIndexPath:newIndex atScrollPosition:UITableViewScrollPositionMiddle animated:NO];
dispatch_async(dispatch_get_main_queue(), ^{
UIImageView *imageView = [(ImageTableViewCell*)[self.tableView cellForRowAtIndexPath:newIndex] iv];
[blockGallery dismissViewControllerAnimated:YES dismissImageView:imageView completion:nil];
});
};
My assumption is that I need to set the finishedCallback variable to a closure with parameters - similar to the Block above.As such I tried to do the same thing. My block is referenced as a variable "closingBlockInputs" below.
weak var blockedGallery = gallery
var closingBlock = {
(currentIndex:NSInteger, image:UIImage, interactiveTransition: MHTransitionDismissMHGallery, viewMode: MHGalleryViewMode) -> () in
}
var tr = MHTransitionDismissMHGallery()
gallery.finishedCallback = closingBlock(1, UIImage(name:"temp"),tr,MHGalleryViewMode.OverView)
However, when I run the code I get an error like:
() is not convertible to Int, UIImage, MHTransitionDismissMHGallery,
MHGalleryViewMode
I'm pretty sure I have the general flow right, just missing something...
Any help would be greatly appreciated...
The finishedCallback is of type (NSInteger, UIImage, MHTransitionDismissMHGallery, MHGalleryViewMode) -> (), that is, a closure that takes four input parameters and returns Void. In your Swift code, you are calling closingBlock and trying to assign its return value (Void) to finishedCallback, which is why the error is telling you that () (aka Void) is not convertible to the closure's type.
There are a few other things worth noting. UIImage(name: "temp") returns an optional UIImage, but the closure is expecting a non-optional UIImage. The init?(name:) initialiser is failable since there might not be an image file with the specified name. So make sure you unwrap the optional UIImage before passing it as a parameter to the closure.
Next thing to consider is that the types of the closure parameters are inferred by the compiler, so there's no need to write them explicitly.
Also, Swift resolves strong reference cycles in closures using capture lists, rather than by declaring a separate weak version of a variable (see Strong Reference Cycles for Closures in The Swift Programming Language for details of this mechanism).
So I'd expect your code to simply look like this in Swift:
gallery.finishedCallback = { [unowned gallery] currentIndex, image, interactiveTransition, viewMode in
// ...
}
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()
}
I have some Swift code that interoperates with Objective C, and I noticed I'm leaking, I narrowed it down to NSMutableArray not removing my closure, here's a pure Swift snippet that reproduces the problem:
let myClosure : ((String?) -> ())! = { (fileName: String?) in
}
let arr = NSMutableArray()
arr.add(myClosure)
arr.remove(myClosure)
Did anyone encounter this -- why does this happen? and how can I get it to work?
The closure doesn't have the reference so the array is unable to compare for removing your closure object that's why this will not remove from the array.
Your code
let arr1 = NSMutableArray()
arr1.add(myClosure)
print(arr1) //("(Function)")
arr1.remove(myClosure)
print(arr1) //("(Function)")
Solution
var arr = Array<Any>()
arr.append(myClosure)
print(arr) //[(Function)]
arr.remove(at: 0)
print(arr) //[]
This will remove by index so you have to use the index for removing the element instead of closure instance also I recommend you to use pure swift classes in Swift.
To re-emphasize, our codebase uses Swift that interops with ObjC, therefore, in my case its not possible to go pure Swift trivially.
I changed our API to use an NSDictionary that maps from NSUInteger handle to the closure and that integer is then used for removal of the closure from the dictionary.
The purpose of that API is to register listener callbacks and have a facility to unregister them. So that NSUInteger handle satisfies the removal bit.
I am having trouble with swift closure syntax. I am trying to check the mute switch using Sharkfood which you can see here: http://sharkfood.com/content/Developers/content/Sound%20Switch/
The Block I'm trying to call is shown below.
typedef void(^SharkfoodMuteSwitchDetectorBlock)(BOOL silent);
This is how I'm trying to call it but it isn't working. I've tried a ton of different ways and I know I'm missing something little since I'm new to swift. The error I'm getting is:
'(Bool) -> Void' is not convertible to 'Bool'
On the first line of this code:
muteDetector.silentNotify({
(silent: Bool) -> Void in
println("this")
println("worked")
})
Any help would be greatly appreciated.
Never used that library, but looking at the documentation linked in your question I notice that silentNotify is a property:
#property (nonatomic,copy) SharkfoodMuteSwitchDetectorBlock silentNotify;
containing the block, so the error stating that a BOOL is expected makes sense.
Instead with your code you are probably trying to call this method:
-(void)setSilentNotify:(SharkfoodMuteSwitchDetectorBlock)silentNotify{
_silentNotify = [silentNotify copy];
self.forceEmit = YES;
}
I don't know which of the 2 you are attempting to do - if you want to call the block, then you have to just provide a bool:
muteDetector.silentNotify(true)
if instead you want to register a new block (closure), then you have to use:
muteDetector.setSilentNotify({
(silent: Bool) -> Void in
println("this")
println("worked")
})
I'm trying a simple example as seen here:
https://developer.apple.com/library/prerelease/ios/documentation/Swift/Conceptual/Swift_Programming_Language/AutomaticReferenceCounting.html#//apple_ref/doc/uid/TP40014097-CH20-XID_88
And this is my code. (Ignore other possible code, this is a empty project with this code written inside an empty UIViewcontroller viewDidLoad)
dispatch_async(dispatch_get_main_queue()) {
[unowned self] in
println(self)
}
I don't understand why it crashes when I run the pro
thread #1: tid = 0x1a796, 0x00284d18 libswiftCore.dylib`_swift_release_slow + 8, queue =
'com.apple.main-thread', stop reason = EXC_BAD_ACCESS (code=1,
address=0x458bc681)
Did something changed on the latest beta(5) and this is not supported anymore?
Thanks
edit:
Interesting that this code works on Objc
__weak MyViewController *weakSelf = self;
dispatch_async(dispatch_get_main_queue(), ^{
NSLog(#"%#", weakSelf);
});
edit2:
The explanation on this link : Shall we always use [unowned self] inside closure in Swift on the difference of weak and unowned is wrong.
It's not just that weak nils and unowned doesn't. If that's the case, this should crash as well:
dispatch_async(dispatch_get_main_queue()) {
[weak self] in
println(self)
}
but it doesn't, and it prints the pointer, so, it's not nil.
[Unowned self] makes it so the closure does not create a strong reference to self and it does not automatically set it to nil if it gets deallocated either. By the time the async method is executed, self has been deallocated. That is why you are getting the crash.
It certainly doesn't make sense to use unowned in a one time asynchronous call. It would be better to capture a strong reference to it to be sure it sticks around. There still won't be a strong reference cycle because self does not own the closure.
Side Note: This cannot be all of your code as self isn't defined anywhere in your code.
unowned and weak are two different things. In Objective-C, unowned is called unsafe unretained. You can use weak in both languages. weak means that the runtime will automatically convert the reference to nil if the object is deallocated. unowned or unsafe unretained means that it will not be set to nil for you (which is why it is called "unsafe" in Objective-C.
Unowned should only ever be used in circumstances where the object will never be deallocated. In those circumstances, use weak.
Keep in mind, that if you capture a variable as weak in Swift, the reference will be made an optional so to use it you will have to unwrap it:
dispatch_async(dispatch_get_main_queue()) {
[weak self] in
if let actualSelf == self {
// do something with actualSelf
}
// you can still print the "wrapped" self because it is fine to print optionals
// even if they are `nil`
println(self)
}
But to be clear, it would still be best to use a strong reference in this circumstance:
dispatch_async(dispatch_get_main_queue()) {
println(self)
}
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 ()
}