Can you really call any Objective-C method on AnyObject? - swift

According to the "Using Swift with Cocoa and Objective-C" iBook (near Loc 9) regarding id compatibility:
You can also call any Objective-C method and access any property without casting to a more specific class type.
The example given in the book (shown below) does not compile, which seems to contradict the above statement. According to the book, the last line below should not execute because the length property does not exist on an NSDate object.
var myObject: AnyObject = UITableViewCell()
myObject = NSDate()
let myLength = myObject.length?
Instead, this fails to compile with a "Could not find an overload for 'length' that accepts the supplied arguments" error on the last line.
Is this a bug, a typo, or is the statement about calling any Objective-C method wrong?

the compiler error is saying length is a function and you call it with incorrect arguments
you can do myObject.length?() which return nil
also myObject.count? will give you nil without compile error
xcrun swift
Welcome to Swift! Type :help for assistance.
1> import Cocoa
2> var obj : AnyObject = NSDate()
obj: __NSDate = 2014-06-25 13:02:43 NZST
3> obj.length?()
$R5: Int? = nil
4> obj.count?
$R6: Int? = nil
5> obj = NSArray()
6> obj.length?()
$R8: Int? = nil
7> obj.count?
$R9: Int? = 0
8>
I think what compiler was doing is search all method and property that called length, it found some class provide length() but no class provide length, hence the error message. Just like when you call [obj nonexistmethod] in ObjC, compiler will give your error saying nonexistmethod does not exist even you call it on id type object
This is what happened if you try to call non-exist method in Swift
14> obj.notexist?
<REPL>:14:1: error: 'AnyObject' does not have a member named 'notexist'
obj.notexist?
^ ~~~~~~~~
14> obj.notexist?()
<REPL>:14:1: error: 'AnyObject' does not have a member named 'notexist()'
obj.notexist?()
^ ~~~~~~~~

Related

In Swift, an Objective-C BOOL inferred as `()` instead of Bool

I've got a method written in Objective-C which returns a BOOL, for example:
(BOOL)methodName:(NSDictionary<NSString *, NSString *> *)params callback:(void(^)(NSString *_Nullable, ErrorInformation *_Nullable))callback error:(NSError *_Nullable *_Nullable)errorPtr;
Usage in Swift
I get the error, Cannot convert value of type '()' to expected condition type 'Bool'. I thinks that ret is of type (), instead of BOOL. Looking at the implementation, this value is mutated inside dispatch_sync.
let ret = try! methodName()
// I've tried a bunch of different syntaxes below:
if (ret) { <--- Xcode warning: Cannot convert value of type '()' to expected condition type 'Bool'
}
It is not nice to see this method has 3 ways of indicating failure, but I didn't design it 😅 and frankly my objective-C is not good:
errorPtr, which is automatically turned into do/try/catch in Swift
ErrorInformation passed in the callback
BOOL return value, which I am struggling with.
The returned BOOL is part of the NSError processing that is converted in Swift into a throws function (if the method returns a value, Swift will convert it from nullable to nonnull).
YES is returned if the method succeeds (there is no error), NO is returned when the method fails.
In Swift:
do {
try methodName()
// method succeeded
} catch {
// method failed
}
The ErrorInformation in the callback is probably related to asynchronous errors, probably similar to a Result<String, Error> in Swift.
References:
Handling Error Objects Returned From Methods (Obj-C)
Improved NSError Bridging (Swift Evolution 0112)

Swift Compiler Error,Expression type 'Error' is ambiguous without more context

typealias SwiftAMapCompletion = (CLLocation?,AMapLocationReGeocode?,Error) -> Void
var locationResult : SwiftAMapCompletion?
I want to give a nil as Error, but "Swift Compiler Error" is
Expression type 'Error' is ambiguous without more context.(SwiftAMapCompletion can't change)
locationResult!(location, reGeocode, nil as! Error)
You cannot force nil to be an Error, not even if you use as!.
Your options are:
Change the declaration to be Error?, because that would mean that you can pass nil.
Pass an Error.
For example:
enum MyError: Error {
case ok
}
locationResult!(location, reGeocode, MyError.ok)
In my opinion, your SwiftAMapCompletion interface does not make any sense, because normally a callback like this would be "here is the result, or here is the error" so all parameters should be declared as optional (with ?). I would get this interface changed if you can.

Converting a string to a class in Swift

I am using Alamofire to make a GET request and am using the ObjectMapper library to convert the response into its own class in Swift.
Alamofire.request(self.REST_METHOD!, self.REQUEST_URL, headers : ["Authentication_Token" : authToken]).responseJSON { response in
if response.response != nil && response.response?.statusCode == 200 {
let json = JSON((response.result.value as? NSDictionary)!)
let classType : AnyObject.Type = NSClassFromString(entityType)! as AnyObject.Type
//let model = Mapper<classType>.map(json.rawString())
}
}
The entityType variable can be one of many types i.e. User, Student, Teacher, Staff, etc. I am trying to dynamically create a class type and create the model based on that.
However it crashes on the line let classType : AnyObject.Type = NSClassFromString(entityType)! as AnyObject.Type giving the error message:
fatal error: unexpectedly found nil while unwrapping an Optional value
Also when I uncomment the line let model = Mapper<classType>.map(json.rawString()), it gives me a compiler error:
classType is not a type
What is wrong with the above code
You're getting the error:
fatal error: unexpectedly found nil while unwrapping an Optional value
Because NSClassFromString is failing and returning nil, which then you are unwrapping, thereby causing the error.
I'm guessing that entityType contains a class name along the lines of myClass. Swift now uses namespaces, so to create a class from a string, the string must contain AppName.myClass.
You could either hardcode your app name, or use the following code to get it:
NSBundle.mainBundle().infoDictionary!["CFBundleName"] as! String
There are other problems with your code too. When NSClassFromString succeeds, it will create an instance of the class. This cannot be cast to a type. Also, you can't pass a variable as a generic in Mapper<classType>, as Swift needs to know the class type at compile time. Instead you could change the Mapper class to take the type as a parameter. For example:
Mapper.map(classInstance.dynamicType, json.rawString())
Although now that I re-read your question, you're using a library for Mapper, and are therefore probably reluctant to change it.
And looking at the doco for ObjectMapper, it needs you to create a Mapper instance - ie. instead of Mapper<MyClass>.map you need Mapper<MyClass>().map.

Swift - extra argument 'count' in call?

All I'm trying to do is to inverse key/values in a dictionary, and I get the following error:
extra argument 'count' in cal
var dictionary = SWIFT_DICTIONARY
var inverseDictionary = NSDictionary.dictionaryWithObjects(dictionary?.keys, forKeys: dictionary?.values, count: dictionary?.count)
Use this instead:
var inverseDictionary = NSDictionary(objects: dictionary.keys.array, forKeys: dictionary.values.array)
I notice that you are unwrapping dictionary in your code, but it is declared as non optional. Code mistake or just copy & paste mistake?
Addendum - if you try the static version without the count parameter:
var inverseDictionary = NSDictionary.dictionaryWithObjects(dictionary.keys.array, forKeys: dictionary.values.array)
the compiler complains with this message:
'dictionaryWithObjects(_:forKeys:) is unavailable: use object construction 'NSDictionary(objects:forKeys:)'
I think the same happens for the other method you want to use, but the compiler doesn't report the proper error message.

Swift CMutablePointers in factories e.g. NewMusicSequence

How do you use C level factory methods in Swift?
Let's try using a factory such as NewMusicSequence().
var status:OSStatus
var sequence:MusicSequence
status=NewMusicSequence(&sequence)
This errors out with "error: variable 'sequence' passed by reference before being initialized".
Set sequence to nil, and you get EXC_BAD_INSTRUCTION.
You can try being explicit like this:
var sp:CMutablePointer<MusicSequence>=nil
status=NewMusicSequence(sp)
But then you get a bad access exception when you set sp to nil. If you don't set sp, you get an "error: variable 'sp' used before being initialized"
I'd expect this to be the way to do it, but it's not:
import AudioToolbox
var sequence: MusicSequence?
var status:OSStatus = NewMusicSequence(&sequence)
error: cannot convert the expression's type 'OSStatus' to type 'inout MusicSequence?'
var status:OSStatus = NewMusicSequence(&sequence)
Here's the reference.
I think the issue is that you need an optional value. Optional values are always initialized to nil and only optional values can ever be nil.
var sequence: MusicSequence?
let status = NewMusicSequence(&sequence)
This works for me in an Xcode project. It seems to throw EXC_BAD_ACCESS in the playground for some reason.
import Foundation
import AudioToolbox
#objc class SwiftSequenceGenerator : NSObject
{
#objc func createMusicSequence() -> MusicSequence
{
var status : OSStatus = 0
var sequence : MusicSequence = MusicSequence()
status = NewMusicSequence(&sequence)
return sequence
}
}
let generator = SwiftSequenceGenerator()
let sequence = generator.createMusicSequence()
I was testing this in an Obj-C project, hence the #objc attributes. Both this and its Obj-C counterpart were able to generate a new MusicSequence.
It is a little tricky because MusicSequence is actually a struct. In Obj-C it is defined as a pointer OpaqueMusicSequence and Swift uses COpaquePointer. They are basically the same, using a void * to pass the MusicSequence struct to the factory method to be mutated.