dev ios, swift

When I started learning Swift for iOS development I also started to compile some notes along the way. This post is the first instalment of my notes. First some basic concepts (in no particular order):

REPL

  • Swift supports REPL (Read Evaluate Print Loop) and you can write code and get feedback very quickly this way by using XCode Playground or command line.

As seen in the screenshot there is no need to explicitly print the values, they are automatically displayed on the right-hand side of the screen.

It can also be executed without specifying the interpreter by adding #!/usr/bin/swift at the top of the file.

Comments

Swift supports C-style comments like // for single-line comments and /* */ for multi-line comments.

The great thing about the multi-line comments is that you can nest them. For example the following is a valid comment:

/* This is a 
    /* valid mult-line */
    comment that is not available in C#
*/

Considering how many times Visual Studio punished me while trying to comment out a block of code that had multi-line comments in it, this feature looks fantastic!

It also supports doc comments (///) and supports markdown. It even supports emojis (Ctrl + Cmd + Space for the emoji keyboard)

Imports

Standard libraries are imported automatically but the main frameworks such as Foundation, UIKit need to be imported explicitly.

Swift 2 supports a new type of import which is preceded by @testable keyword.

@testable import CustomFramework

It allows to access non-public members of a class. So that you can access them externally from a unit test project. Before this they all needed to be public in order to be testable.

Strings

Built-in string type is String. There is also NSString in the Foundation framework. They can be used interchangably sometimes, for example you can assign a String to a NSString but the opposite is not valid. You have cast it explicitly to String first:

import Foundation

var string : String = "swiftString"
var nsString : NSString = "foundationString"

nsString = string // Works fine
string = nsString as String // Wouldn't work without the cast
  • startIndex is not an int but an object. To get the next character

s[s.startIndex.successor()]

To get the last character

s[s.startIndex.predecessor()]

For a specific position s[advance(s.startIndex, 1)]

let vs. var

Values created with let keyword are immutable. So let is used to create constants. Variables can be created with var keyword. If you create a value

let x1 = 7
x1 = 8 // won't compile

var x2 = 10
x2 = 11 // this works

The same principle applies to arrays:

let x3 = [1, 2, 3]
x3.append(4) // no go!

Type conversion

Types are inferred and there is no need to declare them while declaring a variable.

let someInt = 10
let someDouble = 10.0
let x = someDouble + Double(someInt)

Structs and Classes

  • Structs are value objects and a copy of the value is passed around. Classess are reference objects.

  • Constructors are called initializers and they are special methods named init. Must specify an init method or default values when declaring the class.

class Person {
  var name: String = ""
  var age: Int = 0

  init (name: String, age: Int) {
    self.name = name
    self.age = age
  }
}
  • There is no new operator. So declaring a new object looks simply like this:
let p = Person()
  • The equivalent of destructor is deinit method. Only classes can have deinitializers.

Collections

Array: An ordered list of items

  • An empty array xan be declared in a verbose way such as

      var n = Array<Int>()
    

    or with the shorthand notation

      var n = [Int]()
    
  • An array with items can be intialized with

      var n = [1, 2, 3]
    
  • Arrays can be concatenated with +=

      n += [4, 5, 6]
    
  • Items can be added by append method

      n.append(7)
    
  • Items can be inserted to a specific index

      n.insert(8, atIndex: 3)
      print(n) // -> "[1, 2, 3, 8, 4, 5, 6, 7]"
    
  • Items can be deleted by removeAtIndex

      n.removeAtIndex(6)
      print(n) // -> "[1, 2, 3, 8, 4, 5, 7]"
    
  • Items can be accessed by their index

      let aNumber = n[2]
    
  • A range of items can be replaced at once

      var n = [1 ,2, 3, 4]
      n[1...2] = [5, 6, 7]
      print(n) // prints [1, 5, 6, 7, 4]"
    
  • 2-dimensional arrays can be declared as elements as arrays and multiple subscripts can be used to access sub items

      var n = [ [1, 2, 3], [4, 5, 6] ]
      n[0][1] // value 2
    

Dictionary: A collection of key-value pairs

  • Can be initialized without items

      var dict = [String:Int]()
    

    or with items

      var dict = ["key1": 5, "key2": 3, "key3": 4]
    
  • To add items, assign a value to a key using subscript syntax

      dict["key4"] = 666
    
  • To remove an item, assign nil

      dict["key2"] = nil
      print(dict) // prints ["key1": 5, "key4": 666, "key3": 4]"
    
  • To update a value, subscript can be used as adding the item or updateValue method can be called.

    updateValue returns an optional. If it didn’t update anything the optional has nil in it. So it can be used to check the value was actually updated or not.

      var result = dict.updateValue(45, forKey: "key2")
      if let r = result {
          print (dict["key2"])
      } else {
          print ("could not update") // --> This line would be printed
      }	
    

    The interesting behaviour is that if it can’t update it, it will add the new value.

      var dict = ["key1":5, "key2":3, "key3":4]
      var result = dict.updateValue(45, forKey: "key4")
      if let r = result {
          print (dict["key4"])
      } else {
          print ("could not update")
      }
    	
      print(dict) // prints "["key1": 5, "key4": 45, "key2": 3, "key3": 4]"
                    // key4 has been added after calling updateValue
    

    After a successful update it would return the old value

      result = dict.updateValue(45, forKey: "key1")
      if let r = result {
          print (r) // --> This would run and print "5"
      } else {
          print ("could not update")
      }
    

    This is consistent with the unsuccessful update returning nil. It always returns the former value.

  • To get a value subscript syntax is used

      var i = dict["key1"] // 45
    

Set: An unordered list of distinct values

  • Initialization notation is similar to the others

      var emo : Set<Character> = [ "😡", "😎", "😬" ]
    
  • If duplicate items are added it doesn’t throw an error but prunes the list automatically

      var emo : Set<Character> = [ "😡", "😎", "😬", "😬" ]
      emo.count // prints 3
    
  • New items can be added with insert method

      var emo : Set<Character> = [ "😡", "😎", "😬", "😬" ]
      emo.insert("😱")
      emo.insert("🤔")
      print(emo) // prints "["😱", "😎", "🤔", "😬", "😡"]"
    

    There is no atIndex parameter like array and the index is unpredicatable as shown above

Among the three, only arrays have ordered and can have repeated values.

Miscellaneous

  • Semi-colons are not required at the end of each line

  • Supports string interpolation

  • Swift uses reference counting and there is garbage collection.

  • Curly braces are required even if there is only one statement inside the body. For instance the following block wouldn’t compile:

      let x = 10
      if x == 10
          print("Ten!")
    
  • println function has been renamed to print. print adds a new line to the end automatically. This behaviour can be overriden by explicitly specfying appendNewLine attribute

      print ("Hello, world without a new line", appendNewLine: false)
    
  • #available can be used to check compatibility

      if #available(iOS 9, *) {
          // use NSDataAsset
      } else {
          // Panic!
      }
    
  • Range can be checked with … and ~= operators. For example:

      let x = 10
    	
      if 1...100 ~= x {
          print(x)
      }
    

    The variable is on the right in this expression. It wouldn’t compile the other way around.

  • There is Range object that can be used to define, well, ranges!

      var ageRange = 18...45
      print(ageRange) // prints "18..<46"
      print(ageRange.count) // prints "28"
    

    The other range operator is ..< which doesn’t include the end value

      var ageRange = 18..<45
      ageRange.contains(45) // prints "false"
    

Resources

devaws route53, angularjs

Previously in this series:

So far I was using the console client but I thought I chould use a prettier web-based-UI and came up with this:

DomChk53 AngularJS Client

It’s using AngularJS and Bootstrap which significantly improved the development process.

API in the backend is AWS API Gateway on a custom domain (api.domchk53.com) and using Lambda functions to do the actual work. One great thing about API Gatewayis that it’s very easy to set requests rates:

Currently I set it to max 5 requests per second. I chose this value because of the limitation on AWS API as stated here:

All requests – Five requests per second per AWS account. If you submit more than five requests per second, Amazon Route 53 returns an HTTP 400 error (Bad request). The response header also includes a Code element with a value of Throttling and a Message element with a value of Rate exceeded.

Of course limiting this on the client side assumes a single client so you may still get “Rate exceeded” errors even if running single query at a time. I’m planning to implement a Node server using SQS to move the queue to server side but that’s not one of my priorities right now.

The Lambda function is straightforward enough. Just calls the checkDomainAvailability API method with the supplied parameters:

exports.handler = function(event, context) {
    var AWS = require('aws-sdk');
	var options = {
	    region: "us-east-1"
	}
	
	var route53domains = new AWS.Route53Domains(options);
    
    var params = {
        DomainName: event.domain + '.' + event.tld
    };

    route53domains.checkDomainAvailability(params, function (err, data) {

        if (err) {
            context.fail(err);
        } else {
            var result = {
                Domain: event.domain,
                Tld: event.tld,
                CheckDate: new Date().toISOString(),
                RequestResult: "OK",
                Availability: data.Availability
            };
            
            context.succeed(result);  
        }
    });
}

Usage

I wanted this tool as an improvement to AWS already provides. What you can do with Management Console is search a single domain and it searches it against the popular 13 TLDs. If you need anything outside these 13 you have to pick them manually.

In DomChk53 you can search multiple domain names at once against all supported TLDs (293 as of this writing).

Also you can group TLDs into lists so you can for example search the most common ones (com, net, co.uk etc.) and say finance related ones (money, cash, finance etc.). Depending on the domain name one group may be more relevant.

You can cancel a query at any time to avoid wasting precious requests if you change your mind about the domain.

What’s missing

For a while I’m planning to leave it as is but when I have it in me to revisit the project I will implement:

  • Server-side queueing of requests
  • The option to export/email the results in PDF format

I’m also open to other suggestions…

Resources

awsdev route53, angularjs

I’ve been using my own dynamic DNS application (which I named DynDns53 and blogged about it here). So far it had a WPF application and I was happy with it but I thought if I could develop a web-based application I wouldn’t have to install anything (which is what I’m shooting for these days) and achieve the same results.

So I built a JavaScript client with AngularJS framework. The idea is exactly the same, the only difference is it’s all happening inside the browser.

DynDns53 web client

Ingredients

To have a dynamic DNS client you need to have the following

  1. A way to get your external IP address
  2. A way to update your DNS record
  3. An application that performs Step 1 & 2 perpetually

Step 1: Getting the IP Address

I have done and blogged about this several times now. (Feels like I’m repeating myself a bit, I guess I have to find something original to work with. But first I have to finish this project and have closure!)

Since it’s a simple GET request it sounds easy but I quickly hit the CORS wall when I tried the following bit:

app.factory('ExternalIP', function ($http) {
    return $http.get('http://checkip.amazonaws.com', { cache: false });
}); 

In my WPF client I can call whatever service I want whenever I want but when running inside the browser things are a bit different. So I decided to take a detour and create my own service that allowed cross-origin resource sharing.

AWS Lambda & API Gateway

First I thought I could do it even without Lambda function by using the HTTP proxy integration. I could return what the external site returns:

Unfortunately this didn’t work because it was returning the IP of the AWS machine that’s actually running the API gateway. So I had to get the client’s IP from the request and send it back in my own Lambda function.

Turns out in order to get HTTP headers you need to fiddle with some template mapping and assign the client’s IP address to a variable:

This can be later referred to in the Lambda function through event parameter:

exports.handler = function(event, context) {
    context.succeed({
        "ip": event.ip
    })
}

And now that we have our own service we can allow CORS and be able call it from our client inside the browser:

Step 2: Updating DNS

This bit is very similar to WPF version. Instead of using the AWS .NET SDK I just used the JavaScript SDK. AWS has a great SDK builder which lets you to select the pieces you need:

It also shows if the service supports CORS. It’s a relief that Route53 does so we can keep going.

The whole source code is on GitHub but here’s the gist of it: Loop through all the subdomains, get all the resource records in the zone, find the matching record and update it with the new IP:

  $scope.updateAllDomains = function() {
      angular.forEach($rootScope.domainList.domains, function(value, key) {
        $scope.updateDomainInfo(value.name, value.zoneId);
      });
  } 
  $scope.updateDomainInfo = function(domainName, zoneId) {
    var options = {
      'accessKeyId': $rootScope.accessKey,
      'secretAccessKey': $rootScope.secretKey
    };
    var route53 = new AWS.Route53(options);
    
    var params = {
      HostedZoneId: zoneId
    };

    route53.listResourceRecordSets(params, function(err, data) {
        if (err) { 
          $rootScope.$emit('rootScope:log', err.message);
          console.log(err.message);
        } else {
          angular.forEach(data.ResourceRecordSets, function(value, key) {
              if (value.Name.slice(0, -1) == domainName) {
                var externalIPAddress = "";
                ExternalIP.then(function(response){
                     externalIPAddress = response.data.ip;
                     $scope.changeIP(domainName, zoneId, externalIPAddress)
                 });
              }
          });
        }
    });
  }
  $scope.changeIP = function(domainName, zoneId, newIPAddress) {
    var options = {
      'accessKeyId': $rootScope.accessKey,
      'secretAccessKey': $rootScope.secretKey
    };

    var route53 = new AWS.Route53(options);
    var params = {
      ChangeBatch: {
        Changes: [
          {
            Action: 'UPSERT',
            ResourceRecordSet: {
              Name: domainName,
              Type: 'A',
              TTL: 300,
              ResourceRecords: [ {
                  Value: newIPAddress
                }
              ]
            }
          }
        ]
      },
      HostedZoneId: zoneId
    };

    route53.changeResourceRecordSets(params, function(err, data) {
      if (err) { 
        $rootScope.$emit('rootScope:log', err.message); 
      }
      else { 
        var logMessage = "Updated domain: " + domainName + " ZoneID: " + zoneId + " with IP Address: " + externalIPAddress;
        $rootScope.$emit('rootScope:log', logMessage);
      }
    });
  }

The only part that trippped me up was that I wasn’t setting the TTL in the changeResourceRecordSets parameters and I was getting an error but found a StackOverflow question that helped to get past the issue.

Step 3: A tool to bind them

Now the fun part: An AngularJS client to call these services. I guess the UI is straight-forward. Basically it just requires the user to enter AWS IAM keys and domains to update.

I didn’t want to deal with the hassle of sending the keys to a remote server and host them securely. Instead I thought it would be simpler just to use browser’s local storage with HTML5. This way the keys never leave the browser.

It also only updates the IP address if it has changed so saves unnecessary API calls.

Also it’s possible to view what’s going on in the event log area.

I guess I can have my closure now and move on!

Resources