🍒

Genericsの基本的な扱い方

2025/02/18に公開

はじめに

ジェネリクス(Generics)は、柔軟なコードを記述するための仕組みである。ジェネリクスを活用すれば1つの関数・構造体・クラスで複数の型を扱えるようになる。

Generic Functions

Generic Functionsは、型パラメータを角括弧( <T> )で指定して定義する。
T は仮の型であり、関数が呼び出される際に具体的な型に置き換えられる。

  • <T> でジェネリック型パラメータを宣言している。
  • T は任意の型として機能し、実際の型は関数の呼び出し時に決まる。
final class UserDefaultsManager {
    static let shared = UserDefaultsManager(suiteName: "com.example.app") 

    private let userDefaults: UserDefaults

    private init(suiteName: String) {
        self.userDefaults = UserDefaults(suiteName: suiteName) ?? .standard
    }

    func save<T: Codable>(_ item: T, forKey key: String) throws {
        do {
            let data = try JSONEncoder().encode(item)
            userDefaults.set(data, forKey: key)
        } catch {
            throw DataStoreError.encodingFailed
        }
    }

    func load<T: Codable>(forKey key: String) throws -> T {
        guard let data = userDefaults.data(forKey: key) else {
            throw DataStoreError.notFound
        }
        do {
            return try JSONDecoder().decode(T.self, from: data)
        } catch {
            throw DataStoreError.decodingFailed
        }
    }
}

Generic Structures and Classes

Generics は、構造体やクラスにも適用可能である。これにより、異なるデータ型に対応する汎用的なデータ構造を作成できる。

  • Stack<T>T は任意の型を表す。
struct Stack<T> {
    private var elements: [T] = []

    mutating func push(_ item: T) {
        elements.append(item)
    }

    mutating func pop() -> T? {
        return elements.popLast()
    }
}

var intStack = Stack<Int>()
intStack.push(10)
intStack.push(20)
print(intStack.pop()!) // 出力: 20

var stringStack = Stack<String>()
stringStack.push("Swift")
stringStack.push("Generics")
print(stringStack.pop()!) // 出力: "Generics"
ポイント

Generic Protocols

プロトコルにおいてもジェネリクスを活用できる。

  • Undoable プロトコルは汎用的なインターフェースを提供する。
  • プロコトルに準拠することにより、モックの作成が円滑に進められ、テストしやすくなる
import Foundation

/// Undo/Redo機能を提供するプロトコル
protocol Undoable {
    associatedtype ActionType

    var undoStack: [ActionType] { get set }
    var redoStack: [ActionType] { get set }

    mutating func perform(_ action: ActionType)
    mutating func undo() -> ActionType?
    mutating func redo() -> ActionType?
}

struct UndoManager<T>: Undoable {
    typealias ActionType = T

    var undoStack: [T] = []
    var redoStack: [T] = []

    mutating func perform(_ action: T) {
        undoStack.append(action)
        redoStack.removeAll()  // 新しいアクションを追加するとredoはクリアされる
    }

    mutating func undo() -> T? {
        guard let lastAction = undoStack.popLast() else { return nil }
        redoStack.append(lastAction)
        return lastAction
    }

    mutating func redo() -> T? {
        guard let lastRedo = redoStack.popLast() else { return nil }
        undoStack.append(lastRedo)
        return lastRedo
    }
}

// 文字列のUndo/Redo管理
var textUndoManager = UndoManager<String>()
textUndoManager.perform("First Edit")
textUndoManager.perform("Second Edit")
textUndoManager.perform("Third Edit")

print(textUndoManager.undo() ?? "Nothing to undo")  // 出力: "Third Edit"
print(textUndoManager.undo() ?? "Nothing to undo")  // 出力: "Second Edit"
print(textUndoManager.redo() ?? "Nothing to redo")  // 出力: "Second Edit"

// 整数のUndo/Redo管理
var intUndoManager = UndoManager<Int>()
intUndoManager.perform(10)
intUndoManager.perform(20)
print(intUndoManager.undo() ?? "Nothing to undo")  // 出力: 20

まとめ

ジェネリクスを活用することで、再利用性が高いコードを記述できる。

Discussion