🍒
Genericsの基本的な扱い方
はじめに
ジェネリクス(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