Do you need help on a specific subject? Use the contact form (Request a blog entry) on the right hand side.

2015-07-16

Persisting file access rights between sessions

While the Apple sandbox is a good thing, it does tend to complicate matters for us software developers.
For example when a user opens a file or folder from within a NSOpenPanel the application automatically receives access rights to that file/folder.
But when the path is saved to disk, and the application wants to use this path in the next session, the sandbox will interfere and deny access rights.
Luckily Apple has also created a solution for this: the path can be stored as a "Security Scoped Bookmark". The application can -in later sessions- read this data, create a new URL from it and then request the OS (sandbox) to reinstate the same rights as in the previous session. A bit of work, but not intrinsically difficult.

Here is how to create a Security Scoped Bookmark:

// Retrieve the bookmark data from the URL and add it to the bookmarks
                
var error: NSError?

if let data: NSData? = url.bookmarkDataWithOptions(
   NSURLBookmarkCreationOptions.WithSecurityScope,
   includingResourceValuesForKeys: nil,
   relativeToURL: nil,
   error: &error) {
                    
   bookmarks[path] = data      
                    
} else { // 'data' is nil, there should be an error message
                    
   let detail = error?.localizedDescription ?? "missing error description"
}

That example is of course assuming that the path is available as a NSURL, which in the case of a NSOpenPanel will indeed be the case. For drag & drop it will be necessary to convert the string's with file paths to a NSURL first. Also, the above example (and the one below) is for application wide access. Document relative paths must be created slightly different.

The NSData must be saved to file when the session ends:

// Create a string that contains the security scoped bookmark data
                

let bytes = data.base64EncodedStringWithOptions(NSDataBase64EncodingOptions.allZeros)
... save the bytes to file using your favourite method

and in the following session it must be reloaded. After reloading, we recreate the NSURL with:

// Recreate the security scoped bookmark url
                    
var isStale: ObjCBool = false
var error: NSError?
let url = NSURL(
   byResolvingBookmarkData: data,
   options: NSURLBookmarkResolutionOptions.WithSecurityScope,
   relativeToURL: nil,
   bookmarkDataIsStale: &isStale,
   error: &error)
                                       
   if error != nil {
                        
      ... error handling

   } else if isStale { // Exclude stale resources
                        
      ... error handling
                     
   } else if url == nil { // Should never happen when error == nil !!
                        
      ... error handling
                    
   } else {

                        
      // Regain the access rights
                        
      if !url!.startAccessingSecurityScopedResource() {
                            
          errorMessage += "Could not gain access rights for: \(url!.path!)\r\n"

      } 
   }

}

Once the application no longer needs the access rights, they should be released with:

url.stopAccessingSecurityScopedResource()

Failure to release security scoped bookmarks will result in memory leaks within the kernel. This may or may not be a problem for your application, depending on how many resources it needs.

If you want to speed up your development process even more, get our fully functional SecurityScopedBookmarks example. Depending on your experience, this could easily save you a days work for the price of a (cheap) cup of coffee ($2). And you will be helping to keep this blog alive.

Happy coding...

Did this help?, then please help out a small independent.
If you decide that you want to make a small donation, you can do so by clicking this
link: a cup of coffee ($2) or use the popup on the right hand side for different amounts.
Payments will be processed by PayPal, receiver will be sales at balancingrock dot nl
Bitcoins will be gladly accepted at: 1GacSREBxPy1yskLMc9de2nofNv2SNdwqH

We don't get the world we wish for... we get the world we pay for.

2015-07-10

SwifterJSON v0.9.2

I just posted a new release of SwifterJSON.

Version 0.9.2
- Fixed a bug that caused the 'writeJSONHierarchyToFile' to fail when the file already existed
- Added convenience initialiser to accept Array<SwifterJSON>
- Added convenience initialiser to accept Dictionary<String, SwifterJSON>
- Added 'final' to the class definition
- Re-read all text and comments, updated some of it.
- Added the Equatable protocol to the SwifterJSON class definition (this was already defacto the case)

SwifterJSON is -as the name implies- a single class framework that can be used to generate and parse JSON code.

Happy coding...

Did this help?, then please help out a small independent.
If you decide that you want to make a small donation, you can do so by clicking this
link: a cup of coffee ($2) or use the popup on the right hand side for different amounts.
Payments will be processed by PayPal, receiver will be sales at balancingrock dot nl
Bitcoins will be gladly accepted at: 1GacSREBxPy1yskLMc9de2nofNv2SNdwqH

We don't get the world we wish for... we get the world we pay for.

2015-07-06

Code sample: Persisting a session in the Application Support folder in Swift

Persisting a session is when we save internal App data such that on the next startup of the App it shows itself in the same situation as when we last exited the App. This can be done by saving the internal state into a file at 'AppDelegate.applicationWillTerminate' and to reload the file (and apply the settings) on startup.

Since this is something that has to be done time and again (i.e. for every App we create) I was looking for a solution that could be reused by every new App. The solution I came up with is presented below.

The requirements were:

  • No "init" phase where the module has to be called to reload the contents. I.e. automatic and autonomous initialisation when necessary.
  • Available system wide without the need to pass through a reference from one instance to the next.
  • Store the data in a human readable format (in case it must be edited - though editing is of course at the users risk!)
  • Provide default values when the file operations fail for whatever reason.
  • The storage location (folder) should be configurable.
  • The storage location should default to the application support directory when not configured by the programmer.
  • It should be fast.
  • It should be easy to maintain (add new parameters)

That was a whole list, but with Swift it is easier than ever to fulfil each of these requirements. This is my solution:

final class SessionPersistence {
    
    
    // MARK: - Session Persistence Parameters
    
    // ===========================================================================================
    // When adding members, be sure to also set default values for them in 'writeSessionDefaults'.
    //

    private static let SHOW_COLUMN = "ShowColumn"
    
    private static var _showColumn: Bool = { return json[SHOW_COLUMN].boolValue ?? true }()

    static var showColumn: Bool {
        get { return _showColumn }
        set {
            _showColumn = newValue
            json[SHOW_COLUMN].boolValue = newValue
        }
    }
    
    
    private static let SHOW_TAGS = "ShowTags"
    
    private static var _showTags: Bool = { return json[SHOW_TAGS].boolValue ?? true }()
    
    static var showTags: Bool {
        get { return _showTags }
        set {
            _showTags = newValue
            json[SHOW_TAGS].boolValue = newValue
        }
    }

    
    //
    // When adding members, be sure to also set default values for them in 'writeSessionDefaults'.
    // ===========================================================================================
    

    // MARK: - Housekeeping below this line
    
    /// Specifies the path for the directory in which the session persistence file will be created. Note that the application must have write access to this directory and the rights to create this directory (sandbox!). If this variable is to nil, the session persistence file(s) will be written to /Library/Application Support/<<<Application Name>>>/SessionPersistence. Do not use '~' signs in the path, expand them first if necessary.
    ///
    /// Note: When debugging in xcode, the app support directory is in ~/Library/Containers/<<<bundle identifier>>>/Data/Library/Application Support/<<<app name>>>/SessionPersistence.
    
    static var sessionPersistenceDirectoryPath: NSString?
    
    static private var sessionPersistenceDir: NSString? = {
        
        
        // Get the path to the session persistence directory, prioritize a configured directory, but take the default when it is not supplied.
        
        if let tmp = (sessionPersistenceDirectoryPath ?? applicationSupportSessionPersistenceDirectory) as? String {
            
            let fileManager = NSFileManager.defaultManager()

            var error: NSError?
        
            
            // Make sure the directory exists
            
            if fileManager.createDirectoryAtPath(tmp, withIntermediateDirectories: true, attributes: nil, error: &error) {
                
                return tmp
                
            } else {
                
                let message = "Could not create directory \(tmp), error = " + (error?.localizedDescription ?? "Unknown reason")
                log.atLevelError(id: 0, source: "SessionPersistence.sessionPersistenceDir", message: message)
                
                return nil
            }
            
        } else {
            
            return nil
        }
        }()
    
    static private var applicationSupportSessionPersistenceDirectory: String? = {
        
        let fileManager = NSFileManager.defaultManager()
        
        var error: NSError?
        
        
        // Retrieve the path to the application support directory
        
        if let applicationSupportDirectory =
            fileManager.URLForDirectory(
                NSSearchPathDirectory.ApplicationSupportDirectory,
                inDomain: NSSearchPathDomainMask.UserDomainMask,
                appropriateForURL: nil,
                create: true,
                error: &error
                )?.path {
            
            let appName = NSProcessInfo.processInfo().processName
            let dirName = applicationSupportDirectory.stringByAppendingPathComponent(appName)
            
            
            // Add the session persistence directory to the path of the application support directory
                    
            return dirName.stringByAppendingPathComponent("SessionPersistence")
            
        } else {
            
            return nil
        }
        }()
    
    
    // The primary store for session persistence
    
    static private var json: SwifterJSON = {
        
        var _json: SwifterJSON?
        
        
        // Try to locate the file with session persistence info
        
        let sessionPersistenceFilePath = SessionPersistence.sessionPersistenceDir?.stringByAppendingPathComponent("SessionPersistence.json")
        
        
        // If the file exists, try to read the data from it
        
        if sessionPersistenceFilePath != nil {
            
            let (jsonOrNil, errorOrNil) = SwifterJSON.createJSONHierarchyFromFile(sessionPersistenceFilePath!)
            
            if jsonOrNil != nil { _json = jsonOrNil! }
        }
        
        
        // If the file did not exist or it could not be read, then create a new one. Note that this new default object will at some point during app execution overwrite an existing file should there happen to be one.
        
        if _json == nil {
            
            log.atLevelInfo(id: 0, source: "SessionPersistence.json", message: "Creating a new session persistence item")
            
            _json = SwifterJSON()
        }
        
        return _json!
    }()
    
    static func save() {
        
        let sessionPersistenceFilePath = SessionPersistence.sessionPersistenceDir?.stringByAppendingPathComponent("SessionPersistence.json")
        
        if sessionPersistenceFilePath != nil {
            json.writeJSONHierarchyToFile(sessionPersistenceFilePath!)
        } else {
            log.atLevelError(id: 0, source: "SessionPersistence.save", message: "Could not save session persistence information at path: \(sessionPersistenceFilePath)")
        }
    }
    
    private init() {} // No need to allow instance creation

}

Looks complex?

It's not all that difficult to understand. Lets start by simply accessing one of the two session persistence bool's (in a window's awakeFromNib method):

        toggleableColumn.hidden = !SessionPersistence.showColumn

Note that no other "initialisation" calls are necessary. Just access the necessary value directly. The exclamation mark is a byproduct of the naming, the parameter is called "show...", but its usage is to "hide" hence the inverting. (PS: when the user changes something, simply write the new value to the corresponding session persistence parameter, for example: SessionPersistence.showColumn = true)

On the first access to any session persistence parameter, the value will be read from the private value:

    private static var _showColumn: Bool = { return json[SHOW_COLUMN].boolValue ?? true }()

    static var showColumn: Bool {
        get { return _showColumn }
        set {
            _showColumn = newValue
            json[SHOW_COLUMN].boolValue = newValue
        }
    }

in this case _showColumn. But since this value is used for the first time, the initialisation of that value is performed. I.e. it tries to read a value from the private JSON hierarchy. Since that hierarchy is not yet present, it will execute the getter/constructor for var json:

    static private var json: SwifterJSON = {
        
        var _json: SwifterJSON?
        ....

This in turn will start the generation of the directory path in which the JSON file with session persistence data should be stored. If a configured directory is present, it will use that. If not, it will default to the application support directory. If it finds the JSON file, the JSON hierarchy will be created. If not, the internal _json stay's nil and a new _json is created. Note that if anything goes wrong, nothing bad will happen. The internal _showColumn variable will simply return the default value:

    return json[SHOW_COLUMN].boolValue ?? true

The indirection of using the _showColumn instead of a getter/constructor for showColumn is for performance. The private variable has to access the JSON hierarchy, and that takes more time than simply returning a variable. Hence doing this only once is advantageous. If the getter of the public variable would access the JSON hierarchy, the JSON lookup would have to be performed for every read. The private variable thus provides a kind of caching mechanism. The only drawback is that the setter must update two variables. But imo that is an acceptable price to pay.

Adding more parameters to the session persistence object is easy: repeat the given pattern for any new parameter.

At this point you may think: "But the default JSON parameter is never created?". And that is correct, at least explicitly. It is however implicitly created when the user changes something that warrants updating of the session persistence parameter(s). It is a property of SwifterJSON that any object that is not present is automatically created. As long as a parameter is not present in the JSON file, the private variable returns the default value. But as soon as a session persistence parameter is updated it is also created. And from then on it will be present in the session persistence file as well.

Happy coding

Did this help?, then please help out a small independent.
If you decide that you want to make a small donation, you can do so by clicking this
link: a cup of coffee ($2) or use the popup on the right hand side for different amounts.
Payments will be processed by PayPal, receiver will be sales at balancingrock dot nl
Bitcoins will be gladly accepted at: 1GacSREBxPy1yskLMc9de2nofNv2SNdwqH

We don't get the world we wish for... we get the world we pay for.

2015-07-03

Swift extensions: Bit access for UInt8

Sometimes it is necessary to address the individual bits in a byte. This extension adds that capability to the UInt8 type:

extension UInt8 {
    
    static var bit7mask: UInt8 { return 0b1000_0000 }
    static var bit6mask: UInt8 { return 0b0100_0000 }
    static var bit5mask: UInt8 { return 0b0010_0000 }
    static var bit4mask: UInt8 { return 0b0001_0000 }
    static var bit3mask: UInt8 { return 0b0000_1000 }
    static var bit2mask: UInt8 { return 0b0000_0100 }
    static var bit1mask: UInt8 { return 0b0000_0010 }
    static var bit0mask: UInt8 { return 0b0000_0001 }
    
    func toBools() -> (bit7: Bool, bit6: Bool, bit5: Bool, bit4: Bool, bit3: Bool, bit2: Bool, bit1: Bool, bit0: Bool) {
        return (
            self & UInt8.bit7mask == UInt8.bit7mask,
            self & UInt8.bit6mask == UInt8.bit6mask,
            self & UInt8.bit5mask == UInt8.bit5mask,
            self & UInt8.bit4mask == UInt8.bit4mask,
            self & UInt8.bit3mask == UInt8.bit3mask,
            self & UInt8.bit2mask == UInt8.bit2mask,
            self & UInt8.bit1mask == UInt8.bit1mask,
            self & UInt8.bit0mask == UInt8.bit0mask
        )
    }
        
    func bit7IsSet() -> Bool { return self & UInt8.bit7mask == UInt8.bit7mask }
    func bit6IsSet() -> Bool { return self & UInt8.bit6mask == UInt8.bit6mask }
    func bit5IsSet() -> Bool { return self & UInt8.bit5mask == UInt8.bit5mask }
    func bit4IsSet() -> Bool { return self & UInt8.bit4mask == UInt8.bit4mask }
    func bit3IsSet() -> Bool { return self & UInt8.bit3mask == UInt8.bit3mask }
    func bit2IsSet() -> Bool { return self & UInt8.bit2mask == UInt8.bit2mask }
    func bit1IsSet() -> Bool { return self & UInt8.bit1mask == UInt8.bit1mask }
    func bit0IsSet() -> Bool { return self & UInt8.bit0mask == UInt8.bit0mask }
    
    func bit7IsNotSet() -> Bool { return self & UInt8.bit7mask == 0 }
    func bit6IsNotSet() -> Bool { return self & UInt8.bit6mask == 0 }
    func bit5IsNotSet() -> Bool { return self & UInt8.bit5mask == 0 }
    func bit4IsNotSet() -> Bool { return self & UInt8.bit4mask == 0 }
    func bit3IsNotSet() -> Bool { return self & UInt8.bit3mask == 0 }
    func bit2IsNotSet() -> Bool { return self & UInt8.bit2mask == 0 }
    func bit1IsNotSet() -> Bool { return self & UInt8.bit1mask == 0 }
    func bit0IsNotSet() -> Bool { return self & UInt8.bit0mask == 0 }

}

The toBools() function is intended to be used as follows:

struct ID3v2_3_HeaderFlags {

    let unsynchronisation: Bool
    let extendedHeader: Bool
    let experimental: Bool
    
    
    /// Returns nil if the byte does not contain valid header flags
    
    init?(byte: UInt8) {
        
        let b4, b3, b2, b1, b0: Bool // Unused, should be zero
        (unsynchronisation, extendedHeader, experimental, b4, b3, b2, b1, b0) = byte.toBools()
        if b4 || b3 || b2 || b1 || b0 { return nil }
    }

}

What I like about this is that the tuple return guarantees us that the compiler will check if all bools have an assigned variable.

Happy coding...

Did this help?, then please help out a small independent.
If you decide that you want to make a small donation, you can do so by clicking this
link: a cup of coffee ($2) or use the popup on the right hand side for different amounts.
Payments will be processed by PayPal, receiver will be sales at balancingrock dot nl
Bitcoins will be gladly accepted at: 1GacSREBxPy1yskLMc9de2nofNv2SNdwqH

We don't get the world we wish for... we get the world we pay for.