Swift4で数値、配列、Enumに対してのExtension
最近SwiftでiOSアプリ作ったり、フレームワークを作ったりすることが多いのですが、その中で Extension 使って整数型や文字列型に機能追加することが多々ありました。
備忘録として実装方法・実装内容などを記載しておきます。
環境
- Swift 4
Extensionいろいろ
数値型に対しての Extension
Swift で数値というと真っ先に思いつくのが Int
かとおもいます。
Int
に対してだけ拡張を適応させたければ extension Int
とすれば良いのですが、UInt や Int64 なども含めて広い範囲で拡張を適用したい場合は適切な Protocol を拡張する必要があります。
数値型の Protocol の拡張・実装関係を調べたところ下図のようになってました。
スペースの都合で端折ってますが、UInt8 や UInt32 は図中の UInt と同じプロトコルを、Int8 や Int32 は図中の Int と同じプロトコルを、実装しています。
符号有無を含めてすべての整数型に対しての機能を追加する場合は BinaryInteger あたりがよさそうです。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 |
extension BinaryInteger { var bytes: [UInt8] { var mutableValue = self let bytes = Array<UInt8>(withUnsafeBytes(of: &mutableValue) { $0 }) return bytes } static func createFrom(bytes: [UInt8]) -> Self?{ if bytes.count < MemoryLayout<Self>.size { return nil } let value = UnsafePointer(bytes).withMemoryRebound(to: Self.self, capacity: 1) { $0.pointee } return value } var hexString: String { return String(self, radix: 16) } } |
使用例
1 2 3 |
let value: UInt32 = 0x1234 let bytes = value.bytes // = [0x34, 0x12, 0x00, 0x00] |
今回実装したのは、整数型とバイト配列へのシリアライズ・デシリアライズ処理、16進表記の文字列へのコンバート処理です。
文字列に対しての Extension
文字列に対しての Extension はわかりやすいですね。extension String
です。
今回実装したのは、文字列が正規表現を満たすかを調べるというものです。
1 2 3 4 5 6 7 8 9 |
extension String { func matches(_ regularExpression: String) -> Bool { let regularExpression = try! NSRegularExpression(pattern: regularExpression) let match = regularExpression.firstMatch(in: self, range: NSRange(location: 0, length: self.count)) return match != nil } } |
使用例
1 2 |
let matches = "ryuta46".matches("^[a-z0-9][a-z0-9-_]*$") // = true |
配列に対しての Extension
配列に対しての Extension は Array を拡張すれば良いのですが、特定の型を要素として持つ配列のみ拡張したい場合は Swift 4で導入された条件付き適合(Conditional Conformances)という機能を使います。
今回実装したものだと、UInt8 の配列を16進数値表記の文字列にコンバートするというものです。where 以降が条件付き適合の条件の部分ですね。
1 2 3 4 5 6 |
extension Array where Element == UInt8 { var hexString: String { return self.map{ String(format:"%02x", $0) }.reduce("") { $0 + $1 } } } |
使用例
1 2 3 |
let values: [UInt8] = [0x12, 0xab] let hexString = values.hexString // = "12ab" |
同じように、辞書型でも Key と Value の型で条件付き適合できます。
1 2 3 4 |
extension Dictionary where Key == String, Value == Int { ... } |
Enum に対しての Extension
特定の Enum に対しての Extension ではなく、特定の RawValue の型をもつ Enum を拡張する場合の話です。
Swift で Enum を定義するとき、下記のように書くと、
1 2 3 4 5 |
enum TestEnum: UInt8 { case value1 = 1 case value2 = 2 } |
let value = TestEnum(rawValue: 1)
というように、RawValue から Enum値を取得することができますね。
このように RawValue を使って初期化ができる Enum は、RawRepresentable
というプロトコルが実装されていますので、このプロトコルを拡張することで少し汎用的な拡張ができます。
今回実装したのだとこんな感じ。
1 2 3 4 5 6 7 8 9 |
extension RawRepresentable where RawValue : BinaryInteger{ static func parse(_ rawValue: Int) throws -> Self { guard let value = Self(rawValue: RawValue(clamping: rawValue)) else { throw NSError(domain: "\(rawValue) is unknown value as \(String(describing: Self.self)).", code: 0) } return value } } |
RawValue が BinaryInteger を実装したクラス(Int、UInt8 など)をもつ場合に、Int を渡して Enum 値を生成、できなければ例外を投げるというものです。
(本当はもっと複雑なことをしてましたが、簡略化してます)
さいごに
同じことを何度も調べているような気がしたので、ブログに書いておきました。
どうでも良いですが、記事内のクラス図、 Powered by PlantUML です。
最後まで読んでいただきありがとうございます。 このブログを「いいな」と感じていただけましたら、Twiter にてフォローいただけるとうれしいです。ブログ更新情報などもお届けします。
Follow @ryuta461
この記事をシェアする