Simple Text Prediction This is a simple implementation of text prediction using a list of keywords and UITextChecker. 

This is working correctly, but I think I could have implemented better. My goal is to use a protocol oriented approach. 

protocol KeywordSuggester {
    func suggestion(word: String) -&gt; String?
    func loadKeywords() -&gt; Set&lt;String&gt;
}

class EmojiKeywordSuggester: KeywordSuggester {
    var keywords: Set&lt;String&gt; = []
    let language = "en_US"
    private let textChecker = UITextChecker()

    init() {
        self.keywords = self.loadKeywords()
    }

    func suggestion(word: String) -&gt; String? {
        var completions = (textChecker.completionsForPartialWordRange(word.fullRange, inString: word, language: language) ?? []) as! [String]
        let guesses = (textChecker.guessesForWordRange(word.fullRange, inString: word, language: language) ?? []) as! [String]

        completions.appendContentsOf(guesses)
        completions = completions.filter(self.keywords.contains)

        guard completions.count &gt; 0 else { return nil }
        return completions[0]
    }

    func loadKeywords() -&gt; Set&lt;String&gt; {
        //TODO: load CSV from file
        return ["happy", "smile", "nature", "mushroom", "eye", "poop", "icecream", "food", "grin", "face"]
    }
}
First of all, these two lines:


guard completions.count &gt; 0 else { return nil }
return completions[0]



can be simplified as just this:

return completions.first


It does exactly the logic you've implemented, except that it's far more clear what the intent is.  Moreover, if ever you need to do this in the case that completions isn't a local variable, your approach is not thread safe.



Taking a look at the bigger picture, I don't understand the purpose of loadKeywords neither as part of the protocol nor as a method itself.

It appears that the only real reason that they exist is because they are used by the suggestion() method.  Since you haven't included the code that is actually using this protocol, I can only guess, but my best guess is that it's completely not needed.  Instead, I would take an approach like this with the protocol:

protocol KeywordSuggesterProtocol {
    func canSuggestForKeyword(keyword: String) -&gt; Bool
    func suggestionForKeyword(keyword: String) -&gt; String
}


Notice here, no optionals.

It potentially make sense for the protocol to simply be the second method and have it return an optional, simply returning nil where the first method would have return false.  But potentially, suggestionForKeyword might be an expensive operation (although I suppose it could be optimized).  So a protocol like this:

protocol KeywordSuggesterProtocol {
    func suggestionForKeyword(keyword: String) -&gt; String?
}


could work as well.

The code that actually makes use of the protocol would look like one of the following two snippets (based on which protocol you're using):

class Foo {
    weak var suggester: KeywordSuggesterProtocol?

    // ...

    func someFuncThatNeedsKeywords(keyword: String) {
        var suggestedWord: String?
        if suggester?.canSuggestForKeyword(keyword) {
            suggestedWord = suggester?.suggestionForKeyword(keyword)
        } 
    }

    // ...
}


... or ...

class Foo {
    weak var suggester: KeywordSuggesterProtocol?

    // ...

    func someFuncThatNeedsKeywords(keyword: String) {
        let suggestedWord = suggester?.suggestionForKeyword(keyword) 
    }

    // ...
}


Hmm, the latter is probably better honestly.



As for your implementation, let's take a look at your initialization...

class EmojiKeywordSuggester: KeywordSuggester {
    var keywords: Set&lt;String&gt; = []
    let language = "en_US"
    private let textChecker = UITextChecker()

    init() {
        self.keywords = self.loadKeywords()
    }

    // stuff
}


First of all, language and keywords should be private.  But also, the loadKeywords method really isn't useful to us at all beyond initializing the keywords, right?  So, let's take this approach...

class EmojiKeywordSuggester: KeywordSuggester {
    private let keywords: Set&lt;String&gt; = {
        //TODO: load CSV from file
        return ["happy", "smile", "nature", "mushroom", "eye", "poop", "icecream", "food", "grin", "face"]   
    }()

    private let language = "en_US"

    private let textChecker = UITextChecker()

    // init no longer necessary
}


Now, it may come a time when you actually want to implement loading the keywords from CSV, in which case, we'd want something like this:

class EmojiKeywordSuggester: KeywordSuggester {
    private let keywords: Set&lt;String&gt;
    private let language = "en_US"
    private let textChecker = UITextChecker()

    init?(csvFilePath: NSURL) {
        guard let loadedKeywords = loadKeywordsFromCSV(csvFilePath) else {
            keywords = []
            return nil
        }
        keywords = loadedKeywords
    }

    private func loadKeywordsFromCSV(csvFilePath: NSURL) -&gt; Set&lt;String&gt;? {
        // logic to load from the URL (which could be a local file URL)
        // this can of course fail, and return nil
    }
}


It's a failable initializer that fails when we can't load the keywords from the file path.