Skip to content

Instantly share code, notes, and snippets.

@hhyyg
Last active September 1, 2017 12:27
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save hhyyg/3a35b19304fb1ab2c572111ce5e0b6a2 to your computer and use it in GitHub Desktop.
Save hhyyg/3a35b19304fb1ab2c572111ce5e0b6a2 to your computer and use it in GitHub Desktop.
感想ノート:Swift実践入門 ── 直感的な文法と安全性を兼ね備えた言語 (WEB+DB PRESS plus) | 石川 洋資, 西山 勇世 | Amazon https://www.amazon.co.jp/dp/4774187305/?tag=aokj24gaw-22

Plagrounds で試すことについて

  • 他のファイルのコンパイルエラーが影響してしまう場合がある。その時は再起動。

Swift Package Manager

swift package generate-xcodeproj でXcodeのプロジェクトファイルを生成する。

フォルダ構成とPackage.swiftにしたがって、必要に応じて�プロジェクトファイルとか作る感じ SwiftPMはクロスプラットフォームだけど、プロジェクトファイルはMacのXcode用なので

  • REPLモードを終了するにはコマンド「:quit」もしくは「:q」を入力する。

Swift Misc

  • 暗黙的型変換はあまり無い
    • 明示的な型変換 C#でいうこれが無い
    • ExpressibleByStringLiteral
    • "型の安全性を重視したSwiftらしい仕様です。”
  • let v = Int64.init(a) 省略形 -> let v = Int64(a)
    • コード補完が効くのであえて後者で記述した後、前者の書き方に直したりする。
  • CustomStringConvertible プロトコルは、C# でいう.ToString() したいときに実装する。
    • CustomStringConvertibleプロトコルはdescriptionメソッドを持つ。なので、プロトコルに準拠しない場合descriptionメソッドは避けるのが一般的っぽい。
  • ところどころでスペースがある。
    • 例:let array: [Int] = [] 変数名の後:
    • SwiftLintいれる
  • Dictionary<Key,Value>
  • 1..<4 : 1, 2, 3
  • 1...4 : 1, 2, 3, 4
  • let a = "a"..."z" ClosedRange
  • nil を文字列で表したときnilと出力される?→null不許可で防げるのでそういうケースは無いはず。
  • ImplicitlyUnwrappedOptional<Wrapped>は、Int!と表記できます。”
    • 値の取り出し方が違う。

tuple 代入によるアクセス:

let int: Int
let string: String
(int, string) = (1, "a")
int // 1
string // "a"

var a = 1
var b = 2

(b, a) = (a, b)

タプルでのアップキャスト:

class Base {
	
}

class D1 : Base {
	
}

class D2 : Base {
	
}

struct Pair<T1, T2>
{
	let item1: T1
	let item2: T2
}


//普通の変数
let a1: Base = D1()

//Tuple 1
let t1:(D1, D2) = (D1(), D2())
print(t1)

//Tuple 2 エラー
let t0:(Base, Base) = (D1(), D2())
//let t2:(Base, Base) = t1
//let t3:(Base, Base) = t1 as (Base, Base)
//↓これならOK
let t4:(Base, Base) = (t1.0, t1.1)

//Generics

let x1: Pair<Int, Int> = Pair(item1: 1, item2: 2)
let x2: Pair<D1, D2> = Pair(item1: D1(), item2: D2())
//エラー(C#とかでも無理)
//let x3: Pair<Base, Base> = x2 
//let x3: Pair<Base, Base> = x2 as Pair<Base, Base>


下の2つは糖衣構文:

if 1 == 1 && 2 == 2 && 3 == 3 {
}

if 1 == 1, 2 == 2, 3 == 3 {
}

if case:

“if case パターン = 制御式 {
    制御式がパターンにマッチした場合に実行される文
}

case .some(let a) where a > 10:

for case:

“for case パターン in 値の連続 {
    要素がパターンにマッチした場合に実行される文
}
  • defer文
    • スコープ退出後に実行。コンパイル後どうなってる?
func hoge() {
    defer { print("1") }
    defer { print("2") }
    return
    defer { print("3") }
    defer { print("4") }
}

hoge()

出力:

2
1
  • ~= 演算子
  • p.183 Enum 連想値のパターンマッチ
  • 「連想値」は付属情報、みたいな意味。
    • enum の連想値、protocol の連想値。など。

関数の引数、外部引数名と内部引数名:

func invite(user: String, to group: String) {
    //to: 外部引数名
    //group: 内部引数名
    print("\(user) is invited to \(group).")
}

invite(user: "Ishikawa", to: "Soccer Club")

引数名を参照時に使うかどうか:

func sum(_ int1: Int, _ int2: Int) -> Int {
    return int1 + int2
}

let result = sum(1, 2) // 3
//let result = sum(int1: 1, int2: 4) //コンパイルエラー

↑外部引数名(外部ラベル)を省略している場合、呼び出すときは、引数名を使えない。 メソッドの定義者が明確に使い方を指定する。どっちでもいいというのはできない。

  • inout引数。呼び出すときは&をつける。
    • &は参照渡しの一般的な記号。
    • obj-Cの時は*hogeだった? なぜ、Swift では、inout&で分けているのか?→不明。
  • func hoge() -> Void { }Voidはつけてもつけなくても良い。

クロージャ

↓ 文が一つのとき、return は省略できる

let double = { (x: Int) -> Int in
    x * 2
}
  • クロージャ:型とか引数とか、決まっているのならいろいろ省略できる
    • 簡略引数名 do { }do は省略できない。(C# は{}だけでスコープ分けられる)
  • キャプチャという概念
  • @escaping
    • “escaping属性は、関数に引数として渡されたクロージャが、関数のスコープ外で保持される可能性があることを示す属性です。
  • @autoclosure
    • @autoclosureを宣言することで、普通の式をクロージャとしてみなすことができる→遅延評価に使われる
  • トレイリングクロージャ:最後の引数のクロージャを、()の外にかけるやつ。

Property

  • ストアドプロパティ
    • willset
      • newValue
    • didSet
  • コンピューテッドプロパティ:get, set

newValueは任意の名前に変えられる:

    var fahrenheit: Double {        
        get {            
            return (9.0 / 5.0) * celsius + 32.0       
        }
        set(newFahrenheit) {            
            celsius = (5.0 / 9.0) * (newFahrenheit - 32.0)        
        }    
    }
  • 失敗可能イニシャライザ(failable initializer)Int?(...)
  • p.143 いい感じ
  • 戻り値のオーバーロードが可能
  • get {} 省略可能
  • subscript
    • subscript(index: Int) -> Int { ...

サブスクリプトではすべての外部引数名がデフォルトでは_となっている点が 異なります。

  • _が隠れて宣言されている感じ?
    • 外部ラベル_をつけてみる→動く
    • 他の外部ラベルをつけてみる→動かない
  • convenience initializer
    • イニシャライザの中で自身の別のイニシャライザを呼ぶ必要がある
  • 型のネスト、NewsFeedItem.Kind型

遅延評価

struct

mutating 自身を変えようとしている。(ミュータントみたいな)

extension Int {   
    mutating func increment() {        
        self += 1    
    } 
}
var a = 1 // 1 
a.increment() // 2(aに再代入が行われている)

let b = 1 
b.increment() // bに再代入できないためコンパイルエラー 
  • 定数のストアドプロパティは変更できない
  • メンバーワイズイニシャライザ(イニシャライザの定義を省略できる)

class

  • final サブクラスでのオーバーライド禁止
    • classやメソッドで使用可能
  • クラスプロパティ・クラスメソッド:オーバーロード可能
    • static : オーバーロードできない

ルール:

  • 指定イニシャライザは、スーパークラスの指定イニシャライザを呼ぶ
  • コンビニエンスイニシャライザは、同一クラスのイニシャライザを呼ぶ
  • コンビニエンスイニシャライザは、最終的に指定イニシャライザを呼ぶ

ルール2:

  • クラス内で新たに定義されたすべてのストアドプロパティを初期化し、スーパー クラスの指定イニシャライザを実行する。
  • スーパークラスでも同様の初期化を行い、大もとのクラスまでさかのぼる
  • ストアドプロパティ以外の初期化を行う

  • デフォルトイニシャライザ
    • プロパティが存在しない場合や、すべてのプロパティが初期値を持っている場合、記述を省略できる。
  • デイニシャライザdeinit
  • == Equtable
  • ===インスタンス参照

enum

protocol

  • 継承するクラス名→プロトコルの準拠、という順番
  • 可読性を高くするため、extension : protocol = 1:1 が一般的?
  • プロトコルのプロパティにはストアドプロパティやコンピューテッドプロパティといった区別がない   - let は使用できない
  • typealias便利そう
  • AssociatedType(連想型って呼んでる?)
    • typealias AssociatedType = Int
    • 実装から省略できる
    • AssociatedTypeと同じ型をネスト型で定義する

static 宣言できる:

protocol Person {
    static func memo()
}

class Taro: Person {
    static func memo() {}
}

protocol extension

http://swift.sandbox.bluemix.net/#/repl/59982e74add9952abb4026f3

protocol Item {
    var name: String { get }
	//var description: String { get } //←⭐️このプロパティをコメントアウトするかどうかで出力が違う
}

extension Item {
    var description: String {
        return "商品名: \(name)"
    }
}

struct Book : Item {
    let name: String
	
    var description: String {
        return "しょうひんめい2: \(name)"
    }
}

let book:Item = Book(name: "Swift実践入門") //変数はItem型として定義
print(book.description)

C#の場合:

using System;

// C# の場合は object.ToString があるんだけど、挙動の説明のために別のを用意
interface Item
{
    string Name { get; }
    //string ToStr(); //←⭐️このメソッドをコメントアウトするかどうかで出力が違う
}

static class ItemExtensions
{
    public static string ToStr(this Item i) => "extention: " + i.Name;
}

struct Book : Item
{
    public string Name { get; }
    public Book(string name) : this() => Name = name;
    public string ToStr() => "struct: " + Name;
}

class Program
{
    static void Main()
    {
        Item book = new Book("C# 入門");
        Console.WriteLine(book.ToStr());
    }
}

WebContentにも準拠している場合に有効:

extension Item where Self : WebContent {

“struct IntSequence : Sequence, IteratorProtocol {}だと、next()だけの実装でよい。理由は下のような実装があるから:

extension Sequence where Self.Iterator == Self {

    /// Returns an iterator over the elements of this sequence.
    public func makeIterator() -> Self
}

Generics

受け取り変数の方による推論が可能。

func someFunction<T>(_ any: Any) -> T? { return any as? T }

連想型の制約

func sorted<T : Collection>(_ argument: T) -> [T.Iterator.Element]
    where T.Iterator.Element : Comparable {    
    return argument.sorted() 
}
sorted([3, 2, 1]) // [1, 2, 3]

modules

  • open
    • モジュール内外のすべてのアクセスを許可する
  • public
    • モジュール内外のすべてのアクセスを許可するが、モジュール外で継承したりオーバーライドすることはできない
  • internal
    • 同一モジュール内のアクセスのみを許可する
  • fileprivate
    • 同一ソースファイル内のアクセスのみを許可する
  • private
    • 対象の要素が属しているスコープ内のアクセスのみを許可する

fileprivateについて: 4でextension間でprivate参照できるようになって、fileprivate出番自体がほぼなくなった。仕様的にはあるけどほぼ不使用で良い系。

“型全体のデフォルトのアクセスレベルは、internalです。”

コメント:

通常のコメント
// コメント
/*
 * コメント
 */

ドキュメントコメント
/// ドキュメントコメント

/**
 * ドキュメントコメント
 */

型の設計

ImplicitlyUnwrappedOptional型を使う例:

class SuperClass {
    var one = 1
}

class BaseClass : SuperClass {
    var two: Int!

    override init() {
        super.init()
        two = one + 1
    }
}

イベント通知

デリゲートパターン

  • Swift では、C# でいう「interfaceの明示的な実装」がない。Swiftでは、メソッド名・引数名を以下のようにして被らないようにするのが一般的。(例:メソッド名・引数にも主語)
public protocol UITableViewDelegate : NSObjectProtocol,
                                      UIScrollViewDelegate {
  optional public func tableView(
        _ tableView: UITableView,
        didSelectRowAt indexPath: IndexPath)

“こうした命名規則によって、このメソッドを誰が、いつ、どういう場合に呼ぶのかが明確になります。tableView(_:didSelectRowAt:)メソッドの例では、セルが選択された(selectRow)あと(did)にUITableViewクラスが呼ぶということが、メソッド名を見ただけでわかります。”

  • 強参照(デフォルト)
    • 参照カウントを1つカウントアップする
  • 弱参照
    • カウントアップしない
    • 実行時に参照先のインスタンスがすでに解放されている可能性があることを意味

“通常は、デリゲート元からデリゲート先への参照を弱参照として、循環参照を回避します”

クロージャ

キャプチャリスト

  • weak
    • Optionalとして再定義
  • unowned
    • 実行時エラー
import PlaygroundSupport
import Dispatch

// Playgroundでの非同期実行を待つオプション
PlaygroundPage.current.needsIndefiniteExecution = true

class SomeClass {
    let id: Int
    
    init(id: Int) {
        self.id = id
    }
}

do {
    let object = SomeClass(id: 42)
    let closure = { [weak object] () -> Void in
        if let o = object {
            print("objectはまだ解放されていません: id => \(o.id)")
        } else {
            print("objectはすでに解放されました")
        }
    }
    
    print("ローカルスコープ内で実行: ", terminator: "")
    closure()
    
    let queue = DispatchQueue.main
    
    queue.asyncAfter(deadline: .now() + 1) {
        print("ローカルスコープ外で実行: ", terminator: "")
        closure()
    }
}

@escaping

“escaping属性は、関数に引数として渡されたクロージャが、関数のスコープ外で保持される可能性があることを示す属性です。

クロージャの内部は、インスタンス自身にアクセスするときselfキーワードをつける必要がある。循環参照に気づきやすくするため。

typealias による型エイリアス:

�typealias CompletionHandler = (Int?, Error?, Array<String>?) -> Void

非同期

  • GCD : DispatchQueue.main
  • Operation, OperationQueue
  • Threadクラス

エラー処理

Optional

  • 失敗可能イニシャライザとかで使う

Result<T, Error>

“Swiftのコミュニティではデファクトスタンダードとなっており、Swiftにおけるエラー処理を説明するうえでは無視できない概念です。”

↑デファクトスタンダードだとちょっと強いかもしれない、一般的なパターンな感じっぽい。

なぜ、C#ではResult<T, Error>形式が一般的ではないか?

  • 独自実装で使用していることも多くあるが、パターンというほどでもない可能性
    • Associated Value enum が使えないため、エラーと値の組み合わせが担保できない(実装が複雑になる)→パターンにならない
  • C# 7でValue Tuppleが最近でた
  • Associated Value Enum がない (これが一番の理由っぽそう)

do-catch

  • do {} catch {}
  • thows
    • “関数、イニシャライザ、クロージャの定義にthrowsキーワードを追加すると、それらの処理の中でdo-catch文を用いずにthrow文によるエラーを発生させることができます。”
func 関数名(引数) throws -> 戻り値の型 {
    throw文によるエラーが発生する可能性のある処理
}
  • rethrows
    • 引数クロージャのエラー伝搬
    • 引数クロージャのエラー以外のエラーは発生できない
  • try
    • throwsが指定された関数を呼び出すためのキーワード
    • do節・throws付きの関数内でのみ使用可能
  • try!
    • エラー処理を記述しない場合とかに使う
    • do節・throws付きの関数以外でも使用可能
  • try?
    • do節・throws付きの関数以外でも使用可能
    • その代わり関数の戻り値がOptional

失敗可能イニシャライザ(failable initializer)は、throwsにするかinit?にするか:

  • 例外情報を知りたい:throws
  • init? だと、nilかそうでないかを区別するだけでよい(throwsだと例外のことについて考える必要がある)
  • どっちか迷ったらthrowsがよさげ?
    • throwsだと、使う側が選択の余地があり、一手間でOptionalとして扱える

Swift.Error : Errorが標準ライブラリのものを示す時に、Swift.をつけて使う(自分で定義したものと区別するため)

defer併用:

enum Error : Swift.Error {
    case someError
}

func someFunction() throws {
    print("Do something")
    throw Error.someError
}

func cleanup() {
    print("Cleanup")
}

do {
    defer {
        // do節を抜けたタイミングで必ず実行される
        cleanup()
    }
    try someFunction()
} catch {
    print("Error: \(error)")
}

出力:

Do something
Cleanup
Error: someError

↑C#のtry-catch-finalyだと、CleanupError: someErrorが逆になる。(catchとtry内の二重記述になるかもしれない)

  • Result<T, Error>は、Error引数の型しか扱えない。do-catchは、Errorプロトコルに準拠していればOK
  • Result<Void, Error>だと、エラーを無視していることがコード上から分かりにくい。

fatalError(_:)

“本節で説明するfatalError(_:)関数は、その箇所が実行されること自体が想定外であることを宣言するための関数です。”

  • Never型
    • fatalError(_:)関数の戻り値
func randomInt() -> Int {
    fatalError("まだ実装されていません")
}

アサーション

  • assert(_:_:)
  • assertFailure(_:)

Swiftのエラー4分類が素晴らしすぎるのでみんなに知ってほしい - Qiita SwiftはどのようにJavaの検査例外を改善したか - Qiita

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment