M1 MBPがめちゃくちゃ重かった。
セーフモードで起動したりしても変化なし。
アクティビティモニタで1PasswordSLSNativeMessageHostのプロセスが大量にあってCPUを使いまくってた。 これを全部終了させたらサクサク動き始めた。
M1 MBPがめちゃくちゃ重かった。
セーフモードで起動したりしても変化なし。
アクティビティモニタで1PasswordSLSNativeMessageHostのプロセスが大量にあってCPUを使いまくってた。 これを全部終了させたらサクサク動き始めた。
複数の制約を設定しpriorityを変更することで制約を切り替える
@IBOutlet weak var targetViewPortraitCenterX: NSLayoutConstraint! // 初期priority: 999 @IBOutlet weak var targetViewPortraitCenterY: NSLayoutConstraint! // 初期priority: 999 @IBOutlet weak var targetViewLandscapeCenterX: NSLayoutConstraint! // 初期priority: 750 @IBOutlet weak var targetViewLandscapeCenterY: NSLayoutConstraint! // 初期priority: 750
targetViewPortraitCenterX.priority = UILayoutPriority(750) targetViewPortraitCenterY.priority = UILayoutPriority(750) targetViewLandscapeCenterX.priority = UILayoutPriority(999) targetViewLandscapeCenterY.priority = UILayoutPriority(999)
// 90°回転させる let angle = 90 * CGFloat.pi / 180 self.targetView.transform = CGAffineTransform(rotationAngle: angle)
class ExampleViewController: UIViewController { @IBOutlet weak var targetView: UIView! @IBOutlet weak var targetViewPortraitCenterX: NSLayoutConstraint! // 初期priority: 999 @IBOutlet weak var targetViewPortraitCenterY: NSLayoutConstraint! // 初期priority: 999 @IBOutlet weak var targetViewLandscapeCenterX: NSLayoutConstraint! // 初期priority: 750 @IBOutlet weak var targetViewLandscapeCenterY: NSLayoutConstraint! // 初期priority: 750 override func viewDidLoad() { super.viewDidLoad() // 端末の向き検知 NotificationCenter.default.addObserver(self, selector: #selector(self.changedOrientation), name: UIDevice.orientationDidChangeNotification, object: nil) } @objc func changedOrientation() { var angle: CGFloat = 0.0 // 座標移動 let orientation = UIDevice.current.orientation if orientation == .portrait { self.targetViewPortraitCenterX.priority = UILayoutPriority(999) self.targetViewPortraitCenterY.priority = UILayoutPriority(999) self.targetViewLandscapeCenterX.priority = UILayoutPriority(750) self.targetViewLandscapeCenterY.priority = UILayoutPriority(750) angle = 0.0 } else if orientation == .landscapeLeft { self.targetViewPortraitCenterX.priority = UILayoutPriority(750) self.targetViewPortraitCenterY.priority = UILayoutPriority(750) self.targetViewLandscapeCenterX.priority = UILayoutPriority(999) self.targetViewLandscapeCenterY.priority = UILayoutPriority(999) angle = 90 * CGFloat.pi / 180 } // 回転 UIView.animate(withDuration: 0.1, delay: 0.0, options: .curveLinear, animations: { self.targetView.transform = CGAffineTransform(rotationAngle: angle) }) } }
上の例はStoryboardを使用しているがコードでUIを組んでいても同じような方法でできると思う。
方法は他にもあるかもしれない。
コードのロジックを変更しなくても適切な命名をすればプログラムの動作を壊さずに可読性を改善することができます。
書籍「リーダブルコード」では第1部 表面上の改善 で命名方法が解説されています。
書籍「iOSアプリ設計パターン入門」にはモジュールやメソッド、変数において「責務をイメージできない名前をつけた途端に設計は腐敗への道を辿ります」とあります。
設計とは関心の分離によって複雑な問題を単純な問題の群として切り分けることです(解決すべき問題の領域=責務)。責務によって切り分けられた型を使用するプログラマも型が負う責務を理解して型を使用したり変更を加えていかなければ責務の境界が壊れていきます。この責務の境界を守っていくために責務を明確にイメージできる命名が必要です。
設計により責務が適切に分かれていることや可読性が高いことはコード理解を助け開発効率向上と不具合発生率を抑えることに大きく影響すると思うので適切な命名ができるように意識していきたいです。
Swiftの命名については以下にガイドラインが記載されています。
Swift.org - API Design Guidelines
最後に上記ガイドラインを一部日本語訳したものを記載します。
明確な使用を促進する
流暢な使用に努める
x.capitalizingNouns()
❌ x.nounCapitalize()
x.distance(to: y)
x.sort()
x.sort()
z = x.sorted()
y.formUnion(z)
x = y.union(z)
x.isEmpty
line1.intersects(line2)
Collection
Equatable
ProgressReporting
*1 副作用 - メソッドの呼び出しによってオブジェクトの状態、または別のオブジェクトの状態が変化すること
一般的な規則
環境:
M1 Mac
Xcode13.2.1
SwiftLint0.46.3
SwiftLintのreadme
https://zgithub.com/realm/SwiftLint
まずMintでインストールしようとしたが以下のエラーが出た。
warning: SwiftLint not installed, download from https://github.com/realm/SwiftLint
上記はBuild Phasesに記載したコードのelseに入った結果の警告文。
解決には以下が何かしら参考になるかもしれない。
今回はSwiftLintを試すことが目的なのでHomebrewでのインストールに変更した。
SwiftLintは初めからデフォルトルールが有効になっている。
まずデフォルトで実行して必要ないものを無効にする、次にopt_inを眺めて有効にしたいものを見つけるとかがいいのかなと思った。
公式ドキュメントを読んでクロージャの整理。
クロージャはコード内で受け渡しできる自己完結型のブロック。
クロージャは定数および変数への参照をそれらが定義されているコンテキストからキャプチャして保存できる。
グローバル関数とネストされた関数はクロージャの特殊なケース。 クロージャの形式は以下の3つ。
外からクロージャを渡す場合
func backward(_ s1: String, _ s2: String) -> Bool { return s1 > s2 } var reversedNames = names.sorted(by: backward) // reversedNames is equal to ["Ewa", "Daniella", "Chris", "Barry", "Alex"]
クロージャ式の構文の一般的な形式
{ (parameters) -> return type in statements }
一般的なクロージャ式の構文そのままで書くと以下のように書ける。
reversedNames = names.sorted(by: { (s1: String, s2: String) -> Bool in return s1 > s2 })
この時Swiftは引数の型と戻り値の型を推測できるので以下のように書ける。
reversedNames = names.sorted(by: { s1, s2 in s1 > s2 } )
Swiftはインラインクロージャに短縮引数名を自動的に提供する。 inキーワードも省略できる。
reversedNames = names.sorted(by: { $0 > $1 } )
関数の最後の引数としてクロージャ式を関数に渡す必要があるとき末尾のクロージャとして記述できる。末尾のクロージャ構文を使用する場合引数ラベルは記述しない。
sortedメソッドを末尾のクロージャを使って書くと
reversedNames = names.sorted() { $0 > $1 }
クロージャ式が関数またはメソッドの唯一の引数で末尾のクロージャとして書く場合関数またはメソッドの名前の後に括弧のペアは記述しなくていい。
reversedNames = names.sorted { $0 > $1 }
末尾のクロージャは、クロージャが十分に長く、1行にインラインで書き込むことができない場合に最も役に立つ。
関数を呼び出すとき最初の末尾のクロージャの引数ラベルを省略し、残りの末尾のクロージャにラベルを付ける。
定義
func loadPicture(from server: Server, completion: (Picture) -> Void, onFailure: () -> Void) { if let picture = download("photo.jpg", from: server) { completion(picture) } else { onFailure() } }
呼び出し
loadPicture(from: someServer) { picture in someView.currentPicture = picture } onFailure: { print("Couldn't download the next picture.") }
クロージャは自身が定義されている周囲のコンテキストから定数と変数をキャプチャできる。定数と変数を定義した元のスコープが存在しなくなった場合でも、ボディからこれらの定数と変数の値を参照および変更できる。
ネストされた関数の場合
func makeIncrementer(forIncrement amount: Int) -> () -> Int { var runningTotal = 0 func incrementer() -> Int { runningTotal += amount return runningTotal } return incrementer }
incrementer()はrunningTotalとamountを周囲からキャプチャする。
func incrementer() -> Int { runningTotal += amount return runningTotal }
参照によるキャプチャはrunningTotalとamountがmakeIncrementerの呼び出しが終了した時消えないこととrunningTotalが次にincrementerが呼ばれたときに使用可能なことを保証する。
次のincrementByTenはincrementer関数を参照する。incrementer関数は呼び出される度にrunningTotalに10を足す。
let incrementByTen = makeIncrementer(forIncrement: 10) incrementByTen() // returns a value of 10 incrementByTen() // returns a value of 20 incrementByTen() // returns a value of 30
2つ目のincrementerを作ると先述のものとは別の新しいrunningTotalへの参照を持つ。
let incrementBySeven = makeIncrementer(forIncrement: 7) incrementBySeven() // returns a value of 7
関数の引数として渡されたクロージャが関数が返された後に呼ばれるときクロージャが関数をエスケープすると言う。エスケーピングクロージャがselfをキャプチャするときは明示的にselfを記載するかキャプチャリストに含める必要がある。selfを明示的に記載することで意図を表現でき参照サイクルがないことを確認するように促される。
// エスケーピングクロージャ var completionHandlers: [() -> Void] = [] func someFunctionWithEscapingClosure(completionHandler: @escaping () -> Void) { completionHandlers.append(completionHandler) } // 通常のクロージャ func someFunctionWithNonescapingClosure(closure: () -> Void) { closure() } class SomeClass { var x = 10 func doSomething() { someFunctionWithEscapingClosure { self.x = 100 } // xを明示的にキャプチャする someFunctionWithNonescapingClosure { x = 200 } // xを暗黙的にキャプチャする } }
キャプチャリストにselfを含める場合の書き方 // someFunctionWithEscapingClosure { [self] in x = 100 }
※オプショナル型はデフォルトで@escapingになる Optional Non-Escaping Closures – Ole Begemann
selfが構造体または列挙体のインスタンスである時はselfを常に暗黙的に参照できる。ただしこの時エスケーピングクロージャはselfへのmutableな参照をキャプチャできない。構造体と列挙体は共有された可変性を許可していない。
struct SomeStruct { var x = 10 mutating func doSomething() { someFunctionWithNonescapingClosure { x = 200 } // Ok someFunctionWithEscapingClosure { x = 100 } // Error } }
上記でsomeFunctionWithEscapingClosureはmutationgメソッドの中にありselfはmutable なのでエラーになる。
オートクロージャとは関数の引数として渡される式をラップするために自動的に作成されるクロージャのこと。引数をとらず、呼び出されるとその中にラップされている式の値を返す。
オートクロージャを使用すると引数として関数を取る時に{}
を使ってクロージャとして書かずに通常の引数のように書ける。
@autoclosure
を使わずに書く場合
// customersInLine is ["Alex", "Ewa", "Barry", "Daniella"] func serve(customer customerProvider: () -> String) { print("Now serving \(customerProvider())!") } serve(customer: { customersInLine.remove(at: 0) } ) // Prints "Now serving Alex!"
@autoclosure
を使って書く場合
// customersInLine is ["Ewa", "Barry", "Daniella"] func serve(customer customerProvider: @autoclosure () -> String) { print("Now serving \(customerProvider())!") } serve(customer: customersInLine.remove(at: 0)) // Prints "Now serving Ewa!"
オートクロージャを使用すると、クロージャを呼び出すまで内部のコードが実行されないため評価を遅らせることができる。評価の遅延はコードがいつ評価されるかを制御できるため、副作用があるコードや計算コストが高いコードに役立つ。
以下の記事を参考にDispatchqueueの種類ごとにPlaygroundで実行してみた
実行環境:
Xcode13.0
Swift5.2
// メインキューは直列(前のタスクが完了次第、次のタスクが実行される) let mainQueue = DispatchQueue.main // syncを使うと呼び出し元がブロックされる print("start") for i in 0...5 { mainQueue.sync() { print(i) } } print("end")
実行結果
error: Execution was interrupted, reason: EXC_BAD_INSTRUCTION (code=EXC_I386_INVOP, subcode=0x0). The process has been left at the point where it was interrupted, use "thread return -x" to return to the state before expression evaluation.
// 非同期なので呼び出し元がブロックされずstart、endが最初に出力される // 直列なので番号は順番に出力される print("start") for i in 0...5 { mainQueue.async() { sleep(1) print(i) } } print("end")
実行結果
start end 0 1 2 3 4 5
// グローバルキューは並列(前のタスクの処理状況に関わらず、次のタスクが実行される) let grobalQueue = DispatchQueue.global(qos: .default) // 同期なので呼び出し元がブロックされてendが最後に出力される // 並列だが同期なので番号は順番に出力される print("start") for i in 0...5 { grobalQueue.sync() { sleep(1) print(i) } } print("end")
実行結果
start 0 1 2 3 4 5 end
// 非同期なので呼び出し元がブロックされずstart、endが最初に出力される // 並列なので番号が出力される順番はバラバラ print("start") for i in 0...5 { grobalQueue.async() { sleep(1) print(i) } } print("end")
実行結果
start end 4 2 3 0 1 5
// 直列 let serialQueue = DispatchQueue(label: "serialQueue") // 並列 let parallelQueue = DispatchQueue(label: "parallelQueue", attributes: .concurrent)
同期および非同期での実行結果はメインキュー、グローバルキューの結果から推察できる通り。
メインキューではエラーになったが直列で同期の場合は以下のようになる。
print("start") for i in 0...5 { serialQueue.sync() { sleep(1) print(i) } } print("end")
実行結果
start 0 1 2 3 4 5 end