20210514のSwiftに関する記事は8件です。

enum swift

■enum 基本的な定義の仕方 //定義の仕方 enum BloodType { case a case b case o case ab } 基本的な使い方 switch文を使う。 if文でも出来るがswitchの方がいい switchなら定義する時enumの全てのcaseが入っていなければエラーになるので。 抜けていれば教えてくれる。 let type = BloodType.a switch type { case .a: print("血液型A型") case .b: print("血液型b型") default: print("血液型それ以外") } ■値を持たせることもできる。型を指定できる。文字型,整数,浮動小数のみ enum Color: Int { case red = 0 case blue = 1 case green = 2 } //省略してかける。 //インクリメントで値が代入される。 enum Color2: Int { case red = 5, blue,green } //定義の方法 let color1 = Color.red let color2: Color = .green //rawValueで値を指定してアクセスできるがoptinalになる。 let color3 = Color.init(rawValue: 2) //rawValueで値にアクセスできる。 print("\(color1),\(color1.rawValue)") //結果: red 0 print("\(color2),\(color2.rawValue)") //結果 blue 1 print("\(color3),\(color3?.rawValue)") //結果 Optional(__lldb_expr_2.Color.green),Optional(2) ■enumパラメーターを持たせれる。 enum Country { case japan(String,Int) case america(String,Int) case antarctic(Int) } let country1 = Country.japan("日本語",300) let country2 = Country.america("英語", 500) let country3 = Country.antarctic(0) switch country1 { case .japan(let lang, let num): print("日本:\(lang),\(num)人") case .america(let lang, let num): print("アメリカ : \(lang, \(num))") case .antarctic(let num): print("南極 : \(num)人") } //結果 日本: 日本語, 300人 ■caseだけでなく変数や関数も入れる事ができる。 enum Test: Int { case one = 1, two, three var aaa: Int { return self.rawValue * 10 } func bbb()-> String { switch self { case .one: return "Oneは1" case .two: return "Twoは2" case .three: return "Threeは3" } } } let testThree = Test.three print("\(testThree)") // three print("\(testThree.aaa)") // 30 print("\(testThree.bbb)") //Threeは3 このパターンをよく使うらしい 後は慣れていきます!!
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【Swift】Hashable, Identifiableについて

はじめに HashableプロトコルとIdentifiableプロトコルって何のために使うの?違いは?と疑問に思ったので調べました。 もし間違いがありましたらご指摘いただけると幸いです。 Hashable 概要 その型の値を元にハッシュ値が計算可能であることを表します。 下記のHashableの定義から、HashableはEquatableプロトコルを準拠しており、hashValueプロパティとhashメソッドを持っていることがわかります。 Hashable public protocol Hashable : Equatable { /// The hash value. /// /// Hash values are not guaranteed to be equal across different executions of /// your program. Do not save hash values to use during a future execution. /// /// - Important: `hashValue` is deprecated as a `Hashable` requirement. To /// conform to `Hashable`, implement the `hash(into:)` requirement instead. var hashValue: Int { get } /// Hashes the essential components of this value by feeding them into the /// given hasher. /// /// Implement this method to conform to the `Hashable` protocol. The /// components used for hashing must be the same as the components compared /// in your type's `==` operator implementation. Call `hasher.combine(_:)` /// with each of these components. /// /// - Important: Never call `finalize()` on `hasher`. Doing so may become a /// compile-time error in the future. /// /// - Parameter hasher: The hasher to use when combining the components /// of this instance. func hash(into hasher: inout Hasher) } Hashableへの準拠が必要なケース ハッシュ値を用いて表現されるデータ構造において、Hashableに準拠された型の利用が必要になります。 代表的な一例ですが、Set、Dictionaryなどを利用する場合、Hashableに準拠した型が必須です。 Set Setの定義を見ると、Element : Hashableとなっており、Hashableに準拠しなければいけないことがわかります。 Setの定義 @frozen public struct Set<Element> where Element : Hashable そのため、Hashableに準拠していないインスタンスをSetに利用すると、エラーが発生します。 struct GridPoint { var x: Int var y: Int } var tappedPoints: Set = [GridPoint(x: 2, y: 3), GridPoint(x: 4, y: 1)] エラー内容 Generic struct 'Set' requires that 'GridPoint' conform to 'Hashable' Hashableに準拠することでエラーが解消され、Setに利用できるようになります。 struct GridPoint: Hashable { var x: Int var y: Int } var tappedPoints: Set = [GridPoint(x: 2, y: 3), GridPoint(x: 4, y: 1)] Dictionaryのキー Setと同様に、Dictionaryの定義から Key : Hashable キーがHashableに準拠する必要があることがわかります。 Dictionaryの定義 @frozen public struct Dictionary<Key, Value> where Key : Hashable そのため、Hashableに準拠していないインスタンスをDictionaryのキーに利用すると、エラーが発生します。 struct GridPoint { var x: Int var y: Int } let dictionary = [GridPoint(x: 2, y: 3): "point A", GridPoint(x: 4, y:1): "point B"] エラー内容 Generic struct 'Dictionary' requires that 'GridPoint' conform to 'Hashable' Hashableに準拠することでエラーが解消され、Setに利用できるようになります。 struct GridPoint: Hashable { var x: Int var y: Int } let dictionary = [GridPoint(x: 2, y: 3): "point A", GridPoint(x: 4, y:1): "point B"] ForEach SetやDictionaryと同様に、ForEachの定義から ID : Hashable IDがHashableに準拠する必要があることがわかります。 ForEachの定義 public struct ForEach<Data, ID, Content> where Data : RandomAccessCollection, ID : Hashable Set、Dictionaryと同じなので、Hashableに準拠しない(エラーになる)場合の記載は割愛します。 以下のようにForEachのidには、Hahsableへの準拠が必要になります。 struct GridPoint: Hashable { var x: Int var y: Int } struct ContentView: View { let array = [GridPoint(x: 2, y: 3), GridPoint(x: 4, y:1)] var body: some View { List { ForEach(array, id: \.self, content: { elem in Text("x:\(elem.x) y:\(elem.y)") }) } } } ForEachの場合、Identifiableを準拠するケースもよくあるので、そちらについては次節で説明します。 Identifiable 概要 インスタンスが一意のIDを持つエンティティの値を保持するタイプのクラス。 下記のIdentifiableの定義を見ると、IDがHashableを準拠していることがわかります。 Identifiable public protocol Identifiable { /// A type representing the stable identity of the entity associated with /// an instance. associatedtype ID : Hashable /// The stable identity of the entity associated with this instance. var id: Self.ID { get } } また、Identifiableを準拠すると、そのクラスや構造体はidプロパティを持つ必要があります。 struct GridPoint: Identifiable { let id = UUID() var x: Int var y: Int } Identifiableへの準拠が必要なケース Hashableと似ていますが、一意に特定する必要がある要素において、Identifiableに準拠された型の利用が必要となるケースが多いです。 たくさんあるので、一例としてHashableでも出てきたForEachについて説明します。 ForEach Hashableの例ではForEach内でidを引数指定していましたが、Identifiableを準拠したクラスや構造体の場合、そのクラスや構造体のプロパティにidがあるため、ForEach内では指定しません。 Hashableの場合 ForEach(array, id: \.self, content: { elem in Identifiableの場合 ForEach(array, content: { elem in ただ、今までと同様にIdentifiableを準拠していない場合、idが無いため、ForEachでエラーとなります。 struct GridPoint { var x: Int var y: Int } struct ContentView: View { let array = [GridPoint(x: 2, y: 3), GridPoint(x: 4, y:1)] var body: some View { List { ForEach(array, content: { elem in Text("x:\(elem.x) y:\(elem.y)") }) } } } なぜかこんなエラーが出ます。 Failed to produce diagnostic for expression; please submit a bug report (https://swift.org/contributing/#reporting-bugs) and include the project Identifiableを準拠することでエラーが解消され、ForEachで利用できるようになります。 struct GridPoint: Identifiable { let id = UUID() var x: Int var y: Int } struct ContentView: View { let array = [GridPoint(x: 2, y: 3), GridPoint(x: 4, y:1)] var body: some View { List { ForEach(array, content: { elem in Text("x:\(elem.x) y:\(elem.y)") }) } } } Identifiable と Hashable の違い では、IdentifiableとHashableの違いは何でしょうか? こちらの記事によると、違いは以下のようです。 https://nshipster.com/identifiable/ 識別子とは異なり、ハッシュ値は状態に依存し、オブジェクトが変更されると変化する。 識別子は起動間で安定しているが、ハッシュ値はランダムに生成されたハッシュシードによって計算されるため、起動間で不安定になる。 識別子は一意だが、ハッシュ値が衝突する可能性があるため、コレクションからフェッチするときに等価性チェックが必要になる。 識別子は意味のあるものになる可能性があるが、ハッシュ値は無秩序である。 つまりIdentifyとHashの言葉通り、識別子 ≠ ハッシュ値 となります。 最後に 普段何気なく利用していましたが、調べることで理解が深まり、意図を持って利用できるようになりました。 今後も疑問を持ったものは調べてまとめていければと思います。 閲覧いただきありがとうございました。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Swift Loops Cheat Sheet

Swiftのループチットシート (日本語) This is a cheat sheet of the loops available in Swift 5.0 For Since Swift 3.0, C-style for loops have been deprecated! But I include them for people who are migrating old code bases. Loop over Ranges with For-In Looping n times Deprecated C-style version: Swift_<3.0 for var i = 0; i < 10; i++ { print(i) } for-in over a closed range with the ... (closed range) operator : Swift_>=_3.0 for i in 0...10 { print(i) } for-in over a half-open range with the ..< (half-open range) operator : Swift_>=_3.0 for i in 0..<10 { print(i) } Looping n times in reverse Deprecated C-style version: Swift_<3.0 for var i = 10; i > 0; i-- { print(i) } for-in over a closed range that has been reversed using reversed(): Swift_>=_3.0 for i in (1...10).reverse() { print(i) } Looping with Stride Deprecated C-style version: Swift_<3.0 for var i = 0; i < 10; i += 2 { print(i) } for-in over a half-open range, stepping by the specified amount using stride(from:to:by:): Swift_>=_3.0 for i in 0.stride(to: 10, by: 2) { print(i) } Looping through Array Values Deprecated C-style version: Swift_<3.0 let someNumbers = [2, 3, 45, 6, 8, 83, 100] for var i = 0; i < someNumbers.count; i++ { print(someNumbers[i]) } for-in over all the values of an Array: Swift_>=_3.0 let someNumbers = [2, 3, 45, 6, 8, 83, 100] for number in someNumbers { print(number) } forEach over all the values of an Array: Swift_>=_3.0 let someNumbers = [2, 3, 45, 6, 8, 83, 100] someNumbers.forEach { number in print(number) } Reverse Looping through Array Values Deprecated C-style version: Swift_<3.0 let someNumbers = [2, 3, 45, 6, 8, 83, 100] for var i = someNumbers.count - 1; i >= 0; i-- { print(someNumbers[i]) } for-in over all the values of an Array that has been reversed using reversed(): Swift_>=_3.0 let someNumbers = [2, 3, 45, 6, 8, 83, 100] for number in someNumbers.reverse() { print(number) } Looping Through an Array with Index Deprecated C-style version: Swift_<3.0 let someNumbers = [2, 3, 45, 6, 8, 83, 100] for var i = 0; i < someNumbers.count; i++ { print("\(i + 1): \(someNumbers[i])") } for-in over all the indexes and values of an Array enumerated using enumerated(): Swift_>=_3.0 let someNumbers = [2, 3, 45, 6, 8, 83, 100] for (index, number) in someNumbers.enumerated() { print("\(index + 1): \(number)") } forEach over all the indexes and values of an Array enumerated using enumerated(): Swift_>=_3.0 let someNumbers = [2, 3, 45, 6, 8, 83, 100] someNumbers.enumerated().forEach { (index, number) in print("\(index + 1): \(number)") } Looping Through Array Indices Deprecated C-style version: Swift_<3.0 let someNumbers = [2, 3, 45, 6, 8, 83, 100] for var i = 0; i < someNumbers.count; i++ { print(i) } for-in over all the indexes of an Array enumerated using indices: Swift_>=_3.0 let someNumbers = [2, 3, 45, 6, 8, 83, 100] for index in someNumbers.indices { print(index) } Looping Through a Dictionary for-in over all the indexes and values of a Dictionary: Swift_>=_3.0 let scores = ["Bob": 42, "Alice": 99, "Jane": 13] for (name, score) in scores { print("\(name)'s score is \(score)") } While Repeats code until the condition evaluated at the start of the loop becomes false. Swift_>=_3.0 let someNumbers = [2, 3, 45, 6, 8, 83, 100] var i = 0 while(i < someNumbers.count) { print(someNumbers[i]) i += 1 } Repeat-While Repeats code until the condition evaluated at the end of the loop becomes false. Swift_>=_3.0 let someNumbers = [2, 3, 45, 6, 8, 83, 100] var i = 0 repeat { print(someNumbers[i]) i += 1 } while(i < someNumbers.count) Higher-order functions If you want to manipulate arrays, for instance to apply a function to each of the array elements, or to reduce the array to one result, then you should use the higher-order functions. Array.filter(_:) Iterates over all items in array, and return an array containing all the elements filtered by a condition. Swift_>=_3.0 let someNumbers = [2, 3, 45, 6, 8, 83, 100] let numbersUnder10 = someNumbers.filter { $0 < 10 } print(numbersUnder10) Array.map(_:) Iterates over all items in array, transforming them and return an array containing the transformed elements. Swift_>=_3.0 let someNumbers = [2, 3, 45, 6, 8, 83, 100] let doubledNumbers = someNumbers.map { $0 * 2} print(doubledNumbers) Array.reduce(_:_:) Iterates over all items in array, combining them together until you end up with a single value. Swift_>=_3.0 let someNumbers = [2, 3, 45, 6, 8, 83, 100] let sum = someNumbers.reduce(0) { $0 + $1} print(sum) See also For Loops in Swift (How To) Swift Alternatives to C-style for-loops
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【SwiftUI】最低限のコードでカメラのプレビューをMetalKitで表示する

SwiftUI上で必要最低限のコードでカメラのプレビューをMetalKitで表示します。 フロントカメラのみ デバイスの向きはポートレートのみ カメラのアクセス許可のチェックなし 環境 Swift: 5.4 Xcode: 12.5 (12E262) macOS: Big Sur 11.3.1 (20E241) コード ContentView.swift import SwiftUI import AVFoundation import MetalKit struct ContentView: View { var body: some View { CameraView() .edgesIgnoringSafeArea(.all) } } struct CameraView: UIViewRepresentable { func makeUIView(context: Context) -> some UIView { BaseCameraView() } func updateUIView(_ uiView: UIViewType, context: Context) {} } class BaseCameraView: UIView, AVCaptureVideoDataOutputSampleBufferDelegate { let metalLayer = CAMetalLayer() let device = MTLCreateSystemDefaultDevice()! lazy var commandQueue = device.makeCommandQueue() let renderPassDescriptor = MTLRenderPassDescriptor() lazy var renderPipelineState: MTLRenderPipelineState! = { guard let library = device.makeDefaultLibrary() else { return nil } let descriptor = MTLRenderPipelineDescriptor() descriptor.vertexFunction = library.makeFunction(name: "vertexShader") descriptor.fragmentFunction = library.makeFunction(name: "fragmentShader") descriptor.colorAttachments[0].pixelFormat = .bgra8Unorm return try? device.makeRenderPipelineState(descriptor: descriptor) }() let captureSession = AVCaptureSession() var lockFlag = false override func layoutSubviews() { super.layoutSubviews() _ = initMetalAndCaptureSession metalLayer.frame = layer.frame } lazy var initMetalAndCaptureSession: Void = { metalLayer.device = device metalLayer.isOpaque = false layer.addSublayer(metalLayer) renderPassDescriptor.colorAttachments[0].loadAction = .clear renderPassDescriptor.colorAttachments[0].storeAction = .store renderPassDescriptor.colorAttachments[0].clearColor = MTLClearColorMake(0, 0, 0, 0) guard let captureDevice = AVCaptureDevice.DiscoverySession(deviceTypes: [.builtInWideAngleCamera], mediaType: .video, position: .front).devices.first, let input = try? AVCaptureDeviceInput(device: captureDevice) else { return } let output = AVCaptureVideoDataOutput() output.videoSettings = [kCVPixelBufferPixelFormatTypeKey: kCVPixelFormatType_32BGRA] as [String : Any] output.setSampleBufferDelegate(self, queue: DispatchQueue.main) captureSession.addInput(input) captureSession.addOutput(output) captureSession.startRunning() captureSession.connections.forEach { $0.videoOrientation = .portrait $0.isVideoMirrored = true } }() func captureOutput(_ output: AVCaptureOutput, didOutput sampleBuffer: CMSampleBuffer, from connection: AVCaptureConnection) { if lockFlag { return } lockFlag = true guard let buffer = CMSampleBufferGetImageBuffer(sampleBuffer) else { return } CVPixelBufferLockBaseAddress(buffer, .readOnly) let width = CVPixelBufferGetWidth(buffer) let height = CVPixelBufferGetHeight(buffer) var textureCache: CVMetalTextureCache! CVMetalTextureCacheCreate(kCFAllocatorDefault, nil, device, nil, &textureCache) var texture: CVMetalTexture! _ = CVMetalTextureCacheCreateTextureFromImage(kCFAllocatorDefault, textureCache, buffer, nil, .bgra8Unorm, width, height, 0, &texture) guard let drawable = metalLayer.nextDrawable(), let commandBuffer = commandQueue?.makeCommandBuffer() else { return } renderPassDescriptor.colorAttachments[0].texture = drawable.texture guard let encoder = commandBuffer.makeRenderCommandEncoder(descriptor: renderPassDescriptor) else { return } encoder.setRenderPipelineState(renderPipelineState) let aspect = Float(frame.width / frame.height) * Float(height) / Float(width) let vertexData: [[Float]] = [ // 0: positions [ -1, -aspect, 0, 1, -1, aspect, 0, 1, 1, -aspect, 0, 1, 1, aspect, 0, 1, ], // 1: texCoords [ 0, 1, 0, 0, 1, 1, 1, 0, ], ] vertexData.enumerated().forEach { i, array in let size = array.count * MemoryLayout.size(ofValue: array[0]) let buffer = device.makeBuffer(bytes: array, length: size) encoder.setVertexBuffer(buffer, offset: 0, index: i) } encoder.setFragmentTexture(CVMetalTextureGetTexture(texture), index: 0) encoder.drawPrimitives(type: .triangleStrip, vertexStart: 0, vertexCount: vertexData[0].count / 4) encoder.endEncoding() commandBuffer.present(drawable) commandBuffer.commit() CVPixelBufferUnlockBaseAddress(buffer, .readOnly) lockFlag = false } } struct ContentView_Previews: PreviewProvider { static var previews: some View { ContentView() } } Info.plist <key>NSCameraUsageDescription</key> <string>This app uses the camera</string> リポジトリ
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【SwiftUI】最低限のコードでカメラのプレビューをMetalで表示する

SwiftUI上で必要最低限のコードでカメラのプレビューをMetalで表示します。 フロントカメラのみ デバイスの向きはポートレートのみ カメラのアクセス許可のチェックなし 環境 Swift: 5.4 Xcode: 12.5 (12E262) macOS: Big Sur 11.3.1 (20E241) コード ContentView.swift import SwiftUI import AVFoundation import MetalKit struct ContentView: View { var body: some View { CameraView() .edgesIgnoringSafeArea(.all) } } struct CameraView: UIViewRepresentable { func makeUIView(context: Context) -> some UIView { BaseCameraView() } func updateUIView(_ uiView: UIViewType, context: Context) {} } class BaseCameraView: UIView, AVCaptureVideoDataOutputSampleBufferDelegate { let metalLayer = CAMetalLayer() let device = MTLCreateSystemDefaultDevice()! lazy var commandQueue = device.makeCommandQueue() let renderPassDescriptor = MTLRenderPassDescriptor() lazy var renderPipelineState: MTLRenderPipelineState! = { guard let library = device.makeDefaultLibrary() else { return nil } let descriptor = MTLRenderPipelineDescriptor() descriptor.vertexFunction = library.makeFunction(name: "vertexShader") descriptor.fragmentFunction = library.makeFunction(name: "fragmentShader") descriptor.colorAttachments[0].pixelFormat = .bgra8Unorm return try? device.makeRenderPipelineState(descriptor: descriptor) }() let captureSession = AVCaptureSession() var lockFlag = false override func layoutSubviews() { super.layoutSubviews() _ = initMetalAndCaptureSession metalLayer.frame = layer.frame } lazy var initMetalAndCaptureSession: Void = { metalLayer.device = device metalLayer.isOpaque = false layer.addSublayer(metalLayer) renderPassDescriptor.colorAttachments[0].loadAction = .clear renderPassDescriptor.colorAttachments[0].storeAction = .store renderPassDescriptor.colorAttachments[0].clearColor = MTLClearColorMake(0, 0, 0, 0) guard let captureDevice = AVCaptureDevice.DiscoverySession(deviceTypes: [.builtInWideAngleCamera], mediaType: .video, position: .front).devices.first, let input = try? AVCaptureDeviceInput(device: captureDevice) else { return } let output = AVCaptureVideoDataOutput() output.videoSettings = [kCVPixelBufferPixelFormatTypeKey: kCVPixelFormatType_32BGRA] as [String : Any] output.setSampleBufferDelegate(self, queue: DispatchQueue.main) captureSession.addInput(input) captureSession.addOutput(output) captureSession.startRunning() captureSession.connections.forEach { $0.videoOrientation = .portrait $0.isVideoMirrored = true } }() func captureOutput(_ output: AVCaptureOutput, didOutput sampleBuffer: CMSampleBuffer, from connection: AVCaptureConnection) { if lockFlag { return } lockFlag = true guard let buffer = CMSampleBufferGetImageBuffer(sampleBuffer) else { return } CVPixelBufferLockBaseAddress(buffer, .readOnly) let width = CVPixelBufferGetWidth(buffer) let height = CVPixelBufferGetHeight(buffer) var textureCache: CVMetalTextureCache! CVMetalTextureCacheCreate(kCFAllocatorDefault, nil, device, nil, &textureCache) var texture: CVMetalTexture! _ = CVMetalTextureCacheCreateTextureFromImage(kCFAllocatorDefault, textureCache, buffer, nil, .bgra8Unorm, width, height, 0, &texture) guard let drawable = metalLayer.nextDrawable(), let commandBuffer = commandQueue?.makeCommandBuffer() else { return } renderPassDescriptor.colorAttachments[0].texture = drawable.texture guard let encoder = commandBuffer.makeRenderCommandEncoder(descriptor: renderPassDescriptor) else { return } encoder.setRenderPipelineState(renderPipelineState) let aspect = Float(frame.width / frame.height) * Float(height) / Float(width) let vertexData: [[Float]] = [ // 0: positions [ -1, -aspect, 0, 1, -1, aspect, 0, 1, 1, -aspect, 0, 1, 1, aspect, 0, 1, ], // 1: texCoords [ 0, 1, 0, 0, 1, 1, 1, 0, ], ] vertexData.enumerated().forEach { i, array in let size = array.count * MemoryLayout.size(ofValue: array[0]) let buffer = device.makeBuffer(bytes: array, length: size) encoder.setVertexBuffer(buffer, offset: 0, index: i) } encoder.setFragmentTexture(CVMetalTextureGetTexture(texture), index: 0) encoder.drawPrimitives(type: .triangleStrip, vertexStart: 0, vertexCount: vertexData[0].count / 4) encoder.endEncoding() commandBuffer.present(drawable) commandBuffer.commit() CVPixelBufferUnlockBaseAddress(buffer, .readOnly) lockFlag = false } } struct ContentView_Previews: PreviewProvider { static var previews: some View { ContentView() } } Info.plist <key>NSCameraUsageDescription</key> <string>This app uses the camera</string> リポジトリ
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Swift:guard 対 if と、guard let 対 if let

Swift: guard vs if, and guard let vs if let (English) この質問を guard対ifと、 guard let対if letで分けたほうが分かりやすくなると思います。 guard対if ただのguardとifはどちらも単純なフロー制御ステートメントであり、アンラップは行いません。 guardは、その条件が満たされない場合、ただ早期に終了します。 比較表 guard if 機能性 早期に終了 フロー制御 焦点 早期に終了の道: 先に進む前に、条件の正しさを確認すること。 ハッピーパス:すべてが計画どおりに進んだときの機能の動作。 読みやすさ、明瞭さ 改善 運命のピラミッドにつながる可能性があります 入れ子にされたスコープ (深度を増加) ❌ ◯ 外側のスコープ 必要 不必要 必要 チェックが失敗した場合はスコープから戻ります Return / Throw 必要 不必要 役割 バリデーション ただのフロー制御 適切な場所 スコープの開始場所 例 guard let array = ["a", "b", "c"] func at(at index: Int) -> String?{ // インデックスが範囲外の場合は早期に終了 guard index >= 0, index < array.count else { return nil } return array[index] } print(at(1)) if let array = ["a", "b", "c"] let index = 1 if index >= 0, index < array.count { print(array[index]) } else { print(nil) } guard let対if let guard letとif letは両方ともOptional型をアンラップします。 guard letは、guardと同様に、条件が満たされない場合、早期に終了します。 比較表 guard let if let 機能 Optional型をアンラップ Optional型をアンラップ 設計目標 チェックが失敗した場合は、現在のスコープを終了します ただOptional型をアンラップ 焦点 早終了の道: 先に進む前に、条件の正しさを確認すること。 ハッピーパス:すべてが計画どおりに進んだときの機能の動作。 外側のスコープ 必要 不必要 必要 チェックが失敗した場合はスコープから戻ります Return / Throw 必要 不必要 読みやすさ、明瞭さ 改善 運命のピラミッドにつながる可能性があります 入れ子にされたスコープ (深度を増加) ❌ ◯ 可視性 ラップされていないOptional型は、guard letステートメントの後のスコープに可視できます ラップされていないOptional型は、if letステートメントのコードブロックスコープでのみ可視できます 役割 いくつかのOptional型をバリデーションしてからアンラップします. ただのフロー制御 適切な場所 スコープの開始場所 複数のOptional型を同時にアンラップ 可能 可能 副作用 避けるべき 大丈夫 例 guard let すべてのフィールドが有効な場合、すべての文字列(firstNameString、lastNameString、emailString、passwordString)は、guardステートメントの後のスコープでアクセスできます: guard-let func validateThenRegisterUser() { // validation guard let firstNameString = firstName.text where firstNameString.characters.count > 0 else { firstName.becomeFirstResponder() return } guard let lastNameString = lastName.text where lastNameString.characters.count > 0 else { lastName.becomeFirstResponder() return } guard let emailString = email.text where emailString.characters.count > 3 && emailString.containsString("@") && emailString.containsString(".") else { email.becomeFirstResponder() return } guard let passwordString = password.text where passwordString.characters.count > 7 else { password.becomeFirstResponder() return } // The validated information can now be used for registration let newUser = User() newUser.firstName = firstNameString newUser.lastName = lastNameString newUser.email = emailString newUser.password = passwordString APIHandler.sharedInstance.registerUser(newUser) } if let すべての文字列(firstNameString、lastNameString、emailString、passwordString)は、ifステートメントコードブロックのスコープ内でのみアクセスできます。 これにより、次のような多くの問題が発生する「運命のピラミッド」が作成されます: 読みやすさ, コードの並べ替え/リファクタリングのしやすさ。たとえば、フィールドのバリデーション順序が変更された場合、ほとんどのコードを書き直す必要があります。 if-let func validateThenRegisterUser() { // validation: starts the pyramid of doom if let firstNameString = firstName.text where firstNameString.characters.count > 0{ if let lastNameString = lastName.text where lastNameString.characters.count > 0{ if let emailString = email.text where emailString.characters.count > 3 && emailString.containsString("@") && emailString.containsString(".") { if let passwordString = password.text where passwordString.characters.count > 7{ // The validated information can now be used for registration let newUser = User() newUser.firstName = firstNameString newUser.lastName = lastNameString newUser.email = emailString newUser.password = passwordString APIHandler.sharedInstance.registerUser(newUser) } else { password.becomeFirstResponder() } } else { email.becomeFirstResponder() } } else { lastName.becomeFirstResponder() } } else { firstName.becomeFirstResponder() } }
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

やさしい Property Wrapper

この記事では Swift の Property Wrapper が何かをやさしく解説します。 PropertyWrapper とは 公式ドキュメントには下記のように説明されています。 A property wrapper adds a layer of separation between code that manages how a property is stored and the code that defines a property. プロパティラッパーは、プロパティの保存方法を管理するコードと、プロパティを定義するコードとの間に、分離の層を追加します。 上記の説明を理解するために、試しにコードを書いて確認していきましょう! value プロパティに値を保存する際、何か処理を実行する A struct があるとします。 struct A { // プロパティの保存方法を管理するコードと、プロパティを定義するコード private var _value: Bool = false // <- 管理 var value: Bool { // <- 定義 set { // <- 保存方法 print("Do something") _value = newValue } get { _value } } } これを propertyWrapper を使うとこのように書けます。 // 分離の層 @propertyWrapper struct DoSomething { private var _value = false // <- 管理 var wrappedValue: Bool { get { _value } set { // <- 保存方法 print("Do something") _value = newValue } } } struct A { @DoSomething var value: Bool // <- 定義 } 確かに、プロパティの定義と保存方法を分離できてますね! メリット For example, if you have properties that provide thread-safety checks or store their underlying data in a database, you have to write that code on every property. When you use a property wrapper, you write the management code once when you define the wrapper, and then reuse that management code by applying it to multiple properties. 例えば、スレッドセーフのチェックを行うプロパティや、基礎となるデータをデータベースに保存するプロパティがある場合、すべてのプロパティにそのコードを書かなければなりません。プロパティ・ラッパーを使用すると、ラッパーの定義時に管理コードを一度だけ記述し、その管理コードを複数のプロパティに適用して再利用することができます。 property wrapper を活用すると、管理コードを再利用することができるのがメリットのようです。 projectedValue について 上記が propertyWrapper のメインとする機能なのですが、さらに追加機能として projectedValue があります。projectedValue は名前の通り propertyWrapper によってラップされた値の投影された値として使用することができます。 先程の DoSomething に projectedValue の機能を追加すると次のようになります。 @propertyWrapper struct DoSomething { private var _value = false var projectedValue = { // <- ここを追加 print("Do additional something") } var wrappedValue: Bool { get { _value } set { print("Do something") _value = newValue } } } 定義したプロパティ名の前に $ を付けて参照します。 var a = A() print(a.$value()) // Do additional something イニシャライザ DoSomething の value property の初期値は false ですが、イニシャライザを定義することで、変数の定義時に初期値を設定することができます。 struct DoSomething { init(wrappedValue: Bool) { self._value = wrappedValue } ... } struct A { @DoSomething var value = true } 使い方の例 さて、Property Wrapper が何か少しわかってきたところで、実際にどのように利用できるのか見ていきましょう。 ここでは、proposal に記載されている例の一部を紹介します。 Userdefaults へのアクセスをラップしたものです。 @propertyWrapper struct UserDefault<T> { let key: Key let defaultValue: T var wrappedValue: T { get { return UserDefaults.standard.object(forKey: key.rawValue) as? T ?? defaultValue } set { UserDefaults.standard.set(newValue, forKey: key.rawValue) } } } extension UserDefault { enum Key: String { case isFooFeatureEnabled } } Struct A { @UserDefault(key: .isFooFeatureEnabled, defaultValue: false) static var isFooFeatureEnabled: Bool } このような使い方をすることで、ユーザーデフォルトへのアクセスをスッキリと書くことができて良さそうですね! proposal には他の例も書かれていますし、他にも応用できそうなものはないか考えてみると面白そうです! State を自前実装してみる 最後に、SwiftUI の @State を自前実装したらどうなるか考察してみました。 import SwiftUI import Combine class Store<T> { var value: T init(value: T) { self.value = value } } @propertyWrapper struct State2<T> { private let store: Store<T> var viewGraph: ViewGraph? // <- フレームワークによって注入される init(wrappedValue value: T) { self.value = value self.store = Store(value: value) } private var value: T var projectedValue: Binding<T> { Binding { print("get \(store.value)") return store.value } set: { newValue in store.value = newValue print("set \(store.value)") viewGraph.render() // <- body を呼ぶ } } var wrappedValue: T { nonmutating set { store.value = newValue } get { store.value } } } struct PlayerView: View { // @State private var isPlaying: Bool = false @State2 private var isPlaying: Bool = false var body: some View { PlayButton(isPlaying: $isPlaying) } } struct PlayButton: View { @Binding var isPlaying: Bool var body: some View { Button(action: { self.isPlaying.toggle() }) { Image(systemName: isPlaying ? "pause.circle" : "play.circle") } } } struct ContentView_Previews: PreviewProvider { static var previews: some View { PlayerView() } } 結果としてView の body が呼ばれる仕組みが分からなかったので、実際にUIを動かしてみることはできませんでしたが、body が呼ばれる仕組みについてはこちらの記事に詳しく考察と解説をされていました。 Field Descriptor を使ったリフレクションで、View の State プロパティに ViewGraph というオブジェクトを注入し、値が更新されたタイミングで ViewGraph を使って更新させているようです。勉強になりますね! 今回は以上です。ではでは ? 参考リンク https://docs.swift.org/swift-book/LanguageGuide/Properties.html https://github.com/apple/swift-evolution/blob/master/proposals/0258-property-wrappers.md#introduction https://kateinoigakukun.hatenablog.com/entry/2019/06/08/232142
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

swiftでAPIを実装

APIを実装するには以下の手順を行います 変数urlにURLデータを入れる → そこからデータを取得する → そのデータをJSON解析して欲しいデータを返す 具体的にコードで見て行きましょう。 以下はBitcoinのデータを受け取り1Bitcoinが何円、何ドル、、、なのかを返す関数です。 ここでは具体的なapiの取得は割愛します。 具体的なコード func getCoinPrice(for currency: String){ let urlString = "\(baseURL)/\(currency)?apikey=\(apiKey)" if let url = URL(string: urlString){ let session = URLSession(configuration: .default) let task = session.dataTask(with: url){ (data, response, error) in if error != nil{ print(error!) return } if let safeData = data{ if let bitcoinPrice = self.parseJSON(safeData){ let priceString = String(format: "%.2f", bitcoinPrice) self.delegate?.didUpdatePrice(price: priceString, currency: currency) } } } task.resume() } 上のコードはurlに取得したURLを入れ、URLSession関数を使用しデータ処理群をまとめ、dataTaskを用いてURLを使用したtask変数を作ります。その時クロージャーを用いてデータを取得する処理を書いています。データが存在するかどうかで場合分けをしており、存在していた場合parseJSON関数に入れJSONデータを解析します。以下がparseJSON関数です。 func parseJSON(_ data: Data) -> Double? { let decoder = JSONDecoder() do{ let decodedData = try decoder.decode(CoinData.self, from: data) let lastPrice = decodedData.rate return lastPrice } catch { delegate?.didFailWithError(error: error) return nil } } data型のdataを受け取りJSONDecoderを使用することで解析しています。尚、「let decodedData = try decoder.decode(CoinData.self, from: data)」のように解析する際の第一引数として構造体を別ファイルで作っておりそこに当てはめています。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む