脱・文字列ハードコーディング

SwiftがiOSの主な開発言語になってから、多くの良いプログラミング習慣が標準になっています。
型安全な設計やコンパイル時のエラー検出が当たり前になりましたが、まだSwiftの型システムを活用せずに、Objective-C時代から残る慣習でランタイムエラーになりやすいところがあります。

今回の記事は、古くてインタフェースが良くないAPIをいかに現代のSwiftプロジェクトに取り入れるかという話です。
古いAPIを使う前に、拡張するかラッパークラスを作ることが必要になるかもしれません。
特に`UIFont`と`NSAttributedString`が良くないと思うので二つのライブラリを作りました。
この記事を読みながら付随する`UIFont`ライブラリと`NSAttributedString`のライブラリを参考してください。

ここに付随するライブラリがあります:

**もし気になったらスターをつけると嬉しいです!**

[**Attributed**](https://github.com/Nirma/Attributed)
[**UIFontComplete**](https://github.com/Nirma/UIFontComplete)


# 例：Selector
Swift 2.1 以前は `Selector` はまだただの文字列で、このAPIデザインには二つのデメリットがありました。
一つはコンパイラが`Selector`文字列が本当に存在するメソッド名のチェックをしないため、存在しないメソッド名の場合は実行時にクラッシュしてしまいます。
もう一つの問題は、メソッド名のコード補完が効かないのでミスタイプの可能性が高くなります。

Swift 2.1までの`Selector`使い方：

```swift
// Swift 2.1
navigationItem.rightBarButtonItem = UIBarButtonItem(barButtonSystemItem: .Add,
                                                    target: self,
                                                    action: "addNewMemo")
```

幸いにもSwiftチームがこの問題を解決してくれたので、Swift 2.2でもっと良い構文になりました。

```swift
// Swift 2.2
navigationItem.rightBarButtonItem = UIBarButtonItem(barButtonSystemItem: .Add,
                                                    target: self,
                                                    action: #selector(addNewMemo))
```

Swift 2.2から`Selector`はただの文字列から型になりました。
`#selector(methodName)`の`methodName`が存在しないメソッド名なら、コンパイル時にエラーを検出できます。
この新しい型のおかげで`Selector`系のランタイムクラッシュの可能性がなくなりました。
さらにコード補完でメソッド名が出てくるようになったので、このプログラミングインタフェースがもっと使いやすくなりました。


# 例：UIFont
`UIFont`のコンストラクタは一見簡単ですが、正しいフォント名の文字列をタイプしないといけません。
現在の`UIFont`コンストラクタを使ってフォントオブジェクトを作ろうとしたら下記のようになります：

```swift
let font = UIFont(name: "Arial-BoldItalicMT", size: 12.0)!
```
このインタフェースはあまり良くないと思います。
さきほどの`Selector`と同じ問題があり、フォント名の文字列を間違っていてもコンパイル時に気付く事はできません。
`UIFont`は`FileManager`や`URL`と違って、実行時にiOS標準のフォントの存在が保証されています。

iOS標準のフォントは有限なので、`enum`を使うのが良いと思います。
`UIFont`で使えるフォントを全て`enum`にすると、正しいフォント名を知らなくてもコード補完で使いたいフォントを探せます。

```swift
extension UIFont {
    /// Create a UIFont object with a `Font` enum
    public convenience init?(font: Font, size: CGFloat) {
        let fontIdentifier: String = font.rawValue
        self.init(name: fontIdentifier, size: size)
    }
}
```

```swift
public enum Font: String {
    
    // Font Family: Copperplate
    case copperplateLight = "Copperplate-Light"
    case copperplate = "Copperplate"
    case copperplateBold = "Copperplate-Bold"
    .
    .
    .
    // Font Family: Bodoni 72 Oldstyle
    case bodoniSvtyTwoOSITCTTBook = "BodoniSvtyTwoOSITCTT-Book"
    case bodoniSvtyTwoOSITCTTBold = "BodoniSvtyTwoOSITCTT-Bold"
    case bodoniSvtyTwoOSITCTTBookIt = "BodoniSvtyTwoOSITCTT-BookIt"
}
```

下記のように`UIFont`を宣言できます：

```swift
let font = UIFont(name: .arialBoldItalicMT, size: 12.0)!
```

![UIFontGif](https://camo.githubusercontent.com/3aee495963f78be04af655f32ac3ff7d85f12911/687474703a2f2f692e696d6775722e636f6d2f794259525156422e676966)


`enum`を使えば、コード補完が効くようになります。もし名前が間違っていてもコンパイル時にエラーが検出できます。

# 例：NSAttributedString

`NSAttributedString`を使ったことあるなら、不愉快な経験をしたことがあるかもしれません。
`NSAttributedString`を作るとき、属性をDictionaryで指定しますが、キー名と対応する値の型を調べる必要があります。

例えば、`Chalkduster`というフォントで、文字色を赤、文字に下線を引く`NSAttributedString`オブジェクトを作ろうとすると、下記のようなコードになります。

```swift
let attributes: [String: Any] = [
    NSForegroundColorAttributeName: UIColor.red,
    NSFontAttributeName: UIFont(name: "Chalkduster", size: 24.0)!,
    NSUnderlineStyleAttributeName: 1,
]

let text = NSAttributedString(string: "Hello", attributes: attributes)
```
<img width="186" alt="hello.png" src="https://qiita-image-store.s3.amazonaws.com/0/67950/df059bed-3514-7075-c927-ae2521defbbb.png">


`attributes`のDictionaryの型は `[String: Any]`型なので、 `NSFontAttributeName: UIColor.red`と書いてしまっても、コンパイル時にエラーを検出することはできません。
属性付き文字列に値を設定するたびに、そのキーの識別子を確認する必要がありますし、キーに対する値のコード補完も効かないため、正しい値を見つけることができません。

このインターフェースは現代のAPI標準と同じではなく、不便だとみなされるべきだと私は考えています。
私たちができることは、単純な薄いラッパーにこのインタフェースをラップすることです。各ラッパーは、各属性に対して明示的に定義された型を持つすべての値を設定するメソッドを持ちます。

```swift
let attributes = Attributes {
    return $0.foreground(color: .red)
             .font(UIFont(name: "Chalkduster", size: 24.0)!)
             .underlineStyle(.styleSingle)
}

"Hello".attributed(with: attributes)
```

ここでは、入力された値を使って各属性を設定するためのメソッドを定義しているので、どの型の値が期待されているのか正確に知ることができます。
`NSUnderlineStyleAttributeName`もマジックナンバーを使用しなくてもよくなりました。
また、アンダーラインスタイルが通常の下線 (`.styleSingle`) であることをはっきりと見て取ることができます。

このような拡張機能のさらなる利点として、異なる属性の単語を含む属性付き文字列を作成することが簡単にできます。
属性付き文字列のための`+`演算子を定義すると、連結のための非常に簡単なインターフェースを作成できます。

たとえば、ユーザー名が白で強調表示され、テキストを目立たせるために特別なカーニングを使用して赤いテキストのベースを作成する場合、これは通常の`NSAttributedString` APIを使用して構築できます。

```swift
let attributes: [String: Any] = [
    NSForegroundColorAttributeName: UIColor.red,
    NSFontAttributeName: UIFont(name: "Chalkduster", size: 14.0)!,
]

let userName: String = "@trent"
let attributedString = NSMutableAttributedString(string: "\(userName) has commented on your post.", attributes: attributes)

let nameAttributes: [String: Any] = [
    NSForegroundColorAttributeName: UIColor.white,
    NSKernAttributeName: 4.0,
]
attributedString.addAttributes(nameAttributes, range: NSRange(location: 0, length: userName.characters.count))
```

このコードでも正しく動作しますが、少し不自由な感じです。
上記のコードを入力している間は、共通の属性ベースを持っていることが明らかです。フォントのような既存の属性に加えて、文字列の特定の部分に追加の属性を適用したいだけです。

最初に基本となる属性を宣言し、基本属性から派生した`userName`のみにかかる明示的な属性を作成します。

```swift
let baseAttributes = Attributes {
    return $0.foreground(color: .red)
             .font(UIFont(name: "Chalkduster", size: 24.0)!)
}

let nameAttributes = baseAttributes.foreground(color: .white)
                                   .underlineStyle(.styleSingle)
                                   .kerning(4.0)

let message = " has commented on your post.".attributed(with: baseAttributes)
let userName = "@trent".attributed(with: nameAttributes)


messageLabel.attributedText = userName + message
```
両方の方法で同じ結果が得られますが、後者の方がはるかに単純です。

<img width="512" alt="image.png" src="https://qiita-image-store.s3.amazonaws.com/0/67950/a2b9b822-8c24-b977-3120-7edf45a45505.png">


# まとめ
今回は、文字列で値を設定するメソッドの改善方法について紹介しました。

UIKitやFoundationなどの標準の仕組みであっても、使いにくいインターフェースであれば、独自のラッパーを作っても良いと思います。
今回の記事のコード例は、より簡単に操作できる安全なAPIを設計する方法の例ですが、決して唯一の方法ではありません。

私は、今後も既存のAPIを管理しやすくするため、GitHubの既存のプロジェクトなどを見ることを楽しみにしています。