Showing posts with label Objective-C. Show all posts
Showing posts with label Objective-C. Show all posts

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

Monday, January 09, 2012

Making Xcode less difficult

Writing Objective-C code in Xcode can be very difficult.  Especially if you're coming from a different language like Java; not only do you have to learn the new syntax of Objective-C you also have to learn the new development environment and remember what it's like to code in a non-managed environment (i.e. not having the JVM hold your hand when things go awry).

I started working with Xcode and Objective-C about 4 years ago and after many frustrating days (and nights) I found out some great information in an iTunes University video called Advanced IOS Development put out by Madison College.  

It's a very long video, about 3 hours and I'm only about half way through it but I have found some great information in it already that I wanted to share.

Problem: you're running your app on the emulator and it seeming crashes randomly.  You don't get any information about why it crashed the app just quits.

Solution: in Xcode you can set global break points that can stop your app when it crashes, the debugger puts you right on the line that causes the problem!  
Here is how you do it:
  • In Xcode open the Breakpoint Navigator
  • In the bottom left corner of the view is a plus sign click on it to add a new breakpoint.  You will be asked to add an "Exception Breakpoint" or a "Symbolic Breakpoint" choose symbolic.
  • After selecting "Symbolic Breakpoint" a popover window will open fill it in as follows:
    • Symbol: objc_exception_throw
    • Module: libobjc.A.dylib

  • Follow those same steps again to add a new symbolic breakpoint as follows:
    • Symbol: -[NSException raise]
    • Module: CoreFoundation


  • To make the breakpoints global right click (command click - double finger tab - whatever) the break points to move them to the User context


And there you have next time you make a mistake Xcode will stop your app where it breaks - assuming it breaks in your code.  If you tell the frameworks to do something that doesn't work (such as loading a ViewController that won't load for some reason) you're back to the drawing board.

Hope that helps!

-Aaron




Monday, May 23, 2011

Universal iOs Applications

I've spent this afternoon looking at building a universal application for an iPhone application.

First of all I would like to go on the record that I think in general this is a bad software engineering practice - at least in theory .

Having your code check to see if what kind of device it is running and then execute a different set of functions based on the answer feels very hackish to me - at least from a conceptual level.

However, from a user perspective it is sweet software magic that is incredibly awesome - especially when dealing with "markets" similar to what Apple and Google have. For example you only can buy an application once and it can be run on the iPhone or iPad. It's like getting two great applications for the price of one.

I finally decided to buckle down and work through this and as you may expect it is much easier than I expected it to be. I started by:

  • creating a brand new iPhone application
  • then clicked on Application in the top left pane
  • and selected "Universal" in the Deployment Target drop down

Here is a screen shot:

Once xCode runs through it's magic you get a new folder called iPad that contains a new "Window" called "MainWindow-iPad.xib".

Now if you run the application you will be able to run in full iPad mode but the layout will be used by both the iPhone and iPad emulator and that is not what I intended to happen... To get this to change first of all you need another ViewController for the iPad to run so go ahead and create one of those using:
File -> New -> New File... c'mon you know the rest...

So... how do we get our new controller instantiated? Probably have to link into the app delegate, check the InterfaceIdiom to find out what kind of device we're running right?

Nope - not at all. You can handle all of this magic in the Interface Designer. No code change is actually needed! Here is how to do it:
Click on the new "MainWindow-iPad.xib" that xCode created for us
Select the View object in the hierarchy
and change the class under the "Identity Inspector"




Now if you run it you will still get the old layout (and probably crash). What you have to do is tell interface builder to load a different nib file:


Now you can run either emulator and you will get the correct view controller magically instantiated and the correct nib will be laoded.

That doesn't feel very hackish at all now does it?

-A





Tuesday, December 01, 2009

Testing and xCode

Java developers are spoiled.

I was a late comer to the testing band wagon. I'm not a test nazi by any stretch but there is a certain amount of confidence that testing gives you.

I miss that when I'm coding in Objective-C.

I was so happy today when I found out today that xCode has built in support for unit testing. But it sucks horribly. Granted much of my problem may be that I have not fully grokked the underpinnings of this environment. But I have spent all day and most of the night trying to figure it out and this is the extent of what I have figured out.

Unit tests that exercise simple parts of the application (non-framework/Cocoa stuff) can be tested after a build is performed. However:

  • Debugging is not an option
  • Capturing output from sysout is not an option
  • Decent reports don't seem to be available
  • Decent error reporting doesn't seem to be available

It's hard to believe that unit testing is so far behind on this platform. Java developers don't know how good they have it.