'?'must be followed by a call, member lookup or subscript - ios

I have defined a String type computed property:
var name : String? {
//an optional variable
var theName : String?
if SOME_CONDITION {
theName = “I have a name”
}
//ERROR: '?'must be followed by a call, member lookup or subscript
return theName?
}
I want to return whatever theName is, if it is nil, return nil. So I use return theName? , I don’t want to have runtime error. The compiler however raise an error '?'must be followed by a call, member lookup or subscript Why? How to get rid of it.

What about this? Looks more elegant to me:
var name : String? {
let condition = true // your own condition here of course
return condition ? "I have a name" : nil
}
The problem in your code:
var name : String? {
var theName : String?
let condition = true // your own condition here of course
if condition {
theName = "I have a name"
}
return theName // get rid of the ? here
}
The field theName is already optional, no need to add another ? there.
Why is my proposed solution not an alternate solution:
The construct I used is called ternary operator:
The ternary conditional operator is a special operator with three parts, which takes the form question ? answer1 : answer2. It is a shortcut for evaluating one of two expressions based on whether question is true or false. If question is true, it evaluates answer1 and returns its value; otherwise, it evaluates answer2 and returns its value.
It behaves like the if statement but is suitable here as it is shorter and thus clearer to read: Depending on the condition, the value is either theName or nil. You really don't need to assign the value to any other variable, because, afterall, you are computing it, so might as well simply return it as the condition decides, what the value is.

Adding ? to the end of a type makes it Optional.
Adding ? to the end of an optional variable invokes Optional Chaining.
You specify optional chaining by placing a question mark (?) after the
optional value on which you wish to call a property, method or
subscript if the optional is non-nil. This is very similar to placing
an exclamation mark (!) after an optional value to force the
unwrapping of its value. The main difference is that optional chaining
fails gracefully when the optional is nil, whereas forced unwrapping
triggers a runtime error when the optional is nil.
To reflect the fact that optional chaining can be called on a nil
value, the result of an optional chaining call is always an optional
value, even if the property, method, or subscript you are querying
returns a non-optional value. You can use this optional return value
to check whether the optional chaining call was successful (the
returned optional contains a value), or did not succeed due to a nil
value in the chain (the returned optional value is nil).
Specifically, the result of an optional chaining call is of the same
type as the expected return value, but wrapped in an optional. A
property that normally returns an Int will return an Int? when
accessed through optional chaining.
Example
class Foo {
var bar: Int
}
var x: Foo? // ? is attached to type Foo, it makes x an optional variable
let y: Int? = x?.bar // ? is attached to the variable x, this is optional chaining, it makes .bar return Int?

Related

Swift how variable declaration works

Can some one please explain to me how it works
var count: Int?
count = 1
if let count = count {
//do something.
}
Why there is no compiler error at if let count = count as we already created a variable named count as var count: Int?. How come two variables with same name possible?
Swift treated as both variables as a different one. The "count" that declared first can be treated as global one while the constant "count" is only available inside if condition, so it can't access outside if condition.
Optional Variable: It can contain a value or a Nil value. Nil represents the absence of a value or nothing
var count: Int? // Optional Variable
Here variable count is global variable.
Optional Binding: It is the way by which we try to retrieve a values from a chain of optional variable.
if let count = count {
//do something.
}
Here constant count value is available only with in the scope. It cannot be accessed outside the scope.
This is called optional binding
if let constantName = someOptional {
statements
}
someOptional is checked to see if it's nil or has data. If it's nil, the if-statement just doesn't get executed. If there's data, the data gets unwrapped and assigned to constantName for the scope of the if-statement. Then the code inside the braces is executed.

strange optional behaviour in Swift

I have created my own class in Swift as below.
class Product: NSObject {
var product_id:Int?
var product_number:String?
var product_price:Float?
var product_descrption:String?
}
Now i am setting value in each property like this
let p=Product()
p.product_id=1
p.product_price=220.22
p.productdescrption="Some description"
p.product_number="W2_23_233"
But when i get the value from price then for price i get value like "Optional 220.22" But i don't get appended word "Optional" in description".So to resolve this i added "!" for unwrapping the value of float but i did not have to do this for String please tell why this is happening?
If you are printing any of these values should say Optional(...). If you are assigning the values to a label, that will not include the Optional(...), The reason that it shows Optional(...) when you print the value using print(), is just to show you its an optional. For safety, instead of using the !, try using if lets.
An example with your code,
if let id = p.product_id {
print(id) //Does not contain Optional()
}
You can also combine them, to do them all at one time. (Only do this if you don't want to print unless all values are non-nil)
if let id = p.product_id,
let price = p.product_price,
let description = p.productdescrption,
let productNumber = p.product_number {
//Enter code here that does something with these values
}
Note, if you aren't on swift 3, I believe you only have to write let on the first condition.
If you print any optional variable without unwrapping no matter what type it is, Optional will be appended to the variable's value.
print(p.product_price) will print Optional(220.220001)
print(p.product_descrption) will print Optional("Some description")
To print only value you need to unwrap the optional variables.
print(p.product_price!) will print 220.22
print(p.product_descrption!) will print Some description
This forced unwrapping will only work if the optionals does not contain nil. Otherwise it will give you a runtime error.
So to check for nil you can use if let statement.
No matter what type of variable. If you assign a value to an optional variable, It always enclosed with Optional(...)
Optional without forced unwrapping:
print("product_price = \(p.product_price) \n product_descrption = \(p.product_descrption)")
Output:
product_price = Optional(220.22)
product_descrption = Optional(Some description)
Optional with forced unwrapping:
print("product_price = \(p.product_price!) \n product_descrption = \(p.product_descrption!)")
Output:
product_price = 220.22
product_descrption = Some description

Cannot assign a value of type 'nil' to a value of type 'NSMutableArray'

I am writing swift code & having a simple problem.
I declared a MSMutableArray, under certain condition, I set it to nil:
func doJob() -> NSMutableArray {
var arr = NSMutableArray()
arr = addContentToArray()
if CRITICAL_CONDITION {
//ERROR: Cannot assign a value of type 'nil' to a value of type 'NSMutableArray'
arr = nil
}
return arr
}
But when I set arr to nil, I got compiler error:
Cannot assign a value of type 'nil' to a value of type 'NSMutableArray'
Why? How can I set it to nil then?
You can assign nil only to optional variables. However, when you are letting the type to be inferred, the compiler doesn't know that you are planning to assign nil in the future:
var arr: NSMutableArray? = NSMutableArray()
However, the whole thing about assigning nil to a variable that has previously held an array seems a bit dirty to me. Maybe it would be easier to use a new variable?
You haven't posted your real code so we can't do a real review but:
if CRITICAL_CONDITION {
arr = nil
}
return arr
can be more easily written as
if CRITICAL_CONDITION {
return nil
}
return arr
That will solve the problem, too, because you won't need to reassign the variable. A different approach would be to use a second variable:
var result: NSArray? = array
if CRITICAL_CONDITION {
result = nil
}
return result
or even better
let result = CRITICAL_CONDITION ? nil: array
return result;
The whole point of specifying when a variable cannot be nil (non-optional) is the fact that optional variables are dangerous and you have to check them for nil all the time. So, use optionals only for a short time, ideally one condition and then convert them to non-optional. In this case, use the non-optional as long as possible and only when you really need to assign nil, convert to optional (declare a second, optional variable).

Swift if let evaluates successfully on Optional(nil)

I have a custom object called Field. I basically use it to define a single field in a form.
class Field {
var name: String
var value: Any?
// initializers here...
}
When the user submits the form, I validate each of the Field objects to make sure they contain valid values. Some fields aren't required so I sometimes deliberately set nil to the value property like this:
field.value = nil
This seems to pose a problem when I use an if-let to determine whether a field is nil or not.
if let value = field.value {
// The field has a value, ignore it...
} else {
// Add field.name to the missing fields array. Later, show the
// missing fields in a dialog.
}
I set breakpoints in the above if-else and when field.value has been deliberately set to nil, it goes through the if-let block, not the else. However, for the fields whose field.value I left uninitialized and unassigned, the program goes to the else block.
I tried printing out field.value and value inside the if-let block:
if let value = field.value {
NSLog("field.value: \(field.value), value: \(value)")
}
And this is what I get:
field.value: Optional(nil), value: nil
So I thought that maybe with optionals, it's one thing to be uninitialized and another to have the value of nil. But even adding another if inside the if-let won't make the compiler happy:
if let value = field.value {
if value == nil { // Cannot invoke '==' with an argument list of type '(Any, NilLiteralConvertible)'
}
}
How do I get around this? I just want to check if the field.value is nil.
I believe this is because Any? allows any value and Optional.None is being interpreted as just another value, since Optional is an enum!
AnyObject? should be unable to do this since it only can contain Optional.Some([any class object]), which does not allow for the case Optional.Some(Optional) with the value Optional.None.
This is deeply confusing to even talk about. The point is: try AnyObject? instead of Any? and see if that works.
More to the point, one of Matt's comment mentions that the reason he wants to use Any is for a selection that could be either a field for text input or a field intended to select a Core Data object.
The Swifty thing to do in this case is to use an enum with associated values, basically the same thing as a tagged/discriminated union. Here's how to declare, assign and use such an enum:
enum DataSelection {
case CoreDataObject(NSManagedObject)
case StringField(String)
}
var selection : DataSelection?
selection = .CoreDataObject(someManagedObject)
if let sel = selection { // if there's a selection at all
switch sel {
case .CoreDataObject(let coreDataObj):
// do what you will with coreDataObj
case .StringField(let string):
// do what you will with string
}
}
Using an enum like this, there's no need to worry about which things could be hiding inside that Any?. There are two cases and they are documented. And of course, the selection variable can be an optional without any worries.
There's a tip to replace my Any? type with an enum but I couldn't get this error out of my head. Changing my approach doesn't change the fact that something is wrong with my current one and I had to figure out how I arrived at an Optional(nil) output.
I was able to reproduce the error by writing the following view controller in a new single-view project. Notice the init signature.
import UIKit
class Field {
var name: String = "Name"
var value: Any?
init(_ name: String, _ value: Any) {
self.name = name
self.value = value
}
}
class AppState {
var currentValue: Field?
}
class ViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
let f = Field("Amount", AppState().currentValue)
NSLog("\(f.value)")
}
}
In short, I was passing a nil value (AppState().currentValue) to an initializer that accepts Any, and assigns it to a property whose type is Any?. The funny thing here is if I directly passed nil instead, the compiler will complain:
let f = Field("Amount", nil) // Type 'Any' does not conform to protocol 'NilLiteralConvertible'
It seems that somewhere along the way, Swift wraps the nil value of AppState().currentValue in an optional, hence Optional(nil).

Shouldn't an optional be inclusive to a non-optional type in Swift?

Updated: with full code
I have this code:
struct Set<T : Hashable>: Sequence {
var items: Dictionary<Int, T> = [:]
func append(o: T?) {
if let newValue = o {
items[newValue.hashValue] = newValue
}
}
func generate() -> TypeGenerator<T> {
return TypeGenerator ( Slice<T>( items.values ) )
}
}
and I get the error:
Could not find an overload for 'subscript' that accepts the supplied arguments.
for the line:
items[newValue.hashValue] = newValue
As far as I understand it's because newValue's type is T rather than T?, meaning that it's not optional. This is because the Dictionary's subscript for accessing the key/value pair is defined as
subscript (key: KeyType) -> ValueType?
meaning that it can only take an optional as the value. And in my case newValue is not an optional after validating it's not nil.
But isn't an optional inclusive of non-optionals? Isn't a type's optional is the type + nil?
Why would something that can take everything + nil reject a type that can't be nil?
Small clarification: the reason I check that o is not nil is to be able to call its hashValue which is not accessible directly from the optional or the unwrapped optional (o!.hashValue throws a compile error).
I also can't replace the assignment line with
items[newValue.hashValue] = o
because it has validated that o is not an optional worthy of assignment even though it does not allow access to it's hashValue property.
The dictionary is not defined to store an optional value. It is just the assignment operator that accepts an optional value because giving it nil will remove the whole key from the dictionary.
The problem you are having is that you are trying to mutate your property items in a non-mutating method. You need to define your method as mutating:
mutating func append(o: T?) {
if let newValue = o {
items[newValue.hashValue] = newValue
}
}
There is certainly no problem assigning an optional variable to a non-optional value:
var optionalString : String? = "Hello, World"
In the same way, it is perfectly valid to assign a dictionary's key to a non-optional value:
var items : [Int:String] = [:]
items[10] = "Hello, World"
You can then assign the key to nil to remove the key entirely from the dictionary:
items[10] = nil
Also, I think you have a fundamental misunderstanding of what a hashValue is and how to use it. You should not pass the value of hashValue to a dictionary as a key. The dictionary calls hashValue on the value you provide, so you are having the dictionary take the hashValue of the hashValue.
There is no guarantee that a hashValue will be different from all other the hashValues of different values. In other words, the hash value of "A" can be the same hash value as "B". The dictionary is sophisticated enough to handle that situation and still give you the right value for a particular key, but your code is not handling that.
You can indeed store a non-optional value in a Dictionary and in general you can pass an object of type T whenever a T? is expected.
The real problem is that you haven't told the compiler that T is Hashable.
You have to put a type constraint on T, using T: Hashable. You can do this at a class level (I suppose this method lives inside a generic class), like so
class Foo<T: Hashable> {
var items: Dictionary<Int, T> = [:]
func append(o: T?) {
if let newValue = o {
items[newValue.hashValue] = newValue
}
}
}

Resources