Swift casting statement with CF -> NS classes - ios

While trying to integrate the Address Book framework and converting CF types to NS Classes to Swift classes, I noticed something strange:
ABRecordCopyCompositeName(record)?.takeRetainedValue() as? NSString
returns nil
ABRecordCopyCompositeName(record)?.takeRetainedValue() as NSString?
returns Optional("John Smith")
My question is that isn't as? NSString synonymous to as NSString? as? NSString? (If so, why not?)
Therefore,
ABRecordCopyCompositeName(record)?.takeRetainedValue() as? NSString
should be equivalent to
ABRecordCopyCompositeName(record)?.takeRetainedValue() as NSString? as? NSString
which should return "John Smith".
(This was working on iOS 8.3, but iOS 8.4 broke my AddressBook feature.)

as (NS)String? is no supported syntax, even it might work in some way.
Either you can cast forced (as!) or optional (as?) or you can bridge (as) and there's no exclamation/question mark after the type.
ABAddressBookCopyArrayOfAllPeople() returns Unmanaged<CFArray>! and ABRecordCopyCompositeName() returns Unmanaged<CFString>!, both types are unwrapped optionals, so after calling takeRetainedValue() you can bridge to NSString
ABRecordCopyCompositeName(record).takeRetainedValue() as NSString
or further to String
ABRecordCopyCompositeName(record).takeRetainedValue() as NSString as String

Related

Issue with converting NSString * to String and passing Character as parameter in Swift iOS

I am calling a Objective method from Swift by using a Bridging header.
-(NSString *) PatternSetCreator: (char)Signature detection_time_in_sec:(int)detection_time_in_sec patternLength:(int)patternLength maxPatternSetSize:(int)maxPatternSetSize
There are two issuse:
a) I am not able to pass a single character as parameter while calling this method from Swift
b) I am not exactly sure how to get the return type NSString and assign it to a String variable
A single C char in Swift is represented as CChar, a typealias for Int8.
(similarly, C int in Swift is CInt, a typealias for Int32)
If you want a specific character and are using Swift 1.2, there’s an initializer for UInt8 that takes a UnicodeScalar. Annoyingly, though, you have to then convert it to a Int8 to make it compatible with the C method:
let ch = CChar(UInt8(ascii: "x"))
let i = CInt(100)
let s = obj.PatternSetCreator(ch,
detection_time_in_sec: i,
patternLength: i,
maxPatternSetSize: i)
You should not need to do anything special to turn the returned NSString to a String. The bridging will do that automatically.
(or rather, it’ll return a String! – but if the objective c code is guaranteed to return a valid string every time and never a null pointer, the definition can be changed to -(nonnull NSString *) PatternSetCreator: etc… which means it will return a String instead)

How to bridge Swift String to Objective C NSString?

Am I taking crazy pills? Directly out of the documentation:
“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.
To enable string bridging, just import Foundation.”
I've done this... consider:
import Foundation
var str = "Hello World"
var range = str.rangeOfString("e")
// returns error: String does not contain member named: rangeOfString()
However:
var str = "Hello World" as NSString
var range = str.rangeOfString("e")
// returns correct (2, 1)
Am I missing something?
To go from String to NSString use the following constructor:
let swiftString:String = "I'm a string."
let objCString:NSString = NSString(string:swiftString)
With Xcode 7 (beta), using a downcast from String to NSString, as in below example, will result in a warning message, Cast from 'String?' to unrelated type 'NSString' always fails:
let objcString:NSString = swiftString as! NSString // results in error
You already have the answer in your question. You're missing the cast. When writing Swift code, a statement such as this one
var str = "Hello World"
creates a Swift String, not an NSString. To make it work as an NSString, you should cast it to an NSString using the as operator before using it.
This is different than calling a method written in Objective-C and supplying a String instead of an NSString as a parameter.
Here is example for this :
string str_simple = "HELLO WORLD";
//string to NSString
NSString *stringinObjC = [NSString stringWithCString:str_simple.c_str()
encoding:[NSString defaultCStringEncoding]];
NSLog(stringinObjC);

Assign a [String:AnyObject] to [String:AnyObject] in Swift replaces comma with semi-colon

I have
var params = [String:AnyObject]()
I have a function which returns an [String:AnyObject]. So, I want to assign that to a key of params like this:
params["phoneDetails"] = getPhoneDetails()
The problem I am facing is, the return of getPhoneDetails() is different from the value in params["phoneDetails"].
Here is the output of getPhoneDetails()
[locale: en, ostype: 32bit, appversion: 4.0.0, architecture: x86, version: 8.1]
Here is the output of params["phoneDetails"]:
Optional({
appversion = "4.0.0";
architecture = "x86 ";
locale = en;
ostype = 32bit;
version = "8.1";
})
So, instead of a comma, I see a semi-colon, when using println(params["phoneDetails"]).
I want it to be same as the return type of getPhoneDetails. What am I doing wrong?
The reason for this is that Swift is implicitly converting your Swift.Dictionary into an NSDictionary:
let d: [String:AnyObject] = ["one":"1","two":"2"]
// Swift.Dictionary implements Printable.description
// (which println uses) as [key:value, key:value]
d.description
let nsd = d as NSDictionary
// NSDictionary on the other hand, implements
// it as {\n key = value;\n key = value;\n}
nsd.description
The reason for this conversion is your use of AnyObject. In theory, AnyObject can only store reference types (i.e. classes). Try the following in a playground without any import statements at the top i.e. remove import UIKit or whatever:
// this won’t even compile - Strings aren’t classes, they’re structs
// so you can’t assign them to AnyObject
let d: [String:AnyObject] = ["one":"1","two":"2"]
// this fixes that:
let e: [String:String] = ["one":"1","two":"2"]
// but this won’t compile:
let o: AnyObject = e // [String:String] doesn’t conform to protocol AnyObject
But import Foundation and suddenly magic happens. Two bits of magic in fact: string literals can now create NSString objects, which are classes and so do conform to AnyObject. And Swift dictionaries can be silently converted to NSDictionary objects, which also are classes so conform to AnyObject. That latter one is what’s happening to you, and so you get a different output, because your type is indeed of a different type.
If you don’t want them to be of different types, you have two choices – return an NSDictionary from your getPhoneDetails function (ick) or stop using AnyObject and instead declare your dictionary to be of type [String:[String:String]] (yay, but leading to stronger types and value rather than reference semantics, which may mean having to refactor other code).
(or I guess you could switch to [String:Any] but there lies madness)

Swift: difference as String? vs. as? String [duplicate]

This question already has answers here:
Downcasting in Swift with as and as?
(3 answers)
Closed 8 years ago.
Is there a difference between as? String vs. as String? in Swift? If so, what's the difference and when should I use one vs. another?
There's a subtle but important difference:
variable as? String: variable can be any type, such as an array, an integer, etc. Cast to string if it's a string, set to nil otherwise.
variable as String?: variable is a String?, but stored in an opaque type, such as AnyObject?, or it's a non optional string. If it's something different, a runtime exception is generated.
Some examples:
var x: AnyObject? = "Test"
x as String? // OK, the result is an optional string
x as? String // OK, evaluates to an optional string
"string" as String? // OK, evaluates to optional string
x as? Int // OK, evaluates to nil, it's not an Int
x as Int? // Runtime exception, it's not castable to optional Int
So:
as? Type means: cast to this type, if possible, otherwise evaluate to nil
as Type? means: cast to an optional Type, because I know it's an optional Type. I understand that if it's not that, a runtime exception is generated
However, the real difference is between as? and as: the former is an attempt to cast, the latter is a forced cast, resulting in runtime error if not possible.
Update Dec 14, 2015 Since Swift 1.2, there are 3 variations of the as operator:
as? is an attempt to cast, evaluating to nil if cast fails
as! is a forced cast, resulting to an runtime exception if cast fails (this is what as previously did)
as is now a special type of cast to be used when casting to equivalent types, usually bridged types, such as Swift's String and NSString.
From The Swift Programming Language book,
as is a type cast operator which we use to downcast to the subclass and as? is used for an optional form, when we are not sure if the downcast will succeed. Consider the following example
for item in library {
if let movie = item as? Movie {
println("Movie: '(movie.name)', dir. (movie.director)")
} else if let song = item as? Song {
println("Song: '(song.name)', by (song.artist)")
}
}
The example starts by trying to downcast the current item as a Movie. Because item is a MediaItem instance, it’s possible that it might be a Movie; equally, it’s also possible that it might be a Song, or even just a base MediaItem.
String? An optional value either contains a value or contains nil to indicate that the value is missing.
From this,
as? String means when you don't know what you're downcasting, you are assuming that as a String, but it might me Integer or Float or Array or Dictionary
as String? means it's an Optional Value, it may either contain a String or Nil value.
Yes there is a difference.
In the first case, you are doing an optional cast to the type String. This will return a value if the object you are attempting to cast is indeed a String or nil if it is not.
In the second case, you are doing a forced cast to the type String?. If the value you are casting is not a string, it will crash your program.
YES, there is diffrence.
variable as String? downcast to optional String.If variable is not String? it will cause run-time exception.
while variable as? String will return nil if your variable is not String type or return downcast variable to String. This is conditional downcasting, if you not sure about down-casting you need to use this .

'Int' is not identical to 'String.Index'

As an exercise I am rewriting one of my apps using Swift.
In Objective-C I had the following line of code:
NSRange extendedRange = NSUnionRange(range, [[self.backingStore string]lineRangeForRange:NSMakeRange(NSMaxRange(range), 0)]);
In swift it looks like this:
let str = backingStore.string
let extendedRange: NSRange = NSUnionRange(range, str.lineRangeForRange(NSMakeRange(NSMaxRange(range), 0)))
However, for some reason I am getting 'Int' is not identical to 'String.Index' and I can't figure out why. Any help / insight would be appreciated.
EDIT:
If I bridge my string, it works:
let str = backingStore.string
let extendedRange: NSRange = NSUnionRange(range, str.bridgeToObjectiveC().lineRangeForRange(NSMakeRange(NSMaxRange(range), 0)))
Swift strings are not the same as Foundation strings: they can be bridged, and they may have method names in common, but that does not mean they should be treated as being compatible. Here are two views of the type definition of Swift.String.lineRangeForRange.
Swift.String.lineRangeForRange (Swift.String)(Swift.Range<Swift.String.Index>) -> Swift.Range<Swift.String.Index>
String -> (Range<String.Index>) -> Range<String.Index>
Note also that Swift's Range<T> is start/end while Foundation's NSRange is location/length. There are ways to convert between NSRange and Range<Int>, but a Range<String.Index> is a different story: String.Index is more like an "iterator" than an "index".
What I'd say (and I'm highly pragmatic about this sort of thing) is that if you have a block of code that is based on the semantics of Objective-C strings it might be good to keep using them for a while: get your code working, and then evaluate changing to Swift's String later.
Curious myself, I Cmd+Clicked on String.Index and discovered it is actually defined as a struct in an extension type:
extension String : Collection {
struct Index : BidirectionalIndex {
func succ() -> String.Index
func pred() -> String.Index
}
BidirectionalIndex is a protocol that inherits from another protocol, ForwardIndex.
I assume the succ() and pred() methods stand for 'successor' and 'predecessor' respectively.

Resources