くま's Tech系Blog

基本的には技術で学んだことを書き留めようと思います。雑談もやるかもね!

Optional型について

Swiftだけではないですが、SwiftにはOptional型という特徴的な型があります。

今回はそんなOptional型についてお話しできたらと思います!

Optional型とは

Optional型とは通常の値とは別にnullをとれる型です。 他の言語ではString型やInt型といった通常の型にnullという値が入ると思いますが、Swiftでは通常の型にはNULLを入れることができず、もしnullを扱いたい場合はOptional型を使わないといけません。

SwiftのOptional型はnullの場合に文法的にエラーを返してくれるので、文法通りに書けばバグをある程度回避できるメリットがあります。

Optional型を使おう

では実際にどのようにOptional型を使うかというところを見ていきます。

まず、Optional型はを使います。

宣言時には下記のようにします。

var a: Int?

このように、Intの後ろにをつけるとInt型ではなく、Optional型になります。

また、Int型は初期化しないとコンパイルエラーになりますが、Optional型ではコンパイルエラーにはなりません

let a: Int?  //宣言時に初期値を定義しなければ自動的にnilが入る

var b: Int? = 2 //nilじゃない値も入れることが出来る
b = nil //nilを代入することも出来る

var c: String //nilを入れれないのでエラー

はnullが入る可能性があるという意味なので、Optional型ではnullが許容されています。

ちなみに、Optional型の値を見てみると、下記のようになります。

let a: Int = 1
print(a) // 1

let b: Int? = 2
print(b) // Optional(2)

上記の場合だと、Optional型はInt型ではないので、下記のように計算はできません。

let a: Int = 1
let b: Int? = 2

let c = a + b //コンパイルエラー

では、計算したい場合はどうすればいいでしょうか? そのときに使うのが!です。

let a: Int? = 1
print(a) // Optional(1)
print(a!) // 1

!を使うことをアンラップと言います。このアンラップをすることで、nullが入らないことが前提と定義できるので、上記の場合はOptional型ではなくInt型として使うことができます。

しかし、アンラップしている値にnullが入ったらクラッシュするため注意が必要になります。

let a: Int? = nil
let b = a!  //クラッシュ

クラッシュしないためにnullチェックが必要になりますが、Swiftではif-let構文を使うことが多いです。

let a: Int? = nil
if b = a {
    let d = b!
    print(d) // nullではない場合に通る
} else {
    print("nil") //aがnullの場合の処理
}

上記のようにaがnullの場合はelseの方を通るのでnullの場合の処理を記載することができるので、nullチェックを行うことができます。

また、下記のように変数を使わない場合は_(アンダースコアー)で記載することも可能です。

let a: Int? = nil
if _ = a {
    print("nilではない")
} else {
    print("nil")
}

Swiftを学び始めたときにん??となる箇所だと思いますが、他の言語と比較することで学びやすくなるかと思います。

Comuputedプロパティについて

swiftは他の言語とは少し違い、基本的にはgetter/setterが必要ない言語です。

ただ、設定するときに値を加工したりする場合はComuputedプロパティというものがあります。(名前は最近知りました)

今回はそんなComuputedプロパティについて説明しようかと思います。

Comuputedプロパティとは

Comuputedプロパティを説明する前に他の言語でのgetter/setterはこんな感じではないでしょうか??

例えば、Javaでは下記ですよね!

public class Product {
    private String name;
    
    public String getName() {
        return name;
    }
    public void setName(String name) {
        this.name = name;
    }
}

public class Phone {

    Product p = new Product();
     
    p.setName("携帯電話");
        
    System.out.println(p.getName()); //携帯電話
}

このようにgetNameメソッドsetNameメソッドを準備して、必要に応じて、値の取得と設定をメソッドを通じて行います。

つまり、決まった値を保持しておらず、インスタンスが生成される度に値を取得、設定することになります。

では、Swiftではどうでしょうか?

class Product {
    var companyName: String
    
    init(companyName) {
        self.companyName = companyName
    }

    var name:String {
        // 値を取得するときに呼ばれる
        get {
            return companyName
        }
        // 値を設定するときに呼ばれる
        set {
            companyName + "の携帯電話"
        }
    }
}

class Phone {

    let p = Product();
    // setが呼ばれる
    p.name = "A社"
    // getが呼ばれる
    println(p.name) //A社の携帯電話
}

上記のnameプロパティに値を設定するときはset、値を取得するときはgetが呼ばれます。

また、getのみを記載すると読み取り専用のプロパティとなります。

上記のようにせず、別で取得用や設定用のメソッドを作れば同じ動きにすることはできますが、変数の定義を見るとすぐにgetter/setterが見れるのは個人的にわかりやすいと思います。

willSet/didSetについて

Swiftではget・set意外にwillSet/didSetというものがあります。

どういものかみていきましょう!

class Staff {
    var salary: Int { 
        willSet {
            print(salary)// 結果 1000
        }
        didSet {
            print(salary)// 結果  1500
        }
    }
    var worked: Int      //働いた時間
    
    init( _ salary: Int) {
        self.salary = salary
    }
}

class Resutaurant {
    let kuma = Staff(1000)
 kuma.salary = 1500
}

流れは今までみてきた例と同じですが、違いはsalarywillSet/didSetが定義されています。

willSetは値が変わる前の処理を書き、didSetは値が変わった後の処理を書きます。

どんなときに使うかというとdidSetは値が変更したらラベルを変更するなどで使うかもしれません。

willSetは使ったことがないのですが、変更前の値を別の変数で保存しておくなどが考えられます。

Comuputedプロパティとは少し違う話にはなってしまいますが、定義の下に処理がかけるのはComuputedプロパティと同じように見やすく使うこともあるかもしれないので、 参考になると幸いです。

NavigationBarの下にオブジェクトが隠れるのを防ぐ

今回は小ネタです。

最近出くわしたNavigationBarの下にViewが隠れてしまうのを回避する方法です。

iPhone8では大丈夫だったのですが、iPhone Xsでは思いっきり隠れていました。

Viewの位置をソースコードで定義していたので、iPhone Xsのバージョンの位置を追加で定義するのは面倒だと思い、調べてみました。

ちなみにそのViewのTopはSafeareaのTopと同じ位置にしていましたが、うまく表示されませんでした・・・

Storyboardで該当のViewControllerを選択して、Attributes Inspectorを開きます。

Extend EdgesにあるUnder Top Barsのチェックを外すと隠れずに表示されます。

f:id:kumaskun:20201013022749p:plain

これで位置をソースコードで定義する必要もなくなり楽になりました。

この方法は知らなかったので、損してたなあと思いました。

個人的にはStoryBoardでできることはできるだけ設定してしまい、必要な箇所だけコーディングする派なので、理にかなっている方法だと思っています。

Xcodeでのオブジェクトの上下配置について

今回はStroyboardでオブジェクトを配置するときに少しつまずいた上下配置について説明しようと思います。

オブジェクトの配置

XcodeではStroyboardでオブジェクト(ここではViewやボタンなどのパーツのこと)を配置して画面を作ります。

ソースコードでUIを作ることもありますが、コード量が膨大になってしまうので、Stroyboardを使うことが多いです。

例えば、下記のUIの場合で説明します。

f:id:kumaskun:20201011022413p:plain

左側はDocument Outlineといって、オブジェクトの階層を示しています。

1番上に画面いっぱいのViewがあり、その下にFakeLoginViewLoginFormViewという2つのViewが等間隔で配置されています。

FakeLoginViewの下の階層にはラベルが配置されています。

このように配置することで右のような画面が出来上がります。

Constraintsというオブジェクトの位置を決める制約がありますが、ここでは省略します。

オブジェクトの上下配置

では、先ほど使ったイメージのFakeLoginViewを場合によっては別のViewにしたい場合はどうするでしょうか?

別のUIを作れば楽かもしれないですが、1つのUIで管理する方が初期コストはかからないですよね。

今回は初期表示の場合はFakeLoginViewを表示させて、RealLoginViewは場合によって表示させるようにし、これらを1つのViewControllerで管理します。

まずは下記のように配置します。

f:id:kumaskun:20201011023616p:plain

そうすると、下記のようなイメージになってしまいます。

f:id:kumaskun:20201011023801p:plain

FakeLoginViewは消えて、RealLoginViewが前面に表示されてしまします。

これだと、初期表示の場合はRealLoginViewが表示されます。

これはなぜかというとRealLoginViewFakeLoginViewの前面に位置しているからです。

Document Outlineの同じ階層のオブジェクトは下になるほど前面に表示されます。

f:id:kumaskun:20201011024519p:plain
下の方が前面に表示される

なので、対処方法としてはRealLoginViewFakeLoginViewより上に持っていく必要があります。

やり方としては手動で上に持っていくでも可能ですが、 Xcode メニューでもできます。

移動させたいオブジェクトをダブルクリックした状態で、Xcode メニューのEditor > Arrangeを選択して、Send to Backを選択すると選択したオブジェクトが背面に移動します。

ダブルクリックしないとできないので注意です。

f:id:kumaskun:20201011025129p:plain

こうすることで下記のイメージになりました。

f:id:kumaskun:20201011025317p:plain

Document OutlineではRealLoginViewFakeLoginViewより上に配置され、イメージでもFakeLoginViewが表示されています。

このようにiOS開発ではUIの階層があり、これを知らないとUIを作成するのに戸惑うこともあるので、あまり時間をかけずに開発するために知っておく必要があると感じました。

クロージャーの[weak self]について

今回は、クロージャーの[weak self]について書いていこうと思います。

なぜこんな書き方をするのか調べてみると、クロージャーがselfを弱参照し、クロージャーとselfの循環参照を防ぐとの記載がありました。

これを見て??って思いよくわからなかったので、少し調べていきたいと思います。

前提となる参照カウンタなどについては下記で触れています。見ておくと理解が深まるかもしれません。

値型(struct)と参照型(class)について

循環参照とは?

まずは、循環参照について調べていきたいと思います。

循環参照とは、インスタンス間でお互いを強参照しあった場合に参照カウントが0にならず、メモリ上にインスタンスが残り続けてしまう状態のことです。

Swiftでは構造体や列挙隊以外は基本的に強参照です。

class Human {
    let name: String
    var money: Money?

    init(name: String) {
        self.name = name
        print("\(name) is being initialized")
    }

    deinit {
        print("\(name) is being deinitialized")
    }
}

class Money {
    let amount: Int
    var owner: Human?

    init(amount: Int) {
        self.amount = amount
        print("\(amount) is being initialized")
    }

    deinit {
        print("\(amount) is being deinitialized")
    }
}

上記のような2つのクラスがあるとします。初期化すると以下のようなイメージになります。

var kuma: Human? = Human(name: "kuma") // kumaの参照カウントが+1
var money: Money? = Money(amount: 1000) // moneyの参照カウントが+1

初期化後のイメージ

この状態で循環参照を実行します。イメージは下記です。

kuma?.money = money // moneyの参照カウントが+1
money?.owner = kuma // kumaの参照カウントが+1
kuma = nil // kumaの参照カウントが-1
money = nil // moneyの参照カウントが-1

循環参照のイメージ

循環参照を行うと参照カウントが0にならず、kumamoneydeinitされません。結果的にメモリリークを引き起こします。

循環参照しないためには?

では、循環参照しないためにはどうすればいいでしょうか?

循環参照しないためには参照カウントを増やさなければいいのです。 そのために弱参照(weak reference)アンオウンド参照(unowned reference)の2種類の参照方法があります。

弱参照は参照するインスタンスが後からnilになる場合に使用します。また、通常のOptionalと同様にアンラップが必要になります。

一方、アンオウンド参照は設定されたらnilになることがない(インスタンスの存続期間が一方またはお互いに依存する)場合に使用します。

class Human {
    let name: String
    var money: Money?

    init(name: String) {
        self.name = name
        print("\(name) is being initialized")
    }

    deinit {
        print("\(name) is being deinitialized")
    }
}

class Money {
    let amount: Int
    weak var owner: Human? //weak(弱参照にする)

    // 省略
}

上記のようにどちらかの参照をweakもしくはunownedにします。これによって循環参照は解消され、不要なインスタンスは破棄されます。

var kuma: Human? = Human(name: "kuma")  // kumaの参照カウントが+1
var money: Money? = Money(amount: 1000)  // moneyの参照カウントが+1
kuma?.money = money  // moneyの参照カウントが+1
money?.owner = kuma  // kumaの参照カウントは変わらない
kuma = nil   // kumaの参照カウントが-1
money = nil  // moneyの参照カウントが-1

kuma = nil  // kumaの参照カウントが-1, kumaのdeinitが呼ばれ、moneyの参照カウントが-1
money = nil  // moneyの参照カウントが-1, moneyのdeinitが呼ばれる

イメージは下記です。

クロージャの循環参照

クロージャインスタンスなので、クロージャ内でインスタンスを利用した際に循環参照が起こる場合があります。

クラスのプロパティにクロージャを割り当て、その中でselfを参照する場合です。実際に見ていきましょう!

class Human {
    let name: String
    let blood: String

    init(name: String, blood: String) {
        self.name = name
        self.blood = blood
    }

    lazy var getHumanInfo: () -> String = {
        return "\(self.name) \(self.blood)" // selfの参照カウントが+1
    }

}

var kuma: Human? = Human(name: "kuma", blood: "A")
print(john?.getHumanInfo()) // getHumanInfoの参照カウントが+1とselfの参照カウントが+1の合計+2になる
kuma = nil // Human内で循環参照が起きているのでdeinitが呼ばれない

上記の場合は、HumanインスタンスgetHumanInfoインスタンスを参照しており、getHumanInfoインスタンスからHumanインスタンスを参照しているので、循環参照が発生しHumanインスタンスが破棄されません。

循環参照を避けるために、クロージャ内でインスタンスを利用する場合にはキャプチャリストを用います。 キャプチャリストでは弱参照・アンオウンド参照を指定することができるので、 クロージャの解放状況に依存せずにクラスのインスタンスを解放することが可能になります。

class Human {
    let name: String
    let blood: String

 // 省略

    lazy var getHumanInfo: () -> String = { [weak self]  _ in 
        return "\(self.name) \(self.blood)" // selfの参照カウントは変わらない
    }

}

var kuma: Human? = Human(name: "kuma", blood: "A")
print(john?.getHumanInfo()) // getHumanInfoの参照カウントが+1
kuma = nil  // deinitが呼ばれる

キャプチャリストとは上記の場合、[weak self]のことです。inは必ず必要です。

メモリ管理でのバグは調査が大変だと思うので、困ったら[weak self]を使い、[unowned self]を使う場合は設計を確認して使うのがいい気がしました。

参照

speakerdeck.com

medium.com

rakusui.org

値型(struct)と参照型(class)について

今回はタイトルにもある通り、値型と参照型について説明していきます。

プログラミングの基礎的な概念だと思うので、理解しといて損はありません。

今回はSwiftで説明していきたいと思います。

値型

まずは値型について説明します。

値型は変数の中に実際の値が格納されます。正しく言うと、変数の領域に格納されます。

今回は値型であるstruct(構造体)で見てきたいと思います。

struct Human {
    var rank: UInt8
    var age: UInt8
}

上記のような構造体があるとします。説明しやすいように変数はUInt8にしています。

構造体(値型)の場合は値の全てがスタック領域に確保されます。

var kuma = Human(rank:1, age:23)

なので、上記のような宣言をした場合は 1 バイトのUInt8を二つ持っているので2バイト分(Human)の領域が確保されます。 2バイトの領域の最初の1 バイトには1が、次の 1 バイトには23が格納されるイメージです。イメージは下記です。

f:id:kumaskun:20200926222214p:plain

では、次の場合はどうなるでしょう?

var kuma = Human(rank:1, age:23)

var kumaCopy = kuma

kumaという構造体をkumaCopyという変数に代入しています。

次のようなイメージになります。

f:id:kumaskun:20200926223330p:plain

代入した変数はスタックに確保されるのですが、新しく確保された領域にコピーされた値が格納されます。 また、値型の場合変数には値そのものが入ります。

そのためコピー元の値を変更しても代入先の値は変わりません。

var kuma = Human(rank:1, age:23)

var kumaCopy = kuma

kuma.rank = 2

print(kumaCopy.rank)

上記のprintでは1が表示されます。

値型の場合スタックに値がコピーされ続けるのでデータ量の多い構造体の場合メモリを大きく食いつぶす可能性があります。

スタック領域は容量が小さいのですぐ使い切ってしまいます。

この場合スタックを使い切ってしまったエラーをスタックオーバーフローといい、クラッシュします。

参照型

参照型は別の場所に値を格納し、そのアドレス(どこに値を格納したか)を変数に格納します。

値型はスタック領域に格納されていましたが、参照型はヒープ領域に格納されます。

参照型ではクラスを元にして説明します。

class Human {
    var rank: UInt8
    var age: UInt8
    init(rank: UInt8, age: UInt8) { 
        self.rank = rank;
        self.age = age
    }
}

以下のようにして、オブジェクトを生成し変数に格納します。

var kuma = Human(rank:1, age:23)

クラス(参照型)はヒープ領域に確保されますが、その確保のされ方は値型がスタックに確保される時と同じです。

違うのはHuman(rank:1, age:23)が返す値が生成された領域の先頭アドレスということだけです。 つまり、kumaにはオブジェクトのアドレスが格納されています。 イメージは下記です。

f:id:kumaskun:20200927003759p:plain

このように参照型は値そのものではなく、オブジェクトへの参照を変数に格納します。

すると、値型で試した変数の代入を行った場合はどうなるでしょうか?

var kuma = Human(rank:1, age:23)

var kumaCopy = kuma

先にイメージをもとに説明したいと思います。

f:id:kumaskun:20200927004354p:plain

変数はスタックに確保され、変数宣言をすると新しくスタック領域に値のコピーが作成されます。

参照型の場合は変数にはアドレスという値がコピーされます。

指し示すアドレスは同じオブジェクトなのでkumaとkumaCopyは同じものとみなすことができます。

なので、コピー元の値を変更すると代入先の値も変更されます。

var kuma = Human(rank:1, age:23)

var kumaCopy = kuma

kuma.rank = 2

print(kumaCopy.rank)

上記のprintでは2が表示されます。

これは意図せず値が変わってしまう可能性があるので注意が必要です。

struct(値型)とclass(参照型)はどちらを使うのがいいのか?

ここまで値型と参照型を見てきて違いがわかったと思いますが、どちらを使った方がいいのかと思った方もいると思います。

個人的には、class(参照型)がメインで場合によってはstruct(値型)がいいと思います。

まずは、参照型からですが、参照型は代入のパフォーマンスだと思います。

var kuma = Human(rank:1, age:23)

var kumaCopy = kuma

上記は参照型の例なので、Humanはクラスです。

kumaCopykumaのメモリの先頭をコピーするだけです。32 bit アーキテクチャなら 4 バイト、 64 bit アーキテクチャなら 8 バイトのデータだけになります。

一方、値型の場合は値を全てコピーします。

値型で説明したときはrankageだけでしたが、下記のようにより多くのデータがある場合はその分コピーしないといけません。

struct Human {
    var data1: UInt8
    var data2: UInt8
    var data3: UInt8
    var data4: UInt8
    // 省略
   var data100: UInt8
}

上記のように変数が100個あると100個分コピーしないといけなくなり、その分の領域を確保しなくてはいけなくなりパフォーマンスが悪化する可能性があります。

structを使うのは比較的単純な値を扱うときのみの方がいい気がしました。

また、structは継承ができないため、継承する場合はclass一択です。

最後に、Swiftの標準ライブラリで提供される型(IntやString、配列など)はほぼすべてが値型という珍しい言語です。

メモリ領域

最後にメモリ領域について軽く述べたいと思います。

今まででスタック領域やヒープ領域などいきなり説明の中に出てきたと思います。軽く解説します。

スタック領域はメモリがA→B→Cの順で確保された場合、C→B→Aの順で解放される。メモリをいわゆるスタックで管理している。 処理ブロック({}で囲まれたブロック)を抜けると解放されます。 ヒープよりも割り当てられている領域が小さい。

ヒープ領域は確保されたメモリは明示的に解放しないとずっと留まり続ける。そのため気をつけていなければメモリリークを引き起こしクラッシュの原因になる。 自由なサイズのメモリを確保できます。

ヒープ領域の場合は、メモリを使い終わったら開放しないといけないのですが、Swiftでは自動的に解放されるようになっています。

この自動で解放してくれる仕組みをガベージコレクションと呼んでいます。 ガベージコレクションではヒープ領域に対して参照カウンタというものを持ちます。 参照カウンタとは、何個の変数から参照されているのかカウントするものです。

var kuma = Human(rank:1, age:23)

var kumaCopy = kuma

例えば、上記ではkumaとkumaCopyの2つでHumanは参照されています。 なので、参照カウンタは2になります。

この参照カウンタが0になったタイミングでオブジェクトは解放されます。どのようにするのでしょう?

kumaCopy = nil

nilはどこのアドレスも指していないという意味です。 そうすると参照カウンタが-1され1となります。

ただ、毎回毎回nilにする必要はありません。 変数は処理ブロックを抜けると解放されます。

つまり{}(ブロック)を抜けると変数は解放され、参照カウントが自動的に-1されるので、意識する必要がないです。

参照

Swiftで値型と参照型の違いを理解する

Structures and Classes

技術書典9を終えて

9/12(土)〜9/22(火)まで行われた技術書典9が終わったので振り返りたいと思います。

今回初めて1人で「モバイル詰め合わせ」という本を執筆して販売しました。

大したことを書いたわけではないから全然売れないだろうなあと思っていたのですが、終わってみると思ったより売れてうれしかったです!!

最初は謙虚じゃなく、本当に5冊売れればいいかなあと思っていましたが、目標は普通に超えたのでびっくりしました。

ただ、反省点が1つあるとすれば、表紙はちゃんとしたものにすれば良かったと思いました。

表紙がキレイな本はやっぱり売れている気がしました。

あと、物理本を準備すれば良かったと思いました。

オンライン即売会なんですが、意外に物理本が売り切れになることが多かったみたいです。

photoshopIllustratorが必要みたいで、時間的にも間に合わず見送ったのを後悔しました。

次の機会には準備をしたいと思いました。

何より1人で作ってみて売れたときの達成感はすごいものがありました。

技術同人誌は別の機会にも作りたいと思いました。

最後に宣伝です。

技術書典9は終わってしまいましたが、BOOTHで販売することにしました。

気になった方は下記より見ていってください!!

BOOTH -- くまさん研究所 『モバイル詰め合わせ』(https://kumalabo.booth.pm/items/2403310)

技術書典9にサークル参加しました

今回は告知をさせてください!!

初めて1人で技術同人誌を書きました。

そして、昨日(9/12)から始まった技術書典9というオンライン即売会で販売しています。

表紙を作る難しさを今回初めて痛感して、電子版のみの販売としています。(デザイナーに依頼するべきでした・・)

くまさん研究所というサークル名でモバイル開発についての本を執筆しました。

内容はAndroidのMVVM、SwiftUI、Flutterについてです。(主に導入部分がメインです)

後はオマケでAndroid11についても少し記載しています。(本当にオマケ程度です)

モバイル開発をしている方にももちろん読んでいただきたいですが、まだやったことない方にもこんな感じなのかと学びがあると思います。

まあつまり全人類の方に読んでいただきたいですwww

f:id:kumaskun:20200913133117p:plain
表紙はこんな感じです

今後BOOTH等で販売するかは未定ですが、とりあえずは技術書典オンラインマーケットでは販売します。

ぜひ見てみてください!!

くまさん研究所のモバイル詰め合わせはこちらから見れます