constアサーション「as const」とreadonlyの違い
オブジェクトの末尾にconstアサーション「as const」をつけると、オブジェクトすべてのプロパティに「readonly」がついた状態になる。「as const」がついたオブジェクトのプロパティに、再代入はできない。
const foo = { nation: "India", sex: "male", name: "chandora" } as const foo.sex = "female" => Cannot assign to 'sex' because it is a read-only property.
一方、readonlyはプロパティごとにつけられる。
type Person = { name: string, readonly age: number, } const person:Person = { name: "murata", age: 12, } // nameプロパティには再代入できている。 person.name = "nakata" person.age = 33 => Cannot assign to 'age' because it is a read-only property.
【Swift】RxSwift
主な特徴として、値変化の検知や非同期処理を簡単に書けるというものがある。UI変更の検知やAPIで取得した値の検知などが書きやすくなる。メソッドチェーンでかける。
例えば、以下の場合、ボタンが増えるに従い、IBActionも増えて可読性が低下する。RxSwiftなら、ボタンに対してタップした時の処理をプロパティとして定義するだけで済む。状況にもよるが、コンポーネントが複雑な画面はRxSwiftでの実装をした方がいい。また、@objc func...をaddTargetでコンポーネントで紐づけるより、こっちの方が良さそう。
@IBAction func tapButton(_ sender: Any) { label.text = "button tapped" } //viewDidLoad()の中で定義する button.rx.tap .subscribe(onNext: {[weak self] in self?.label.text = "butoton tapped" }).disposed(by: disposeBag)
RxSwiftの書き方は以下の通り。
- イベントの購読(ボタンのタップ、APIからのデータ取得などのストリームの購読)
- イベントを購読したときにどうするか定義
- クラスが破棄されると、購読も破棄される
主な用語
Observable...観測可能なものを意味する。イベントを検知するためのクラス。ストリームとも呼ばれる。Observableが通知するイベントは次の種類がある。
- onNext...デフォルトのイベントを流す。イベント内に値を格納でき(下の例ではmessage)、何回でも呼べる。
- onError...エラーイベント。1度だけ呼ばれる。その時点で終わり、購読を破棄する。
- onCompleted...完了イベント。1度だけ呼ばれる。その時点で終わり、購読を破棄する。
以下では、onNextイベントが来るとそのクロージャが実行され、onErrorイベントが流れてくるとそのクロージャが実行され、onDisposedイベントが流れてくるとそのクロージャが実行される。
helloSubject // Observable(イベント発生元) .subscribe(onNext: {message in print(message) // Observer(イベント処理) }, onError: { error in print(error) },onCompleted: { print("onCompleted") }, onDisposed: { print("onDisposed") }).disposed(by: disposeBag) // onError / onCompletedは省略できる helloSubject.subscribe(onNext: {message in print(message) }).disposed(by: disposeBag)
SubjectとRelay
イベントの検知に加えて、イベントの発生もできる。 以下の4つがある。
- PublishSubject...oNext, onError, onCompleteを流せる。
- BehaviorSubject...oNext, onError, onCompleteを流せる。
- PublishRelay...oNextを流せる。
- BehaviorRelay...oNextを流せる。
Subject・・・通信処理やDB処理でエラーが発生したときにその内容で処理を分岐させる。APIでデータをフェッチしたときに使う。
Relay・・・UIに値をBindする。購読が止まらないように、onNextだけ流せる。
bind
指定したものに、イベントストリームを接続する。単方向バインド。 下ではTextFieldに名前が入力された時、ViewModelに値を反映させる。
nameTextField.rx.text .bind(to: viewModel.username) .disposed(by: disposeBag)
参考サイト
【Swift】コレクションに関する処理
- for -in- > 終了値を含まない
for i in 1..<4 { print("i: \(i)") } => i: 1 i: 2 i: 3
- for -in- > 終了値を含む
for i in 1...4 { print("i: \(i)") } => i: 1 i: 2 i: 3 i: 4
- for-in コレクション
let arrayData = [3, 5, 8, 10] for data in arrayData { print("data: \(data)") } => // 【結果】 // data: 3 // data: 5 // data: 8 // data: 10
- forEach 要素に対して順次アクセスする。戻り値はVoid。
let array = [1,2,3,4] var addedArray = [] as [Int] array.forEach({ element in addedArray.append(element * 2) }) print(addedArray) => [2, 4, 6, 8]
- filter 要素を絞り込む。指定された条件に合致する要素だけのシーケンスを返す。
let array = [1,2,3,4] let filtered = array.filter({element in element % 2 == 0}) print(filtered) => [2, 4]
- map 要素を変換してそれらのシーケンスを返す。
let array = [1,2,3,4] print(array.map({element in element * 2})) print(array.map({element in String(element)})) => [2, 4, 6, 8] ["1", "2", "3", "4"]
- flatMap 要素をシーケンスに変換して、さらに一つのシーケンスに連結する。
let array = [1,2,3,4] print(array.flatMap({value in [value, value + 1]})) =>[1, 2, 2, 3, 3, 4, 4, 5]
- compactMap nilや変換できない要素を無視して、シーケンスに変換する
let optionalNumberList: [Int?] = [1, 2, nil, 4, 5, nil] print(optionalNumberList.compactMap { $0 }) => [1, 2, 4, 5] let strings = ["1", "2", "3", "abc"] print(strings.compactMap({value in Int(value)})) => [1, 2, 3]
- reduce 要素をまとめる
let array = [1,2,3,4,5] let sum = array.reduce(0, {result, element in result + element}) print(sum) => 15
- コレクションのプロトコル
let array = [1,2,3,4,5] print(array.isEmpty) print(array.count) print(array.first) print(array.last) => false 5 1 5
- String.Index 文字の位置を示す。 以下のようにして文字にアクセスできる。
let string = "abcde78" print(string[string.startIndex]) => a // endIndexには注意が必要。これ自体は最後の文字の次のインデックスを返す。故に前に1ずらす。 let lastIndex = string.index(string.endIndex, offsetBy: -1) print(string[lastIndex]) => 8
- for...where whereで条件を指定。条件に合致するものだけ、ループの中で実行される。
for i in 0...9 where i % 2 == 0 { print(i) } => 0 2 4 6 8
プロトコル
プロトコルとは型が特定のプロパティやメソッドを持つために必要なインターフェースを定義するものである。プロトコルが要求するインターフェースを型が満たすことを準拠と呼ぶ。
定義方法と準拠方法
protocol User { func printName() } struct JapaneseUser : User { func printName() { print("中山です") } } // プロトコルUserに準拠しながら、メソッドprintName()を実装していないので、コンパイルエラーとなる。 struct ChineseUser : User { } protocol Nation { func printPassport() } // 構造体JapaneseUserにNationに準拠した処理を追加 extension JapaneseUser : Nation { func printPassport() { print("これがパスポートです") } } // 1つのextensionに対して、1つのプロトコルへの準拠が良い。どのextensionがどのプロトコルに準拠しているのかが、明確になる。
プロトコルの構成要素
- プロパティ
プロトコルのプロパティは常にvarで宣言し、{}の中にゲッターは必ず、また必要ならばセッターも追記する必要がある。
protocol User { // varで宣言、getは必須 var name: String {get set} } //ゲッターの実装 protocol User { var name: String {get} } struct JapaneseUser : User { // varのまま var name: String func printName() { print("中山です") } } struct ChineseUser : User { // 定数にする let name: String func printName() { print("我叫李") } } struct UKUser : User { // コンピューテッドプロパティもOK var name: String { return "Tom" } func printName() { print("I am Tom") } }
- メソッド
メソッドも定義できる。 メソッド名、引数の型、戻り値の型のみを定義する。またプロトコルでは実装しないので、{}は省く。
protocol User { var name: String {get} // {}は不要 func instanceMethod() -> String } struct JapaneseUser : User { var name: String func instanceMethod() -> String { return "中山です" } }
構造体・クラス・列挙型
構造体
値型の一種。ストアドプロパティの組み合わせで、一つの値を示す。 構造体のプロパティを変更することは、その構造体を別の値にすることであり、変数への再代入が必要である。
struct User { let name = "Murata" let age = 20 }
定数のストアドプロパティは変更できない。
struct User { var name = "Murata" var age = 20 } var user1 = User() user1.name = "123" let user2 = User() user2.name = "345" => コンパイルエラーになる
クラス
参照型のデータ構造。 構造体との違いは、1・参照型であること 2・継承ができること の2つである。
クラスに紐づく要素
クラスプロパティとクラスメソッドがある。スタティックプロパティ・スタティックメソッドとは違い、オーバーライドができる。
class User { class var name: String { return "bakuro" } class func printUser() { print("this is class method") } } print(User.name) => bakuro User.printUser() => this is class method
列挙型
値型の一種で、複数の識別子をまとめる。それぞれのケースは排他的である。
enum Weekday { case monday case tuesday //... } let monday = Weekday.monday
一般的にWeekday.monday
のようにケース名を指定してインスタンスを生成する。イニシャライザを持つこともできる。
enum Weekday { case monday case tuesday // nilになるかもしれないので失敗可能イニシャライザ init?(name: String) { switch name { case "月": self = .monday case "火": self = .tuesday // switchは全てを網羅すること default: return nil } } } let monday = Weekday(name: "月") // Optional(Main.Weekday.monday) let tuesday = Weekday(name: "火") // Optional(Main.Weekday.tuesday) let sunday = Weekday(name: "日") // nil
また、メソッドやコンピューテッドプロパティを持つこともできる。
enum Weekday { case monday case tuesday func isDay() { print(true) } var japaneseName: String { switch self { case .monday: return "月曜日" case .tuesday: return "火曜日" } } }
実体の定義
列挙型のケースにはそれぞれ対応する値を設定できる。これをraw valueと呼ぶ。そして全てのケースのraw valueは同じであること。Int, String, Doubleなどを型に指定できる。また、暗黙的に失敗可能イニシャライザが用意され、対応するケースがあれば、そのケースはインスタンスにし、なければnilを返す。
enum 列挙型名 : ローバリューの型 { case ケース名1 = ローバリュー1 case ケース名2 = ローバリュー2 } // 例 enum Nation : String { case russia = "ロシア" case china = "中国" case korea = "朝鮮" } let n = Nation(rawValue: "ロシア") print(n) // Optional(Main.Nation.russia) // 失敗可能イニシャライザをもつので、「?」がいる。 print(n?.rawValue) // Optional("ロシア") let u = Nation(rawValue: "イタリア") // イタリアというローバリューに一致するケースがないので、nilがかえる。 print(u) // nil print(u?.rawValue) // nil
ローバリューのデフォルト値
Int, Stringではローバリューにデフォルト値があり、値を指定しない場合はデフォルト値が適用される。 Intなら、最初が0で1,2,3...と適用される。 Stringなら、ケース名がそのまま適用される。
enum Number : Int, CaseIterable { case zero case one case two } enum Alphabet : String, CaseIterable { case a case b case c } print(Number.zero.rawValue) // 0 print(Number.one.rawValue) // 1 print(Alphabet.a.rawValue) // a print(Alphabet.b.rawValue) // b // enumを, CaseIterableに準拠させるとallCasesによって、enumの全てのケースを配列で取得できる。 print(Number.allCases) //[Main.Number.zero, Main.Number.one, Main.Number.two] print(Alphabet.allCases) // [Main.Alphabet.a, Main.Alphabet.b, Main.Alphabet.c]
型の構成要素
Swiftの型には、
- クラス
- 構造体(struct)
- 列挙型(enum)
の3種類がある。標準ライブラリの型の多くはstructで実装されている。
型に共通する要素
- 型が持つ値を保存するプロパティ(型に紐づく変数や定数)
- 振る舞いを示すメソッド(型に紐づく関数)
- 初期化を行うイニシャライザ
- コレクションの要素を取得するサブスクリプト
- 型の中に型を定義するネスト型
定義方法
struct User { } class User { } enum User { }
インスタンス化の方法
型名()
でインスタントを生成する。()の中には、必要に応じて引数を渡す。クラスと構造体はデフォルトのイニシャライザを持つので、引数なしで初期化できる。列挙型はデフォルトのイニシャライザを持たないので()だけではインスタンスにできない。ケースを指定して、生成する。
struct structUser {} class classUser {} let structUser = structUset() let classUser = classUset()
プロパティの定義方法
struct User { var name: String = "Murata" // var name = "Murata"でもいい。再代入できる。 let age: Int = 12 // 再代入不可能 } let user = User() print(user.name) // "Murata" print(user.age) // 12
プロパティの型の整合性を保つためにインスタンス化が完了するまでに、全てのプロパティに値が代入されている必要がある。ゆえに宣言時に初期値を持っているか、イニシャライザの処理の中で初期化されるかのいずれかの方法が必要である。
プロパティの種類
- インスタンスプロパティ・・・型のインスタンスに紐づく。宣言時に初期化かイニシャライザで初期化を行う。
- スタティックプロパティ・・・型自身に紐づく。イニシャライザによる初期化のタイミングがないので、宣言時に必ず値を代入する必要がある。
- ストアドプロパティ・・・値を保持するプロパティ。letやvarで定義する。
- コンピューテッドプロパティ・・・プロパティ自身では値を持たずストアドプロパティなどから計算して値を返す。セッターとゲッターを使う。ゲッターは必須である。
コンピューテッドプロパティの例
struct Square { let width = 10 let height = 20 var area: Int { get { return width * height } } } let square = Square() print(square.area)
※プロパティオブザーバ
ストアドプロパティの変更の監視をする。変更前と変更後に実行される。willSet
didSet
で実行する。
struct User { var name = "murata" { willSet { print("No change") } didSet { print("it was changed") } } } var user = User() user.name = "abe" => No change => it was changed
エクステンション
既存の型にコンピューテッドプロパティやメソッド、イニシャライザを追加できる。
//メソッドの追加 extension String { func printSelf() { print(self) } } let text = "abc" text.printSelf() => abc //コンピューテッドプロパティの追加 extension String { var enclosedSelf: String { return "『\(self)』" } func printSelf() { print(self.enclosedSelf) } } let text = "abc" text.printSelf() => 『abc』 //イニシャライザの追加 struct Book { let id: String let title: String var price: Int } extension Book { init() { id = "000" title = "タイトルなし" price = 0 } } let book1 = Book(id: "001", title: "SwiftyBook", price: 1500) print(book1.title) => SwiftyBook let book2 = Book() print(book2.title) => タイトルなし
AppDelegateのまとめ
AppDelegateはアプリの起動や終了といったライフサイクルを司る重要な仕組み。
AppDelegate.swift
Xcodeでプロジェクトを生成した時点で自動生成されるファイルの一つであり、アプリ全体のライフタイムイベントを管理するためにAppDelegateクラスが記述されている。そしてそのAppDelegateクラスはUIResponderを継承して、UIApplicationDelegateプロトコルとUNUserNotificationCenterDelegateプロトコルを適合している。
class AppDelegate: UIResponder, UIApplicationDelegate, UNUserNotificationCenterDelegate { ・・・中略 }
UIResponderクラス
UIRespondeは、UIKitアプリのイベント処理バックボーン(裏側の処理)を構成する。つまりUIResponderクラスはイベントの処理を管理するクラスであり、UIViewやUIApplicationなどのスーパークラスになる。そのためタッチイベントやモーションイベントをはこのクラスが管理しており、UIViewを継承しているUITextFieldやUITextViewはUIResponderのプロパティやメソッドを使える。
UIApplicationDelegateプロトコル
UIApplicationDelegateプロトコルとは、アプリの共有動作を管理するために使用する一連のメソッドを宣言しているメソッドである。起動完了時・終了時、メモリの低下、ダウンロードの完了通知など、アプリケーション実行中に置ける重要なイベントを実行する。
UNUserNotificationCenterDelegateプロトコル
着信通知および通知関連のアクションを処理するためのインターフェース。