I am writing a library which can parse typed Ids from JSON. However, I am finding the typecasting rules a little baffling.
Example:
class AccountId : NSString { }
let json : AnyObject? = "user-1" // Returned by NSJSONSerialization.JSONObjectWithData
let s = json as? NSString // Succeeds, s == Some("user-1")
let a = json as? AccountId // Fails, a == nil
Why does the first typecast succeed while the second one fail? Is there something magical about NSString which does not crossover to Swift-only classes?
I am using XCode Version 6.1 (6A1030) (the latest at the time of writing).
As a general rule, if you have a hierarchy of classes A -> B -> C (C inherits from B, which in turn inherits from A), and you have an instance of B, you can upcast to A, but you cannot downcast to C.
The reason is that C might add properties which are not available in B, so the compiler or the runtime wouldn't know how to initialize the additional data, not to mention that it must also be allocated.
Note that polymorphism allows you do to upcast and downcast with variables - which means, if you have an instance of C stored in a variable of type A, you can cast that variable to B and C, because it actually contains an instance of C. But if the variable contains an instance of B, you can downcast to B, but not to C.
In your case, rather than downcasting you should specialize a constructor accepting NSString, but I suspect that in this specific case of NSString it cannot be done (there's no NSString designated initializer accepting a string as argument). If you are able to do that, then your code would look like:
var json: AnyObject? = "test"
if let string = json as? NSString {
let a = AccountId(string: string)
}
and at that point you can use a where an instance of either AccountId or NSString is expected
Is there something magical about NSString which does not crossover to Swift-only classes?
Yes. Swift's String class is automatically bridged to NSString and vice versa. From the docs:
Swift automatically bridges between the String type and the NSString
class. This means that anywhere you use an NSString object, you can
use a Swift String type instead and gain the benefits of both
types—the String type’s interpolation and Swift-designed APIs and the
NSString class’s broad functionality. For this reason, you should
almost never need to use the NSString class directly in your own code.
In fact, when Swift imports Objective-C APIs, it replaces all of the
NSString types with String types. When your Objective-C code uses a
Swift class, the importer replaces all of the String types with
NSString in imported API.
Why does the first typecast succeed while the second one failed?
Because of the strong relationship between String and NSString, the compiler knows how to convert from one to the other. On the other hand, because your AccountId class is a more specialized version of NSString, you can't cast from String to AccountId.
Related
I need to cast a return value to a specific type that I need to keep dynamic, like
let cellType = "CellTypeToBeResolved"
cell = (tableView.dequeueReusableCellWithIdentifier("myID") as? CellTypeToBeResolved)!
How is this possible in Swift 2.0?
Thnx!
You can't do it, because Swift is (deliberately) missing not one but two pieces of the puzzle:
You can't turn a string into a class.
More important, you can't cast down to a class expressed as a variable. The class to cast down to must be a literal class, i.e. it must be known at compile time. The compiler needs to know that this cast is legal, plus it needs to know at compile time how to treat this variable. If you want to be able to send MyCoolTableCell instance messages to this cell, you need to use the literal MyCoolTableCell name in your code — not a string, not a variable containg the type, but the type itself.
You could use NSClassFromString to convert your string to the class you want, then you can use that to cast the tableviewCell
Sometimes I get errors when defining a variable as NSArray/String/URL, it can often be solved by changing it to NSMutableArray/String/URL.
What is the difference between them? People say you can't change the value of a NSString, but why not since I defined it as a variable with a var?
Please use Swift 2 code to explain.
Here's code example:
enum Router: URLRequestConvertible {
static let baseURLString = "https://api.500px.com/v1"
static let consumerKey = "My_Key"
case PopularPhotos(Int)
case PhotoInfo(Int, ImageSize)
case Comments(Int, Int)
var URLRequest: NSURLRequest {
//Error: Type does not conform to protocol 'URLRequestConvertible' with Alamofire.
//I solved this problem by changing it to NSMutableRequest.
let (path, parameters) : (String, [String: AnyObject]) = {
switch self {
//3 cases.
}
}()
let URL = NSURL(string: Router.baseURLString)
let URLRequest = NSURLRequest(URL: URL!.URLByAppendingPathComponent(path))
let encoding = Alamofire.ParameterEncoding.URL
return encoding.encode(URLRequest, parameters: parameters).0
}
}
NS types are coming from Cocoa (some would say Objective-c, but actually they don't exist in Objective-C so they were created in the NextStep libraries and then used by Apple in Cocoa after they bought NextStep -> hence the NS prefix)
NSArray/String/URL are similar to let myVar = "aString"
and
NSMutableArray/String/URL are similar to var myVar = "aString"
the first one cannot be changed after it's set the second one can be. Why are you using the Cocoa/Objective-C types and not native Swift types ?
EDIT (see comments)
in Swift you can define a string:
var anEmtpyString = String()
or
var anEmtpyString = ""
in the second case Swift is intelligent enough to deduct that the type is a String. If you use 'var' the string is Mutable, meaning you can change it afterwards. So I can do :
var myString = "first text"
myString = "another text"
if you're using 'let' you create a references to the string, also called Immutable
let myString = "first text"
myString = "another text" // <- the compiler won't accept this !
In those examples I have used the native String type from Swift, which exists in both Swift 1.2 and 2
I could also use the Cocoa/Objective types NSString, NSMutableString but most of the time there is no need for it. It can be handy if you're using frameworks which are written in Objective-C. It's easier then to you since you don't have to cast from String to NSString/NSMutableString. (which is of course possible)
In Objective-C there is no let or var keyword so there you used NSString (for let) and NSMutableString (for var).
The same goes for NSArray/NSMutableArray
More info : https://developer.apple.com/library/ios/documentation/Swift/Conceptual/Swift_Programming_Language/StringsAndCharacters.html
PS the answer from gnasher729 is a bit more technical and tell you how it goes around behind the scenes. It al has to do with pointers, which were very present in Objective-C but are much more hidden in Swift. If you come from a background where you have learned pointers it will make thing clearer, if not I'm not sure if it'll help you. If you want me to expand on this please tell.
A.
In Objective-C many types of your daily routine are not built-in types. That means that they are not part of the programming language.
Therefore the framework Foundation defines that types as classes as any other class. There is no difference to classes like NSImage, NSManagedObject, NSWhatever. You do not have a language level documentation. They are documented as part of the Framework. You can treat them as every other class including subclassing, categories on it and so on. There is simply nothing special with them. (You could completely replace them, but there is little reason to do so.)
B.
In Objective-C every instance object is allocated on heap ("dynamically"). Since instances of NSString are usual instance objects, you do the same with them. The return value of such a construction is not an instance object, but a reference to an instance object. That means that all instance objects including instances of NSString are treated "by reference".
When you define a variable with the "type of string" you do not define a string instance, but a reference to the string:
NSString *referenceToString;
This referene is always a variable. In Swift it would look like this:
var string : String;
The analogon to a const in Objective-C is defined with the key word const.
NSString * const referenceToString;
But there is a big difference: Since the variable you declare is a reference, the constness can be related to the reference or to the object. In the first case you couldn't change the reference, in the latter case you could not change the object. The above syntax means: Constant reference to an NSString instance. Therefore you are not allowed to assign a value to that reference. (I. e. referencing another string object.)
These constant references are not very popular in Objective-C, because in most cases the constness of the reference is meaningless.
The constness of the string object itself is modeled by the type of the string object. NSString means constant string object, NSMutableString means variable string object.
Therefore, if you want to have a sting that can be mutated, use NSMutableString instead of NSString.
NSMutableString *referenceToAMutableString;
NSString *referenceToAnImmutableString;
There are value objects and reference objects. For example, Int and String are value objects, NSNumber and NSString are reference object. Swift struct instance are value objects, Swift class instances are reference objects.
For a reference object, the variable is not the object. It is a reference to the object. So a "let" variable cannot be changed, which means it refers to one object and cannot be made to refer to another object. A "var" variable can be changed, which means it can be made to refer to a different object. But both say nothing about the object itself. An NSArray object cannot be modified, an NSMutableArray can be modified. Whether you use a let or var variable to refer to the object only means the variable cannot or can be made to refer to a different variable, it doesn't say whether you can modify the object itself.
I'm aware of what the as operator does and how to use them. But I'm more curious in the architectural side of the as operator. Why is it there? What's the huge reason? What does it help?
Example:
var objectData: NSData = NSUserDefaults.standardUserDefaults().objectForKey("myKey") as NSData
...wouldn't it be better if it's:
var objectData: NSData = NSUserDefaults.standardUserDefaults().objectForKey("myKey")
You're right to be a bit confused, as prior to Swift 1.2, the as keyword was overloaded with multiple meanings. Swift 1.2 makes this a lot clearer, as there are now 3 versions of as: as, as? and as!. (in prior versions, as and as! where conflated as just as)
In your example (which I'm assuming is prior to 1.2 as otherwise it wouldn't compile), you are using as to do two things simultaneously.
NSUserDefaults.objectForKey's return type is an AnyObject?, an optional AnyObject. That is, it could be any kind of object, and it might not be an object at all (because there might not have been a value set for the key "myKey").
To make use of this result, you need to do two things – unwrap the optional (i.e. test if it contains a value), and convert the AnyObject to a more useful type (in this case, NSData).
Here's how you can do this:
if let objectData = NSUserDefaults.standardUserDefaults()
.objectForKey("myKey") as? NSData
// note the question mark --^
{
// use the value
}
else {
// handle the value not being present – perhaps set it to a default value
}
This is using as? to test, tentatively, if the value returned by objectForKey is of type NSData. The if let tests if there was a valid value, and if it is of the correct type.
Bear in mind, there is a chance that the value stored for "myKey" is not compatible with the type you want. Suppose instead of an NSData you wanted an Int. You'd do if let intData = NSUserDefaults.standardUserDefaults().objectForKey("myKey") as? Int. If the value stored for "myKey" was the string "foo", this cannot be converted to an Int an you'd get an error.
If instead of using as? above, you use as, you force Swift to unwrap the value and convert the type without checking. If everything works out, that's fine. But if there was no value or if the data was not compatible with the type, you'll get a runtime assertion and your program will crash.
This is so dangerous that in Swift 1.2, this use of as has been renamed as!, the exclamation part indicating a dangerous "forcing" of the conversion. If you try to compile your example code in 1.2, you'll get an error asking if you meant as? or as!.
The as keyword remains for type clarifications that are always safe. For example, suppose you have an overloaded function like so:
func f(i: Int?) { println("called with Int") }
func f(s: String) { println("called with String") }
// try to call f with nil – Swift will complain because it
// doesn't know if this is a nil int or a nil string
f(nil)
// you can tell it which with as:
f(nil as Int?) // prints "called with Int"
f(nil as String?) // prints "called with String"
There are also some kinds of Swift <-> Objective-C bridging conversions that are guaranteed to always succeed, which you can also use as for:
let a = [1,2,3]
// you can always convert a Swift array to an NSArray
let n = a as NSArray
The benefit of as is that it confirms that the object on its left side is of the type on its right side. It differentiates between, for example:
myAnimalReallyADog as Dog
and
myAnimalReallyACat as Dog
Without it you would have to use other, probably more long-winded ways to test whether the object cast is legal.
The as concept is common among many languages and not unique to Swift, so it's a well-established paradigm and pretty clearly useful.
First, we should know when to use it. Usually you use this operator in order to perform a "downcast" - that is, the operator tries to return an object reference as a derived class - and succeeds iff the object is actually of that kind. That implies, that the call-site has to make a guess about the actual kind of the object. This downcast can fail, too.
Thus, the answer is really twofold:
As OldPeculier already pointed out, "it's a well established paradigm and clearly useful".
However, there are other, better designs which could avoid a downcast which can potentially fail. Frequent use of a downcast is a code smell. You should strive to re-think your design, possibly using protocols, or class extensions and so force.
I'm new here so please help me figure this out. I wonder why does Swift have both NSDictionary and Dictionary classes? My question applies also to the other standard classes like String/NSString, Array/NSArray etc. I'm asking this because i have problem in my code, having to do a lot of casts.
For example, i found the method to load a plist file into a dictionary only in NSDictionary contentsOfFile and not in the native Dictionary class, the latter being the 'advertised' way to go with Swift.
Is this an implementation problem (Swift being new and all) or are there more, deeper, reasons for having the old Objective-C classes? What are the differences?
Both types are there in order to use Objective-C code in swift. Swift array and dictionary are generic type and can hold non-class instances (enum, struct), while NSArray/NSDictonary can only hold NSObject.
In order to simplify your question lets take an example of NSString
NSString is class for a long time in iOS development, it is the predecessor of String
String is the new Swift specific string class where as NSString is the older objective C version and there is a way to convert or bridge between the two,
As per Apple “The entire NSString API is available to call on any String value you create”
now why you want to do this because String class does’t necessarily have all the methods that is on NSString at the moment, so if you want to use method of NSString you can convert a String to NSString and take advantage of those methods
for example
// Double conversion
var strCon = Double(("3.14" as NSString).doubleValue)
// Contains String
var stack = "Stack overflow"
(stack as NSString).containsString("Stack")
// Substring
var overflow = (stack as NSString).substringToIndex(8)
In short if you are trying to do something and there doesn’t seem to be method available on String I recommend to take a look at NSString as there are tons of methods there and chances are it will help you out
same is the case with NSDictionary, NSArray and so on…
I hope it helps
In Swift documentation, it says the following:
Swift provides two special type aliases for working with non-specific types:
AnyObject can represent an instance of any class type.
Any can represent an instance of any type at all, apart from function types.
NOTE
Use Any and AnyObject only when you explicitly need the behavior and capabilities they provide. It is always better to be specific about the types you expect to work with in your code.
If I understand it correctly, AnyObject should be used when it is compatible with class types, which means it cannot represent Swift's built-in String, Int, Dictionary, Array, etc... since those are all represented as structure, not class, in Swift.
However, when I write the following code, it can be compiled without any errors:
var myInt: Int = 10
var myStr: String = "st"
var myDict:Dictionary<String, AnyObject> = ["k1": "1", "k2": 2, "k3": true]
var myArr:Array<AnyObject> = []
myArr.append(myInt)
myArr.append(myStr)
myArr.append(myDict)
println(myArr)
Because I define myArr as AnyObject[], it should be able to contain only class types, while myInt, myStr, and myDict are all compatible to Any, not AnyObject, and Any is not compatible with `AnyObject.
However, I think the reason it can be compiled is because myInt is convertible to Objective-C's NSInteger internally, which is a subtype of AnyObject. Same is true of String/NSString and Dictionary/NSDictionary. Is my guess correct? Then, here's my question:
Even if you can define Swift's Array as AnyObject and still store Swift's structure like Int or String, what's the point of using Any? Is there any situation that you have to define an Array as Any? How about the Dictionary's value type?
How should I decide when to use Any and when to use AnyObject, especially if I deal with an type which can be convertible to Objective-C's Object, like String, Int, Dictionary, etc?
I think the most methods in Cocoa and Cocoa Touch return AnyObject!, not Any!. So I wonder what is the best case to use Any in Swift's Array and Dictionary's value type.
Well you can use Any[] to store custom structs.
However, when I write the following code, it can be compiled without
any errors:
Not if you don't have import Foundation. Without importing Foundation, there are lots of errors. It only compiles for you due to the bridging that Foundation has for numeric types to NSNumber, String to NSString, etc.