Objective C to Swift Nonnull conversion - objective-c

Im converting some code from Objective C.
-(nonnull NSString*) endpoint {
return #"LoginRequest";
}
The Swift converter produces
func endpoint() -> nonnull NSString {
return "LoginRequest"
}
However nonnull is not recognised by swift. This should also be an overrider function.
I believe it should be along the lines of
override func endpoint() -> NSString {
return "LoginRequest"
}
but it brings up an error.
Method does not override any Method from its superclass. I shouldnt need to remove the override, if I do, it conflicts with the original in the Objective C imported library.
Could you help please?

solved it
override func endpoint() -> String {
return "LoginRequest"
}

Related

Using Obj-C completion block in Swift

In Objective-C, I have a completion block class defined as:
File.h
typedef void (^MYCompletionBlock)(BOOL success, NSDictionary *result, NSError *error);
Then, in a Swift file, I try to use the completion block as follows:
Swift.swift
class MyClass: NSObject{
...
func MyFunction() -> Void {
...
objcMethod(param1, withCompletion: {(MYCompletionBlock) -> Void in
if (success){ // Error:"Use of unresolved identifier 'success'"
}
}
...
}
...
}
But, I keep getting an error: "Use of unresolved identifier 'success'".
I've tried the following as well:
objcMethod(param1, withCompletion: {(success:Bool, result: NSDictionary, error:NSError) -> Void in
if (success){ // Error:"Cannot convert value of type '(Bool, NSDictionary, NSError) -> Void' to expected argument type "MYCompletionBlock!"
}
}
Can somebody help me understand how to correctly specify a Obj-C completion block in Swift?
Given that your closure doesn't specify nullability qualifiers (where they almost certainly are optional), one can safely assume that your Objective-C API has not been audited for nullability. Thus, Swift will treat pointers as implicitly unwrapped optionals. Furthermore, nowadays the NSDictionary is mapped to a [NSObject : AnyObject] Swift dictionary.
Thus, it would be:
obj.objcMethod(param) { (success: Bool, result: [NSObject : AnyObject]!, error: NSError!) in
if success {
// do something
}
}
Or, as Kobi points out, you can let the compiler infer the types:
obj.objcMethod(param) { success, result, error in
if success {
// do something
}
}
Note, you don't have to remember this yourself. You can leverage Xcode's code completion as you enter the code. So, type enough to match the method name and when it matches objcMethod, then hit enter:
When you get to MYCompletionBlock, hit enter again, and it will show you the correct signature:
If this Objective-C method was my own class, I would audit it for nullability. So, for example, let's assume the param is optional, the closure is required, and the result and error were optional, you might define it like so:
NS_ASSUME_NONNULL_BEGIN
typedef void (^MYCompletionBlock)(BOOL success, NSDictionary * _Nullable result, NSError * _Nullable error);
#interface MyObject : NSObject
- (void)objcMethod:(NSDictionary * _Nullable)param1 withCompletionHandler:(MYCompletionBlock)completionHandler;
#end
NS_ASSUME_NONNULL_END
And, if that was the case, your Swift code would call it like so:
obj.objcMethod(param) { (success: Bool, result: [NSObject : AnyObject]?, error: NSError?) in
if success {
// do something
}
}
Or, again, just let the compiler infer the types for you (but this time they'd be inferred as optionals that are not implicitly unwrapped):
obj.objcMethod(param) { success, result, error in
if success {
// do something
}
}
You shouldn't specify types for the completion block parameters, as some types defer between Swift and Objective C (e.g. BOOL is actually ObjCBool in Swift).
This should work:
objcMethod(param1) { (success, result, error) in
if (success){
// Do something
}
}

Convert Swift 2 closure to Objective-C block

I'm trying to build an Objective-C block in Swift 2 in order to add it to an NSArray like so :
typealias CompletionBlock = () -> Void
let aBlock:CompletionBlock = {
print("Hello world!")
}
let nsArray = NSMutableArray()
nsArray.addObject(aBlock) // Error
I know it will work just fine with a Swift array, but I need an NSArray here for compatibility with existing Objective-C code. And if I use a swift array the compiler will refuse to cast it to an NSArray because it won't be a [AnyObject] (it will be a [Any]).
The problem here is that a swift closure is not an object contrary to Objective-C blocks which are objects behind the scene (they are instances of NSBlock which is a subclass of NSObject)
So my question is : How do a create an Objective-C block in swift ? I've tried using #convention (block) in the typealias but it doesn't work.
EDIT : As of Swift 3, this is completely unnecessary (and doesn't even work). Adding closures to Objective-C arrays works out of the box in Swift 3. The answer below is valid for Swift 2 only.
I know this is a duplicate but I will still post a refactored answer from swift-closure-as-anyobject and cast-closures-blocks in case anyone lands on this one first.
The solution is to use the unsafeBitCast function to convert the Swift closure to an Objective-C compatible object before adding it to an NSArray and back before using it in Swift.
// The `#convention(block)` is important here in order to get
// Objective-C like memory management
typealias CompletionBlock = #convention(block) () -> Void
let aBlock:CompletionBlock = {
print("Hello world!")
}
let nsArray = NSMutableArray()
let blockObject = unsafeBitCast(aBlock, AnyObject.self)
nsArray.addObject(blockObject)
let closureObject = nsArray[0]
let closure = unsafeBitCast(closureObject, CompletionBlock.self)
closure()

Use delegate method written in Objective-C in Swift code

I'd like to use a delegate method written in Objective-C in Swift. The method is included in the MGSwipeTableCell framework (MGSwipeTableCell.h).
Objective-C:
-(BOOL) swipeTableCell:(MGSwipeTableCell*) cell tappedButtonAtIndex:(NSInteger) index direction:(MGSwipeDirection)direction fromExpansion:(BOOL) fromExpansion;
I try to convert it into Swift to and use the method:
func swipeTableCell(cell:MGSwipeTableCell, index:Int, direction:MGSwipeDirection, fromExpansion:Bool) -> Bool {
return true
}
But I don't know why but the function isn't getting called. Did I something wrong? I just want to get the indexPath of the swiped cell with this function.
You should implement MGSwipeTableCellDelegate protocol in your table view controller first. So you can just write:
class TableViewController : UITableViewController, MGSwipeTableCellDelegate {
....
....
....
func swipeTableCell(cell:MGSwipeTableCell, index:Int, direction:MGSwipeDirection, fromExpansion:Bool) -> Bool {
return true
}
}
and then when creating cells in cellForRowAtIndexPath: method you should create it like this:
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell
{
let reuseIdentifier = "cell"
var cell = self.table.dequeueReusableCellWithIdentifier(reuseIdentifier) as! MGSwipeTableCell!
if cell == nil {
cell = MGSwipeTableCell(style: UITableViewCellStyle.Subtitle, reuseIdentifier: reuseIdentifier)
}
cell.delegate = self
return cell
}
Then you'll be able to track when swipe method is called because you set the cell delegate property.
It appears that the library you're trying to use has not adopted Objective-C nullability annotations yet, as such, any return values or arguments which are objects will be translated into Swift as implicitly unwrapped optionals (with the exclamation mark).
So, the signature you're looking for is this:
func swipeTableCell(cell: MGSwipeTableCell!, tappedButtonAtIndex: Int, direction: MGSwipeDirection, fromExpansion: Bool) -> Bool
But with that said, you need to add the protocol conformance anyway. If you do that first then try to write this method out, it should autocomplete to exactly how Swift expects it to look.
And then just make sure you're actually setting the cell's delegate property to whatever object is implement this method.

Swift 2.0 interop with Objective C not working as expected?

I'm porting a Swift 1.2 framework to 2.0. After fixing the 3 million compiler errors courtesy of the new Swift 2.0 error handling scheme, I was finally able to link my test app (written in Objective C) to use the updated framework.
Xcode version 7.0 beta 3 (7A121l)
However, I ran into a problem. Some Swift functions are no longer generated into the automatically generated Objective C header (MyFramework-Swift.h) used by the Objective C test app.
Here is an example of a function that is not exposed: (My actual framework function returned an enum, but I tried to simplify to illustrate the problem more clearly).
public func calculateImportantValueDangerously() throws -> Int
{
return 42
}
Note that other functions like the following actually do get exposed as expected (and can be called):
public func doSomething()
{
}
public func doSomethingDangerous() throws
{
}
public func calculateMeaninglessValue() -> Int
{
return -1
}
Here's the Objective C side:
MyClass *newInstance = [[MyClass alloc] init];
[newInstance doSomething];
NSError *error = nil;
[newInstance doSomethingDangerousAndReturnError:&error];
long meaninglessValue = [newInstance calculateMeaninglessValue];
NSLog(#"%ld", meaninglessValue);
long importantValue = [newInstance calculateImportantValueDangerouslyAndReturnError:&error]; <-COMPILE ERROR
NSLog(#"%ld", importantValue);
From watching this video, I had expected that it should "just work":
https://developer.apple.com/videos/wwdc/2015/?id=401
...but it seems like we can't currently use functions that both throw and return a value.
Is this a bug, or not implemented feature? My apologies if I missed something in the release notes somewhere.
Any advice appreciated.
It is not possible.
If you annotate your method with #objc you will see the problem.
Throwing method cannot be marked #objc because it returns a value of
type 'Int'; return 'Void' or a type that bridges to an Objective-C
class
You can return only objects, primitives are not supported.
Although the selected answer is correct; you can't return an Int for a throwing function, I wanted to note an alternate solution since Apple has this same issue for a function in the CoreData framework:
func countForFetchRequest(request: NSFetchRequest, error: NSErrorPointer) -> Int
Note that in this case Apple is not using the throws pattern and instead reverting to the classic NSError out parameter mechanism.

What is the swift equivalent to setting properties on `id`?

I wonder what's the Swift equivalent in calling a method on id in which the availability of the method is determined at runtime. Specifically I'm looking to do this pattern in Swift:
-(IBAction) handleEvent:(id) sender {
BOOL didDisable = NO;
if([sender respondsToSelector:#selector(setEnabled:)]) {
[sender setEnabled:NO];
didDisable = YES;
}
[self doSomethingAsyncWithCompletionHandler:^{
if(didDisable) {
[sender setEnabled:YES];
}
}];
}
The biggest problem is that setEnabled: is imported in Swift as a property (e.g. UIBarItem) and none of the following constructs compile
func handleEvent(sender: AnyObject) {
// Error: AnyObject does not have a member named "enabled"
sender.enabled? = false
// Error: (BooleanLiteralCompatible) -> _ is not identical to Bool
sender.setEnabled?(false)
}
You can in fact do it exactly the same way you were doing it before: by calling respondsToSelector:. Indeed, that is exactly what your proposed expression does:
sender.setEnabled?(false)
That expression is actually a shorthand - it calls respondsToSelector: first, and then calls setEnabled: only if the respondsToSelector: test passes. Unfortunately, as you say, you can't get that code to compile. That, however, is merely a quirk of Swift's known repertory of available methods. The fact is that, although it is a little tricky to get it to compile, it can be done - and once you get it to compile, it behaves just as you would expect.
However, I'm not going to explain how to make it compile, because I don't want to encourage this kind of trickery. This sort of dynamic messaging is discouraged in Swift. In general, dynamic messaging tricks such as key-value coding, introspection, and so forth are not needed in Swift and are not consonant with Swift's strong typing approach. It would be better to do things the Swift way, by casting optionally to something that you have reason to believe this thing might be and that has an enabled property. For example:
#IBAction func doButton(sender: AnyObject) {
switch sender {
case let c as UIControl: c.enabled = false
case let b as UIBarItem: b.enabled = false
default:break
}
}
Or:
#IBAction func doButton(sender: AnyObject) {
(sender as? UIControl)?.enabled = false
(sender as? UIBarItem)?.enabled = false
}
In Swift 2.0 beta 4, your prayers are answered; this code becomes legal:
#IBAction
func handleEvent(sender: AnyObject) {
if sender.respondsToSelector("setHidden:") {
sender.performSelector("setHidden:", withObject: true)
}
}
If you want to avoid using the respondsToSelector: method you could define a protocol instead. Then extend the classes you want to use that is already in conformance with this protocol's definition (enabled) and define the function with a generic variable conforming to your protocol.
protocol Enablable{
var enabled:Bool { get set }
}
extension UIButton : Enablable {}
extension UIBarButtonItem : Enablable {}
//....
func handleEvent<T:Enablable>(var sender: T) {
sender.enabled = false
}
If you need to use it with an IBAction method a little bit of a work around is required since you cannot use generics directly on them.
#IBAction func handleEventPressed(sender:AnyObject){
handleEvent(sender);
}
We also need a matching generic function without Enablable conformance so that we can call handleEvent without knowing wether or not sender is Enablable. Luckily the compiler is smart enough to figure out which of the two generic functions to use.
func handleEvent<T>(var sender: T) {
//Do Nothing case if T does not conform to Enablable
}
As a workaround/alternative, you can use Key-Value Coding:
#IBAction func handler(sender: AnyObject) {
if sender.respondsToSelector("setEnabled:") {
sender.setValue(false, forKey:"enabled")
}
}
This works with both Swift 1.2 (Xcode 6.4) and Swift 2.0 (Xcode 7 beta).