Useful obscure Foundation types in Swift - SwiftRocks

Useful obscure Foundation types in Swift

Foundation has a huge amount of classes that makes iOS development easier but we only interact with a few of them, like Data and URLSession. As these types related to things that are used daily, they end up being more popular than other ones. But what about the other ones?

There's a bunch of Foundation types are so situational that people doubt they even existed in first place. In fact, they are so rarely mentioned as the solutions to problems that people end up making code to mimic things that already exist in the language. But they exist, and although they are old most of these types are still very useful. Let's see a few of them.

NSScanner

NSScanner can progressively extract numbers and strings from a base string similarly to how scanf works in C:

public func extractIntsFrom(string: String) -> [Int] {
    var result: [Int] = []
    let scanner = Scanner(string: string)
    // Jump over everything that isn't a number
    scanner.charactersToBeSkipped = CharacterSet.decimalDigits.inverted
    var pointer: Int = 0
    while scanner.isAtEnd == false {
        if scanner.scanInt(&pointer) {
            result.append(pointer)
        }
    }
    return result
}

let string = "1M very L337 700"
let ints = extractIntsFrom(string: string)
// [1, 337, 700]

It has many variations like scanString, scanDouble and scanHexInt, and can be configured to scan at specific locations in a string and to take case sensitivity in consideration. This is a more direct solution for searching occurrences of a string when you're looking for specific patterns (like being a number) instead of a concrete match.

The fact that it comes from OBJ-C means that it isn't very Swifty (given away here by the use of pointers to a dummy object), but you can wrap it up in an extension to make it better to the end user.

NSCountedSet

There are many problems in computer science that requires you to keep track of the quantity of a certain element, like the classic anagram interview problem:

func isAnagram(_ first: String, _ second: String) -> Bool {
    guard first.count == second.count else {
        return false
    }
    var dict = [Character: Int]()
    for character in first {
        firstDict[character, default: 0] += 1
    }
    for character in second {
        dict[character, default: 0] -= 1
        if dict[character] == 0 {
            dict[character] = nil
        }
    }
    return dict.isEmpty
}

This is easy to solve with dictionaries with Int values, but we don't need to, because that's exactly what NSCountedSet does.

func isAnagram(_ first: String, _ second: String) -> Bool {
    guard first.count == second.count else {
        return false
    }
    let countedSet = NSCountedSet(array: Array(first))
    for character in second {
        countedSet.remove(c)
    }
    return countedSet.count == 0
}

Elements can be repeatedly added to the set with countedSet.add(element) and have their counts inspected with countedSet.count(element). If the count reaches zero, the element is removed from the set.

CFBinaryHeap

The Swift Standard Library has a notorious lack of data structures, but you can find Tree and Heap implementations in Foundation in the shape of CFTree and CFBinaryHeap. Heaps are a very efficient way to implement priority queue structures, and since they're very extensive to code, CFBinaryHeap can save you some time.

I'm adding it to the list because it's obscure and cool, but I have to say that it's unfortunately very hard to use. CFBinaryHeap is a C CoreFoundation class that isn't bridged to Foundation, so you won't be able to use without managing tons of pointers. Your best bet would be to write or use a wrapper like MCBinaryHeap.

let array = [8,3,5,4,1]
let heap = MCBinaryHeap(array: array)
heap?.popMinimumObject() // 1
heap?.popMinimumObject() // 3
heap?.popMinimumObject() // 4
heap?.popMinimumObject() // 5
heap?.popMinimumObject() // 8

Priority queues are popular in graph searching problems, and can be used to bring efficiency to anything where you need to keep track of the smallest or largest elements in an unordered array.

NSCache

NSCache is a collection type that works similarly to a dictionary, but it has two important key differences. First, it does not copy the key objects that are put into it, and second, it automatically removes entries from itself it the system is running out of memory.

The actual policies for removing entries are cryptic, but if you have objects that expensive to create, usage of NSCache can be a very system friendly alternative to regular dictionary caches. Here's how it can be used for caching UIImages:

final class ImageDownloader {
    let client: HTTPClient
    let cache = NSCache<NSString, NSData>()

    init(client: HTTPClient) {
        self.client = client
    }

    func load(imageUrl: URL, intoImageView imageView: UIImageView) {
       let key = imageUrl.absoluteString as NSString
        func apply(data: NSData) {
            let image = UIImage(data: data as Data)
            imageView.image = image
        }
        if let cachedData = cache.object(forKey: key) {
            apply(data: cachedData)
            return
        } else {
            client.data(from: imageUrl) { data in
                cache.setObject(data as NSData, forKey: key)
                apply(data: data as NSData)
            }
        }
    }
}

NSOrderedSet

Sets are great for keeping track of elements when duplication doesn't matter, but because set access is O(1), there's no guarantee that they will be ordered when you do so. As the name implies, NSOrderedSet works like a regular Set with the exception that the elements are ordered.

let set = NSMutableOrderedSet()
set.add(1)
set.add(4)
set.add(1)
set.add(1)
set.add(1)
set.add(6)
set.add(4)
set.add(6)
for a in set {
    print(a)
    // 1, 4, 6
}

Because this is an old type you'll notice that there's no generics in it -- all operations are based on the Any type. This isn't very Swifty, so you'll probably want to wrap it in a Swift type:

class OrderedSet<T: Hashable>: Sequence {
    private let _set = NSMutableOrderedSet()
    init() {}

    func makeIterator() -> NSFastEnumerationIterator {
        return _set.makeIterator()
    }

    func add(_ element: T) {
        _set.add(element)
    }

    func remove(_ element: T) {
        _set.remove(element)
    }
}

NSByteCountFormatter

There are tons of obscure formatters in Foundation, but I specifically like a few of them. ByteCountFormatter formats byte counts into human readable file size formats. Useful if your apps downloads content as you don't need to calculate these values manually.

let bytes = 1024 * 1024
let formatter = ByteCountFormatter()
formatter.allowsNonnumericFormatting = false // Uses '0' instead of 'Zero'
formatter.countStyle = .file
let string = formatter.string(fromByteCount: Int64(bytes))
// 1 MB

NSDataDetector

NSDataDetector is similar to NSScanner, with the difference that it extracts contextual data from strings like phone numbers, addresses and links.

let string = "I write for https://swiftrocks.com and my phone is 555-111-111."
let range = NSRange(0..<string.count)

let types: NSTextCheckingResult.CheckingType = [.link]
let dataDetector = try NSDataDetector(types: types.rawValue)
dataDetector.enumerateMatches(in: string, options: [], range: range) { (match, _, _) in
    switch match?.resultType {
    case .link?:
        print(match?.url)
        // https://swiftrocks.com
    case .phoneNumber?:
        print(result?.phoneNumber)
        // "555-111-111"
    default:
        return
    }
}

The downside is that it relies on Foundation's regex API, once again, because it's old. This is again not very Swifty, but NSDataDetector is still a very solid type if you need to extract such information.

Conclusion

There are tons of useful types in Foundation that we don't hear much of. Knowing them can help you prevent writing unnecessary code.

Follow me on my Twitter (@rockthebruno), and let me know of any suggestions and corrections you want to share.

References and Good reads

NSScanner
NSCountedSet
NSOrderedSet
NSByteCountFormatter
NSDataDetector
NSCache
CFBinaryHeap

Contact Info

Bruno Rocha works as Senior iOS Software Engineer at iFood and is the developer of several open sources libraries like SwiftInfo and SwiftShield.
bruno@swiftrocks.com

Newsletter

Click here to subscribe to my newsletter to get notified of new posts by e-mail.

RSS / Social

Info

This website's static HTML pages are generated by WriteIt.