Pages Menu
Categories Menu

Posted by on Jun 26, 2009 in Code |

Setting up automated testing in Flex SDK 3.3.0

Automated testing tools like FlexMonkey, RIATest, and QTP in Flex require you to add a few .swc files from Adobe to the compiler arguments in order to run. This is easy enough to set up in the project settings:

-locale en_US -include-libraries “${flexlib}/libs/automation.swc” “${flexlib}/libs/automation_agent.swc” “${flexlib}/libs/automation_dmv.swc”

But if you try to do this in version 3.3.0 of the Flex SDK, the first thing that you’ll notice is that these three files are not included in the 3.3.0 SDK distribution. Adobe tech support has reportedly been telling developers to copy these files from the 3.2.0 release of the SDK. These files are located at sdks/3.2.0/frameworks/libs/. You probably also want to copy automation_dmv.swc and qtp.swc, as well as the 2.0.1.automation_swcs directory. However, once you’ve copied these files over to the corresponding directory in the 3.3.0 SDK, you will get new errors about being unable to locate the resource bundle for the selected locale:

Unable to locate resource bundle automation.swc for local en_US
Unable to locate resource bundle automation_agent.swc for local en_US
The trick is that you also have to copy over automation_agent_rb.swc and automation_rb.swc from 3.2.0. These files are located in sdks/3.2.0/frameworks/locale/[your locale]/, where [your locale] is something along the lines of “en_US” or “ja_JP”.

This issue is mentioned over in the FlexMonkey bug tracker, though it’s not a bug with FlexMonkey.

Facebooktwitterredditpinterestlinkedinmail Read More

Posted by on Apr 10, 2009 in Code, The Cloud |

Force.com Flex Toolkit AIRConnection updates

We’ve been working more and more with Adobe AIR and Salesforce.com recently, and as such we’ve been putting the AIRConnection class through its paces, fixing bugs and adding functionality along the way. Here are a few of the modifications we’ve made, and why:

SQL Reserved Words

One of the first things that we noticed was that we needed to amend the the SOQL_To_SQL() method in order to make sure SQLite queries didn’t bomb when querying Case objects. “Case”, unfortunately, is a reserved word in SQL, but not in SOQL, so the query doesn’t translate perfectly. An easy fix for this is to always add back-ticks around the table name in the SQL statement, which is accomplished easily enough with a regular expression:

        private function SOQL_To_SQL(soql:String):String {
//TG: We need to put back-ticks around the table names because

            //Case is an SQL reserved word, which causes bad things.
//We should probably eventually back-tick column names as well,
//but this is good enough until somebody names a
//column with a reserved word, I guess.
var sqlRegEx:RegExp = /FROM\s+(\S*)/gi;
var sql:String = soql.replace( sqlRegEx, “FROM `$1`”);

As noted, it would be a good idea to put back-ticks around the column names too, but we haven’t run into any reserved words in column names, and well, “if it ain’t broke…”

Database Encryption

This is a pretty easy modification since the SQLConnection class allows you to encrypt the database by simply providing an encryption key to the open() method. We basically just added an encryptionKey:ByteArray parameter to the AIRConnection login() method, since this is where the SQLite database is first created or opened (if it already exists):

        public override function login(lreq:LoginRequest,encryptionKey:ByteArray=null):void {
This allows the parent application to create and provide an optional encryption key using whatever method is preferred by the developer, and the default of null ensures that we don’t break any existing applications using this library. Since this is an override function (AIRConnection extends Connection), you’ll want to add the same parameter to the login() method of the Connection class as well. That goes for any modification to an override method in the AIRConnection class. From there, we just have to pass the encryptionKey along to the openSQLiteDatabase method:

 

              if (openDatabases[lreq.username] == undefined)
{
if(encryptionKey==null)
{
openSQLiteDatabase(lreq.username);
}
else
{
//encrypt the db (or, if it exists, open it with the provided key)
openSQLiteDatabase(lreq.username,false,encryptionKey);
}
}

Then, in the openSQLiteDatabase() method, we have to include the encryptionKey in the SQLConnection open() method:


                syncSQL.open(file, SQLMode.CREATE, false, 1024, encryptionKey);
From this point on, to open the SQLite database, you’ll need to use the encryption key, so it’s probably a good idea to either save it somewhere, or make sure you can reliably regenerate it, based on some user-provided information, like a username and password. If you’re looking for an SQLite DB admin tool that will allow you to provide an encryption key, I recommend Lita. It’s an AIR app, and all of the encryption stuff works.

openSQLiteConnection Mods

The openSQLiteConnection method has a few random bugs that cause things not to work properly with very large data sets. For instance, the answer to this question in the stock code…

            /* do we need this?
if (openDatabases[dbName])
{
sql.close();
openDatabases[dbName] = false;
}
*/

…is yes:

            /* do we need this?*/
//TG: Yes we do. We want to close and re-open the database connection after we create the schema
//    because the schema is created with the async connection, and after it is created, the sync
//    connection won’t work anymore because the schema will have changed.  RefreshDatabase is the
//    public method that does this.
if (openDatabases[dbName])
{
sql.close();
openDatabases[dbName] = false;
}

Additionally, in the stock code opens up the asynchronous connection to the database before the synchronous connection, which can cause a crash because we can’t guarantee that the async open will be completed by the time the sync open is called, so the database may not exist yet. Reordering the two open statements is a simple fix:

            //TG: Note, we need to do the sync open before the async open because we can’t guarantee
//    that the async will be completed by the time the sync open is called (i.e. the database
//    won’t exist. This seems to mainly be a problem when using encryption. Alterately, we could
//    use the openAsync responder, but re-ordering the two statements works fine.

syncSQL = new SQLConnection();
if(encryptionKey != null)
syncSQL.open(file, SQLMode.CREATE, false, 1024, encryptionKey);
else
syncSQL.open(file);

//open a second connection for asynchronous calls
if(encryptionKey != null)
sql.openAsync(file, SQLMode.CREATE, null, false, 1024, encryptionKey);
else
sql.openAsync(file);

 

SQLite Serialization on OSX

One major problem we ran into recently is that there is a bug with AIR 1.5 on OSX that causes an application to crash when you try to deserialize an object (as opposed to a primitive data type) from an SQLite database. Serializing the object works fine, but trying to run a SELECT statement on a row that has a serialized object in it will cause SQLite to bomb. This means that  some of the fields in the _describe_sobject_cache table will crash an application, namely “childRelationships”, “fields”, and “recordTypeInfos”. Until the AIR bug is fixed, a workaround is to do the serialization yourself, and store the serialized object as a blob. First, you’ll want to modify the CREATE statement in openSQLiteDatabase as follows:

 

            createDescribeSObjectCacheTable.text = “create table if not exists ” + DESCRIBE_SOBJECT_TABLE +
” (id integer primary key autoincrement, activateable boolean, childRelationships text, childRelationshipsBytes blob, createable boolean, custom boolean, ” +
” deletable boolean, fields text, fieldsBytes blob, keyPrefix text, layoutable boolean, label text, labelPlural text, mergeable boolean, name text, queryable boolean, ” +
” recordTypeInfos text, recordTypeInfosBytes blob, replicateable boolean, retrieveable boolean, searchable boolean, undeletable boolean, updateable boolean, urlDetail text, ” +
” urlEdit text, urlNew text)”;

 

Notice the addition of these fields: “childRelationshipsBytes”, “fieldsBytes”, and “recordTypeInfosBytes”. We’ll then want to add a method to serialize any given object:

        //serialize an object…
//this is necessary because with the OSX version of AIR 1.5, apps will crash when you try to deserialize an object
//from the SQLite database.
public function serializeObject(someObject:Object):ByteArray
{
var objectBytes:ByteArray = new ByteArray();
objectBytes.writeObject(someObject);
return objectBytes;
}

And then we need to use our new serializeObject() method in cacheDescribeSObjectResult(). First we need to add those fields to our INSERT and UPDATE statements:

              if (getCachedDescribeSObjectResultByType(d.name) != null) {
// update
dbStatement.text = “update ” + DESCRIBE_SOBJECT_TABLE + ” set activateable = :activateable, childRelationshipsBytes = :childRelationshipsBytes, ” +
“createable = :createable, custom = :custom, deletable = :deletable, fieldsBytes = :fieldsBytes, keyPrefix = :keyPrefix, layoutable = :layoutable, ” +
“label = :label, labelPlural = :labelPlural, mergeable = :mergeable, queryable = :queryable, recordTypeInfosBytes = :recordTypeInfosBytes, ” +
“replicateable = :replicateable, retrieveable = :retrieveable, searchable = :searchable, undeletable = :undeletable, ” +
“updateable = :updateable, urlDetail = :urlDetail, urlEdit = :urlEdit, urlNew = :urlNew  where name = :name”;
} else {
// insert
dbStatement.text = “insert into ” + DESCRIBE_SOBJECT_TABLE + ” (activateable, childRelationshipsBytes, createable, custom, deletable, fieldsBytes, ” +
“keyPrefix, layoutable, label, labelPlural, mergeable, name, queryable, recordTypeInfosBytes, replicateable, retrieveable, ” +
“searchable, undeletable, updateable, urlDetail, urlEdit, urlNew) values (:activateable, :childRelationshipsBytes, :createable, :custom, :deletable, :fieldsBytes, ” +
“:keyPrefix, :layoutable, :label, :labelPlural, :mergeable, :name, :queryable, :recordTypeInfosBytes, :replicateable, :retrieveable, ” +
“:searchable, :undeletable, :updateable, :urlDetail, :urlEdit, :urlNew)”;
}

 

And then instead of this line:

              dbStatement.parameters[“:childRelationships”] = d.childRelationships;
We’ll want to do this:

              dbStatement.parameters[“:childRelationshipsBytes”] = serializeObject(d.childRelationships);            
Same goes for “fields” and “recordTypeInfos”. Ultimately, then, we’ll have to deserialize these objects ourselves too, so in getCachedDescribeSObjectResultByType(), we need to add a few lines to do that:

        private function getCachedDescribeSObjectResultByType(type:String):DescribeSObjectResult {
var dbStatement:SQLStatementExt = new SQLStatementExt(syncSQL);

dbStatement.text = “select * from ” + DESCRIBE_SOBJECT_TABLE + ” where name = :name”;
logger.debug(dbStatement.text);
dbStatement.parameters[“:name”] = type;
dbStatement.itemClass = DescribeSObjectResult;
dbStatement.execute();

var result:SQLResult = dbStatement.getResult();

if ((result != null) && (result.data != null) && (result.data.length > 0)) {
var dsr:DescribeSObjectResult = result.data[0];

                  //TG deserialize:
dsr.fields = dsr.fieldsBytes.readObject();
dsr.childRelationships = dsr.childRelationshipsBytes.readObject();
dsr.recordTypeInfos = dsr.recordTypeInfosBytes.readObject();

if (dsr.fieldMap == null) {
dsr.fieldMap = {};
for each (var f:Object in dsr.fields)
{
dsr.fieldMap[f.name] = f;
}
}
return dsr;
}

return null;
}

 

And then, since the dbStatement result is cast to a type of DescribeSObjectResult, we need to add these three new fields to that class:

     public dynamic class DescribeSObjectResult
{
public var activateable:Boolean;
public var childRelationships:Array;
        public var childRelationshipsBytes:ByteArray;
public var createable:Boolean;
public var custom:Boolean;
public var deletable:Boolean;
public var fields:Array;
        public var fieldsBytes:ByteArray;
public var keyPrefix:String;
public var layoutable:Boolean;
public var label:String;
public var labelPlural:String;
public var mergeable:Boolean;
public var name:String;
public var queryable:Boolean;
public var recordTypeInfos:Array;
        public var recordTypeInfosBytes:ByteArray;

Online Caching

One of the biggest and most useful changes we made to AIRConnection was to make sure queries are reflected in the local database when remote SOQL insert and update statements are made ONline. In the stock AIRConnection class, local inserts and updates are only made when made offline, so if you are querying the local database directly using the public asynchronous or synchronous connection accessor methods, your queries won’t reflect the previous updates you’ve made remotely. This is solved easily for updates by simply doing the update both to SFDC and to the local SQLite database:

       override public function update(sobjects:Array, callback:IResponder):void
{
logger.debug(‘updating ‘ + ObjectUtil.toString(sobjects));
var apex:Connection=super;callback

if (super.loginResult && connected)
{
//online
super.update(sobjects,callback);

                //TG: cache the data locally too
//I thought of adding a switch to update so data isn’t always cached locally, but I can’t think of any reason to do so…
//may as well keep the local data in sync with the remote data.
if ( !syncSQL) { logger.debug(“no open database to store into”); return }
if (sobjects.length > 0)
{
for( var i:int=0; i<sobjects.length; i++ )
{
syncSQL.begin();
var dbCacheStatement:SQLStatementExt = new SQLStatementExt(syncSQL, sobjects[i]);
// create the update statement
dbCacheStatement.text = buildUpdateTableStatement(sobjects[i]);
// write record to table ( assumes table exists)
logger.debug(dbCacheStatement.text);
dbCacheStatement.execute();

syncSQL.commit();

}
}
}
else
{
//offline
// we are offline or not yet logged in
if (!connected) { logger.debug( ‘create made while offline’); }
else if ( ! super.loginResult ) { logger.debug(‘user not yet logged in’); }

if ( !syncSQL) { logger.debug(“no open database to store into”); return }
if (sobjects.length > 0) {
for( var j:int=0; j<sobjects.length; j++ ) {
syncSQL.begin();
var dbStatement:SQLStatementExt = new SQLStatementExt(syncSQL, sobjects[j]);
// create the update statement
dbStatement.text = buildUpdateTableStatement(sobjects[j]);
// write record to table ( assumes table exists)
logger.debug(dbStatement.text);
dbStatement.execute();

// do not add this to the OFFLINE_UPDATES_TABLE if this is a new record
if ((sobjects[j].Id as String).indexOf(NEW_RECORD_TEMP_ID_PREFIX) != 0) {
var insertOfflineUpdate:SQLStatementExt = new SQLStatementExt(syncSQL);
insertOfflineUpdate.text = “insert into ” + OFFLINE_UPDATES_TABLE + ” (table_name, record_id) values (:table_name, :record_id)”;
insertOfflineUpdate.parameters[“:table_name”] = sobjects[j].type;
insertOfflineUpdate.parameters[“:record_id”] = sobjects[j].Id;
logger.debug(insertOfflineUpdate.text);
insertOfflineUpdate.execute();
}

syncSQL.commit();

dispatchEvent(new Event(“updateSyncingOfflineUpdates”));
}
}

// todo: return something more meaningful, but what?
(callback as AsyncResponder).resultHandler({message: OFFLINE_UPDATE_SUCCEEDED });
}
}

Caching of inserts takes a bit more effort because if we’re online, an Id is automatically generated by SFDC, along with any auto-populated fields. So, an API call of create has to be followed by a query in order to make sure the local data is updated properly. This involves hijacking the callback passed in by the parent application:

   //TG: queryAfterCreate is used to automatically query the result from SFDC after the Create operation has
//    completed. This is useful because not only do we pull down the Id of the newly created record, we also
//    pull down any auto-filled fields.
public override function create( sobjects:Array, callback:IResponder, queryAfterCreate:Boolean=false):void {
var apex:Connection=super;

if (super.loginResult && connected)
{
//TG: do we want to pull down the full record from SFDC after we create it? Default is false because that’s how it comes from SFDC
if(!queryAfterCreate)
{
//just do what the Flex lib normally does…
super.create(sobjects,callback);
}
else
{
//TG: hijack the callback to grab the ID from SFDC, so we cache this record locally
super.create(sobjects, new AsyncResponder(
function(result:Object):void
{
for(var i:Number=0;i<result.length;i++)
{
if(result[i].success)
{
//add the object type to the result object so the calling application knows what type of object to refresh
result[i][‘type’]=sobjects[i].type;

trace(“cache this create result”);
//get the ID returned by SFDC, and create the local record
sobjects[i].Id=result[i].id;

//first, we need to get a list of fields to query from the _describe_sobject_cache table
var queryStatement:SQLStatement = new SQLStatement();
queryStatement.sqlConnection = syncSQL;
queryStatement.text = “SELECT fieldsBytes FROM _describe_sobject_cache WHERE name = ‘”+sobjects[i].type+”‘”;
queryStatement.execute();

var fieldsResult:Array = queryStatement.getResult().data;
if(fieldsResult.length > 0)
{
var fields:Object = fieldsResult[0].fieldsBytes.readObject();

//build a list of fields to select from SFDC for this object
var soqlFieldList:String = “”;
var soqlFieldList2:String = “”;
for(var aField:String in fields)
{
//is query length going to be more than 10000?
if((“SELECT Id,”+soqlFieldList+aField+”,”+” FROM “+sobjects[i].type+” WHERE Id = ‘”+result[i].id+”‘”).length<10000)
{
if(aField.toLowerCase() != “id”)
soqlFieldList+=aField + “,”;
}
else
{
//query the rest…
if((“SELECT Id,”+soqlFieldList2+aField+”,”+” FROM “+sobjects[i].type+” WHERE Id = ‘”+result[i].id+”‘”).length<10000)
{
if(aField.toLowerCase() != “id”)
soqlFieldList2+=aField + “,”;
}
else
{
//TODO: make this recursive, so it’s not limited to two queries…
break;
}
}
}
//remove the last comma
soqlFieldList = soqlFieldList.substr(0,soqlFieldList.length-1);

//assemble the soql query string
var soqlQueryStatement:String = “SELECT Id, “+soqlFieldList+” FROM “+sobjects[i].type+” WHERE Id = ‘”+result[i].id+”‘”;

var soqlQueryStatement2:String;
if(soqlFieldList2.length > 0)
{
soqlFieldList2 = soqlFieldList2.substr(0,soqlFieldList2.length-1);
soqlQueryStatement2 = “SELECT Id, “+soqlFieldList2+” FROM “+sobjects[i].type+” WHERE Id = ‘”+result[i].id+”‘”;
}

//query SFDC for the complete record and then trigger the callback the user passed in
query(soqlQueryStatement,new AsyncResponder(
function(queryResult:Object):void
{
//any more to query?
if(soqlFieldList2.length == 0)
{
// call back the original method passed in
callback.result(result);
}
else
{
//more to do…
query(soqlQueryStatement2,new AsyncResponder(
function(queryResult2:Object):void
{
callback.result(result);
},
function(queryResult2:Object):void
{
//the create worked okay, but the query failed, so the local data will be out of sync…
//maybe not the biggest deal in the world, so just return the create result
trace(“Create Query Error: “+queryResult2.faultstring);
callback.result(result);
}
),false);
}
},
function(queryResult:Object):void
{
//the create worked okay, but the query failed, so the local data will be out of sync…
//maybe not the biggest deal in the world, so just return the create result
trace(“Create Query Error: “+queryResult.faultstring);
callback.result(result);
}
),false);

//working insert code, but it’s being replaced with the query above
/*syncSQL.begin();

var dbStatement:SQLStatementExt = new SQLStatementExt(syncSQL, sobjects[i]);

// create the insert statement
dbStatement.text = buildInsertTableStatement(sobjects[i]);

// write record to table ( assumes table exists)
logger.debug(dbStatement.text);

dbStatement.execute();
syncSQL.commit();*/
}
else
{
//no fields to query from SFDC…dunno what happened, so just finish up and act like nothing happened.
trace(“no fields to query from SFDC”);
callback.result(result);
}
}
else
{
callback.fault(result);
}
}
//callback.result(result);         // call back the original method passed in
},
function (info:Object) :void {
callback.fault(info);     // incase this was passed also
}
));
}
}
else
{
// we are offline or not yet logged in
if (!connected) { logger.debug( ‘create made while offline’); }
else if ( ! super.loginResult ) { logger.debug(‘user not yet logged in’); }

if ( !syncSQL) { logger.debug(“no open database to store into”); return }
if (sobjects.length > 0) {
if (tableExists(sobjects[0].type)) {

for( var j:int=0; j<sobjects.length; j++ ) {
// toss objects in
sobjects[j].Id = NEW_RECORD_TEMP_ID_PREFIX + UIDUtil.createUID();   // must be unique to the db

syncSQL.begin();
var dbStatement:SQLStatementExt = new SQLStatementExt(syncSQL, sobjects[j]);
// create the insert statement
dbStatement.text = buildInsertTableStatement(sobjects[j]);
// write record to table ( assumes table exists)
logger.debug(dbStatement.text);
//trace(dbStatement.text);
dbStatement.execute();

var insertOfflineCreate:SQLStatementExt = new SQLStatementExt(syncSQL);
insertOfflineCreate.text = “insert into ” + OFFLINE_CREATES_TABLE + ” (table_name, record_id) values (:table_name, :record_id)”;
insertOfflineCreate.parameters[“:table_name”] = sobjects[j].type;
insertOfflineCreate.parameters[“:record_id”] = sobjects[j].Id;

logger.debug(insertOfflineCreate.text);
insertOfflineCreate.execute();
syncSQL.commit();

dispatchEvent(new Event(“updateSyncingOfflineCreates”));
}
}
}

// todo: return something more meaningful, but what?
(callback as AsyncResponder).resultHandler({message: OFFLINE_CREATE_SUCCEEDED});
}
}      

Anyway, these are just some handy updates that we’ve made recently. Many of them have been made in a bit of a hurry, so they could use some massaging, but hopefully they help somebody out.

Facebooktwitterredditpinterestlinkedinmail Read More

Posted by on Feb 9, 2009 in Code, Mobile |

Clang! Powerful Memory Profiling for the iPhone

 

I’ll start by saying that Clang is a must-have tool for every iPhone developer. It’s easy to use, and it does a fantastic job of profiling the memory usage of your apps.

The iPhone is a powerful hand-held device, but the memory constraints are tight enough that they can cause serious problems with application performance. With 128 MB of memory on board, other applications running in the background, and a virtual memory model that does not include swap space, it’s easy to run out of memory and crash an app. Even though your iPhone has 8GB or 16GB of Flash storage, memory that your application allocates will not be swapped out to the disk to free up system memory. Since over-releasing objects will crash an application in difficult to understand ways, it can be tempting to be cautious about releasing objects. However, the system monitors memory usage, and shuts down any application that takes up too much memory. This is the cause of a great many application crashes on the iPhone. You can use around 30 MB safely for a sustained period of time. Get closer to 40 or 45 MB, and the system will very likely shut your app down with an exit status of 101.

In order to make sure you’re not leaking memory, it’s important to use memory profiling tools to catch leaks and inefficiencies in your code. The iPhone SDK ships with a handful of useful profiling applications in a package called “Insruments”. These programs allow you to watch memory and processor usage while your app is running on your iPhone. Some of the Instruments that are especially useful are Leaks, Object Allocations, and Memory Monitor. These are great tools, but they have some serious limitations. For instance, while Leaks and Object Allocations are fantastic for catching memory leaks, neither one will alert you to a leak of an image object, which seems kind of important. Who cares if you’ve leaked a 48 byte NSString, when you just leaked a 3MB UIImage? Memory Monitor can be useful in this regard, because it lets you monitor total memory usage of all apps running on the device, even the various built-in subsystems like SpringBoard, MobileSafari, and a handful of various daemons. However, it still makes finding an elusive memory leak difficult.

Enter Clang, a static analyzer that is a tremendous asset to any iPhone developer. To use Clang, you build (but not run) your app for the simulator in Debug mode, and then run a Clang analysis on the binary. It will take 15 to 20 seconds to run, at the end of which it generates an HTML file that lists all of the problems that it has found in your code, complete with line numbers and comment blocks explaining the problem. The first time you run it can be pretty shocking. Here are some example screen-shots from a sample run on an XCode iPhone SDK project. First you’ll see a summary box showing the various types of issues that were found in your code:

 

As you can see, it’s found a few leaks, and a missing super dealloc method call. If we drill into one of these, we can see some extremely detailed information about the problem:

 

Notice that your actual code is shown, with the problem area highlighted, and comment boxes added in to describe the issue. It looks like we’ve forgotten to release the ns object here. This makes finding and resolving memory leaks much easier than trying to use Instruments to profile an app in real time. Clang will also catch leaked images, which is a huge benefit. Here’s another example of an issue found by Clang:

 

As you can see, this method returns an object with a retain count of 1, but the method name doesn’t start with “new”, “copy”, or “alloc”, and thus violates Objective-C naming conventions. This could be fixed by changing the name of the method to “newSomeMethod”, which would likely cause an leak to be flagged in the calling method, or you could autorelease the object on return, and retain it in the caller.

Where to get Clang?

You can download Clang here:

http://clang.llvm.org/StaticAnalysis.html

Using Clang

Clang is run from the command-line, so you’ll have to run it from the Terminal. After installing it, you will also probably want to add Clang to your system path. To do so, in the terminal, create a file named .bash_profile (assuming you’re using the default Bash shell), and add this line:

export PATH=/Developer/Applications/Clang:$PATH

I’ve installed Clang in /Developer/Applications/Clang. You would obviously change this to be your own install location.

To run Clang, you’ll have to restart your terminal session. You then want to build the app in XCode with the Simulator/Debug profile, and navigate to your project directory in the Terminal. Once there, you run these two commands:

rm -rf /tmp/scan-build*
rm -rf build/;scan-build –view xcodebuild

You’ll see a lot of text scroll by pretty rapidly, and when it’s finished, your browser will open up an analysis log on port 8181 (by default). Sometimes the Clang server fails to start. I don’t know why. It seems to happen to me about 25% of the time. If this happens, just repeat the process. It’ll probably work next time.

Facebooktwitterredditpinterestlinkedinmail Read More

Posted by on Jan 28, 2009 in Code, Mobile |

iPhone Programming: Adding a Contact to the iPhone Address Book

 

Adding a contact to the iPhone’s address book isn’t horribly complicated, but it’s not the most straightforward process in the world, either, because the documentation leaves a bit to be desired. There is an Address Book Programming Guide published by Apple, but at 28 pages, it feels a bit bloated when you’re just trying to quickly figure out a simple process like this, and somewhat ironically, the “Creating a New Person” section is less than a page, and doesn’t go into much detail. The XCode documentation is helpful, but it still takes some effort to put all the pieces together, so this is basically just a walk-through of the process of creating a new contact, and adding some common fields to it.

To create a new record, we start out by creating a CFErrorRef variable that will hold any errors that get generated throughout the rest of the process. In my experience, most errors here tend to generate exceptions anyway, but there’s an error parameter, so we may as well use it. Anyway, here’s the line:

        CFErrorRef error = NULL; 

We then create our reference to the iPhone Address Book with a call to ABAddressBookCreate():

        ABAddressBookRef iPhoneAddressBook = ABAddressBookCreate();

And then we create a new person record:

        ABRecordRef newPerson = ABPersonCreate();
At this point, we haven’t saved anything to the address book yet, but we can start adding data to the person record. To do this, we use ABRecordSetValue, but some fields need to be formatted differently from others. For some, like first name and last name, we can just pass in a string:

        ABRecordSetValue(newPerson, kABPersonFirstNameProperty, @”John”, &error);
ABRecordSetValue(newPerson, kABPersonLastNameProperty, @”Doe”, &error);

kABPersonFirstNameProperty and kABPersonLastNameProperty are constants defined by Apple that specify which fields you’re saving. They’re listed in the XCode documentation under Personal Information Properties in the ABPerson Reference document. We can also set some other fields in this manner, such as company and title:

        ABRecordSetValue(newPerson, kABPersonOrganizationProperty, @”Model Metrics”, &error);
ABRecordSetValue(newPerson, kABPersonJobTitleProperty, @”Senior Slacker”, &error);

Where it gets a bit trickier is when we want to set our phone, email, or address properties, because these fields use ABMutableMultiValueRef rather than strings to store the data, and the specific data types of the values vary a bit depending on which one we’re talking about. For phone, we would do something like this:
        ABMutableMultiValueRef multiPhone = ABMultiValueCreateMutable(kABMultiStringPropertyType);
ABMultiValueAddValueAndLabel(multiPhone, @”1-555-555-5555″, kABPersonPhoneMainLabel, NULL);
ABMultiValueAddValueAndLabel(multiPhone, @”1-123-456-7890″, kABPersonPhoneMobileLabel, NULL);
ABMultiValueAddValueAndLabel(multiPhone, @”1-987-654-3210″, kABOtherLabel, NULL);
ABRecordSetValue(newPerson, kABPersonPhoneProperty, multiPhone,nil);
CFRelease(multiPhone);

The first two phone types (kABPersonPhoneMainLabel and kABPersonPhoneMobileLabel) are listed as Phone Number Properties in the ABPerson Reference, along with kABPersonPhoneHomeFAXLabel, kABPersonPhoneWorkFAXLabel, and kABPersonPhonePagerLabel. Despite the fact that the two fax numbers and the pager number seem fairly useless (you can’t send a fax from your phone, and who has a pager anymore?) but there’s nothing listed there for Other, or Work Phone or anything like that. That’s where the Generic Property labels come into play:

      kABWorkLabel;
kABHomeLabel;
kABOtherLabel;

Those will file the phone numbers as Work, Home, and Other, respectively. After adding the values to the ABMutableMultiValueRef, we need to call ABRecordSetValue, only this time instead of passing a string in for the third parameter, we pass in multiPhone. Then be sure to free up the memory with CFRelease.

Adding email addresses to the record is pretty similar to adding phone numbers, where we create an ABMutableMultiValueRef of strings:

        ABMutableMultiValueRef multiEmail = ABMultiValueCreateMutable(kABMultiStringPropertyType);
ABMultiValueAddValueAndLabel(multiEmail, @”johndoe@modelmetrics.com”, kABWorkLabel, NULL);
ABRecordSetValue(newPerson, kABPersonEmailProperty, multiEmail, &error);
CFRelease(multiEmail);

Where it gets a little different is when we go to set the street address values. While we do still use an ABMutableMultiValueRef, we won’t be using kABMultiStringPropertyType. To set the street address, we use kABMultiDictionaryPropertyType instead, so we have to create an NSMutableDictionary, and the method calls end up being a bit different:

        ABMutableMultiValueRef multiAddress = ABMultiValueCreateMutable(kABMultiDictionaryPropertyType);

NSMutableDictionary *addressDictionary = [[NSMutableDictionary alloc] init];

            [addressDictionary setObject:@”750 North Orleans Street, Ste 601″ forKey:(NSString *) kABPersonAddressStreetKey];
[addressDictionary setObject:@”Chicago” forKey:(NSString *)kABPersonAddressCityKey];
[addressDictionary setObject:@”IL” forKey:(NSString *)kABPersonAddressStateKey];
[addressDictionary setObject:@”60654″ forKey:(NSString *)kABPersonAddressZIPKey];

          ABMultiValueAddValueAndLabel(multiAddress, addressDictionary, kABWorkLabel, NULL);
ABRecordSetValue(newPerson, kABPersonAddressProperty, multiAddress,&error);
CFRelease(multiAddress);

kABWorkLabel means that we’re setting this as the contact’s work address. And to add it to the contact record, we call ABRecordSetValue as before, releasing the memory afterward.

The last step is to add the new record to the address book, and save it back to the device:

        ABAddressBookAddRecord(iPhoneAddressBook, newPerson, &error);
ABAddressBookSave(iPhoneAddressBook, &error);

And then we can check for any errors:

        if (error != NULL)
{

                  NSLog(@”Danger Will Robinson! Danger!”);

        }

 

So that concludes this introduction to creating new records in the address book.

Tom

Facebooktwitterredditpinterestlinkedinmail Read More

Posted by on Jan 23, 2009 in Code, Mobile |

Lessons for the Beginning iPhone Developer

Getting started with iPhone development can be an intense process. If you’re not coming from the world of OSX development, you’re probably more familiar with the myriad languages that look more like C++ than Smalltalk, and you have a lot of new syntactical things to learn, with square-brackets in abundance. It can be easy to focus completely on learning the syntax of Objective-C, and forget that the iPhone is a device with more severe limits on resources than computers, and that you will ultimately have to get your app approved by Apple before you can distribute it, so you need to follow their guidelines to the letter.

Following are some lessons indented for developers who are well versed in any number of other programming languages, but who are coming to the iPhone for the first time.

Memory Management

This one is huge, and easy to underestimate. Don’t develop your entire application and then decide to check to see if you have any memory problems. The iPhone has about 128 MB of volatile memory on board, and the virtual memory model does not include swap space. This means that, even though your iPhone has 8GB or 16GB of Flash storage, memory that your application allocates will not be swapped out to the disk to free up system memory. Since over-releasing objects will crash an application in often difficult to understand ways, it can be tempting to never release objects. However, the system monitors memory usage, and shuts down any application that takes up too much memory. This is the cause of a great many application crashes on the iPhone. You can use around 30 MB safely. Get closer to 40 or 50, and the system will very likely shut your app down with an exit status of 101.

Technically speaking, the system monitors total memory usage in order to maintain continuity of critical applications, such as the phone system, the audio system, and other systems that run constantly on the phone. When total memory usage climbs high enough that the functionality of these systems is put in jeopardy, the application currently on top of the stack will be shut down. This does mean that it is possible for one of these background processes to be using too much memory, causing your app to be shut down. Shutting down the phone and restarting usually takes care of these sorts of conditions.

Definitely read and understand the Memory Management Programming Guide for Cocoa, it’s one of the most important documents to read.

Generally, the Memory Management guide can be summed up by saying that you must release any object that you create with alloc, new, or copy, or one that you have specifically retained with retain.

Human Interface Guidelines

Read and understand the Apple Human Interface Guidelines. Apple calls this document “required reading” for a reason. They’re not being allegorical. Failure to follow the guidelines has resulted in many apps being rejected from the app store. You can always resubmit, of course, but resubmitting means you’ll have to wait for your app to be approved again.

Understanding the Review Process

Before you can distribute your app on the App Store, you’ll have to submit the app to Apple for review. This can take anywhere from a few days to…unfortunately…a few months. Most apps will get some sort of response from Apple within a week. These responses fall into three categories:

  1. Approved: Hooray! Your app will appear in the App Store shortly.
  2. Rejected: Unless your app has been deemed inappropriate for the app store, you will be given a list of items to fix. Fix them and resubmit for approval.
  3. Unexpected Additional Time to Review: This is bad. iPhone developer forums are full of stories of apps that have gotten this feedback, and have been stuck in the queue for months to follow. It may be a good idea to resubmit your app at this point and hope for another reviewer. I haven’t found any definitive answers on what the best course of action is when this happens.

Exception Breakpoints

By default, exceptions are not very easy to trace in XCode. This is a pain because it makes it impossible to debug application crashes. Luckily, the solution is pretty simple. Adding a global breakpoint for “obj_exception_throw” and “–[NSException raise]” will cause XCode to break on exceptions, allowing the stack trace to be viewed. Global breakpoints can be added in XCode under Run => Show => Breakpoints.

“malloc_error_break” can be used to obtain information on a malloc error.

Zombies!

Over-releasing objects can cause difficult to track down problems, usually resulting in an EXC_BAD_ACCESS exception. Setting the NSZombieEnabled environment variable will dynamically change the class of deallocated objects to _NSZombie rather than actually freeing the memory. This does mean that memory is never freed, so obviously, this isn’t something to be left on all the time, but it can come in handy when trying to track down over-releases and premature releases. NSZombieEnabled is set by editing the Active Executable, and adding an Environment Variable named “NSZombieEnabled” with a value of “YES”.

GDB Console Commands

Debugging in XCode is frustrating until you realize the power of GDB in the console. Set a breakpoint in your code, and you can learn all sorts of things with these commands:

  • print (unsigned int)[someObject retainCount]
    • display the retain count of an object
  • print (unsigned int)[[someObject size] retain]
    • Show the size of an object. The retain is necessary to keep the object from being released here.
  • po someObject
    • display information about an object. In the case of an NSString, the string will be printed.
  • po [someView subviews]
    • Print an array of subviews of a view. 

 

Facebooktwitterredditpinterestlinkedinmail Read More