Swift Trimming Trailing Punctuation

Lately, I’ve been working on a project that requires some basic MLP text processing to be performed on device.  A component of our text processing involved removing any trailing punctuation, no matter the language.

I’ve found the below String Extension is a good balance between effectiveness, performance, and readability.  Especially if you are collecting input from users prone to excessive punctuation.

extension String {
func trimTrailingPunctuation() -> String {
return self.trimmingCharacters(in: .whitespacesAndNewlines)
.trimmingCharacters(in: .punctuationCharacters)
.trimmingCharacters(in: .whitespacesAndNewlines)
}
}
let example1 = "How are you???".trimTrailingPunctuation()
>>> How are you
let example2 = "Hi!!!!".trimTrailingPunctuation()
>>> Hi
let example3 = "Act limited time offer now!".trimTrailingPunctuation()
>>> Act limited time offer now

 

Sentiment Analysis using Swift

Lately I’ve been working on several projects using conversational UI or “Bot” interactions.  A key interaction pattern when designing “Bots” you must listen to both the intent and sentiment of the text the user is providing.  Through the use of sentiment analysis you can help determine the mood of the user. This can be an important tool in determining when to offer help or involve a human.  You might think of this as the “help representative” moment we are all familiar with.  Using sentiment analysis you can try to offer help before that moment occurs.

There are several great sentiment analysis node.js packages but nothing I could find to run offline in Swift. A majority of the node.js projects seem to be a forks of the Sentiment package. The Sentiment-v2 package worked best for many of my cases and became my starting point.

A majority of the sentiment analysis  packages available through NPM use the same scoring approach. First they parse a provided phrase into individual words. For example “Cats are amazing” would be turned into an array of words, ie [“cats”, “are”,”amazing”].

Next a dictionary of keywords and associated weights are created. These scoring dictionary is created using the AFINN wordlist and Emoji Sentiment Ranking. In a nutshell, words like “amazing” would have a positive weight whereas words like “bad” would have a negative weight. The weight of each word in the provided phrase is added together to get the total weight of the phrase. If the phrase has a negative weight, chances are your user is starting to get frustrated or at least talking about a negative subject.

Using this approach I created the SentimentlySwift playground to demonstrate how this can be done on device using Swift.  This playground uses the same FINN wordlist and Emoji Sentiment Ranking weights to determine a sentiment analysis score without the network dependency.   To make comparisons easier, I tried to mirror the Sentiment package API as must as possible.  The below demonstrates the output for a few of the test phrases included with Sentiment.

let sentiment = Sentimently()
print(sentiment.score("Cats are stupid."))
analysisResult(score: -2, comparative: -0.66666666666666663, positive: [], negative: ["stupid"], wordTokens: [wordToken(word: "cats", wordStem: Optional("cat")), wordToken(word: "are", wordStem: Optional("be")), wordToken(word: "stupid", wordStem: Optional("stupid"))])
print(sentiment.score("Cats are very stupid."))
analysisResult(score: -3, comparative: -0.75, positive: [], negative: ["stupid"], wordTokens: [wordToken(word: "cats", wordStem: Optional("cat")), wordToken(word: "are", wordStem: Optional("be")), wordToken(word: "very", wordStem: Optional("very")), wordToken(word: "stupid", wordStem: Optional("stupid"))])
print(sentiment.score("Cats are totally amazing!"))
analysisResult(score: 4, comparative: 1.0, positive: ["amazing"], negative: [], wordTokens: [wordToken(word: "cats", wordStem: Optional("cat")), wordToken(word: "are", wordStem: Optional("be")), wordToken(word: "totally", wordStem: Optional("totally")), wordToken(word: "amazing", wordStem: Optional("amaze"))])
var testInject = [sentimentWeightValue]()
testInject.append(sentimentWeightValue(word: "cats", score: 5))
testInject.append(sentimentWeightValue(word: "amazing", score: 2))
print(sentiment.score("Cats are totally amazing!", addWeights: testInject))
analysisResult(score: 7, comparative: 1.75, positive: ["cats", "amazing"], negative: [], wordTokens: [wordToken(word: "cats", wordStem: Optional("cat")), wordToken(word: "are", wordStem: Optional("be")), wordToken(word: "totally", wordStem: Optional("totally")), wordToken(word: "amazing", wordStem: Optional("amaze"))])

Although the APIs are similar there is one important difference between the two approaches. The SentimentlySwift playground uses NSLinguisticTagger to tokenize the provided phrase. Using NSLinguisticTagger, SentimentlySwift first parsers each word into a series of word slices. Each slice is a word tokenized using the options provided to the NSLinguisticTagger. Next the slides are enumerated and an optional “tag” or word stem is calculated. For example, in the phrase “cats are amazing”, the “amazing” word generates a word stem of “amaze”. A better example would be the word “hiking” produces the word stem of “hike”.

The following snippet shows an example on how this can be implemented.

public struct wordToken {
let word: String
let wordStem: String?
init(word: String, wordStem: String?) {
self.word = word
self.wordStem = wordStem
}
}
func lemmatize(_ text: String) -> [wordToken] {
let text = text.lowercased()
let options: NSLinguisticTagger.Options = [.omitWhitespace, .omitPunctuation, .omitOther]
let tagger = NSLinguisticTagger(tagSchemes: NSLinguisticTagger.availableTagSchemes(forLanguage: "en"),
options: Int(options.rawValue))
tagger.string = text
var tokens: [wordToken] = []
tagger.enumerateTags(in: NSMakeRange(0, text.characters.count), scheme: NSLinguisticTagSchemeLemma, options: options) { tag, tokenRange, _, _ in
let word = (text as NSString).substring(with: tokenRange)
tokens.append(wordToken(word: word, wordStem: tag))
}
return tokens
}
view raw lemmatize.swift hosted with ❤ by GitHub

You might be asking why this is important? By implementing Lemmatisation you increase your AFINN hit rate and improve your overall analysis scoring.

This is one trick I’ve found for improving or at least monitoring your conversational UI or “Bot” interactions.

Thinking about Memory: Converting UIImage to Data in Swift

How often do you convert a UIImage into a Data object? Seems like a relatively straight forward task, just use UIImageJPEGRepresentation and your done.

After doing this I started seeing memory spikes and leaks appear which got me thinking on how I can better profile different options for performing this conversion. If you want to follow along you can create your own Swift Playground using this gist.

Approaches

The first step was looking at the different ways you can convert a UIImage into Data. I settled on the following three approaches.

UIImageJPEGRepresentation

Out of all the options this is the most straightforward and widely used. If you look at the testing blocks later in the post you can see I’m simply inlined the UIImageJPEGRepresentation with the test suite compression ratio.

UIImageJPEGRepresentation(image, compressionRatio)

UIImageJPEGRepresentation within an Autorelease Pool

Out of the box UIImageJPEGRepresentation provides everything we need. In some cases I’ve found it holds onto memory after execution. To determine if wrapping UIImageJPEGRepresentation in a autoreleasepool has any benefit I created the convenience method UIImageToDataJPEG2. This simply wraps UIImageJPEGRepresentation into a autoreleasepool closure as shown below. We later use UIImageToDataJPEG2 within our tests.

func UIImageToDataJPEG2(image: UIImage, compressionRatio: CGFloat) -> Data? {
return autoreleasepool(invoking: { () -> Data? in
return UIImageJPEGRepresentation(image, compressionRatio)
})
}

Using the ImageIO Framework

The ImageIO framework gives us a lower level APIs for working with images. Typically ImageIO has better CPU performance than using UIKit and other approaches. NSHipster has a great article with details here. I was interested to see if there was a memory benefit as well. The below helper function wraps the ImageIO functions into an API similar to UIImageJPEGRepresentation.  This makes testing much easier. Keep in mind you’ll need to have image orientation yourself.  For this example we just use Top, Left. If you are implementing yourself you’ll want read the API documentation available here.

func UIImageToDataIO(image: UIImage, compressionRatio: CGFloat, orientation: Int = 1) -> Data? {
return autoreleasepool(invoking: { () -> Data in
let data = NSMutableData()
let options: NSDictionary = [
kCGImagePropertyOrientation: orientation,
kCGImagePropertyHasAlpha: true,
kCGImageDestinationLossyCompressionQuality: compressionRatio
]
let imageDestinationRef = CGImageDestinationCreateWithData(data as CFMutableData, kUTTypeJPEG, 1, nil)!
CGImageDestinationAddImage(imageDestinationRef, image.cgImage!, options)
CGImageDestinationFinalize(imageDestinationRef)
return data as Data
})
}

What about UIImagePNGRepresentation?

UIImagePNGRepresentation is great when you need the highest quality image. The side effect of this is it has a largest Data size and memory footprint. This disqualified UIImagePNGRepresentation as an option for these tests.

Testing Scenarios

For my scenarios it was important to understand how memory is impacted based on the following:

  • Number of executions, i.e. what is the memory impact for calling an approach on one or many images.
  • How the Compression ratio impacts memory usage.

Image quality is an important aspect of my projects, so the tests where performed using the compression ratios of 1.0 and 0.9.  These compression ratios where then run using 1, 2, 14, 20, and 50 executions.  These frequencies demonstrate when image caching and Autorelease Pool strategies start to impact results.

Testing Each Approach

I test each of the above mentioned approaches using the template outlined below.  See the gist of the details for each approach.

  1. At the top of the method a memory sample is taken
  2. The helper method for converting a UIImage to a Data object is called in a loop.
  3. To make sure we are measure the same resulting data across tests, we record the length of the first Data conversion.
  4. When the loop has completed the proper number of iterations the memory is again sampled and the delta is recorded.

There is some variability on how each approach is tested.

The implementation for each approach is slightly different, but the same iteration and compression ratios are used to keep the outcome as comparative as possible.  Below is an example the strategy used to test the JPEGRepresentation with Autorelease Pool approach.

func Test_JPEGRepresentation_AutoRelease(iterations: Int, compressionRatio: CGFloat,image: UIImage) {
//Gather the initial information
let startReading = report_memory()
print("Memory at start: \(startReading / 1024 / 1024) mb")
//Loop through the number of test iterations specified
for index in 1...iterations {
if let data = UIImageToDataJPEG2(image: image, compressionRatio: compressionRatio) {
//Sample the length of the first result to make sure we are comparing the same size
if index == 1 {
//Report out results
let dataSize = Int(data.count / 1024 / 1024)
print("Data Length: \(dataSize) mb")
}
}
}
let endReading = report_memory()
print("Memory at finish: \(Int(endReading / 1024 / 1024)) mb")
let delta = (Int(Int(endReading) - Int(startReading)) / 1024 / 1024)
print("Memory delta: \(delta) mb")
}

Test Results

Below is the result broken down by iteration.

Results for 1 Iteration

uiimagetodata-1

Results for 2 Iterations

uiimagetodata-2

Results for 14 Iterationsuiimagetodata-14

Results for 20 Iterations

UIImageToData-20.png

Results for 50 Iterations

uiimagetodata-50

Conclusion

I am sure there is a ton of optimizations that could be made to bring these numbers down. Overall the usage of UIImageJPEGRepresentation wrapped within an Autorelease Pool looks to be the best approach.  There is more work to be done on why the compression ratio has an inconsistent impact, my guess is this is a result to caching within the test.

Although the ImageIO strategy was better in a single execution scenario I question if the proper handling of image orientation would reduce or eliminate any of your memory savings.

Caveats

There are more comprehensive approaches out there. This is just an experiment using Playgrounds and basic memory sampling.  It doesn’t take into account any memory spikes that happen outside of the two sampling points or any considerations around CPU utilization.

Resources

  • Gist of the Swift Playground is available here

Stopping Tap Gesture from bubbling to child controls

The default behavior is for your tap or other gestures to bubble up to their child controls. This avoids the need to add a recognizer on all of your child controls. But this isn’t always the behavior you are looking for. For example imagine you create the below modal view. You want to add a tap gesture recognizer to the view itself so when your user taps the grey area it closes the modal. But you don’t want the gesture to be triggered when a tap is made on the content UIView of the modal.

modal-example

First adding the UITapGestureRecognizer

For our use case the first thing we need to do is add a UITapGestureRecognizer to the root UIView of the UIViewController. This will close our UIViewController when the grey area is tapped.

internal class ConfirmationViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
let tap = UITapGestureRecognizer(target: self, action: #selector(self.handleTapOffModal(_:)))
tap.delegate = self
view.addGestureRecognizer(tap)
view.isUserInteractionEnabled = true
}
}

Limiting the scope of the Gesture

So that the UITapGestureRecognizer isn’t triggered when a tap is made to the content UIView we simply need to add protocol method to restrict the tap to the view it is associated with.

extension ConfirmationViewController: UIGestureRecognizerDelegate {
func gestureRecognizer(_ gestureRecognizer: UIGestureRecognizer, shouldReceive touch: UITouch) -> Bool {
return touch.view == gestureRecognizer.view
}
func handleTapOffModal(_ sender: UITapGestureRecognizer) {
dismiss(animated: true, completion: nil)
}
}

Putting it all together

Below is the full code associated with the modal UIViewController. If you are not familiar with how to create a modal UIViewController I would recommend checking out Tim Sanders tutorial here.

import UIKit
internal class ConfirmationViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
let tap = UITapGestureRecognizer(target: self, action: #selector(self.handleTapOffModal(_:)))
tap.delegate = self
view.addGestureRecognizer(tap)
view.isUserInteractionEnabled = true
}
}
extension ConfirmationViewController {
@IBAction func cancel_click(_ sender: Any) {
dismiss(animated: true, completion: nil)
}
@IBAction func confirm_click(_ sender: Any) {
print("Perform Confirmation action")
dismiss(animated: true, completion: nil)
}
}
extension ConfirmationViewController: UIGestureRecognizerDelegate {
func gestureRecognizer(_ gestureRecognizer: UIGestureRecognizer, shouldReceive touch: UITouch) -> Bool {
return touch.view == gestureRecognizer.view
}
func handleTapOffModal(_ sender: UITapGestureRecognizer) {
dismiss(animated: true, completion: nil)
}
}

Swift excluding files from iCloud backup

Having your device automatically back-up to iCloud is one of the better services Apple provides.

Although not new, iCloud (introduced in June of 2011) back-up is one of the better services Apple has rolled out. If used right this removes a whole class of end user data lose fears.

However as an app developer you need to take this into consideration. Where it is privacy, size constraints, or other you need to be aware what you might be sending to iCloud.

You might wish to not back-up specific files. To do this you must set the file’s isExcludedFromBackup attribute to true. Below is a helper method to help get you started.

struct FileHelpers {
@discardableResult static func addSkipBackupAttribute(url: URL) throws -> Bool {
var fileUrl = url
do {
if FileManager.default.fileExists(atPath: fileUrl.path) {
var resourceValues = URLResourceValues()
resourceValues.isExcludedFromBackup = true
try fileUrl.setResourceValues(resourceValues)
}
return true
} catch {
print("failed setting isExcludedFromBackup \(error)")
return false
}
}
}

Check if GestureRecognizer added

One of my new projects dynamically generates a UI based on a DSL entered by the user. This leads to a host of defensive coding challenges since there is multiple code paths that can add controls and their associated gesture recognizers.

I found it helpful to add guard statements that stop recognizers from being added twice. The same approach is used for controls, but that is a different topic.

The below utility function can be used to check if the provided recognizer is already in the recognizer collection associated with an object.

func containsGestureRecognizer(recognizers: [UIGestureRecognizer]?, find: UIGestureRecognizer) -> Bool {
if let recognizers = recognizers {
for gr in recognizers {
if gr == find {
return true
}
}
}
return false
}

The following code shows the utility function in action. All you have to do is incorporate containsGestureRecognizer into your guard statements.

/// Example adding it the first time
let recognizer = UITapGestureRecognizer(target: self, action: #selector(self.handleTap(_:)))
view.addGestureRecognizer(recognizer)
/// Test if gesture already added, if added return else add the recognizer
guard containsGestureRecognizer(recognizers: view.gestureRecognizers, find: recognizer) == false else { return }
/// Add the recognizer
view.addGestureRecognizer(recognizer)
view raw example.swift hosted with ❤ by GitHub

Although really an edge case this was a fun bit of code to try out.

Removing CGPDFDocument Passwords

Working with PDFs on mobile is a pain. On the bright side at least Apple provides us CGPDFDocument

On the bright side Apple does provide CGPDFDocument. If you’re on Android you are left to 3rd party alternatives such as iText and PDFBox.

However examples on how to use CGPDFDocument are limited to the bare basics. On a recent project, I though I had a pretty basic requirement. Provide the user with the ability to remove the password for a protected PDF file. How hard could this be? Unfortunately I wasn’t able to find any examples on how to do this so decided to put my own together.

The most important thing you need to know about working with CGPDFDocument is it is immutable. This means you can’t just call unlockWithPassword and update a property to remove the password.

This means you will need to first unlock the PDF, then create an entirely new document. If you are working with large files you will need to be careful about the memory you are using. Instruments will be your best friend.

Let’s walk through an example. First we need to load a PDF file into a Data object as shown below.

let pdfData = try Data(contentsOf: URL(fileURLWithPath: "Path to pdf"))

Next you need to unlock your PDF using the existing password. In the example below we create a helper function called unlock that takes the Data from our PDF file and returns an unlocked CGPDFDocument. If the method is unable to either cast the Data object or unlock using the provided password a nil is returned.

if let pdf = unlock(data: pdfData, password: "Hello World") {
print("You now have an unlocked CGPDFDocument")
}
func unlock(data: Data, password: String) -> CGPDFDocument? {
if let pdf = CGPDFDocument(CGDataProvider(data: data as CFData)!) {
guard pdf.isEncrypted == true else { return pdf }
guard pdf.unlockWithPassword("") == false else { return pdf }
if let cPasswordString = password.cString(using: String.Encoding.utf8) {
if pdf.unlockWithPassword(cPasswordString) {
return pdf
}
}
}
return nil
}
view raw unlock.swift hosted with ❤ by GitHub

We now have an unlocked CGPDFDocument object. Since this is immutable we need to use this as the source to create a new CGPDFDocument without a password. This is done by looping through the now unlocked CGPDFDocument and copying each CGPDFPage into a new CGPDFDocument. The copyPDFtoData helper function in the below example shows how the Core Graphics context is managed as part of the looping process. The end result is a Data object that is a copy of the PDF we started with, but without the password.

if let pdf = unlock(data: pdfData, password: "Hello World") {
print("You now have an unlocked CGPDFDocument")
print("Create a copy of the unlocked CGPDFDocument")
let pdfWithoutPassword = copyPDFtoData(pdf: pdf)
print("You how have a pdf without password information")
}
func copyPDFtoData(pdf: CGPDFDocument) -> Data {
let data = NSMutableData()
autoreleasepool {
let pageCount = pdf.numberOfPages
UIGraphicsBeginPDFContextToData(data, .zero, nil)
for index in 1...pageCount {
let page = pdf.page(at: index)
let pageRect = page?.getBoxRect(CGPDFBox.mediaBox)
UIGraphicsBeginPDFPageWithInfo(pageRect!, nil)
let ctx = UIGraphicsGetCurrentContext()
ctx?.interpolationQuality = .high
// Draw existing page
ctx!.saveGState()
ctx!.scaleBy(x: 1, y: -1)
ctx!.translateBy(x: 0, y: -(pageRect?.size.height)!)
ctx!.drawPDFPage(page!)
ctx!.restoreGState()
}
UIGraphicsEndPDFContext()
}
return data as Data
}

Keep in mind this isn’t an exact copy. The CGPDFDocument info and other meta data won’t be copied as part of this process. This can easily be added to the copyPDFtoData but was overkill for this example.

For convenience I’ve combined the above helper functions into a single class for easy maintenance and experimentation.

//
// PDF.swift
//
// Created by Ben Bahrenburg on 1/1/17.
// Copyright © 2017 bencoding.. All rights reserved.
//
import UIKit
open class PDFHelpers {
class open func unlock(data: Data, password: String) -> CGPDFDocument? {
if let pdf = CGPDFDocument(CGDataProvider(data: data as CFData)!) {
guard pdf.isEncrypted == true else { return pdf }
guard pdf.unlockWithPassword("") == false else { return pdf }
if let cPasswordString = password.cString(using: String.Encoding.utf8) {
if pdf.unlockWithPassword(cPasswordString) {
return pdf
}
}
}
return nil
}
class open func removePassword(data: Data, existingPDFPassword: String) throws -> Data? {
if let pdf = unlock(data: data, password: existingPDFPassword) {
let data = NSMutableData()
autoreleasepool {
let pageCount = pdf.numberOfPages
UIGraphicsBeginPDFContextToData(data, .zero, nil)
for index in 1...pageCount {
let page = pdf.page(at: index)
let pageRect = page?.getBoxRect(CGPDFBox.mediaBox)
UIGraphicsBeginPDFPageWithInfo(pageRect!, nil)
let ctx = UIGraphicsGetCurrentContext()
ctx?.interpolationQuality = .high
// Draw existing page
ctx!.saveGState()
ctx!.scaleBy(x: 1, y: -1)
ctx!.translateBy(x: 0, y: -(pageRect?.size.height)!)
ctx!.drawPDFPage(page!)
ctx!.restoreGState()
}
UIGraphicsEndPDFContext()
}
return data as Data
}
return nil
}
}

For more comprehensive PDF handling check out my PDFUtilities project.

Checking if device Passcode is enabled using Swift

If you spent any time building apps for the Enterprise or business space odds are you have turn into the requirement to check if the device has a passcode enabled.

Mobile Device Management (MDM) Solutions are full of tools that handle this for you.  More often I’m finding that organizations are opting for lighter weight solutions to simplify their deployments.

The approach of checking for a passcode would easily become an anti-pattern in that the user at anytime can disable this feature.  I would recommend keeping this in mind and implement the check as only one part of a larger more comprehensive security strategy.

I would call the approach of checking for a passcode somewhat of an anti-pattern in that the user could disable this feature at anytime. If you implement this approach I would highly recommend that this is only one part of your security strategy.

Starting in iOS 8 you could finally check, without jailbreaking, if the device had a passcode set. All you have to do is create a new Keychain entry using the new kSecAttrAccessibleWhenPasscodeSetThisDeviceOnly access option.

Below is an example of using the Keychain to test if the device passcode is enabled.

@available(iOS 8.0, *)
public func devicePasscodeEnabledUsingKeychain() -> Bool {
let query: [String:Any] = [
kSecClass as String : kSecClassGenericPassword,
kSecAttrAccount as String : UUID().uuidString,
kSecAttrAccessible as String: kSecAttrAccessibleWhenPasscodeSetThisDeviceOnly,
kSecValueData as String: "HelloWorld".data(using: String.Encoding.utf8)!,
kSecReturnAttributes as String : kCFBooleanTrue
]
var dataTypeRef: AnyObject?
var status = withUnsafeMutablePointer(to: &dataTypeRef) { SecItemCopyMatching(query as CFDictionary, UnsafeMutablePointer($0)) }
if status == errSecItemNotFound {
let createStatus = SecItemAdd(query as CFDictionary, nil)
guard createStatus == errSecSuccess else { return false }
status = withUnsafeMutablePointer(to: &dataTypeRef) { SecItemCopyMatching(query as CFDictionary, UnsafeMutablePointer($0)) }
}
guard status == errSecSuccess else { return false }
return true
}

Then in iOS 9 Apple introduced a much better way to perform this check. In the same way you check if Touch ID is enabled you can check if the device has a passcode.

Below is an example on how you can do this using canEvaluatePolicy.

public func devicePasscodeEnabled() -> Bool {
return LAContext().canEvaluatePolicy(.deviceOwnerAuthentication, error: nil)
}

The below PasscodeUtils.swift combines the two approaches to allow for you to support this functionality back to iOS 8.

//
// PasscodeUtils.swift
//
// Created by Ben Bahrenburg on 12/31/16.
// Copyright © 2016 bencoding.com. All rights reserved.
//
import Foundation
import LocalAuthentication
public final class PasscodeUtils {
fileprivate var context = LAContext()
@available(iOS 8.0, *)
private func devicePasscodeEnabledUsingKeychain() -> Bool {
let query: [String:Any] = [
kSecClass as String : kSecClassGenericPassword,
kSecAttrAccount as String : UUID().uuidString,
kSecAttrAccessible as String: kSecAttrAccessibleWhenPasscodeSetThisDeviceOnly,
kSecValueData as String: "HelloWorld".data(using: String.Encoding.utf8)!,
kSecReturnAttributes as String : kCFBooleanTrue
]
var dataTypeRef: AnyObject?
var status = withUnsafeMutablePointer(to: &dataTypeRef) { SecItemCopyMatching(query as CFDictionary, UnsafeMutablePointer($0)) }
if status == errSecItemNotFound {
let createStatus = SecItemAdd(query as CFDictionary, nil)
guard createStatus == errSecSuccess else { return false }
status = withUnsafeMutablePointer(to: &dataTypeRef) { SecItemCopyMatching(query as CFDictionary, UnsafeMutablePointer($0)) }
}
guard status == errSecSuccess else { return false }
return true
}
public func devicePasscodeEnabled() -> Bool {
if #available(iOS 9.0, *) {
return context.canEvaluatePolicy(.deviceOwnerAuthentication, error: nil)
} else {
return devicePasscodeEnabledUsingKeychain()
}
}
@available(iOS 8.0, *)
public func deviceBiometricsEnabled() -> Bool {
return context.canEvaluatePolicy(.deviceOwnerAuthenticationWithBiometrics, error: nil)
}
}

Simplifying using Keychain Access Groups

Keychain sharing is an easy and secure way to share information between apps on the same device. This is even more useful when you think about sharing information between an app and it’s extensions. In my opinion Keychain is just as easy as using NSUserDefaults but provides a greater level of security.

Although Keychain sharing is easy to use, I found the use of Access Groups or kSecAttrAccessGroup somewhat tricky.  There are a few different ways of working with Keychain Groups I thought it might be useful to go through the approach I adopted for others looking to implement Keychain sharing within their own apps.

First you will need to setup Keychain sharing in your app.  The below are some great tutorials to get you started.

Next, you will need to try to pass information between apps in your Keychain Group.  Here is where I ran into my problem.  At first I assumed that you simply set the kSecAttrAccessGroup value to the name of the Keychain Group you want to use.  No matter the KeyChain Group value I tried, it always returned a nil.

Why?  If you open your app’s entitlement file you can see why. Your keychain access group is a calculated value of your Keychain Group and your $(AppIdentifierPrefix).

<key>keychain-access-groups</key>
<array>
<string>$(AppIdentifierPrefix)com.bencoding.awesome-group</string>
</array>
view raw entitlement.xml hosted with ❤ by GitHub

This means if you set kSecAttrAccessGroup to your keychain access group of com.bencoding.awesome-group it would always return nil. You would need to add the App ID Prefix (also called Team ID) to your Keychain Group. In our example this would be something like ABC1234DEF.com.bencoding.awesome-group.

Getting your App ID Prefix from the developer portal isn’t the end of the world and the tutorials at the beginning of this article do a good job covering why this is necessary.

What is another “magic” string in your app?

If you are building enterprise apps which are often resigned or just hate using “magic” strings this approach leaves you looking for an alternative.

Another approach is to use the Keychain’s own meta data find the App ID Prefix .  All you need to do is create a keychain item and then read the returned kSecAttrAccessGroup value from the meta data.  Using the provided kSecAttrAccessGroup value you can parse the  App ID Prefix and default Keychain Access Group.  Removing (or at least reducing) the need for “magic” strings.

I’ve encapsulated this approach into the KeyChainAccessGroupHelper.swift shown at the end of this post.

This approach makes using Keychain sharing with any of the popular Keychain libraries easier.  Below demonstrates how to use KeyChainAccessGroupHelper with the KeychainAccess library.

let info = KeyChainAccessGroupHelper.getAccessGroupInfo()
//Use the default Keychain group
let keychain = Keychain(service: "com.example.github-token", accessGroup: info.rawValue)
//Use the prefix to connect to another keychain group
let keychain = Keychain(service: "com.example.github-token", accessGroup: String(format: "%@.com.bencoding.awesome-group", info.prefix))
view raw example.swift hosted with ❤ by GitHub

The below gist shows the implementation details of KeyChainAccessGroupHelper.swift
//
// KeyStorage - Simplifying securely saving key information
//
// KeyChainAccessGroupHelper.swift
// Created by Ben Bahrenburg on 12/30/16.
// Copyright © 2017 bencoding.com. All rights reserved.
//
import Foundation
import Security
public struct KeyChainAccessGroupInfo {
public var prefix: String
public var keyChainGroup: String
public var rawValue: String
}
open class KeyChainAccessGroupHelper {
public class func getAccessGroupInfo() -> KeyChainAccessGroupInfo? {
let query: [String:Any] = [
kSecClass as String : kSecClassGenericPassword,
kSecAttrAccount as String : "detectAppIdentifierForKeyChainGroupIdUsage",
kSecAttrAccessible as String: kSecAttrAccessibleAlwaysThisDeviceOnly,
kSecReturnAttributes as String : kCFBooleanTrue
]
var dataTypeRef: AnyObject?
var status = withUnsafeMutablePointer(to: &dataTypeRef) { SecItemCopyMatching(query as CFDictionary, UnsafeMutablePointer($0)) }
if status == errSecItemNotFound {
let createStatus = SecItemAdd(query as CFDictionary, nil)
guard createStatus == errSecSuccess else { return nil }
status = withUnsafeMutablePointer(to: &dataTypeRef) { SecItemCopyMatching(query as CFDictionary, UnsafeMutablePointer($0)) }
}
guard status == errSecSuccess else { return nil }
let accessGroup = ((dataTypeRef as! [AnyHashable: Any])[(kSecAttrAccessGroup as String)] as! String)
if accessGroup.components(separatedBy: ".").count > 0 {
let components = accessGroup.components(separatedBy: ".")
let prefix = components.first!
let elements = components.filter() { $0 != prefix }
let keyChainGroup = elements.joined(separator: ".")
return KeyChainAccessGroupInfo(prefix: prefix, keyChainGroup: keyChainGroup, rawValue: accessGroup)
}
return nil
}
}

KeyChainAccessGroupHelper is also used as part of the KeyStorage project.

Logging Exceptions to Google Analytics Using SwiftyBeaver

There has been an avalanche of Swift based logging frameworks lately.  After trying several, I found SwiftyBeaver was both flexible and simple enough to move all of my apps onto.  Similar to most of the other logging frameworks, SwifyBeaver is extensible. But, unlike many others, there is no real magic injected into the framework making it easy to read and hopefully maintain.  This is best demonstrated by how compacted it’s plugin system is.  You really only need to write you business specific information, the rest is handled for you.

I use Google Analytics for behavior tracking and basic exception tracking today.  Although all of my Google Analytics code is centralized exception reporting is handled explicitly.  With the move to SwiftyBeaver I wanted to see how reporting exceptions to Google Analytics could be handled automatically as part of my logging strategy.  To accomplish this I created the SwiftyBeaver Google Analytics Exception Logger, available here.  Just as the very long name indicates this plugin will automatically post error information to Google Analytics.

Before you start

The Google Analytics Exception plugin requires that you first install SwiftyBeaver and Google Analytics.  You can find information on how to do this below.

After both SwiftyBeaver and Google Analytics have been installed you need to copy the plugin into your project. Instructions for doing this are available here.  You’re now ready to start configuring logging in your project.

Creating your Logger

Creating a logger in your project is extremely simple. Typically you will want to add the below to the top of your AppDelegate.swift so you can use logging throughout your project.

import SwiftyBeaver
let logger = SwiftyBeaver.self

Adding the Google Analytics Logger Destination

Now that you have created your SwiftyBeaver logger you need to add destinations.  Without adding any destinations SwiftyBeaver wont actually do anything.  For this example I’m going to add two destinations. The first will be the built-in Console destination which simply writes to the Xcode console.

let console = ConsoleDestination()
logger.addDestination(console)

Next we’ll add the Google Analytics Exception Logger.  When creating the GABeaver plugin you must added your Google Analytics key.  This will be used when reporting Exceptions.  You can also specify the reporting threshold.  This parameter controls the minimum logging level that should be reported to Google Analytics as an exception. By default this is set to only report error levels or greater.  If you wanted to report warnings or higher you could simply provide a threshold of warning and the plugin will automatically send both warnings and errors.

Below illustrates how to add the Google Analytics Exception plugin with the default settings.

let gaErrorLogger = GABeaver(googleAnalyticsKey: &amp;quot;your GA Key&amp;quot;)
logger.addDestination(gaErrorLogger)

Optional Configurations

By default only Error Log messages will be sent to Google Analytics.  You can change this by setting the Threshold property on the logger.

For example, you can record all message with a level of WARNING or great by doing the following:

gaErrorLogger.Threshold = SwiftyBeaver.Level.Warning

You can also configure the logger to write to the console each time a message is logged. To enable console logging, set the printToConsole property to true as shown below:

gaErrorLogger.printToConsole = true

More Information

Additional information is available on github at SwiftyBeaver-GA-Exception-Logger.

SwiftyBeaver