Comparing String Objects in Swift - swift

Everyone knows you can use the == Operator to compare things.
if (stringValue1 == stringValue2)
If you do this in Objective-C the program will check if these objects are the same not if both strings do contain the same text. If you want to compare the text values you need to use a compare-Method.
To my understanding the same code in Swift does compare the text values. That is nice. A lot of programing language work like that. But what do I have to do to check if these two values refer to the same object?

For objects of class type you can you the === operator to check whether two objects refer to the same instance. However, you ask specifically about strings. Strings in swift are not of class type - they are values. The === operator will not work for them - the same way as it does not work for integers. So the answer to your question - how to check if two strings are the same instance - in Swift is: it is not possible. With strings in Swift you should use normal operators like == etc. only.

You can't as strings are values types, not object types. The === operator only works with object types (of AnyObject) but String is of type Any.
6> "abc" === "abc"
repl.swift:6:1: error: type 'String' does not conform to protocol 'AnyObject'
"abc" === "abc"
^
Swift.lhs:1:5: note: in initialization of parameter 'lhs'
let lhs: AnyObject?
^
6> var str : String = "abc"
str: String = "abc"
7> str === str
repl.swift:7:1: error: type 'String' does not conform to protocol 'AnyObject'
str === str
^
Swift.lhs:1:5: note: in initialization of parameter 'lhs'
let lhs: AnyObject?
^

Related

How to compare types in Swift?

I declare two variables in a Swift file, with the following values:
let m = 1
let n = 2
Now, I would like to compare their types:
type(of: m) === type(of: n)
The following compliling error occurs:
Argument type 'String.Type' expected to be an instance of a class or class-constrained type
Is there a way to compare types ?
Also, why does this line fail ?
String === String
The === operator is only defined for reference types, since it checks reference (pointer) equality, so you can't use it on metatypes of value types.
However, you can use the normal equality operator, ==.
type(of: m) == type(of: n)
As for String === String, that's not even valid code. What you need if String.self == String.self. You can access metatypes using TypeName.self.

Why does string.contains ("text") not work with strings in swift 5?

let string = "hello Swift"
if string.contains("Swift") {
print("exists")
}
Cannot convert value of type 'String' to expected argument type 'String.Element' (aka 'Character')
Why is version 5 such an error and what should I do?
When you use contains() and pass it a String, Swift tries to use an overload of the function that takes some kind of string like contains(_ other: StringProtocol) function that is not part of the pure Swift String. Instead it finds contains(_ element: Character) and it can't accept String as an argument and only accepts 'String.Element' (aka 'Character'). Referring to contains
The function you are looking for is defined in a protocol that String conforms to it, called StringProtocol that lives inside the Foundation.
So if you need it, make sure you import Foundation or a higher level framework like UIKit.

Using init() in map()

TL;DR
Why doesn't this work?
"abcdefg".characters.map(String.init) // error: type of expression is ambiguous without more context
Details
One really cool thing I like in Swift is the ability to convert a collection of one thing to another by passing in an init method (assuming an init() for that type exists).
Here's an example converting a list of tuples to instances of ClosedInterval.
[(1,3), (3,4), (4,5)].map(ClosedInterval.init)
That example also takes advantage of the fact that we can pass a tuple of arguments as a single argument as long as the tuple matches the function's argument list.
Here another example, this time converting a list of numbers to string instances.
(1...100).map(String.init)
Unfortunately, the next example does not work. Here I am trying to split up a string into a list of single-character strings.
"abcdefg".characters.map(String.init) // error: type of expression is ambiguous without more context
map() should be operating on a list of Character (and indeed I was able to verify in a playground that Swift infers the correct type of [Character] here being passed into map).
String definitely can be instantiated from a Character.
let a: Character = "a"
String(a) // this works
And interestingly, this works if the characters are each in their own array.
"abcdefg".characters.map { [$0] }.map(String.init)
Or the equivalent:
let cx2: [[Character]] = [["a"], ["b"], ["c"], ["d"]]
cx2.map(String.init)
I know that I could do this:
"abcdefg".characters.map { String($0) }
But I am specifically trying to understand why "abcdefg".characters.map(String.init) does not work (IMO this syntax is also more readable and elegant)
Simplified repro:
String.init as Character -> String
// error: type of expression is ambiguous without more context
This is because String has two initializers that accept one Character:
init(_ c: Character)
init(stringInterpolationSegment expr: Character)
As far as I know, there is no way to disambiguate them when using the initializer as a value.
As for (1...100).map(String.init), String.init is referred as Int -> String. Although there are two initializers that accept one Int:
init(stringInterpolationSegment expr: Int)
init<T : _SignedIntegerType>(_ v: T)
Generic type is weaker than explicit type. So the compiler choose stringInterpolationSegment: one in this case. You can confirm that by command + click on .init.

Swift Binary operator '+=' cannot be applied to operands of type 'AnyObject!' and 'String!'

I'd like to add the current user's username at the end of the object Participants in Parse using Swift but I got the error in the title.
obj["Participants"] += kCurrentUser.username
I don't know how to fix it. I have already tried .append and some other methods. Do you guys have any ideas?
It looks like obj is an NSDictionary. NSDictionaries contain anonymous values for each key. So when you look up a key, the value is of type AnyObject. You can't append a string to an AnyObject.
You'll have to use a more verbose statement:
obj["Participants"] = obj["Participants"] as! String + kCurrentUser.username
(That will only work if obj is a mutable dictionary. If it's immutable, it will crash.)
Swift language does not do implicit conversion, if two operands of different type, you have to convert any one of them to the other operand type to apply the binary operator.
So in your case convert the operand which is of AnyObject type to String.
Only couple of weeks into swift was hit by this but as mentioned "Swift Language doesn't do implicit type conversion,.."
For me I had this code:
if objects == nil {
objects = NSMutableArray()
}
Converted to:
if objects.count == 0 {
objects = NSMutableArray()
}

Filter function syntax?

This works:
func removeObject<T : Equatable>(object: T, array: [T]) -> Array<T>
{
return array.filter() { $0 != object }
}
let threeThings = ["one", "two", "three"]
twoThings = removeObject("three", threeThings)
However, I'd like to check for inequality with this !==. Then I get error "Type 'T' does not conform to protocol 'AnyObject'"
How can this code be fixed? (I've see the code here but I'd like to learn how to use filter properly).
The identical operator === and its negation !== are only defined for
instances of classes, i.e. instances of AnyObject:
func removeObject<T : AnyObject>(object: T, array: [T]) -> Array<T>
{
return array.filter() { $0 !== object }
}
=== checks if two variables refer to the same single instance.
Note that your code
let threeThings = ["one", "two", "three"]
twoThings = removeObject("three", threeThings)
does still compile and run, but gives the (perhaps unexpected) result
[one, two, three]
The Swift strings (which are value types and not class types) are automatically
bridged to NSString, and the two instances of NSString representing "three"
need not be the same.
If you want to use !== instead of !=, then, instead of the type constraint <T : Equatable> say <T : AnyObject>. All you have to do is listen to what the error message is telling you!
Note that this has nothing to do with using the filter function. It is simply a matter of types. You cannot use a method with an object of a type for which that method is not implemented. !== is implemented for AnyObject so if you want to use it you must guarantee to the compiler that this type will be an AnyObject. That is what the type constraint does.
!== checks for "identity", not "equality". Identity is a property of reference types which all support the AnyObject protocol, and it means that the two variables that you are comparing point to the same actual object, and not just another object with the same value.
That means you can't use === or !== with normal value types, like strings, integers, arrays, etc.
Try typing this into a playground:
let a = "yes"
let b = a
println(a === b)
You should get a message saying that String doesn't conform to the AnyObject protocol, because String is a value type (a struct) and doesn't support AnyObject, so you can't use === with it.
You can use === with any instance of a class, because classes are reference types, not value types.
You could put a constraint on T requiring that it conform to AnyObject.
func removeObject<T : Equatable where T: AnyObject>...
It should work, then, for reference types (including all class instances). But then your function won't work for value types (which include all structs).
Why do you need to check for identity (===) instead of equality (==)?