🦔

Geminiにコードをフィードバックしてもろたらめっちゃ吸収できた!

に公開

Geminiにコード書いてフィードバックしてもらったら、めちゃくちゃ理解できたので共有します。

質問

import SwiftUI

class TaskManager: ObservableObject {

・クラスはStructのようなもの。機能も基本Structと同じ

    @Published var tasks: [TimeSlot: [Task]] = [:]

・Published var taskは、taskという変数を定義し、この変数の値が変わるたびにViewを変えるようにしてくれる。[TimeSlot: [Task]] の部分は、辞書型であり、どっかで定義したTaskの配列をTimeSlotに合わせて1対1で出してくれる。

    init() {

        for slot in TimeSlot.allCases {

            tasks[slot] = []

        }

・クラスを使うときはinit()を使う。var body some viewの代わりみたいなやつ。for slot in はfor inループで、slot1つ1つをTimeslot全ケースに割り振られている。

    func addTask(to timeSlot: TimeSlot, title: String) {

・このfuncはaddtaskというTime Slotに追加する関数を定義

        let newTask = Task(title: title)

・新しいタスクを作るために、Task配列それぞれの(タイトル)をここで出している。

    tasks[timeSlot]?.append(newTask)

}

・ taskをタイムスロットごとに追加できるようにする。

    func toggleTask(in timeSlot: TimeSlot, taskId: UUID) {

・in timeSlot:がよくわかんない

        if let index = tasks[timeSlot]?.firstIndex(where: { $0.id == taskId }) {

            tasks[timeSlot]?[index].isCompleted.toggle()

        }

    }

・toggletaskというtaskの完了を切り替える関数を作る。

    func deleteTask(in timeSlot: TimeSlot, taskId: UUID) {

        tasks[timeSlot]?.removeAll {

            $0.id == taskId }

    }

    func getCompletionRate(for timeSlot: TimeSlot) -> Double {

・for timeSlotがわかんない。TimeSlotの中にあるtimeSlotっていう変数への関数っていうこと?

        guard let slotTasks = tasks[timeSlot],

・slotTasks = tasks[timeSlot]以外nillを返す。

!slotTasks.isEmpty else { return 0 }

slotTasksがからの時は強制終了、他はnillを返す

        let completedCount = slotTasks.filter { $0.isCompleted }.count

・slotTasks.filterがわかんないけど、slotTasksにフィルターかけて、$0.isCompleted と同じのだけを完了かけるようにするってこと?

        return Double(completedCount) / Double(slotTasks.count)

SlotTaskのうち、どのくらい完了してるかを返す

このコード全体について、自分で解説したからフィードバックして

Geminiの答え全文

はい、承知いたしました。
ご自身の言葉でコードを解説してみる、という試み、本当に素晴らしいです!プログラムの理解を深める上で、最高の学習方法の一つですよ。

全体的によく理解できていますが、いくつか誤解している重要なポイントがあるので、一緒に確認していきましょう。

フィードバック
1.

class TaskManager: ObservableObject

クラスはStructのようなもの。機能も基本Structと同じ

フィードバック:
その通り、classもstructも、自分でデータ型を設計するためのものという点では似ていますね!
ただ、一つだけ決定的で非常に重要な違いがあります。

struct (構造体): 値型。コピーすると、中身が全く同じ別のものがもう一つ作られます。
例: 写真をコピーするようなもの。コピーした写真を加工しても、元の写真は変わりません。
class (クラス): 参照型。コピーすると、全く同じ**「一つのもの」を指し示す情報が渡されます。
例: オンライン上の写真へのリンクを共有するようなもの。誰かがそのリンク先で写真を加工すると、同じリンクを持っている人全員が見る写真が変わります。
TaskManagerのように、アプリ内の複数の画面で
「同じ一つのデータ」を共有して、監視したい**場合にclassを使います。この「参照型」という性質のおかげで、@StateObjectや@ObservedObjectが機能するわけですね。

@Published var tasks

Published var taskは、taskという変数を定義し、この変数の値が変わるたびにViewを変えるようにしてくれる。[TimeSlot: [Task]] の部分は、辞書型であり、どっかで定義したTaskの配列をTimeSlotに合わせて1対1で出してくれる。

フィードバック:
完璧な理解です! 素晴らしい。
@Publishedは、まさに「このデータが変わったら、みんなにお知らせ(Publish)するよ!」という宣言です。このお知らせを受け取ったビューが、自動的に再描画される仕組みです。辞書型の理解もその通りです。

init()

クラスを使うときはinit()を使う。var body some viewの代わりみたいなやつ。

フィードバック:
init()がクラスで使われることが多いのはその通りですが、var body: some Viewの代わり、というのは少し違います。

init() (イニシャライザ):
クラスや構造体のインスタンスが初めて作られる時に、一度だけ呼ばれる初期設定用の関数です。「こういう状態でスタートしてください」と準備をする役割です。
var body: some View:
これはViewプロトコルに準拠した型だけが持つ、画面のレイアウトや見た目を定義するためのものです。
TaskManagerは、データを管理するモデルであり、見た目を持つViewではないので、bodyはありません。
**init()は「設計図から物を作る時の初期設定」、bodyは「見た目を描くための指示書」**と、役割が全く異なります。

func addTask(...) / func toggleTask(...)

in timeSlot:がよくわかんない

フィードバック:
addTaskの理解はバッチリです!
toggleTask(in timeSlot: ...)のinは、**引数ラベル(Argument Label)**と呼ばれるものです。

これは、関数を呼び出す側が、より自然な文章のようにコードを読めるようにするためのものです。

呼び出す時のコード:

taskManager.toggleTask(in: .morning, taskId: someUUID)

このように、in: .morningと書くことで、「morningという時間帯の中のタスクを切り替える」という意味が、英語の文章のように分かりやすくなります。

forやinといった単語自体に特別な機能があるわけではなく、コードを読みやすくするためのラベルだと覚えておけばOKです。

func getCompletionRate(...)

for timeSlotがわかんない。
guard let slotTasks = tasks[timeSlot], !slotTasks.isEmpty else { return 0 }
slotTasksがからの時は強制終了、他はnillを返す
slotTasks.filterがわかんないけど、...

フィードバック:
この関数は少し複雑なので、一番多くの誤解がありました。一つずつ見ていきましょう。

for timeSlot:

これも先ほどのinと同じ引数ラベルです。「morningという時間帯のための達成率を取得する」という意味で、getCompletionRate(for: .morning)のように呼び出せます。
guard let ..., !slotTasks.isEmpty:

guard文は「関門」や「門番」のようなもので、条件を満たさない場合は、そこで処理を中断してelseの中を実行します。
guard let slotTasks = tasks[timeSlot]
「tasks辞書からtimeSlotキーで値(タスクの配列)を取り出せますか?もし取り出せない(nilの)場合は、処理を中断してelseに進んでください」
, !slotTasks.isEmpty
「そして、取り出したslotTasks配列が**空っぽではない(!は否定)ですか?**もし空っぽの場合は、処理を中断してelseに進んでください」
else { return 0 }
「上記のどちらかの条件を満たさなかった場合は、0を返して、この関数の処理はここで終わりです」
まとめると: 「指定された時間帯のタスクリストが存在し、かつ、そのリストが空っぽでなければ先に進む。そうでなければ0を返して終了する」という安全装置です。「強制終了」や「nilを返す」わけではありません。

slotTasks.filter { $0.isCompleted }.count:

.filterは、配列の中身を特定の条件で**絞り込む(フィルタリングする)**機能です。
{ $0.isCompleted }
$0は「配列の中の各要素(この場合は一つのTask)」を指す省略形です。
つまり、「配列の中の各タスクのうち、isCompletedプロパティがtrueのものだけを集めてください」という意味になります。
.count
そうして集められた「完了済みのタスク」が何個あるかを数えています。
全体として、ご自身でここまで読み解けているのは本当に素晴らしいです!
特に難解なguard文や.filterは、多くの人がつまずくポイントです。このフィードバックを元に、もう一度コードを見返すと、さらに理解が深まるはずですよ。
この調子で頑張ってください!

Discussion