![Swift 4.1](https://habrastorage.org/getpro/habr/post_images/5a9/da1/bb9/5a9da1bb94c55a3084ddabcef722cd30.jpg)
この記事では、 Swift 4.1で導入された最も重要な変更について学びます。
この記事ではXcode 9.3が必要なので、このバージョンのXcodeがインストールされていることを確認してください。
最初のステップ
Swift 4.1はSwift 4のソースコードと互換性があるため、XcodeでSwift Migratorを使用してプロジェクトをSwift 4に既に移行している場合、新しい機能はコードに違反しません。
次のセクションでは、[SE-0001]などの関連タグが表示されます。 これらは、 Swift Evolutionのオファー番号です。 各提案へのリンクを追加して、特定の各変更の詳細を掘り下げることができるようにしました。 作品で何が正確に変わるかをよりよく理解するために、プレイグラウンドを使用して実際に機能を試すことをお勧めします。
開始するには、Xcode 9.3を起動し、 ファイル▸新規▸プレイグラウンドを選択します 。 プラットフォームとしてiOSを選択し、テンプレートとしてBlankを選択します。 必要に応じて名前を付けて保存します。 この記事を最大限に活用するには、プレイグラウンドですべての機能を練習してみてください。
注: Swift 4で何が変更されたのかを見逃して、追いつくつもりですか? 問題ありません! Swift 4 の新機能を読んで、Swift 4をチェックしてください。
言語の改善
このリリースでは、条件付き一致、プロトコルの関連タイプの再帰的制限など、多くの言語が改善されています。
条件付き一致
条件付き一致により、型引数が特定の条件を満たしている汎用型のプロトコル一致が可能になります[SE-0143] 。 これは、コードをより柔軟にする強力な機能です。 いくつかの例でどのように機能するかを見ることができます。
標準ライブラリの条件付き一致
Swift 4では、要素がEquatableプロトコルに準拠している場合、配列、辞書、およびオプションを比較できます。 これは、次のような基本的なシナリオで完全に機能しました。
// Arrays of Int let firstArray = [1, 2, 3] let secondArray = [1, 2, 3] let sameArray = firstArray == secondArray // Dictionaries with Int values let firstDictionary = ["Cosmin": 10, "George": 9] let secondDictionary = ["Cosmin": 10, "George": 9] let sameDictionary = firstDictionary == secondDictionary // Comparing Int? let firstOptional = firstDictionary["Cosmin"] let secondOptional = secondDictionary["Cosmin"] let sameOptional = firstOptional == secondOptional
Swift 4 では IntはEquatableであるため、 ==演算子を使用してこれらの例の等価性を検証することは非常に公平でした 。 Swift 4.1は条件付き一致を使用してこの問題を修正し、追加の型をEquatableの基礎となる型と比較できるようにします。
// Array of Int? let firstArray = [1, nil, 2, nil, 3, nil] let secondArray = [1, nil, 2, nil, 3, nil] let sameArray = firstArray == secondArray // Dictionary with Int? values let firstDictionary = ["Cosmin": 10, "George": nil] let secondDictionary = ["Cosmin": 10, "George": nil] let sameDictionary = firstDictionary == secondDictionary // Comparing Int?? (Optional of Optional) let firstOptional = firstDictionary["Cosmin"] let secondOptional = secondDictionary["Cosmin"] let sameOptional = firstOptional == secondOptional
Int? はSwift 4.1ではEquatableであるため、 ==演算子は[Int?]、[String:Int?]およびInt ??に対して機能します。 。
同様の問題は、配列を比較するときに解決されました(たとえば、[[Int]])。 Swift 4では、セットがEquatableプロトコルに準拠しているため、セットの配列([Set </ Int />]など)を比較できます。 Swift 4.1はこれを解決します。これは、配列(および辞書)とその基礎となる値がEquatableであるためです。
let firstArrayOfSets = [Set([1, 2, 3]), Set([1, 2, 3])] let secondArrayOfSets = [Set([1, 2, 3]), Set([1, 2, 3])] // Will work in Swift 4 and Swift 4.1 // since Set<Int> is Equatable firstArrayOfSets == secondArrayOfSets let firstArrayOfArrays = [[1, 2, 3], [3, 4, 5]] let secondArrayOfArrays = [[1, 2, 3], [3, 4, 5]] // Caused an error in Swift 4, but works in Swift 4.1 // since Arrays are Equatable in Swift 4.1 firstArrayOfArrays == secondArrayOfArrays
通常、Swift 4.1のOptional 、 Array、およびDictionaryは、ベース値または要素がこれらのプロトコルに準拠している場合は常に、 EquatableおよびHashableプロトコルに準拠しています。
以下は、標準ライブラリで条件付きマッチングがどのように機能するかの例です。 次に、これをコードに実装します。
実際の条件付きコンプライアンス
現在、条件付きマッチングを使用して、独自の楽器グループを作成しています。 次のコードブロックをPlaygroundに追加します。
// 1 class LeadInstrument: Equatable { let brand: String init(brand: String) { self.brand = brand } func tune() -> String { return "Standard tuning." } static func ==(lhs: LeadInstrument, rhs: LeadInstrument) -> Bool { return lhs.brand == rhs.brand } } // 2 class Keyboard: LeadInstrument { override func tune() -> String { return "Keyboard standard tuning." } } // 3 class Guitar: LeadInstrument { override func tune() -> String { return "Guitar standard tuning." } }
このコードがステップバイステップで行うことは次のとおりです。
- LeadInstrumentクラスは、Equatableプロトコルに準拠しています。 特定のブランドと、ツールの調整に使用するtune()メソッドがあります。
- Keyboardクラスのtune()メソッドをオーバーライドして、オブジェクトのデフォルト設定を返します。
- ギターのクラスでも同じことをします。
次に、ツールグループを宣言します。
// 1 class Band<LeadInstrument> { let name: String let lead: LeadInstrument init(name: String, lead: LeadInstrument) { self.name = name self.lead = lead } } // 2 extension Band: Equatable where LeadInstrument: Equatable { static func ==(lhs: Band<LeadInstrument>, rhs: Band<LeadInstrument>) -> Bool { return lhs.name == rhs.name && lhs.lead == rhs.lead } }
手順は次のとおりです。
- バンドタイプのクラス-LeadInstrumentを作成します。 各グループには、一意の名前(リード)とリード楽器(メイン楽器)があります。
- LeadInstrumentが特定のことを行うように、バンドをEquatableプロトコルに準拠させる場所を使用します。 これは、条件付きマッチングが現れる場所です-汎用LeadInstrumentsのEquatableプロトコルにコンプライアンスを割り当てることができます。
次に、お気に入りのツールグループを作成して比較します。
// 1 let rolandKeyboard = Keyboard(brand: "Roland") let rolandBand = Band(name: "Keys", lead: rolandKeyboard) let yamahaKeyboard = Keyboard(brand: "Yamaha") let yamahaBand = Band(name: "Keys", lead: yamahaKeyboard) let sameBand = rolandBand == yamahaBand // 2 let fenderGuitar = Guitar(brand: "Fender") let fenderBand = Band(name: "Strings", lead: fenderGuitar) let ibanezGuitar = Guitar(brand: "Ibanez") let ibanezBand = Band(name: "Strings", lead: ibanezGuitar) let sameBands = fenderBand == ibanezBand
このコードスニペットでは、2つのキーボードとギターをそれぞれのバンドとともに作成します。 次に、前に定義した条件付き一致のおかげで、バンドを直接比較します。
JSON解析の条件付き一致
Swift 4.1では、配列、辞書、セット、アドオンがCodableプロトコルに準拠している場合、それらの要素もこのプロトコルに準拠しています。 次のコードをプレイグラウンドに追加します。
struct Student: Codable, Hashable { let firstName: String let averageGrade: Int } let cosmin = Student(firstName: "Cosmin", averageGrade: 10) let george = Student(firstName: "George", averageGrade: 9) let encoder = JSONEncoder() // Encode an Array of students let students = [cosmin, george] do { try encoder.encode(students) } catch { print("Failed encoding students array: \(error)") } // Encode a Dictionary with student values let studentsDictionary = ["Cosmin": cosmin, "George": george] do { try encoder.encode(studentsDictionary) } catch { print("Failed encoding students dictionary: \(error)") } // Encode a Set of students let studentsSet: Set = [cosmin, george] do { try encoder.encode(studentsSet) } catch { print("Failed encoding students set: \(error)") } // Encode an Optional Student let optionalStudent: Student? = cosmin do { try encoder.encode(optionalStudent) } catch { print("Failed encoding optional student: \(error)") }
[Student] 、 [String:Student] 、Set </ Student />およびStudentのエンコードにこのコードを使用しますか? 。 StudentはCodableであるため、このコードはSwift 4.1で正常に機能します。これにより、これらのコレクションタイプもCodableに適切になります。
JSONを使用する場合のCamelCaseとSnake_Case間の変換
Swift 4.1では、JSON解析中にCamelCaseプロパティをsnake_caseキーに変換できます。
var jsonData = Data() encoder.keyEncodingStrategy = .convertToSnakeCase encoder.outputFormatting = .prettyPrinted do { jsonData = try encoder.encode(students) } catch { print(error) } if let jsonString = String(data: jsonData, encoding: .utf8) { print(jsonString) }
エンコーダーオブジェクトを作成するときに、 keyEncodingStrategyプロパティを.convertToSnakeCaseに設定します。 コンソールを見ると、次のように見えるはずです。
[ { "first_name" : "Cosmin", "average_grade" : 10 }, { "first_name" : "George", "average_grade" : 9 } ]
JSONを使用して、snake_caseからCamelCaseに戻すこともできます。
var studentsInfo: [Student] = [] let decoder = JSONDecoder() decoder.keyDecodingStrategy = .convertFromSnakeCase do { studentsInfo = try decoder.decode([Student].self, from: jsonData) } catch { print(error) } for studentInfo in studentsInfo { print("\(studentInfo.firstName) \(studentInfo.averageGrade)") }
今回は、 keyDecodingStrategyプロパティに値.convertFromSnakeCaseを割り当てます。
EquatableおよびHashableプロトコルのコンプライアンスと互換性
Swift 4では、構造がEquatableおよびHashableプロトコルに準拠するように、定型コードを記述する必要がありました 。
struct Country: Hashable { let name: String let capital: String static func ==(lhs: Country, rhs: Country) -> Bool { return lhs.name == rhs.name && lhs.capital == rhs.capital } var hashValue: Int { return name.hashValue ^ capital.hashValue &* 16777619 } }
このコードを使用して、 ==(lhs:rhs :)とhashValueを実装して、 EquatableとHashableの両方をサポートします。 Countryオブジェクトを比較し、 Setに追加したり、辞書のキーとして使用することもできます。
let france = Country(name: "France", capital: "Paris") let germany = Country(name: "Germany", capital: "Berlin") let sameCountry = france == germany let countries: Set = [france, germany] let greetings = [france: "Bonjour", germany: "Guten Tag"]
Swift 4.1では、すべてのプロパティがEqualableおよびHashableであるため、 EquatableおよびHashableに対応する構造のデフォルト実装を追加します[ SE-0185 ]。
これによりコードが大幅に簡素化され、次のように簡単に書き換えることができます。
struct Country: Hashable { let name: String let capital: String }
関連する値を持つ列挙体は、Swift 4でEquatableおよびHashableを使用するために追加のコードも必要です。
enum BlogPost: Hashable { case tutorial(String, String) case article(String, String) static func ==(lhs: BlogPost, rhs: BlogPost) -> Bool { switch (lhs, rhs) { case let (.tutorial(lhsTutorialTitle, lhsTutorialAuthor), .tutorial(rhsTutorialTitle, rhsTutorialAuthor)): return lhsTutorialTitle == rhsTutorialTitle && lhsTutorialAuthor == rhsTutorialAuthor case let (.article(lhsArticleTitle, lhsArticleAuthor), .article(rhsArticleTitle, rhsArticleAuthor)): return lhsArticleTitle == rhsArticleTitle && lhsArticleAuthor == rhsArticleAuthor default: return false } } var hashValue: Int { switch self { case let .tutorial(tutorialTitle, tutorialAuthor): return tutorialTitle.hashValue ^ tutorialAuthor.hashValue &* 16777619 case let .article(articleTitle, articleAuthor): return articleTitle.hashValue ^ articleAuthor.hashValue &* 16777619 } } }
実装==(lhs:rhs :)およびhashValueを記述するために列挙ケースを使用しました 。 これにより、ブログの投稿を比較して、セットや辞書で使用できます。
let swift3Article = BlogPost.article("What's New in Swift 3.1?", "Cosmin Pupăză") let swift4Article = BlogPost.article("What's New in Swift 4.1?", "Cosmin Pupăză") let sameArticle = swift3Article == swift4Article let swiftArticlesSet: Set = [swift3Article, swift4Article] let swiftArticlesDictionary = [swift3Article: "Swift 3.1 article", swift4Article: "Swift 4.1 article"]
Hashableとは異なり、 EquatableとHashableの実装のおかげで、このコードのサイズはSwift 4.1では大幅に小さくなりました 。
enum BlogPost: Hashable { case tutorial(String, String) case article(String, String) }
20行のテンプレートコードで作業する必要がなくなりました。
![画像](https://habrastorage.org/getpro/habr/post_images/edd/0db/e14/edd0dbe14cde5ca78a0bd2ad6fb67c57.png)
ハッシュ可能なインデックスタイプ
Swift 4でインデックスパラメータタイプがハッシュ可能な場合、キーパスはインデックスを使用できます。これにより、ダブルパスで動作できるようになりました。 例えば:
let swiftVersions = [3, 3.1, 4, 4.1] let path = \[Double].[swiftVersions.count - 1] let latestVersion = swiftVersions[keyPath: path]
keyPathを使用して、 swiftVersionsから現在のSwiftバージョン番号を取得します。
Swift 4.1は、標準ライブラリ[ SE-0188 ]のすべての添え字タイプにハッシュ可能なマッチングを追加します。
let me = "Cosmin" let newPath = \String.[me.startIndex] let myInitial = me[keyPath: newPath]
インデックスにより、文字列の最初の文字が返されます。 Swift 4.1 では文字列インデックスタイプがハッシュ可能であるため機能します。
プロトコルの関連タイプの再帰的制限
Swift 4は、プロトコルの関連する型に対する再帰的制約の定義をサポートしていませんでした。
protocol Phone { associatedtype Version associatedtype SmartPhone } class IPhone: Phone { typealias Version = String typealias SmartPhone = IPhone }
この例では、SmartPhoneに関連付けられたタイプを特定しましたが、すべてのスマートフォンは電話であるため、Phoneに制限すると便利な場合があります。 Swift 4.1 [ SE-0157 ]で可能になりました:
protocol Phone { associatedtype Version associatedtype SmartPhone: Phone where SmartPhone.Version == Version, SmartPhone.SmartPhone == SmartPhone }
バージョンとスマートフォンの両方が電話と同じになるように制限する場所を使用します。
プロトコル内の弱いリンクとビジーでないリンク
Swift 4は、 脆弱で所有されていないプロトコルプロパティをサポートしています。
class Key {} class Pitch {} protocol Tune { unowned var key: Key { get set } weak var pitch: Pitch? { get set } } class Instrument: Tune { var key: Key var pitch: Pitch? init(key: Key, pitch: Pitch?) { self.key = key self.pitch = pitch } }
特定のキーとピッチで楽器をチューニングしました。 ステップはゼロだった可能性があるため、Tuneプロトコルでは弱いものとしてシミュレートします。
ただし、プロトコル自体で定義されている場合、弱いものと所有されていないものの両方は実質的に意味がないため、Swift 4.1はそれらを削除し、プロトコル[ SE-0186 ]でこれらのキーワードを使用して警告を受け取ります:
protocol Tune { var key: Key { get set } var pitch: Pitch? { get set } }
コレクション内のインデックス距離
Swift 4はIndexDistanceを使用して、コレクション内の要素の数を宣言しました。
func typeOfCollection<C: Collection>(_ collection: C) -> (String, C.IndexDistance) { let collectionType: String switch collection.count { case 0...100: collectionType = "small" case 101...1000: collectionType = "medium" case 1001...: collectionType = "big" default: collectionType = "unknown" } return (collectionType, collection.count) }
typeOfCollection(_ :)メソッドは、コレクションのタイプと量を含むタプルを返します。 配列、辞書、コレクションなどのコレクションに使用できます。 例えば:
typeOfCollection(1...800) // ("medium", 800) typeOfCollection(greetings) // ("small", 2)
where句を使用してIndexDistanceをIntに制限することにより、関数の戻り値の型を改善できます。
func typeOfCollection<C: Collection>(_ collection: C) -> (String, Int) where C.IndexDistance == Int { // , }
Swift 4.1は標準ライブラリのIndexDistanceをIntに置き換えているため、この場合、 where句[ SE-0191 ]は不要です。
func typeOfCollection<C: Collection>(_ collection: C) -> (String, Int) { // , }
モジュールの構造イニシャライザー
プロパティをパブリック構造に追加すると、Swift 4の初期変更が発生する可能性があります。この記事では、 View \ Navigators \ Show Project Navigatorに移動してXcodeでProject Navigatorが表示されることを確認します。 次に、「ソース」を右クリックして、メニューから「新規ファイル」を選択します。 DiceKit.swiftファイルの名前を変更します。 その内容を次のコードブロックに置き換えます。
public struct Dice { public let firstDie: Int public let secondDie: Int public init(_ value: Int) { let finalValue: Int switch value { case ..<1: finalValue = 1 case 6...: finalValue = 6 default: finalValue = value } firstDie = finalValue secondDie = 7 - finalValue } }
構造イニシャライザは、両方のダイスが1〜6の有効な値を持つようにします。 プレイグラウンドに戻り、最後にこのコードを追加します。
// 1 let dice = Dice(0) dice.firstDie dice.secondDie // 2 extension Dice { init(_ firstValue: Int, _ secondValue: Int) { firstDie = firstValue secondDie = secondValue } } // 3 let newDice = Dice(0, 7) newDice.firstDie newDice.secondDie
このコードで行ったことは次のとおりです。
- 有効なサイコロのペアを作成しました。
- プロパティに直接アクセスできる別の初期化子を介してDiceを追加しました。
- 新しい構造イニシャライザを使用して、無効なサイコロのペアを特定しました。
Swift 4.1では、 クロスターゲットイニシャライザーはデフォルト値を発生させる必要があります。 Dice拡張を次のように変更します。
extension Dice { init(_ firstValue: Int, _ secondValue: Int) { self.init(abs(firstValue - secondValue)) } }
この変更により、構造はクラスのように動作します。クロスモジュール初期化子は、Swift 4.1 [ SE-0189 ]の簡易初期化子でなければなりません。
![画像](https://habrastorage.org/getpro/habr/post_images/cce/b10/bc6/cceb10bc6e06882cbc5f878988b10f0f.png)
Swift 4.1では、サイコロゲームをごまかすことはできません!
プラットフォーム設定と構成の更新
Swift 4.1は、コードテストに必要なプラットフォームとビルド機能の一部を追加します。
インポートのビルド
Swift 4では、特定のプラットフォームで使用可能なモジュールをテストし、オペレーティングシステム自体を定義しました。例:
#if os(iOS) || os(tvOS) import UIKit print("UIKit is available on this platform.") #else print("UIKit is not available on this platform.") #endif
UIKitはiOSおよびtvOSで使用できるため、テストが成功した場合はインポートしました。 Swift 4.1では、モジュール自体をチェックできるため、このプロセスが簡素化されます。
#if canImport(UIKit) print("UIKit is available if this is printed!") #endif
Swift 4.1では、 #if canImport(UIKit)を使用して、特定の構造がインポートに利用できることを確認します[ SE-0075 ]。
ターゲット環境
Swift 4でコードを記述するとき、シミュレーターまたは物理デバイスでコードの実行をチェックする最も有名な方法は、アーキテクチャとオペレーティングシステムをチェックすることでした。
#if (arch(i386) || arch(x86_64)) && (os(iOS) || os(tvOS) || os(watchOS)) print("Testing in the simulator.") #else print("Testing on the device.") #endif
Intelに基づいたプロセッサアーキテクチャと、オペレーティングシステム(iOS、tvOS、またはwatchOS)はシミュレータでテストしましたか。 それ以外の場合は、デバイスをテストしました。
このテストは非常に面倒で、エラーの種類を完全には説明していませんでした。 Swift 4.1はこのテストを簡単にします。 次のように、targetEnvironment(シミュレーター)[SE-0190]を使用するだけです。
#if targetEnvironment(simulator) print("Testing in the simulator.") #endif
その他の小片
Swift 4.1には、知っておく価値のある他の更新がいくつかあります。
圧縮シーケンス
Swift 4では、 flatMap(_ :)を使用してシーケンスからnil値をフィルタリングすることはかなり一般的でした :
let pets = ["Sclip", nil, "Nori", nil] let petNames = pets.flatMap { $0 } // ["Sclip", "Nori"]
残念ながら、 flatMap(_ :)はさまざまな方法で過負荷になり、この特定のシナリオでは、 flatMap(_ :)の割り当ては実行されたアクションをあまり説明しませんでした。
これらの理由から、Swift 4.1はflatMap(_ :)の名前をcompactMap(_ :)に変更し、その意味をより明確でユニークなものにしました[ SE-0187 ]:
let petNames = pets.compactMap {$ 0}
安全でないポインター/安全でないポインター
Swift 4は、一時的に安全でない可変ポインタを使用して、安全でない可変バッファポインタを作成および変更しました。
let buffer = UnsafeMutableBufferPointer<Int>(start: UnsafeMutablePointer<Int>.allocate(capacity: 10), count: 10) let mutableBuffer = UnsafeMutableBufferPointer(start: UnsafeMutablePointer(mutating: buffer.baseAddress), count: buffer.count)
Swift 4.1では、安全でない可変バッファーポインターと同じアプローチを使用して、安全でない可変バッファーポインターを直接操作できます[ SE-0184 ]:
新しい遊び場機能
Swift 4では、 Playground Xcodeでタイプの説明をカスタマイズできました。
class Tutorial {} extension Tutorial: CustomPlaygroundQuickLookable { var customPlaygroundQuickLook: PlaygroundQuickLook { return .text("raywenderlich.com tutorial") } } let tutorial = Tutorial()
チュートリアルに CustomPlaygroundQuickLookableを実装し、短い説明を返しました。 customPlaygroundQuickLookの説明タイプは、 PlaygroundQuickLookケースに限定されていました。 Swift 4.1のこのようなしゃれはもうありません:
extension Tutorial: CustomPlaygroundDisplayConvertible { var playgroundDescription: Any { return "raywenderlich.com tutorial" } }
今回はCustomPlaygroundDisplayConvertibleを実装しています。 説明タイプはAnyなので、playgroundDescriptionから何でも返すことができます。 これにより、コードが簡素化され、より柔軟になります[ SE-0198 ]。
次は?
Swift 4.1は、今年のSwift 5で発生するより深刻な変更に備えて、Swift 4の機能の一部を強化しています。 これには、ABIの安定性、改良されたジェネリックと文字列、メモリの所有権と同時実行性の新しいパターンなどが含まれます。
冒険家の気分になったら、 標準のSwiftライブラリまたは公式のSwift CHANGELOG Webサイトにアクセスして 、このバージョンのすべての変更に関する詳細情報を参照してください。
Swift 5の変更点に興味がある場合は 、新機能、変更、追加を確認できるSwift Evolutionの提供をよく理解することもお勧めします。