Wednesday, September 17, 2014

Swift Pains

Learning a new programming language can be frustrating. Some more so than others, Python for example is pretty easy, there are a few oddities but it's easy enough.  In contrast Objective-C for me was difficult but now that I "get it" I prefer it over most other languages. Swift seems to be following the Objective-C pattern, apparently I'm still not quite "getting it".

In case you weren't paying attention - or just don't follow iOS development, this year at WWDC(2014) Apple released a new language called Swift. It's supposed to perform faster and handle memory much better; that alone seems compelling enough to test it out, but I have to say it has not been a great experience. To be honest, it's possible I'm just grumpy about learning something new; but it seems to me that there are some oddities about the language that are a bit limiting.

The first oddity I ran into was the lack of multi-dimensional array. If you aren't familiar with the term I'm sure you're familiar with the concept; basically an array is a collection of of objects - you could think of it as a line of soldiers.  To make it multi-dimensional it would be like a troop of soldiers instead of a single line) you have rows and columns (of soldiers). Often times the example used to illustrate a multi-dimensional array is a spreadsheet with rows and columns but you get the idea.

To create an multi-dimensional array most languages use some type of syntax like this:

  • int grid[][] =... 
  • or int[][] grid = ... 
Hmm... not so with Swift from what I can tell there is no native support for multi-dimensional arrays.  You have to create a structure like this (feel free to copy and paste here it's pretty much copy and paste from the Swift manual):

import Foundation

struct Matrix {
    let rows: Int, columns: Int
    var grid:[String]
    init(rows: Int, columns: Int)
    {
        self.rows = rows
        self.columns = columns
        grid = Array(count: rows * columns, repeatedValue:"")
    }
    
    func indexIsValidForRow(row: Int, column: Int) -> Bool
    {
        return row >= 0 && row < rows && column >= 0 && column < columns
    }
    
    subscript(row: Int, column: Int) -> String
    {
        get
        {
            assert (indexIsValidForRow(row, column: column), "Index out of range)")
            return grid[(row * columns) + column]
        }
        set
        {
            assert(indexIsValidForRow(row, column: column), "Index out of range")
            grid[(row * columns) + column] = newValue
        }
    }

}

So now that you have this structure set up here is an example on how to use it:
        var letters: [String] = ["A", "B", "C", "D", "E", "F", "G", "H", "I", "J", "K", "L", "M", "N", "O", "P", "Q", "R", "S", "T", "U", "V", "W", "X", "Y", "Z"]
        var rows = 6
        var columns = 8
        
        //create our Matrix struct

        var matrix = Matrix(rows: rows, columns: columns) 
        for (var row = 0; row < matrix.rows; row++)
        {
            for (var column = 0; column < matrix.columns; column++)
            {
                var rnd = Int(arc4random_uniform(26))
                var randomLetter = letters[rnd]
                // sets the value in a row/column to a random letter
                matrix[row, column] = randomLetter
            }
        }
So you  get a taste of the oddity that is swift - we access our variables in our matrix like this:
        println("First Element: \(matrix[0, 0])")
        println("Last Element:  \(matrix[6, 8])")
Typically in most languages you would access an element like this: matrix[6][8] but since we're not really working with a proper multi-dimensional array we use a comma to separate the subscript call.

So that is different and pretty weird if you don't know about it before hand, even after knowing about it, I still have to scratch my head and wonder what Apple engineer's are thinking on that one.

Next in our list of frustrating things about Swift is the core data integration...

CoreData is a very strange beasty when it comes to Swift.  No one has said this about Swift but from what I have gathered, Swift is a different language the compiler processes to generate binary compatible code with Objective-C.   Basically two languages that do the same thing.  I believe that Apple has been able to take their existing frameworks and generate swift code off of them and regenerated the mountains of documentation that they have written over the years.

You can have a project that uses both languages in it, you have to create a bridge file for it to work but it's possible and probably will be very common to do this in the future.   The tricky part is that if you  need to use certain frameworks (and CoreData is one of them) you have to make your Swift code available to the land of Objective-C, even if you aren't coding Objective-C.

I've gone a couple of rounds with CoreData and swift and have decided the best thing to do (if you're in a pure swift code base) is to edit the generated Swift NSManagedObject classes.  It is always a bad idea to do this but we just need to tweak them a tiny bit and add the "@objc" attribute to the swift files that are generated.  Editing code that is generated is always a problem but for now this is not a huge problem.  First of all thanks to Christer from stack overflow who answered a similar question I was working on that lead me to the @objc solution.

Here is what the Swift documentation says about @objc:
“Apply this attribute to any declaration that can be represented in Objective-C—for example, non-nested classes, protocols, properties and methods (including getters and setters) of classes and protocols, initializers, deinitializers, and subscripts. The objc attribute tells the compiler that a declaration is available to use in Objective-C code.”
...
“The objc attribute optionally accepts a single attribute argument, which consists of an identifier. Use this attribute when you want to expose a different name to Objective-C for the entity the objc attribute applies to. You can use this argument to name classes, protocols, methods, getters, setters, and initializers. ...”

Excerpt From: Apple Inc. “The Swift Programming Language.” iBooks. https://itun.es/us/jEUH0.l
What this all means is that when you generate your Swift Managed Object classes open them up and
change this:
import Foundation 
import CoreData 

class Patient: NSManagedObject 
{ 
    @NSManaged var firstName: String 
    @NSManaged var lastName: String 
    @NSManaged var games: NSSet 
} 


to this:
import Foundation 
import CoreData 

@objc(Patient) //add this line to make everything work
class Patient: NSManagedObject 
{ 
    @NSManaged var firstName: String 
    @NSManaged var lastName: String 
    @NSManaged var games: NSSet 
}

So with that bit of knowledge in had we can start getting productive again(just to be clear without the @objc attribute in our generated class files the following code will not work).
var newPatient:Patient =      NSEntityDescription.insertNewObjectForEntityForName("Patient",               inManagedObjectContext: context) as Patient 
newPatient.firstName = firstNameTextField.text 
newPatient.lastName  = lastNameTextField.text 
context.save(nil)
Querying the data is simple as well:                var req = NSFetchRequest(entityName: "Patient")
        var sortDescriptor = NSSortDescriptor(key: "lastName", ascending: true)
        req.sortDescriptors = [sortDescriptor]
        self.patients = context.executeFetchRequest(req, error: nil)

Maybe it will get better, as I get more comfortable but so far Swift is a poorly named language chocked full of gotchas, and facepalms.

Good luck if you diving into iOS development with Swift.

-Aaron

No comments: