Exploring Swift generic functions with WallpapersKit
Today I’ve been updating BingWallpapers to use the last version of Swift and also include the latest changes of WallpapersKit, the framework that is shared across the Mac and iOS versions.
I’ve been developing WallpapersKit letting the code guide it’s own design and today I made some changes using generic functions.
In WallpapersKit there is an extension to the main core class that contains a function with the following signature:
fetchImageFromWallpaper(wallpaper: Wallpaper,
downloadImage: (url: NSURL, success: (Image) -> (), fail: (NSError) -> ()) -> (),
completion: (url: NSURL?, image: Image?, error: NSError?) -> ())
In the source it’s nicer with some typealias but here I want to be explicit. This function contains the logic to download the right image from the wallpaper
. It tries to download the image with the desired quality and if it fails it has some fallbacks. The nice part is the downloadImage
closure.
downloadImage: (url: NSURL, success: (Image) -> (), fail: (NSError) -> ()) -> ()
This closure takes an URL and a success and failure closures. In this way WallpapersKit can contain the required logic to deal with the wallpapers, the different urls and qualities, but it doesn’t have to care on how to actually download the image. That has turn out to be really great because the Mac and iOS apps do different things when downloading the images.
On iOS I’m currently using the Haneke library to download and cache the images. For convenience I have defined a subspec, thanks to Cocoapods, that includes a version of the fetchImageFromWallpaper
function but with a default downloadImage
closure. This is the only place where the framework has the dependency. Having this in a subspec means that the iOS App can use this handy function in the main app and in the extension (Today widget).
The OS X app needs something different. There I don’t import the subspec, so it doesn’t have any dependency with the third party library to cache images. It has it’s own implementation of the downloadImage
function that downloads the data of the image and saves it directly to disk (in a Application Support subfolder).
It doesn’t need at all to create an NSImage
, it only has to download the NSData
. That’s because the OS X API to change the Desktop background only needs a disk path, so is less work that we have to do.
To accomplish that difference in the requirements I’ve modified the original function to make it generic. Now instead of talking about Image it talks about a generic type.
fetchFromWallpaper<T>(wallpaper: Wallpaper,
download:(url: NSURL, success: (T) -> (), fail: (NSError) -> ()) -> (),
completion: (url: NSURL?, data: T?, error: NSError?) -> ())
Thanks to that change the OS X app can pass the downloadImageData
function (that predates Swift itself, was ported form the original Objective-C version ^^) that only deals with NSData
.
In order to maintain the compatibility with the iOS version and not make a breaking change I just reimplemented the previous fetchImageFromWallpaper
function to use the new generic function specifying T
as an Image
type.
The best part about Swift is that I can call the generic function giving it just the downloadImageData
function (named closure)…
...
fetchFromWallpaper(wallpaper,
download: downloadImageData,
completion: { (url, data, error) -> () in
...
})
...
.. and it infers the right type for the generic function. Like magic.
Nothing new here but it has been cool to combine this things:
- Closures to externalize behavior.
- Use Cocoapods subspecs with default implementation for iOS.
- Use generic functions to decouple some logic from specific types.
- Have functions that constraint the type of generic functions.
- Leverage Swift type inference
- ❤️ Swift