A Swift implementation of the Free Spaced Repetition Scheduler (FSRS-6) algorithm.
Swift FSRS is a native Swift package that implements the FSRS-6 spaced repetition scheduler algorithm for flashcard applications. It provides a clean, type-safe API following Swift best practices.
- ✅ Complete FSRS-6 algorithm implementation
- ✅ Protocol-oriented design - works with your own card types
- ✅ Generic type system for type safety
- ✅ Card scheduling with learning steps support (BasicScheduler)
- ✅ Long-term scheduling without learning steps (LongTermScheduler)
- ✅ Reschedule functionality for replaying review history
- ✅ Rollback capability to undo reviews
- ✅ Forget functionality to reset cards
- ✅ Retrievability calculation (formatted and numeric)
- ✅ Customizable strategies (seed, learning steps)
- ✅ Parameter migration support (FSRS-4/5/6 compatibility)
- ✅ Comprehensive logging support with FSRSLogger protocol
Add the following to your Package.swift:
dependencies: [
.package(url: "https://github.com/yourusername/swift-fsrs.git", from: "1.0.0")
]Or add it via Xcode:
- File → Add Packages...
- Enter the repository URL
- Select version and add to your target
First, create a card type that conforms to the FSRSCard protocol:
import FSRS
struct Flashcard: FSRSCard {
// Your custom properties
let id: UUID
var question: String
var answer: String
// FSRS required properties
var due: Date
var state: State
var lastReview: Date?
var stability: Double
var difficulty: Double
var scheduledDays: Int
var learningSteps: Int
var reps: Int
var lapses: Int
init(question: String, answer: String) {
self.id = UUID()
self.question = question
self.answer = answer
// Initialize FSRS properties for new card
self.due = Date()
self.state = .new
self.lastReview = nil
self.stability = 0
self.difficulty = 0
self.scheduledDays = 0
self.learningSteps = 0
self.reps = 0
self.lapses = 0
}
}import FSRS
// Create FSRS instance with default parameters (specify your card type)
let f = fsrs<Flashcard>()
// Create a new card
let card = Flashcard(question: "What is the capital of France?", answer: "Paris")
let now = Date()
// Preview all rating scenarios
let recordLog = try f.repeat(card: card, now: now)
// Get card for a specific rating
let goodCard = recordLog[.good]!.card
let goodLog = recordLog[.good]!.log
// Review with a specific grade
let result = try f.next(card: card, now: now, grade: .good)
let nextCard = result.card
// Get retrievability as formatted string
let retrievability = f.getRetrievability(card: card, now: now)
print(retrievability) // "90.00%"
// Get retrievability as numeric value
let retrievabilityValue = f.getRetrievabilityValue(card: card, now: now)
print(retrievabilityValue) // 0.9let params = PartialFSRSParameters(
requestRetention: 0.9,
maximumInterval: 36500,
enableFuzz: true,
enableShortTerm: true
)
let f = fsrs<Flashcard>(params: params)Replay a card's review history to recalculate its state:
let now = Date()
let reviews: [FSRSHistory] = [
FSRSHistory(rating: .good, review: now),
FSRSHistory(rating: .good, review: now.addingTimeInterval(86400)),
FSRSHistory(rating: .again, review: now.addingTimeInterval(172800))
]
let options = RescheduleOptions<Flashcard>(
updateMemoryState: true,
now: Date()
)
let result = try f.reschedule(
currentCard: card,
reviews: reviews,
options: options
)
print(result.collections.count) // Number of replayed reviews
if let rescheduleItem = result.rescheduleItem {
let updatedCard = rescheduleItem.card
}Undo a review and restore the card to its previous state:
// After reviewing a card, you get a RecordLogItem
let result = try f.next(card: card, now: Date(), grade: .good)
let currentCard = result.card
let reviewLog = result.log
// Rollback to previous state
let previousCard = try f.rollback(card: currentCard, log: reviewLog)Reset a card back to the new state:
let forgottenResult = f.forget(card: card, now: Date(), resetCount: false)
let newCard = forgottenResult.card
// Card is now in .new state with reset stability and difficultyThe package is organized into the following modules:
- Models: Core data structures (ReviewLog, Parameters, Enums, ValueObjects)
- Protocols: Protocol definitions (FSRSCard, AlgorithmProtocols, SchedulerProtocol)
- Core: Main algorithm (FSRSAlgorithm, FSRS, Factory) and parameter management
- Schedulers: Scheduling logic (BasicScheduler, LongTermScheduler, BaseScheduler)
- Calculators: Separate calculators for stability, difficulty, and intervals
- Strategies: Extensible strategy system (seed, learning steps)
- Features: Advanced features (Reschedule, RetrievabilityService, CardStateService)
- Utilities: Helper functions (DateHelpers, MathHelpers, Alea PRNG, FSRSLogger)
- Protocol-Oriented: Use
FSRSCardprotocol to work with your own card types - Generic Types: FSRS is generic over any card type conforming to FSRSCard
- Value Objects: Type-safe wrappers for domain values (Stability, Difficulty, etc.)
- Service Layer: Separate services for retrievability, card state, and rescheduling
Requires Swift 5.9+
- State Transition Tests: Verify correct state machine behavior for all card states
- Parameter Tests: Test parameter validation, migration, and clipping
- Integration Tests: End-to-end testing of complete workflows
- API Tests: Verify all public API methods work correctly
How to run tests:
swift testRun specific test suites:
swift test --filter StateTransitionTests
swift test --filter IntegrationTestsThis is a complete, production-ready implementation of FSRS-6 for Swift. All core functionality has been implemented and tested:
- ✅ Complete FSRS-6 algorithm with all formulas
- ✅ Short-term and long-term scheduling modes
- ✅ Protocol-based generic design for flexibility
- ✅ Comprehensive test suite with 125+ tests
- ✅ Type-safe value objects for domain values
- ✅ Service-oriented architecture for clean separation of concerns
Contributions are welcome! Please ensure:
- All changes are made through forked repository
- Code follows Swift style guidelines
- Tests are added for new features
- All tests pass:
swift test - Documentation is updated