Showing posts with label xcode. Show all posts
Showing posts with label xcode. Show all posts

Thursday, July 17, 2014

AppEngine With Patience

My previous blog post was about using Google Cloud Endpoints as REST framework. I was a bit skeptical when I finished the blog but now that I've played with it for a coupled of days I have to say it's truly amazing (and very easy).

One of the first services I usually write when trying out a new REST framework is a service to retrieve the server time.  This is useful in a couple of ways, it lets you exercise the REST framework without getting caught up in a lot of business logic and it let's you know if the server is running quickly and easily.  Here is the code for that service:

package com.xyz.pdq;

import java.util.Calendar;

import com.com.xyz.pdq.Status;
import com.google.api.server.spi.config.Api;
import com.google.api.server.spi.config.ApiMethod;
import com.google.api.server.spi.config.ApiNamespace;

@Api(name="statusAPI",
     version= "v1",
     namespace = @ApiNamespace(ownerDomain = "com.xyz.pdq",
        ownerName = "com.xyz.pdq",
        packagePath=""))
public class ServerStatus 
{
  @ApiMethod(name="serverTime", httpMethod="get")
  public Status serverTime()
  {
    Status status = new Status();
    status.setCurrentTime(Calendar.getInstance().getTime().toString());
    return status;
  }

}

A little bit heavy on annotations I suppose, but they're pretty easy to understand and mostly copy and paste between classes you want to use as services.  The name attribute in the Api annotation is important, and the @ApiMethod is entirely optional, I believe if you don't specify an httpMethod the default is post, however this is pretty much a moot point as you will see if you keep reading.

So that is pretty cool stuff.  Arguably easier or as easy as jersey - however since we're using AppEngine  it's absolutely easier because there are ZERO configuration changes or jars to import.  Did you hear that?  No configuration or extra jars - that is AMAZING!

But wait that's not all!  Once you have your service working the way you like it there is a little maven command that will generate a discovery document for you

 mvn appengine:endpoints_get_discovery_doc

So, what is a discovery document and why is that important?  It is a description of the services contained in that file and it can be used to generate client code to hit those services (or endpoints).

Once you generate that file you can run a command using Google's Library generator to auto generate a whole bunch of code.  You download the source and compile it yourself using XCode, an important note there was one line of code that was wouldn't compile.  It's on line 34 of the FHUtils.m file here is the naughty line:

    NSMutableCharacterSet *setBuilder = [NSCharacterSet characterSetWithRange:NSMakeRange('a', 26)];
and this is the very simple fix:
    NSMutableCharacterSet *setBuilder = [NSMutableCharacterSet characterSetWithRange:NSMakeRange('a', 26)];

Hardly worth noting but if you are in a panic that is how you fix it.  Follow the instructions on the link and you should be generating all kinds of code.  Now there are some pretty specific instructions you need to follow.  There is a section labeled "Adding Required files to your iOS project" step 1g has you doing something I didn't even know was possible!  You can disable arc management on a file by file basis!  Holy smokes - that is a useful trick.

Once you get it all up and running you have probably added over 20 files to your project - it's a lot but keep in mind you didn't have to write it - and since it's code you can jump in and take a look to see how Google thinks things should be done!

Here is a snippet of code that uses the auto generated code that we created above:
    static GTLServiceStatusAPI *service = nil;
    if (!service)
    {
        service = [[GTLServiceStatusAPI alloc] init];
        service.retryEnabled = YES;
    }
    GTLQueryStatusAPI *query = [GTLQueryStatusAPI queryForServerTime];

    [service executeQuery:query completionHandler:^(GTLServiceTicket *ticket, GTLStatusAPIStatus *object, NSError *error){
        if (error != nil)
        {
            NSLog(@"Error getting time: %@", [error localizedDescription]);
        }
        else
        {
            NSString *serverTime = [object currentTime];
            NSLog(@"Server time is %@", serverTime);
        }

    }];

Here is the same code in pseudo code so you can get the theory down:

ServiceObject service = new ServiceObject
ServiceQuery query = new ServiceQuery

[service executeQuery: query handleCallback:^(ServiceTicket ticket, ServiceResponse response, Error error){
  handle response
}] 

That pattern is followed when ever you want to call the server - posting and getting is abstracted away from you, even the URL that you're communicating is hidden away.  All in all an amazing framework.

There is so much magic in serializing the response and deserializing it on the backend, I can't believe how easy it was to get working.  It is sort of like writing EJB's in the old days except this actually works.

There were a couple of gotchas I feel obligated to mention.  
  • Your AppEngine app seems to need to be deployed out on app engine.  This means you can't hit the dev server while you're developing the client code.  There is probably a very clever work around but it wasn't worth the effort for me.
  • Also since the project we created in the previous blog is all maven based I ended up writing a couple of scripts to:
    1. deploy to appengine (if you have 2Factor authentication enabled this is a bit of a hassle) 
    2. generate the code once the discovery file was generated.  This process is well documented I just wrote a script to be lazy.
My next post will hopefully cover using Objectify as a persistence layer and how you can use JUnit tests to test it.  Exciting stuff!

Cheers!

-Aaron




Monday, June 23, 2014

Core Data Fears

Fears in general are base on rumors and a fundamental lack of understanding.  Hopefully by the end of this post all of our fears will be resolved and we can move forward with Core Data with rumors dispelled and a solid understanding of what we are doing.

Core Data is maybe on of the best reasons to do development on OSX or iOS it is an amazingly well done framework.  There is one very scary deterrent to it though; if you change the underlying data model incorrectly you can break your app.  The only way to fix it is to force users to uninstall and reinstall your app, which is a scenario you never want to find yourself in.

There is actually a large amount of very technical (and scary) documentation on how to do this correctly:
https://developer.apple.com/library/ios/documentation/Cocoa/Conceptual/CoreDataVersioning/Articles/Introduction.html

You will definitely need to check out that document to go along with this post, but I'll try to boil it down to keep it simple for us.

Before we get started, I want to stress to you as you begin developing your app with core data, not to take the easy way out.  When you're developing your app and making model changes it is easy to uninstall and reinstall your app when you make data changes, don't do that!
Take the time now to learn the versioning process from the beginning, otherwise when you need to make the data change for an app in production you'll be too scared and unsure of yourself to do it properly.

At a high level this is what you need to do to make changes to your Core Data Model:

  1. Create a new version of your model (see the documentation)
  2. Mark the new model as as active

Once you have gone through and made the data model changes you need to do, you should have the information you need to decide if you can do a lightweight migration or if you will have to use a Migration Manager.

Unfortunately the documentation on when you can and can't use the lightweight migration is not very clear, however as a general rule if you are adding, removing or renaming things (entities or attributes) lightweight migration changes will "probably" work for you.  If you are completely re-working your data model then you will need to to use a migration manager, which may be covered in a future post.

OK, so having said all that let's talk a bit about a simple common scenario.  I am writing a super simple app that collects the user's name and stores it into an entity called TraxUser.   This is what that data model looks like:


Next we want to add another entity so that we can store some info about an iBeacon the user might run across:

So, now we have versioned our data model, made the changes.  Now if you were to be so naive as to run your app, you will get a pretty scary crash that looks like this:
2014-06-23 12:05:54.280 Trax[6122:60b] Unresolved error Error Domain=NSCocoaErrorDomain Code=134100 "The operation couldn’t be completed. (Cocoa error 134100.)" UserInfo=0x14d40530 {metadata={
    NSPersistenceFrameworkVersion = 479;
    NSStoreModelVersionHashes =     {
        TraxUser = <21f8bf1e 14685e4a="" 51457eb2="" 749ddd4d="" 83698e64="" a319d28f="" ceec364d="" db05b3aa="">;
    };
    NSStoreModelVersionHashesVersion = 3;
    NSStoreModelVersionIdentifiers =     (
        ""
    );
    NSStoreType = SQLite;
    NSStoreUUID = "78366830-BF50-4F5A-9142-893EE6C91619";
    "_NSAutoVacuumLevel" = 2;
}, reason=The model used to open the store is incompatible with the one used to create the store}, {
    metadata =     {
        NSPersistenceFrameworkVersion = 479;
        NSStoreModelVersionHashes =         {
            TraxUser = <21f8bf1e 14685e4a="" 51457eb2="" 749ddd4d="" 83698e64="" a319d28f="" ceec364d="" db05b3aa="">;
        };
        NSStoreModelVersionHashesVersion = 3;
        NSStoreModelVersionIdentifiers =         (
            ""
        );
        NSStoreType = SQLite;
        NSStoreUUID = "78366830-BF50-4F5A-9142-893EE6C91619";
        "_NSAutoVacuumLevel" = 2;
    };
    reason = "The model used to open the store is incompatible with the one used to create the store";
}

If/when you get this error it feels like the world has just ended and you go into panic mode big time.  Fear not! There is good news! You can go back to the previous version of your model and make it active and your app will start just again.  

For our change (adding a new entity) we can simply add this code into our app delegate under this method
- (NSPersistentStoreCoordinator *)persistentStoreCoordinator  

by default this method has a large block of comments in it guiding you a bit on what needs to be done.  In all actuality the code change to make the migration happen is very simple.  

The default code that you are given should look like this:
// Returns the persistent store coordinator for the application.
// If the coordinator doesn't already exist, it is created and the application's store added to it.
- (NSPersistentStoreCoordinator *)persistentStoreCoordinator
{
    if (_persistentStoreCoordinator != nil) {
        return _persistentStoreCoordinator;
    }
    
    NSURL *storeURL = [[self applicationDocumentsDirectory] URLByAppendingPathComponent:@"Trax.sqlite"];
    
    NSError *error = nil;
    _persistentStoreCoordinator = [[NSPersistentStoreCoordinator alloc] initWithManagedObjectModel:[self managedObjectModel]];
    
    if (![_persistentStoreCoordinator addPersistentStoreWithType:NSSQLiteStoreType configuration:nil URL:storeURL options:nil error:&error])

All that needs to change for this to work is to add a new dictionary to this method:

    
    NSDictionary *options = [NSDictionary dictionaryWithObjectsAndKeys:[NSNumber numberWithBool:YES],
                             NSMigratePersistentStoresAutomaticallyOption,
                             [NSNumber numberWithBool:YES],
                             NSInferMappingModelAutomaticallyOption, nil];
and change this line:
    if (![_persistentStoreCoordinator addPersistentStoreWithType:NSSQLiteStoreType configuration:nil URL:storeURL options:nil error:&error])
to use the new dictionary that was just created:
    if (![_persistentStoreCoordinator addPersistentStoreWithType:NSSQLiteStoreType configuration:nil URL:storeURL options:options error:&error])

Adding this dictionary in to the options you are telling the core data framework to migrate the data automatically and to infer the changes without help.  Easy enough!

And that is all there is to it!  By versioning the changes you make to your data model and making those two small changes to your AppDelegate you can make small incremental changes to your map very easily.

Good Luck using and versioning Core Data.  Remember to start getting familiar with CoreData early in your development process so when the inevitable time comes to change your data you can do it with confidence.

Happy Coding!

-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