Wednesday, 18 August 2010

Logging in Objective-C with Blocks

I've been learning Objective-C in my spare time, focusing on coding for iOS devices in particular. I tend to use debugging as the last resort when writing code, so logging is important. I have found the lack of decent logging in iOS to be a problem.

Being used to logging on java and .Net, NSLog does seem very primitive, and one solution does appear to be cocoalumberjack. I will be investigating this and doing a post soon.

In the mean time I am experimenting with using blocks to give me the flexibility I need as a minimum. This was partly motivated as a learning exercise for closures in Objective-C

The basic Logger header looks like this:
typedef enum {
kError = 0,
kWarn,
kInfo,
kDebug,
} TRLLogLevel;

@interface TRLLog : NSObject

+(void)logWithBlock:(NSString* (^)(void))block withLevel:(TRLLogLevel)level;
+(void)setLogLevel:(TRLLogLevel)level;

+(void)error:(NSString* (^)(void))block;
+(void)warn:(NSString* (^)(void))block;
+(void)info:(NSString* (^)(void))block;
+(void)debug:(NSString* (^)(void))block;
@end

.. so a simple logger API then. The interesting part as far as I am concerned is this bit...
(NSString* (^)(void))

There is a full discussion on block syntax here, so I am not going to go into lots of detail. The example above is defining a block which takes no parameters and returns a string.

Inside the implementation, the block is used like a normal C function

+(void)logWithBlock:(NSString* (^)(void))block withLevel:(TRLLogLevel)level {

if (level > currentLogLevel)
return;

NSString *msg = block();
NSLog(@"%@",msg);
[msg release];
}

This means that any expensive operations inside the block are not called if the log level is not met. To use the logger, the code looks like this

CGPoint point = [recogniser translationInView:self.view];

[TRLLog debug:^{
return [[NSString alloc]initWithFormat:@"handlePan [%f, %f]",point.x,point.y];
}];

Note how because the block is a closure, it can use other variables that are in scope.

Wednesday, 8 April 2009

DBDeploy + Maven + MySQL

I wanted to have a way of controlling database updates for the project I am working on. I was aiming for the following:

1. A series of database deltas
2. A Database on each developer machine, and on each of the CI environments
3. The deltas to be applied in order and only once
4. Each database to have a list of applied deltas
5. I wanted the database versioning to be part of CI

These goals were fairly straightforward, and I did not see the need to write code to achieve this. At the suggestion of a colleague, I looked at DBDeploy.

This seemed to provide the features I wanted and is pretty trivial to set up, as described here. However there were a few complications, primarily due to Maven and MySQL.

1. DBDeploy has an Ant target, but no maven plugin. I therefore used the Maven Antrun plugin to call the DBDeploy Ant target

2. MySql has no differentiation between end of statement and end of block. This can be a problem when scripting procedures. The work-around is to use a different delimiter. In my delta scripts I use #.

Unfortunately (for me) however DBDeploy works by concatenating all deltas into one script and adding it's own versioning statements at either end. These statements are hard coded to use a semi-colon delimiter.

In order to be able to use a different delimiter, it was therefore necessary to do some post processing on the file. Since I was already using the Antrun plugin and ant has some good targets for this kind of thing, I used the Ant replace and replaceregex Ant targets to do this.

3. In order to run the script as part of CI, I used the sql-maven-plugin and changed the delimiter to #

The plugin configuration was as follows:

...
<plugins>
<plugin>
<artifactId>maven-antrun-plugin</artifactId>
<executions>
<execution>
<phase>compile</phase>
<configuration>
<tasks>
<taskdef name="dbdeploy" classname="net.sf.dbdeploy.AntTarget"/>
<taskdef name="ReplaceRegExp" classname="org.apache.tools.ant.taskdefs.optional.ReplaceRegExp"/>
<mkdir dir="${basedir}/target/db"/>
<dbdeploy
driver="com.mysql.jdbc.Driver"
url="${db.url}"
userid="${db.user}"
password="${db.password}"
dir="${basedir}/src/main/resources/db/deltas"
outputfile="${basedir}/target/db/all-deltas.sql"
dbms="mysql"
undoOutputfile="${basedir}/target/db/undo-all-deltas.sql"
/>
<replace dir="${basedir}/target/" token="COMMIT;" value="COMMIT#">
<include name="**/*.sql"/>
</replace>
<replaceregexp byline="true">
<regexp pattern=" changelog(.*);"/>
<substitution expression=" changelog\1#"/>
<fileset dir="${basedir}/target/">
<include name="**/*.sql"/>
</fileset>
</replaceregexp>
<replace dir="${basedir}/target/" token="@DBNAME@" value="${db.name}">
<include name="**/*.sql"/>
</replace>
</tasks>
</configuration>
<goals>
<goal>run</goal>
</goals>
</execution>
</executions>
<dependencies>
<dependency>
<groupId>dbdeploy</groupId>
<artifactId>dbdeploy</artifactId>
<version>2.1.1</version>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>5.1.6</version>
</dependency>
<dependency>
<groupId>org.apache.ant</groupId>
<artifactId>ant-nodeps</artifactId>
<version>1.7.1</version>
</dependency>
</dependencies>
</plugin>
<plugin>
<groupId>org.codehaus.mojo</groupId>
<artifactId>sql-maven-plugin</artifactId>
<version>1.1</version>
<dependencies>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>5.1.6</version>
</dependency>
</dependencies>
<configuration>
<driver>com.mysql.jdbc.Driver</driver>
<autocommit>true</autocommit>
<delimiter>#</delimiter>
</configuration>
<executions>
<execution>
<id>update-schema-db</id>
<phase>generate-test-resources</phase>
<goals>
<goal>execute</goal>
</goals>
<configuration>
<url>${db.url}</url>
<username>${db.user}</username>
<password>${db.password}</password>
<fileset>
<basedir>${basedir}/target/db</basedir>
<includes>
<include>all-deltas.sql</include>
</includes>
</fileset>
</configuration>
</execution>
</executions>
</plugin>
</plugins>
...

Wednesday, 25 March 2009

Tuckey URL Rewriter

I have been using a url rewriting plugin for Spring, that allows urls to be less cluttered with querystring params. I am using it as an interim step before sorting a proper photo cache.

What I want is to have a url like:

/cache/player/1/picture



Spring 3.0 will allow this kind of thing, but for the moment there is a url rewriting filter from http://tuckey.org

The steps I used were:

1. Add a dependency to the pom file

<dependency>
 <groupid>org.tuckey</groupid>
 <artifactid>urlrewrite</artifactid>
 <version>2.5.2</version>
</dependency>



2. Add a filter to web.xml

<filter>
<filter-name>UrlRewriteFilter</filter-name>
<filter-class>org.tuckey.web.filters.urlrewrite.UrlRewriteFilter</filter-class>
</filter>



3. Add a file called urlwrite.xml to WEB-INF

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE urlrewrite PUBLIC "-//tuckey.org//DTD UrlRewrite 3.0//EN" "http://tuckey.org/res/dtds/urlrewrite3.0.dtd">
<urlrewrite>
<rule>
<from>/cache/player/([0-9]+)/picture$</from>
<to>/cache/player/picture?accountId=$1</to>
</rule>


Where <from> and <to> are regular expression match and replace values

4. Create a normal Spring controller and configuration to handle requests like

/cache/player/picture?accountId=1



This means that later I can cache the picture as a file and change the controller to being a 404 handler.