Swift for Ruby Devs: The Basics pt. 2

In our last post, Swift for Ruby Devs: The Basics pt. 1, we looked at the differences in syntax between Swift and Ruby of the most basic data types. We covered variables and constants, strings, integers and floats, operators, and collection types such as arrays and hashes (known as dictionaries in Swift). With an understanding of these basic data types, we'll examine how to write control flow structures in Swift and see how they differ in Ruby. We'll also learn about methods, structs, and classes in Swift.

Control Flow Structures


Loops and Iterators

In Swift, there are quite a few different mechanisms for iterating over arrays, dictionaries, strings, ranges, and other sequences. These are common in most programming languages, though the syntax may be a little different. If you're a Ruby/Rails developer, you should be familiar with these already especially if you've written some Javascript before.

The for-in loop is common in most programming languages; even Ruby implements it though we prefer other methods of iteration to write more idiomatic Ruby.

# Arrays

creatures = ["Loch Ness Monster", "Sasquatch", "Donald Trump"]

for creature in creatures do  
  puts "Hello, my name is #{creature}."
end  
# Hashes

creatures = { dog: "woof", cat: "meow", donald_trump: "huugge!"}

for key, value in creatures do  
  puts "#{key} and #{value}"
end  


If you've written Javascript before, then you're already familiar with functions, classes, control flow statements, etc, opening and closing with curly braces. This is also true in Swift and one of the major differences you'll notice when comparing Swift's syntax with Ruby's.

// Arrays

let creatures = ["Loch Ness Monster", "Sasquatch", "Donald Trump"]

for creature in creatures {  
  print("Hello, my name is \(creature).")
}
// Dictionaries 

let creatures = ["dog": "woof", "cat": "meow", "donald trump": "huuge!"]

for (key, value) in creatures {  
    print("\(key) and \(value)")
}


It's worth mentioning that dictionaries in Swift are iterated in reverse order, while hashes in Ruby are iterated from the first key-value pair defined in the hash. If you're using a hash/dictionary though, then you probably don't care about the order of things.

When writing idiomatic Ruby, however, we prefer to use methods such as each, map, collect, and reduce, over for-in loops when iterating over an array or hash to perform some task. Luckily, Swift's standard library provides us with similar methods to accomplish the same tasks.

let creatures = ["Loch Ness Monster", "Sasquatch", "Donald Trump"]

creatures.forEach { (creature) in  
  print("Hello, my name is \(creature).")
}
let numbers = [1, 2, 3]

var newArray = numbers.map { $0 + 1 }

newArray = numbers.map { (number) in  
    // Do work spanning multiple lines
    number + 1
}

listOfStrings = numbers.map { (number) -> String in  
  return "This is the number \(number)."
}


In the examples above we only looked at forEach and map, but Swift's standard library provides many more methods for iterating over and transforming arrays and dictionaries. Such methods include flatMap, reduce, and filter which can accomplish the same tasks as Ruby's select and reject methods.

In the example above, you may have noticed $0 and wondered what the heck it means. Swift provides a shorthand for each element that's passed into the closure from the collection. It can reduce the verbosity of your code (sometimes at the cost of readability) to keep you from writing long argument lists. Here's an excerpt from Apple's Swift language guide on closures:

Swift automatically provides shorthand argument names to inline closures, which can be used to refer to the values of the closure’s arguments by the names $0, $1, $2, and so on.

If you use these shorthand argument names within your closure expression, you can omit the closure’s argument list from its definition, and the number and type of the shorthand argument names will be inferred from the expected function type. The in keyword can also be omitted, because the closure expression is made up entirely of its body:

reversedNames = names.sorted(by: { $0 > $1 } )

Here, $0 and $1 refer to the closure’s first and second String arguments.

Pro Tip: To learn about all the methods and properties available to you in Swift's standard library, search for "Array", "Dictionary", etc., in Xcode's Documentation and API Reference Guide. From within Xcode, enter Shift + Command + 0 to open up the documentation.


Conditional Statements


Swift provides two ways to add conditional branches to your code. These are the if and switch statements.

if statements

if isWeekday && (isRaining || isSnowing) {  
  print("Reluctantly prepare to get dressed")
} 
else if isWeekend && (isRaining || isSnowing) {  
  print("Stay in bed")
}
else {  
  print("Happily get dressed")
}
if weekday? && (raining? || snowing?)  
  puts "Reluctantly prepare to get dressed"
elsif weekend? && (raining? || snowing?)  
  puts "Stay in bed"
else  
  puts "Happily get dressed"
end  


Comparing these equivalent if statements, we see two major differences. The first is that, in Swift, we must open and close our statements with curly braces {}. The second difference is the way in which we write else if statements. In Ruby, we simply use the elsif keyword. In Swift, we write else if. If you've written Javascript, then Swift's syntax should look familiar.

switch statements

let food = "watermelon"

switch food {  
  case "strawberry": print("Dip in chocolate")
  case "watermelon": 
    print("Cut into pieces")
    print("eat")
  default: 
    print("throw it away")
}
food = "watermelon"

case food  
when "strawberry"  
  puts "Dip in chocolate"
when "watermelon"  
  puts "Cut into pieces"
else  
  puts "throw it away" 
end  


There are a few differences between Swift and Ruby when writing switch/case statements. Swift uses the switch keyword when examining a value, and the case keyword when comparing that value against possible matching patterns. Ruby, on the other hand, uses the case keyword when examining a value, and the when keyword when comparing that value against several possible matching patterns.

Another difference is that Swift uses and requires a default keyword to do some work if it is possible that the value we're switching on doesn't match any of the case statements. This is because Swift requires switch statements to be exhaustive; meaning all possible cases of the value we're matching against must be taken under consideration. If not, then we must supply a default at the end of the statement. In Ruby, else accomplishes the same task as default. The only difference is that Ruby doesn't require an else statement, and will simply return nil if the value is never matched.


Functions


Functions (or methods) in Swift are written much differently than they are written in Ruby and are best understood with an example. Lets examine two methods. One that adds two numbers together, and another that prints the result.

def add_two_numbers(a, b)  
  a + b
end

def print_the_sum(first_num, second_num)  
  sum = add_two_numbers(first_num, second_num)
  puts "The sum of #{first_num} and #{second_num} is #{sum}"
end

print_the_sum(3, 3)  
func addTwoNumbers(a: Int, b: Int) -> Int {  
  return a + b
}

func printTheSum(firstNum: Int, secondNum: Int) {  
  let sum = addTwoNumbers(a: firstNum, b: secondNum)
  print("The sum of \(firstNum) and \(secondNum) is \(sum)")
}

printTheSum(firstNum: 3, secondNum: 3)  


Every function in Swift has a type. The type of a function consists of the types of it's parameters and the return type of the function. This means that we must specify the types of each parameter as well as the return type if the method returns something. If a function simply prints something to the console, for example, specifying a return type isn't necessary. In that case, Swift will infer a return type of Void.

We specify the parameter type by providing the name of the parameter followed by a colon : and then the type (a: Int). We specify a return type with the -> Type syntax after we've specified our parameters (if any). In the example above, addTwoNumbers specifies a return type of Int and printTheSum does not specify a return type at all.

The parameter syntax in Swift looks similar to keyword arguments in Ruby. Though we don't specify types in Ruby, we call methods with keyword arguments much the same way:

def add_two_numbers(a:, b:)  
  a + b
end

add_two_numbers(a: 2, b: 3)  
  => 5



One unique feature of Swift functions are what are known as argument labels. When defining the parameters that your function will accept, you can provide each parameter with an argument label. The argument label is used when calling the function and it's purpose is to make the code more readable at the call site. The parameter name associated with the argument label is used within the implementation of the function. Argument labels are written immediately before the parameter name in the function definition. Here's an example of a sayHello method that allows you to say "Hello" to two people.

func sayHello(to person: String, and anotherPerson: String) -> String {  
    return "Hello \(person) and \(anotherPerson)"
}

sayHello(to: "Danny", and: "Fred")  



In the example above, person and anotherPerson are the argument names used in the implementation of the function. The argument labels are to and and. Calling the method now reads more like a complete sentence. In the implementation of the method, however, to and and would make less sense than person and anotherPerson.

Another unique feature of functions in Swift that you won't find in Ruby is the ability to define functions with the same name within a class, struct, etc. Due to the fact that every function in Swift has a type or signature, defined by it's parameter and return types, functions with the same name but with different types are considered two completely different functions as far as the compiler is concerned. Revisiting the sayHello function, let's say we also wanted to write a function that only said "Hello" to one person.

func sayHello(to person: String, and anotherPerson: String) -> String {  
    return "Hello \(person) and \(anotherPerson)"
}

sayHello(to: "Danny", and: "Fred")

func sayHello(to person: String) -> String {  
  return "Hello \(person)"
}

sayHello(to: "Billy")  



Functions in Swift come equipped with many powerful features. For an in-depth overview, see the Swift 3 language guide.


Structs


Though structures in Swift serve the same purpose as they do in Ruby, they have some features unique to the language. Let's take a look at a Customer struct with name and address properties.

Customer = Struct.new(:name, :address)

a_customer = Customer.new("Danny", "123 Main St.")  
struct Customer {  
  var name: String
  var address: String
}

let aCustomer = Customer(name: "Danny", address: "123 Main St.")  



In Ruby, structs are created by initializing an instance of the Struct class and supplying it with member values, as well as an optional name for the Struct. We can then create as many instances of this struct as we'd like, supplying values for :name and :address.

To create a struct in Swift, we use the struct keyword followed by a capitalized name for the struct. Within the closure, we supply properties via variables or constants. In the Swift example above, we create new instances of the Customer struct by supplying values for each property in the same manner as we supply parameters to a method.

In both Ruby and Swift, we can supply methods to structs if we need some sort of behavior. The unique thing about Swift is that we can override the initializer method to add custom behavior every time we create a new struct.

Customer = Struct.new(:name, :address) do  
  def greeting
    "Hello, #{name}. Your address is #{address}."
  end
end

a_customer = Customer.new("Danny", "123 Main St.")

a_customer.greeting  
  => "Hello, Danny. Your address is 123 Main St."
struct Customer {  
  var name: String
  var address: String

  init(name: String, address: String) {
    self.name = name
    self.address = address
    // Do some other stuff when initializing struct.
  }

  func greeting() -> String {
    return "Hello, \(name). Your address is \(address)."
  }
}

let aCustomer = Customer(name: "Danny", address: "123 Main St.")

aCustomer.greeting()  
  => "Hello, Danny. Your address is 123 Main St."


Classes


When comparing the differences in syntax between creating classes in Swift and Ruby, what stood out the most to me was the initializer method and inheriting from a base class. Here's an example of creating a Lamborghini subclass that inherits from an Automobile base class.

class Automobile  
  attr_reader :make, :model

  def initialize(make, model)
    @make = make
    @model = model
  end

  def turn_on
    puts "Insert key in ignition"
  end
end

class Lamborghini < Automobile  
  attr_reader :owner

  def initialize(model, owner)
    @owner = owner
    super("Lamborghini", model)
  end

  def turn_on
    puts "Push to start"
  end
end

lambo = Lamborghini.new("Aventador", "Danny")  
class Automobile {  
  let make: String
  let model: String

  init(make: String, model: String) {
    self.make = make
    self.model = model
  }

  func turnOn() {
    print("Insert key in ignition")
  }
}

class Lamborghini: Automobile {  
  let owner: String

  init(model: String, owner: String) {
    self.owner = owner
    super.init(make: "Lamborghini", model: model)
  }

  override func turnOn() {
    print("Push to start")
  }
}

let lambo = Lamborghini(model: "Aventador", owner: "Danny")  


In this example, Automobile includes two properties common in all cars; a make and model. It also includes a method to turn on the car. The Lamborghini class inherits from Automobile and includes an additional owner property. It also overrides the method to turn on the car.

In Ruby, we define logic for initializing an instance of a class within the initialize method. Swift provides similar behavior by including the init method. When defining an init method, the func keyword is not required as it is in all other functions. The init method doesn't return a value, and it's only purpose is to ensure an instance of a class is correctly initialized before it is used.

When inheriting from a base class in Ruby, we use the < syntax and provide the name of the base class.

class Lamborghini < Automobile  
end  



In Swift, we instead use the : syntax.

class Lamborghini: Automobile {  
}



Another thing you may have noticed is how methods from a base class are overridden in the subclass. In both Ruby and Swift, we simply re-write the function in the subclass. The only difference is that in Swift, we must also include the override keyword before the function declaration.

class Lamborghini < Automobile  
  def turn_on
    puts "Push to start"
  end
end  
class Lamborghini: Automobile {  
  override func turnOn() {
    print("Push to start")
  }
}



One last thing you may have noticed is how we call methods in the base class from the subclass. In Ruby, we simply call super from within a method along with any parameters. In Swift, we must be explicit about the method we want to call on the superclass.

def initialize  
  super(param1, param2)
end  
init() {  
  super.init(param1: param, param2: param)
}



For an in-depth explanation of structs and classes in Swift, see the Swift 3 language guide.


Summary


Now that we have a grasp of the basics of Swift, we can start writing simple programs and are better prepared to start learning about more complex concepts that are unique to Swift. Concepts that may be foreign to you as a Ruby developer, such as, protocols, enumerations (not to be confused with enumerables in Ruby), optionals, tuples, and type casting. To really solidify your skills on the basics of Swift, the Swift 3 language guide linked above is a great place to start. Also, exercism.io has many code challenges that'll allow you to practice your basic Swift skills and get feedback for free!