With the release of Xcode 6 and new OSs, Apple is pushing the use of a new language: Swift. Swift, unlike the venerable Objective-C, is meant to be a “modern” language, which means moving away from much of the computer language world that’s based on C. You can argue that the classes, structures, typedefs are all more powerful in Swift than in the C/C++/Objective-C world. Furthermore, Swift mostly hides one of the biggest banes in C programming: the pointer.
However, the pluses of Swift also present a bit of a problem: how to interact with C-based languages. Apple did spend some time making Swift compatible with Objective-C and C (no joy for C++, at least not yet). Objective-C is quite easy to incorporate with Swift. After all, Objective-C has been Apple’s primary language since transitioning to Mac OS X. C, on the other hand, is also compatible with Swift but appears quite tricky to a Swift newbie, like me.
I thought I might do a bit of work to understand how Swift might work with C. So, my question is “can I access NetCDF from Swift?” NetCDF is a file format for storing large datasets. It’s powerful and quite useful in the work I do. There are several forms for the library, C, C++, and Java. The only one of these compatible with Swift is the C library. The C library is an API that is loaded with pointers: exactly what Swift is designed to hide.
It is possible to make this easier. I could just use my own Objective-C interface to the NetCDF library. The work is done and compatible with Swift. It doesn’t feel clean to me, though. If Apple really wants us to eventually migrate to Swift, then why not access NetCDF from Swift? Indeed, I work with many libraries that are C-based APIs. So, avoiding working with these types of libraries doesn’t solve anything. Instead, it’s time to dive in and get my hands dirty with Swift.
Installing NetCDF is fairly easy. Download the source from unidata (http://www.unidata.ucar.edu/downloads/netcdf/index.jsp), configure (I configured with NetCDF 4 and open dap turned off, but I’ll use the NetCDF 3 format), and compile. Since I’m not a fan of having to install libraries all over the place, I used the static C library in my app. You must also configure xCode to use the library and to be able to locate the header.
The final requirement for using NetCDF in Swift is making the library accessible to Swift. You must include the following in your app’s Bridging Header:
Now, Swift can see the netcdf API. If this #import statement is not in the bridging header (which xCode can create for you), then you will not be able to access any of the APIs. A bonus is that autocomplete of netcdf function calls will appear as Swift calls, which greatly helps the process.
Opening a File
The first step in opening a NetCDF file is, well, opening it. The C command for opening a NetCDF file is:
int nc_open (const char *path, int omode, int *ncidp);
Where path is a string representing the path to the file, omode is a integer value telling the library how to open the file, and a pointer which will return an ID value which will be used to identify the open file. It returns an error code. Fairly straightforward C.
In Swift, the command looks a little different.
nc_open(<#path: UnsafePointer#>, <#mode: Int32#>, <#ncidp: UnsafeMutablePointer#>) -> Int32
The Swift version is essentially the same, but with different details. The path string is now called an “UnsafePointer”, which means that the string can’t be changed and has an unknown size. The pointer for the ncidp is now labeled as an “UnsafeMutablePointer”, which means it’s an unknown size and the data can be changed. The unknown sizes are what makes the pointers unsafe: in theory overruns are possible which can be dangerous. Swift is meant to avoid this problem, yet you still have to work with C.
First, how do we fill the ncidp pointer? This one is relatively easy. We create and initialize a variable for hold the ncid:
var ncHandle: Int32 = 0
We assign the type to make sure we’re compatible with the command, in the case of the ncHandle, it’s a Int32. We have to be specific since the “Int” type can be a difference size on different machines. On 64-bit machines, the Int type is 64-bit, but the NetCDF library is expecting a 32-Bit integer. Since we can’t be uninitialized in Swift, we set it equal to 0 (perhaps it might be better to assign a negative number).
The tricky part here is the path. It must be a C string, not a Swift or NSString class. In my case, I’m starting with a NSURL (I get the path from a dialog box). So, we must get the NSURL into a C String. The way I’ve done this is the following:
let thePath = URL.path?.cStringUsingEncoding(NSUTF8StringEncoding)
Where we tell the URL we want the path and convert it into a C String using the UTF character set. Note the (?) after path. That’s an example of optional chaining. We are calling a property of URL (path is a property) and it’s possible that path could be nil. We need to fail gracefully, so the optional chaining unwraps the path (in Swift, a variable can have a value or be nil and unwrapping gets you to the value) or fails.
Now, we can put together our call:
var ncError = nc_open(thePath!, openMode, &ncHandle)
Notice in the above I still have to use an “!” with thePath. Since thePath is an optional, which means thePath actually represents a container that could have a value or be nil), I have to use thePath’s value, which I can “unwrap” with the “!”. However, it’s better to check to make sure that thePath isn’t nil. So, we can change it up:
if let thePath = URL.path?.cStringUsingEncoding(NSUTF8StringEncoding)
var ncError = nc_open(thePath, openMode, &ncHandle)
if ncError == NC_NOERR
//we’ve successfully opened the file.
Here, we error check immediately when we create thePath. If thePath is nil, this code won’t run. Another advantage is that thePath is already unwrapped by this if statement, so we no longer need the “!” in the nc_open call.
Because Swift is meant to be safe, we have to spend a bit of extra time building up information held within a NetCDF file. Let’s start with dimensions (dims). Dims in the file are what’s used to define the size of our datasets. They each have an ID, length, and a name. We must collect all of this for an unknown file, but we should collect the data for known files as well.
First, we need to know how many dims exist in the file. In the C library, we would call:
int nc_inq_ndims(int ncid, int *ndimsp);
The ncid is the file ID we created earlier, the ndims pointer is where the call will store the number of dims in the file.
In Swift, the call looks like:
nc_inq_ndims(<#ncid: Int32#>, <#ndimsp: UnsafeMutablePointer#>)
So, in this case, we have to come up with variables for the number of dims and their ids (an array). The number of dims is easy, you simply need an Int32 variable set to 0:
var ndims: Int32 = 0
However, the array takes a bit more work:
var dimids = [Int32](count: Int(NC_MAX_DIMS), repeatedValue: 0)
Here, we have to have a buffer big enough to hold the maximum number of dims. Notice that we’re using the macro NC_MAX_DIMS and making sure it’s typed correctly. We’re also seeding the array with 0 at each point with the repeatedValue.
Now we can make the call:
var ncError = nc_inq_dimids(ncid, &ndims, &dimids, 0)
Similar to normal C, we’re passing pointers for ndims and dimids.
However, we’re not yet done with dims. What we still need are their names and lengths. We can get that information using the dimids and the following C call:
int nc_inq_dim (int ncid, int dimid, char* name, size_t* lengthp);
And in Swift:
nc_inq_dim(<#ncid: Int32#>, <#dimid: Int32#>, <#name: UnsafeMutablePointer#>, <#lenp: UnsafeMutablePointer#>) -> Int32
Assuming we didn’t get an error getting our dimids, we can now step through all of the dimids and build up our list of dim names and lengths. So, we create our string variable:
var tempString = [CChar](count:Int(NC_MAX_NAME+1), repeatedValue: 0
And size variable:
var length: size_t = 0
But we also need a place to store all of our values, so let’s create some empty arrays
At this point, things started changing for me. I started to getting the hang of Swift, at least the parts of swift I needed. It was time to start over. Now, instead of developing a class for specific files, I decided to at least least start a Swift version of my Objective-C interface for NetCDF. I’m not going to do the whole thing immediately, as that’s too much work for the moment. The first goal is to instead just build a skeleton framework for reading NetCDF 3. However, I’m not going to go over the details of this here. But, I do want to reflect on my first reactions working with Swift.
Swift’s limitations were becoming clear at this point. I really have to ask myself whether the Swift-native version of my NetCDF framework is better to use than my Objective-C version. Right now, I’d say stick with the Objective-C. There are several reasons for this:
1. Swift is almost entirely dependent on the Objective-C class structure and APIs. While you can do a little without any Objective-C, you’re often still stuck making Swift classes that are subclasses of Objective-C classes. As such, Objective-C works just fine with Swift. Why make too much work? Just use existing Objective-C APIs in Swift.
2. At present, the best place for Swift, based on my efforts, is in realm of view controllers and views. Swift can really speed the process of developing user interfaces. Deeper logic where you have to push data around can be a bit more difficult in Swift (and you drop down to Objective-C classes anyway).
3. Fear, uncertainty, doubt… FUD. I’m going to spread a bit of FUD here based entirely on my own fears, rather than anything I’ve directly heard. I started developing on MacOS X fairly early, and I remember there were two languages you could use: Objective-C and Java. Indeed, Steve Jobs once claimed they would make the Mac a top-notch Java development platform. The Java bridge support was discontinued, however, in 2005. It was no skin off my nose; I never used the Java bridge. I have no idea what ultimately led to this decision (maybe lack of use?), nor am I not sure how many apps used the Java bridge. Will Swift have the same fate? Given that you can do very little without bridging back to Objective-C, it does seem tenuous at best. If I’m right, however, that interface work is better in Swift, then Swift might survive long enough to become a powerful language on it’s own. Time will tell, but it’s a risk to write production code in Swift and it’s a risk not to write production code in Swift (Apple might fully transition to Swift… maybe).