Optional chaining and binding - swift

Inside the optional binding when I assign the variable ammo (and ammo2) I am pretty sure that I should be using ! to unbox the optional, but on my first attempt I put ? by mistake and was a little confused why it still worked, can anyone cast some light onto whats going on there?
let soldierA = Soldier(name: "Brian")
soldierA.weapon = Weapon()
soldierA.weapon!.grenadeLauncher = GrenadeLauncher()
let soldierB = Soldier(name: "Gavin")
soldierB.weapon = Weapon()
let soldierC = Soldier(name: "Berty")
soldierC.weapon = Weapon()
soldierC.weapon!.grenadeLauncher = GrenadeLauncher()
soldierC.weapon!.grenadeLauncher!.ammo = 234
let missionTeam = [soldierA, soldierB, soldierC]
for eachSoldier in missionTeam {
if let launcherAvailable = eachSoldier.weapon?.grenadeLauncher? {
var ammo = eachSoldier.weapon!.grenadeLauncher!.ammo // PRETTY SURE THIS IS RIGHT
var ammo2 = eachSoldier.weapon?.grenadeLauncher?.ammo // SHOULD THIS WORK, IT DOES?
println("SOLDIER: \(eachSoldier.name), Weapon has launcher AMMO: \(ammo)")
} else {
println("SOLDIER: \(eachSoldier.name), Weapon does not have launcher ")
}
}
.
// CLASSES
class Soldier {
var name: String
var weapon: Weapon?
init(name: String) {
self.name = name
}
}
class Weapon {
var ammo = 500
var grenadeLauncher: GrenadeLauncher?
}
class GrenadeLauncher {
var ammo = 20
}
EDIT
Thank you, I was getting confused about how this works, but I now see what is happening. Here is the modified eachSoldier section again, using optional binding with optional chaining...
for eachSoldier in missionTeam {
if let weapon = eachSoldier.weapon? {
if let launcher = eachSoldier.weapon?.grenadeLauncher? {
println("SOLDIER: \(eachSoldier.name) Weapon has launcher with \(launcher.ammo) ammo")
} else {
println("SOLDIER: \(eachSoldier.name) Weapon does not have launcher ")
}
} else {
println("SOLDIER: \(eachSoldier.name) does not have weapon ")
}
}

soldierC.weapon = Weapon()
soldierC.weapon!.grenadeLauncher = GrenadeLauncher()
soldierC.weapon!.grenadeLauncher!.ammo = 234
it is correct in the current pattern.
var ammo = eachSoldier.weapon!.grenadeLauncher!.ammo
implicitly unwraps the weapon and its grenadeLauncher; it does not care of whether or not they have been inited before, therefore it could lead a direct crash if your code tries to unwrap when any of them is still a nil value.
var ammo2 = eachSoldier.weapon?.grenadeLauncher?.ammo
tries to access the weapon and its grenadeLauncher; if the object does not exist, they will be left alone, therefore nothing happens but the ammo2 will be nil only, and application can proceed.
therefore your flow could be similar to that:
for eachSoldier in missionTeam {
var ammo2 = eachSoldier.weapon?.grenadeLauncher?.ammo
if ammo2 != nil {
println("SOLDIER: \(eachSoldier.name), Weapon has launcher AMMO: \(ammo2)")
} else {
println("SOLDIER: \(eachSoldier.name), Weapon does not have launcher ")
}
}

In addition to what #holex has stated, I would like to say that your case called Optional Chaining, in which if you use ? instead of ! on an optional variable (or constant), that means you are checking if the variable (or the constant) is not nil. In other words, it has a value.
The lovely thing about optional chaining is that you can apply it to many levels.
For example:
Let's say you have these two classes:
class Student{
var subjects: [Subject]?
}
class Subject{
var name: String?
}
and you created a variable:
var william = Student()
At any time, you can print the name of the first subject as this:
print(william.subjects?[0].name)
Notice that the result of that print statement is nil, while if you unwrapped it like this:
print(william.subjects![0].name)
You would get a run time error

Related

Swift optional binding in while loop

According to Swift documentation:
Optional binding can be used with if and while statements to check for a value inside an optional, and to extract that value into a constant or variable, as part of a single action.
The documentation only shows an example of optional-binding using if statement like:
if let constantName = someOptional {
statements
}
I'm looking for an example of optional-binding using while loop?
It's the same
while let someValue = someOptional
{
doSomethingThatmightAffectSomeOptional(with: someValue)
}
Here is a concrete example of iterating a linked list.
class ListNode
{
var value: String
var next: ListNode?
init(_ value: String, _ tail: ListNode?)
{
self.value = value
self.next = tail
}
}
let list = ListNode("foo", ListNode("bar", nil))
var currentNode: ListNode? = list
while let thisNode = currentNode
{
print(thisNode.value)
currentNode = thisNode.next
}
// prints foo and then bar and then stops
I think the advantage of using while let ... is infinite loop that check some variable for any changes. But it's weird. For this kind of work you should use didSet. Or other good example is List data structure. Anyway, here is the example:
var value: Int? = 2
while let value = value {
print(value) // 2
break
}

Get and Set Array in Swift Class

I was build an IOS application, and somehow I have to do an aggregation in swift class, but every time I want to get the data, it always return an error. it seems like I the data always return a nil result.
I want to push the DoctorList member object (schedule) in the view controller class
I have create the object, and I also called the init() function. but, however, when I push the (or call) the init function for the DoctorList class and pass the array of ScheduleList, the content of the schedule member in doctorlist class will always be empty.
so when I try to get the schedule, it will return a nil result.
can every one tell me what I did wrong, because I already change the code but it still give a nil result.
I have two class like this
class DoctorList: NSObject {
var DoctorID: String?
var DoctorName: String?
var SpecialtyID: String?
var SpecialtyName: String?
var ImageURL: String?
var Schedule: [ScheduleList]?
init(_ DoctorID:String, _ DoctorName:String, _ SpecialtyID:String, _ SpecialtyName:String, _ ImageUrl:String, _ Schedule:[ScheduleList] ){
self.DoctorID = DoctorID
self.DoctorName = DoctorName
self.SpecialtyID = SpecialtyID
self.SpecialtyName = SpecialtyName
self.ImageURL = ImageUrl
for sc in Schedule {
self.Schedule?.append(ScheduleList(sc.DoctorID!, sc.DayName!, sc.FirstHour!, sc.LastHour!))
}
}
var getSchedule: [ScheduleList] {
get {
return self.Schedule!
}
}
and this one
class ScheduleList: NSObject {
var DoctorID: String?
var DayName: String?
var FirstHour: String?
var LastHour: String?
init(_ DoctorID:String, _ DayName:String, _ FirstHour:String, _ LastHour:String ){
self.DoctorID = DoctorID
self.DayName = DayName
self.FirstHour = FirstHour
self.LastHour = LastHour
}
the return value for the schedule was always empty
I'm sorry, could anyone give a suggestion how to make a global variable in swift?
You haven't initialized the Schedule array.
The append statement in the loop just never execute:
for sc in Schedule {
// self.Schedule is nil so anything come after the question mark does not run
self.Schedule?.append(ScheduleList(sc.DoctorID!, sc.DayName!, sc.FirstHour!, sc.LastHour!))
}
To fix it initialize your array before use:
self.Schedule = [ScheduleList]()
for sc in Schedule {
self.Schedule?.append(ScheduleList(sc.DoctorID!, sc.DayName!, sc.FirstHour!, sc.LastHour!))
}
Also, your code is a pain to read:
Optionals everywhere! You should decide what properties can and cannot be nil and get rid of the unnecessary ? and !
The convention in Swift is lowerCamelCase for variable names, and CamelCase for class names
No need to inherit from NSObject unless you want something from the ObjC world, whether working with ObjC or use KVO
Just noticed, recommendations from #CodeDifferent should be appropriate and helpful.
It is because you haven't initialised the Schedule in DoctorList
init(_ DoctorID:String, _ DoctorName:String, _ SpecialtyID:String, _ SpecialtyName:String, _ ImageUrl:String, _ Schedule:[ScheduleList] ){
self.DoctorID = DoctorID
self.DoctorName = DoctorName
self.SpecialtyID = SpecialtyID
self.SpecialtyName = SpecialtyName
self.ImageURL = ImageUrl
// Use this for init an empty array and append the content
self.Schedule = [ScheduleList]()
self.Schedule?.append(contentsOf: Schedule)
}
An example of the result:
let schedule = ScheduleList("scheduleId", "name", "1st", "last")
let doctorList = DoctorList("docId", "docName", "specId", "scpecName", "imgURL", [schedule])
if let list = doctorList.Schedule {
print("\(list[0].DoctorID)")
}

Compiler choosing the wrong initializer

I have an EXC_BAD_ACCESS(code=2 ...) error that keeps popping up that I need some help with. I believe I've managed to pin down the source of the malformed pointer, but I'm at a loss as to how to fix it.
Apparently the swift compiler is choosing the wrong initializer for one of my classes. According to Instruments, sometimes when the class Description is initialized, it calls the initializer for LyricBlock. Not all the time, just sometimes. It does this regardless of whether the compiler is set to -Onone or -O whole-module-optimization.
Here's what the two classes look like:
class LyricBlock: Node, LeftDelimited, RightDelimited {
var leftDelimiter: Delimiter
var rigthDelimiter: Delimiter
init(start: Int, body: Range<Int>, end: Int) {
self.leftDelimiter = Delimiter(range: start..<body.lowerBound)
self.rightDelimiter = Delimiter(range: body.upperBound..<end)
super.init(range: body)
}
}
class Description: Node, LeftDelimited, RightDelimited {
var leftDelimiter: Delimiter
var leftDelimiter: Delimiter
init(start: Int, body: Range<Int>, end: Int) {
self.leftDelimiter = Delimiter(range: start..<body.lowerBound)
self.rightDelimiter = Delimiter(range: body.upperBound..<end)
super.init(range: body)
}
}
As you can see, LyricBlock and Description inherit from a Node base class and share a couple protocols in common, but otherwise they have nothing to do with each other.
Some possibly relevant code:
class Node {
weak var parent: Node?
var next: Node?
var firstChild: Node?
weak var lastChild: Node?
let offset: Int
internal(set) var length: Int
init(range: Range<Int>) {
self.offset = range.lowerBound
self.length = range.upperBound - range.lowerBound
}
func addChild(_ child: Node) {
if firstChild == nil {
firstChild = child
} else {
lastChild?.next = child
}
lastChild = child
child.parent = self
}
}
class Parser {
// ...
func processLine(in buffer: Buffer) {
// Parse the current line as a block node.
var block = blockForLine(in: buffer)
// Try to find an appropriate container node. If none can be found, block will be replaced with Description.
let container = appropriateContainer(for: &block, in: buffer)
container.addChild(block)
// Edge case to parse first-line lyrics
if let cueBlock = block as? CueBlock {
if let lyricBlock = scanForLyric(in: buffer, at: cueBlock.direction.range.lowerBound) {
let lyricContainer = LyricContainer(range: lyricBlock.range.lowerBound..<endOfLineCharNumber)
lyricContainer.addChild(lyricBlock)
cueBlock.replaceDirection(with: lyricContainer)
parseInlines(for: lyricBlock, in: buffer)
}
}
// Parse inlines as appropriate
switch block {
case is FacsimileBlock, is Description, is LyricBlock:
parseInlines(for: block, in: buffer)
// ...
}
}
func blockForLine(in buffer: Buffer) -> Node {
let whitespace = buffer.scanForFirstNonspace(at: charNumber, limit: endOfLineCharNumber)
// ...
let endWhitespace = buffer.scanBackwardForFirstNonspace(at: endOfLineCharNumber, limit: wc)
let description = Description(start: charNumber, body: whitespace..< endWhitespace, end: endOfLineCharNumber)
return description
}
func appropriateContainer(for block: inout Node, in buffer: Buffer) -> Node {
switch block {
// These block types can only ever be level-1
case is Header, is Description, is EndBlock, is HorizontalBreak:
return root
// ...
case is LyricBlock:
guard let cueContainer = root.lastChild as? CueContainer else { break }
guard let cueBlock = cueContainer.lastChild as? CueBlock else { break }
guard let direction = cueBlock.direction as? LyricContainer else { break }
direction.extendLengthToInclude(node: block)
cueBlock.extendLengthToInclude(node: direction)
cueContainer.extendLengthToInclude(node: cueBlock)
return direction
default:
break
}
let whitespace = buffer.scanForFirstNonspace(at: charNumber, limit: endOfLineCharNumber)
let endWhitespace = buffer.scanBackwardForFirstNonspace(at: endOfLineCharNumber, limit: wc)
// Invalid syntax, time to fail gracefully
block = Description(start: charNumber, body: whitespace..< endWhitespace, end: endOfLineCharNumber)
return root
}
func parseInlines(for stream: Node, in buffer: Buffer) {
// ... scans buffer for inlines and enques them in queue
while let next = queue.dequeue() {
let nextRange = next.rangeIncludingMarkers
if nextRange.lowerBound > j {
let lit = Literal(range: j..<nextRange.lowerBound)
stream.addChild(lit)
}
stream.addChild(next)
j = nextRange.upperBound
}
if j < endOfLineCharNumber {
let lit = Literal(range: j..<nodeRange.upperBound)
stream.addChild(lit)
}
}
// ...
}
As a side note, I wondered if I might be running into a mangling issue with the class signatures and tried making rightDelimiter and leftDelimiter properties of Node instead of using protocols. This resulted in the compiler calling my Identifier initializer instead. I don't know what that proves. Frankly I'm at a loss. Help?
After some more tinkering I was able to at least partially solve this problem.
It turns out that, for whatever reason, ARC craps out on linked lists above a certain size. I was able to confirm this by creating a generic linked list in a new project and slowly stress testing it with progressively longer lists.
class LLNode<T> {
var value: T?
var next: LLNode<T>?
weak var previous: LLNode<T>?
}
class LList<T> {
var head: LLNode<T>
var tail: LLNode<T>
// boring old linked list implementation
}
let list = List<Int>()
for i in 0..<10000 {
list.append(i)
}
Sure enough, after about 10,000 nodes I started getting EXC_BAD_ACCESS again on the generic list, but only when list was deinitialized. This matched the behavior I was getting exactly with the parser above. Since my code uses linked lists to model children in a tree (two dimensions of reference counting!), ARC was having to resolve all those references on its own -- and failing. Why it crashes like that is still beyond my ability to explain, but that at least explains the source of the crash.
To confirm, I created another linked list in C and just made a Swift wrapper around it, with all of the garbage collection being implemented in C.
import liblist
class List<T> {
var list: UnsafeMutablePointer<llist>
// Swift interface with list
deinit {
list_free(list)
}
}
The wrapper was able to handle every size I threw at it -- as much as 500,000 nodes, at which point I felt satisfied and stopped testing.
If anyone is interested in the full code I used to get around this problem, I've created a Swift package at https://github.com/dmcarth/List

Swift Optionals - Variable binding in a condition requires an initializer

I am new to Swift and trying to figure out the Optional concept. I have a small piece of code in Playground which is giving me "Variable binding in a condition requires an initializer" error. Can someone please explain why and how do I fix it?
I only want to print "Yes" or "No" depending on if "score1" has a value or not. Here is the code:
import Cocoa
class Person {
var score1: Int? = 9
func sum() {
if let score1 {
print("yes")
} else {
print("No")
}
}//end sum
}// end person
var objperson = person()
objperson.sum()
The if let statement takes an optional variable. If it is nil, the else block or nothing is executed. If it has a value, the value is assigned to a different variable as a non-optional type.
So, the following code would output the value of score1 or "No" if there is none:
if let score1Unwrapped = score1
{
print(score1Unwrapped)
}
else
{
print("No")
}
A shorter version of the same would be:
print(score1 ?? "No")
In your case, where you don't actually use the value stored in the optional variable, you can also check if the value is nil:
if score1 != nil {
...
}
Writing
if let score1 {
doesn't make sense. If you want to see if score has a value, use
if score1 != nil {
or
if let score = score1 {
The last case binds a new non-optional constant score to score1. This lets you use score inside the if statement.
The code in your question is similar to something I saw in the swift book and documentation and they are correct.
Your playground is just using an old version of swift which currently doesn't support this syntax. Using a beta version of XCode should fix
https://www.reddit.com/r/swift/comments/vy7jhx/unwrapping_optionals/
the problem is that if let assumes you want to create a constant score1 with some value. If you just want to check if it contains a value, as in not nil you should just do it like below:
if score1! != nil {
// println("yes")
So your full code would look like this:
class Person {
var score1: Int? = 9
func sum() {
if score1 != nil {
println("yes")
}
else {
println("no")
}
}
}
var objperson = Person()
objperson.sum()
You can unwrap it using this:
import Cocoa
class Person {
var score1: Int? = 9
func sum() {
print("\(score1 != nil ? "YES" : "NO")")
}
}
And then call it like:
var objperson = Person()
objperson.sum()

Unwrapping Swift optional without variable reassignment

When using optional binding to unwrap a single method call (or optional chaining for a long method call chain), the syntax is clear and understandable:
if let childTitle = theItem.getChildItem()?.getTitle() {
...
}
But, when provided the variable as a parameter, I find myself either using:
func someFunction(childTitle: String?) {
if let theChildTitle = childTitle {
...
}
}
or even just redefining it with the same name:
if let childTitle = childTitle { ... }
And I've started wondering if there is a shortcut or more efficient of performing a nil check for the sole purpose of using an existing variable. I've imagined something like:
if let childTitle { ... }
Does something like this exist, or at least an alternative to my above two interim solutions?
No. You should unwrap your optionals just redefining it with the same name as you mentioned. This way you don't need to create a second var.
func someFunction(childTitle: String?) {
if let childTitle = childTitle {
...
}
}
update: Xcode 7.1.1 • Swift 2.1
You can also use guard as follow:
func someFunction(childTitle: String?) {
guard let childTitle = childTitle else {
return
}
// childTitle it is not nil after the guard statement
print(childTitle)
}
Here's the only alternative I'm aware of.
func someFunction(childTitle: String?) {
if childTitle != nil {
...
}
}
But then childTitle is still an optional, so you would have to unwrap it every time you use it: childTitle!.doSomething(). It shouldn't affect performance, though.
I've gotten into the habit of just reassigning to the same name. That eliminates the need to come up with a separate name and avoids any confusion about whether the new name is supposed to be used anywhere. However, note the output after the block:
var test: String? = "123"
if var test = test {
test += "4"
test += "5"
}
print(test) // prints "123"
If you need to access the modified value after the block, you can assign the variable to a new name and assign the new name back to the old name inside the block:
var test: String? = "123"
if var testWorking = test {
testWorking += "4"
testWorking += "5"
test = testWorking
}
print(test) // prints "12345"