Swift: Deconstruct SPF: Strings and SubStrings
Tags: swift, beginners, substrings, generics
Published: 2021-05-24 06:35:02

I have been working on my SPF project in **Swift** of late. As part of this I have started to deal with **substrings**. "*A slice of a string*" according to [Apple Documentation](https://developer.apple.com/documentation/swift/substring). In dealing with this, I have found myself quite often converting substrings back to normal strings. Today we will look at how we can pass **substrings** directly by making our functions **generic**.

## Reference
According to the documentation listed above. There are two options when dealing with substrings.
  1. Create a new string by calling `String()` on the substring.
  2. Create a function which is **generic** over the **StringProtocol**.

Initially I was taking option 1. As I am new to **Swift**. But It just seemed a bit excessive to keep creating a new **String** from a **substring**.

### How am creating all these substrings

I am doing a lot of `split()` calls as I am parsing a given string and breaking it down into its parts.

The documentation for [split](https://developer.apple.com/documentation/swift/string/2894564-split). Note there are several flavours of split.

```swift
func split(separator: Character, maxSplits: Int = Int.max,
           omittingEmptySubsequences: Bool = true) -> [Substring]
```

### Original Code Example
Here is some of the original `parse()` function code.
```swift
mutating func parse() {
    let splitString = self.source.split(separator: " ")
    for subString in splitString {
        if (subString.starts(with: "v=") || subString.starts(with: "spf2")) {
            self.version = String(subString)
        }
        --snip--
        else if (subString.range(of: "redirect=") != nil) {
            let qmSplit = subString.split(separator: "=")
            self.redirect = Mechanism(k: MechanismKind.Redirect,
                                      q: identifyQualifier(prefixStr: String(qmSplit[0])),
                                      m: String(qmSplit[1]))
            self.is_redirect = true
        }
        else if (subString.range(of: "ip4:") != nil) {
            self.ip4 = processIp(ipStr: String(subString), kind: MechanismKind.Ip4)
        }
        else if (subString.range(of: "ip6:") != nil) {
                self.ip6 = genericProcessIp(subString: subString, kind: MechanismKind.Ip6)
        }
        --snip---
    }
}

private func processIp(ipStr: String, kind: MechanismKind) -> Mechanism {
    let qmSplit = ipStr.split(separator: ":", maxSplits: 1, omittingEmptySubsequences: true)
    let q = identifyQualifier(prefixStr: String(qmSplit[0]))
    return Mechanism(k: kind, q: q, m: String(qmSplit[1]))
}
```

Take note that the **ip6:** block is using a `genericProcessIp()` function.  
This should become clear later in the article.  


Let's look at the code block starting with `else if (subString.range(of: "ip4:") != nil)`.

#### range()
This function is being used to check if the strings **contains** the given string. From what I have seen. There is no `contains()` function. I could well be wrong.

Within this block I have a function `processIp()`. Its job is to take the given string and create a **Mechanism struct** of **MechanismKind.Ip4**.

#### processIp()
```swift
private func processIp(ipStr: String, kind: MechanismKind) -> Mechanism {
    let qmSplit = ipStr.split(separator: ":", maxSplits: 1, omittingEmptySubsequences: true)
    let q = identifyQualifier(prefixStr: String(qmSplit[0]))
    return Mechanism(k: kind, q: q, m: String(qmSplit[1]))
}
```
It takes two arguments, a **String** and a **MechanismKind**. But if you look above in the block for `ip4` I am passing in `String(subString)`. So I am first needing to make my **substring** into a new **String** and pass that to `processIp()`

Within `processIp()`, I again split the string. And once more I am calling `String()` to make a new string before passing it to `identifyQualifier()`.  
This all seems like a lot of converting / splitting.

### Making processIp Generic

Let's see the two together to make things easier to compare.
```swift
private func processIp(ipStr: String, kind: MechanismKind) -> Mechanism {
    let qmSplit = ipStr.split(separator: ":",
                              maxSplits: 1,
                              omittingEmptySubsequences: true)
    let q = identifyQualifier(prefixStr: String(qmSplit[0]))
    return Mechanism(k: kind, q: q, m: String(qmSplit[1]))
}

private func genericProcessIp<S: StringProtocol>(subString s: S, kind: MechanismKind) -> Mechanism {
    let qm = s.split(separator: ":",
                     maxSplits: 1,
                     omittingEmptySubsequences: true)
    let q = genericIdentifyQualifier(prefix: qm[0])
    return Mechanism(k: kind, q: q, m: String(qm[1]))
    
}
```
The only real difference is the function declaration.  
`<S: StringProtocol>(subString s: S, kind: MechanismKind)`  
This function conforms to or supports the **StringProtocol**  
In the documentation it is declared without a label.  
`<S: StringProtocol>(_ s: S, ......)`   
This function conforms to or supports the **StringProtocol**

You might also notice that `identifyQualifier()` has also been made **generic**.  
There is the code for these two version of the function.
```swift
private func identifyQualifier(prefixStr: String)  -> Qualifier {
    let char = String(prefixStr.prefix(1))
    let c = Character(char)
    if !c.isLetter {
        if char == "+" {
            return Qualifier.Pass
        } else if char == "-" {
            return Qualifier.Fail
        } else if char == "~" {
            return Qualifier.SoftFail
        } else if char == "?" {
            return Qualifier.Neutral
        }
    }
    return Qualifier.None
}

private func genericIdentifyQualifier<S: StringProtocol>(prefix s: S)  -> Qualifier {
    let char = String(s.prefix(1))
    let c = Character(char)
    if !c.isLetter {
        switch char {
        case "+":
            return Qualifier.Pass
        case "-":
            return Qualifier.Fail
        case "~":
            return Qualifier.SoftFail
        case "?":
            return Qualifier.Neutral
        default:
            return Qualifier.None
        }
    }
    return Qualifier.None
}
```
#### prefix()
This function also returns **substring** [Documentation](https://developer.apple.com/documentation/swift/string/2894830-prefix).

## Closing
I suspect I can refactor some of the `split()` calls on substring out of the code, by finding the `index(of: )` and then the end of the *substring*.

I am not sure this is overly useful for others, But it was an interesting find for me. I hope it does help others.