【Swift】公式サンプル Logger の使い方


print("debug: \(value)")

とかではつらいのかな。

と思いつつ抽出して使ってみる。

👉 Code search results hatena-bookmark


import OSLog
private let logger = Logger(subsystem: "BackyardBirds", category: "BackyardBirdsPassStatus")


logger.log("""
  Processing transaction ID \(unsafeTransaction.id) for \
  \(unsafeTransaction.productID)
""")


logger.debug("""
  Transaction ID \(t.id) for \(t.productID) is verified
""")


logger.error("""
  Transaction ID \(t.id) for \(t.productID) is unverified: \(error)
""")


logger.info("Providing updated status to data generation")

出力レベルがあるんですね。

出力は、


2023-09-16 18:26:11.842691+0900 Develop[99999:999999] [Standard] Debug: Uhooi
2023-09-16 18:26:11.842722+0900 Develop[99999:999999] [Standard] Info: Uhooi
2023-09-16 18:26:11.842766+0900 Develop[99999:999999] [Standard] Notice: Uhooi
2023-09-16 18:26:11.842811+0900 Develop[99999:999999] [Standard] Error: Uhooi
2023-09-16 18:26:11.842858+0900 Develop[99999:999999] [Standard] Fault: Uhooi

{Timestamp} {Library}[{PID}:{TID}] [{Category}] {Message} の形式で出力されています。

👉 os.Loggerの説明と使い方(Swift) #Swift - Qiita hatena-bookmark

なるほど。

 

■ やってみる


import OSLog
private let logger = Logger(subsystem: "subsystem", category: "category")

// ... 

  static func create(modelContainer: ModelContainer) {
    
    logger.debug("debug Creating service instance.")
    logger.trace("trace Creating service instance.")
    logger.info("infoCreating service instance.")
    logger.notice("notice Creating service instance.")
    logger.error("error Creating service instance.")
    logger.warning("warning Creating service instance.")
    logger.fault("fault Creating service instance.")
    logger.critical("critical Creating service instance.")

    shared = SoundDataService(modelContainer: modelContainer)
  }


debug Creating service instance.
trace Creating service instance.
infoCreating service instance.
notice Creating service instance.
error Creating service instance.
warning Creating service instance.
fault Creating service instance.
critical Creating service instance.

あれ、タイムスタンプ的なのデフォルトで出ないのか。

subsystem とか category とかも何の意味があるのか。

print() で良くね?

なんなのこれ。

あ、これか。

extension 化しとくのが楽かもしれません。


extension OSLog {
  static let ui = Logger(subsystem: "com.satoriku.OSLog", category: "ui")
  static let network = Logger(subsystem: "com.satoriku.OSLog", category: "network")
  static let viewCycle = Logger(subsystem: "com.satoriku.OSLog", category: "viewcycle")
}

👉 【Xcode/Swift】OSLogを使ってアプリログを出力する方法(ロギング) - iOS-Docs hatena-bookmark


import Foundation
import os

extension Logger {
    private static var subsystem = Bundle.main.bundleIdentifier!
    static let segue = Logger(subsystem: subsystem, category: "segue")
    static let note = Logger(subsystem: subsystem, category: "note")
    static let flashcard = Logger(subsystem: subsystem, category: "flashcard")
    static let deck = Logger(subsystem: subsystem, category: "deck")
    static let settings = Logger(subsystem: subsystem, category: "settings")
    static let option = Logger(subsystem: subsystem, category: "option")
}

👉 Flashcard-Adder/Flashcard Adder/Logger.swift at bfa5f3526fba48f5c6596f92d7648e4137fa60c9 · Nonameentered/Flashcard-Adder hatena-bookmark
👉 OSLog and Unified logging as recommended by Apple - SwiftLee hatena-bookmark

👉 Logger | Apple Developer Documentation hatena-bookmark


【SwiftUI】カスタム ViewModifier の使い方と使いどころ 🤔

あまり使ってないので上手に使いたい。

なんとなく使ってる感じなのではっきりさせておきたい。

 

🤔 ViewModifier - 公式ドキュメント

公式ドキュメントをきちんと読んでみます。

To create consistent views, you might reuse the same view modifier or group of modifiers repeatedly across your views.

一貫したビューを作成するために、同じ ViewModifier、または、それのグループを View で繰り返し再利用することができます。

A modifier that you apply to a view or another view modifier, producing a different version of the original value.

View または別の ViewModifier に適用する Modifier で、元の異なるバージョンを生成します。


struct CaptionTextFormat: ViewModifier {
  func body(content: Content) -> some View {
    content
      .font(.caption)
      .foregroundColor(.secondary)
  }
}

Text("Some additional information...")
  .modifier(CaptionTextFormat())

To make your custom view modifier conveniently accessible, extend the View protocol with a function that applies your modifier

カスタム ViewModifier を便利にアクセスできるようにするには、カスタム View Modefier を適用する関数を使用して View プロトコルを拡張します。


extension View {
  func captionTextFormat() -> some View {
    modifier(CaptionTextFormat())
  }
}

Text("Some additional information...")
  .captionTextFormat()

👉 ViewModifier | Apple Developer Documentation hatena-bookmark
👉 Reducing view modifier maintenance | Apple Developer Documentation hatena-bookmark

「一貫性のため」か。

確かに新規作成も変更も面倒ですよね。

 

🤔 Custom ViewModifier vs View extension

@State が必要かどうか。

ViewModifier let you have @State variables, but View extensions do not.

ViewModifier では @State が持てるが、View extension では持てない。

👉 ios - Difference between creating ViewModifier and View extension in SwiftUI - Stack Overflow hatena-bookmark

View を拡張したい場合は原則として extension を使用し、状態保持が必要な場合のみ `ViewModifier` を実装する。

👉YusukeHosonuma/Effective-SwiftUI · Discussion #31 hatena-bookmark

また、

以下、WWDC 2022 SwiftUI Q&A での話のようです。

What’s the difference between a custom ViewModifier vs View extension

Q: What’s the difference between a custom ViewModifier (without DynamicProperty) that uses some built-in modifiers in body(content:), and a custom View extension func that just use those built-in modifiers?

Similarly, what’s the difference between a custom ViewModifier with some DynamicProperty and a custom View with some DynamicProperty (also has a @ViewBuilder content property to receive content to modify) ?

I think two have the same render result and behavior.

A: Because of the way a ViewModifier is expressed, the engine knows it’s not changing the content passed in and can apply performance optimizations (compared to just an extension on View)

カスタム ViewModifier と View extension の違いは何ですか ?

質問:
body(content:) でいくつかの組み込み modifier を使用するカスタム ViewModifier (DynamicPropertyなし)と、それらの組み込み Modifier を使用するカスタム View extension の違いは何ですか?

同様に、いくつかの DynamicProperty を持つカスタム ViewModifier と、いくつかの DynamicProperty を持つカスタム View の違いは何ですか ? (modify する content を受け取るための @ViewBuilder content property もあります)

2つは同じレンダリング結果と動作を持っていると思います。

回答:
ViewModifier の表現方法により、エンジンは渡されたコンテンツを変更していないことを知っており、パフォーマンスの最適化を適用できます。Viewの単なる拡張と比較すると。

👉 WWDC22 SwiftUI Q&A | Swift Discovery hatena-bookmark
👉 https://wwdc22.slack.com/ - Apple Events ​に⁠サ⁠イ⁠ン⁠イ⁠ン⁠す⁠る​ | Slack 

「一度 ViewModifier に切り出すとパフォーマンスが最適化される。」

ということのようです。

一発で View extension にしたほうが間違いなく見通しは良いので、

適正化でどれくらいパフォーマンスが変わるか知りたいところです。

ここで、上の質問で挙げられている4つのパターンのコードを想像してみます。


// body(content:) でいくつかの組み込み Modifier を使用する
// カスタムViewModifier (DynamicPropertyなし)

struct CustomViewModifier: ViewModifier {
  var body(content: Content) -> some View {
    // use built-in modifier
  }
}


// それらの組み込み Modifier を使用する
// カスタム View extension

extension View {
  func customModifier() -> some View {
    modifier(CustomViewModifier())
  }
}


// いくつかの DynamicProperty を持つ
// カスタム ViewModifier

struct CustomViewModifier: ViewModifier {
   @State var state: State = .loading

   var body(content: Content) -> some View {
    // use built-in modifier
  }
}


// いくつかの DynamicProperty を持つカスタム View
// modify する content を受け取るための
// @ViewBuilder content property

struct CustomView<Content: View>: View {
  @State var state: State = .loading
  @ViewBuilder var content: Content

  var body: some View {
    // use built-in modifier
  }
}

この質問者の人、さらっと聞いてるように見えますが、

結構ナイスな質問です。

初心者的には、カスタム View が自然に見えますが。

考えさせられます。

 

🤔 公式サンプルコード

続いて、WWDC2023 のサンプルコードを見てみます。


private struct BackyardViewportContentModifier: ViewModifier {
  var value: BackyardViewportContent
    
  func body(content: Content) -> some View {
    content.layoutValue(key: BackyardViewportContentKey.self, value: value)
  }
}

fileprivate extension View {
  func backyardViewportContent(_ value: BackyardViewportContent) -> some View {
    modifier(BackyardViewportContentModifier(value: value))
  }
}


struct GenerateDataViewModifier: ViewModifier {
  @Environment(\.modelContext) private var modelContext
    
  func body(content: Content) -> some View {
    content.onAppear {
      DataGeneration.generateAllData(modelContext: modelContext)
    }
  }
}

fileprivate extension View {
  func generateData() -> some View {
    modifier(GenerateDataViewModifier())
  }
}

👉 apple/sample-backyard-birds hatena-bookmark

ここでは、カスタム ViewModifier の利用のほとんどが、

「データコンテナ」と「通信API」処理まわりです。

これは #Preview でも使いやすいし、定型のパターン。

👉 【 #SwiftData 】ModelContainer の View へのセット hatena-bookmark

 

🤔 まとめ

やっぱ、View で切り出したくなるのですが。

[参考]
👉 ViewBuilderやViewModifierでSwiftUIのViewを分割する|TAAT hatena-bookmark


【 Swift 】2次元配列と1次元配列の相互変換 🤔

こんな12年前の so の書き込み。


p.x = index / 3;
p.y = index % 3;

👉 c# - Convert 1D array index to 2D array index - Stack Overflow hatena-bookmark

さすが、先人先生。

やってみました。

長ったらしいですが考え方を思い出したいときのために。

 

🤔 配列をn個ずつに分割する

副産物としてこんなかんじに書けました。


extension Array {
  func chunked(by: Int) -> [[Element]] {
    return  (0 ..< (self.count / by)).map {
      Array(self[$0 * by ..< ($0 + 1) * by])
    }
  }
}

print(
  [1, 2, 3, 4, 5, 6].chunked(by: 3)
)
// [[1, 2, 3], [4, 5, 6]]

 

🤔 まとめ

配列の変換


let d2: [[String]] = [
  ["あ", "い", "う", "え", "お"],
  ["か", "き", "く", "け", "こ"],
  ["さ", "し", "す", "せ", "そ"]
]

// 2次元から1次元 *
let d1 = d2.flatMap { $0 }
print(d1)
// ["あ", "い", "う", "え", "お", "か", "き", "く", "け", "こ", "さ", "し", "す", "せ", "そ"]

print(
  // 1次元から2次元 *
  (0 ..< d1.count / numCols).map {
    Array(d1[($0 * numCols) ..< ($0 + 1) * numCols])
  }
)
// [["あ", "い", "う", "え", "お"], ["か", "き", "く", "け", "こ"], ["さ", "し", "す", "せ", "そ"]]

extension 化する。


extension Array where Element: Collection { 
  func toD1() -> [Element.Element] { // nested
    return self.flatMap { $0 } 
  }
}

extension Array {
  func toD2(numCols: Int) -> [[Element]] {
    return (0 ..< self.count / numCols).map {
      Array(self[($0 * numCols) ..< ($0 + 1) * numCols])
    }
  }
}

1次元配列インデックスと2次元配列座標の関係


index = y * numCols + x

x = index % numCols
y = index / numCols

SwiftData の inMemory で実装してみたが遅かったので、@Observable クラスへ。

👉 【 SwiftUI 】 Pong Wars を SwiftUI に移植してみた hatena-bookmark

しかし、正直、勉強すればするほど謎が増えます !


👉 【Swift】2次元配列 で 転置行列 ( transpose matrix ) hatena-bookmark