การย้ายจาก Core Data ไปยัง SwiftData: คู่มือทีละขั้นตอน 2026
คู่มือฉบับสมบูรณ์สำหรับการย้ายแอป iOS จาก Core Data ไปยัง SwiftData พร้อมตัวอย่างใช้งานจริง กลยุทธ์การอยู่ร่วมกัน และแนวทางปฏิบัติที่ดี

SwiftData แสดงถึงอนาคตของการคงสภาพข้อมูลบนแพลตฟอร์ม Apple เปิดตัวที่งาน WWDC 2023 เฟรมเวิร์กนี้เสนอไวยากรณ์ Swift แบบเนทีฟและการผสานรวมที่ราบรื่นกับ SwiftUI สำหรับแอปพลิเคชัน Core Data ที่มีอยู่ การย้ายระบบเป็นขั้นตอนเชิงกลยุทธ์ไปสู่โค้ดที่ทันสมัยและบำรุงรักษาง่ายขึ้น
คู่มือนี้อธิบายรายละเอียดกระบวนการย้ายระบบทั้งหมดจาก Core Data ไปยัง SwiftData: การประเมินความเข้ากันได้ การแปลงโมเดล กลยุทธ์การย้ายข้อมูล และรูปแบบการอยู่ร่วมกันสำหรับการเปลี่ยนผ่านแบบค่อยเป็นค่อยไป
การประเมินความเป็นไปได้ของการย้ายระบบ
ก่อนเริ่มการย้ายระบบ การประเมินอย่างรอบคอบช่วยให้ระบุอุปสรรคที่อาจเกิดขึ้นได้ Core Data และ SwiftData ใช้เอนจินการคงสภาพ SQLite เดียวกัน ทำให้ข้อมูลเข้ากันได้อย่างสมบูรณ์
// Migration assessment checklist
/*
FEATURES SUPPORTED BY SWIFTDATA:
✅ Simple models with basic properties
✅ One-to-one and one-to-many relationships
✅ Optional properties and default values
✅ Transformable attributes (via Codable)
✅ CloudKit synchronization (basic)
✅ Automatic lightweight migrations
✅ Class inheritance (iOS 26+)
FEATURES REQUIRING ATTENTION:
⚠️ NSFetchedResultsController → @Query + manual observation
⚠️ NSCompoundPredicate → #Predicate with combined logic
⚠️ Dynamic predicates → Workarounds required
UNSUPPORTED FEATURES:
❌ Advanced CloudKit Sharing
❌ Derived attributes
❌ Fetched properties
*/
// Example of typical Core Data model to migrate
import CoreData
// Existing Core Data entity
class CDTask: NSManagedObject {
@NSManaged var id: UUID
@NSManaged var title: String
@NSManaged var isCompleted: Bool
@NSManaged var createdAt: Date
@NSManaged var priority: Int16
@NSManaged var category: CDCategory?
}
class CDCategory: NSManagedObject {
@NSManaged var id: UUID
@NSManaged var name: String
@NSManaged var color: String
@NSManaged var tasks: NSSet?
}ความเข้ากันได้ในระดับข้อมูลหมายความว่าผู้ใช้จะคงข้อมูลที่มีอยู่ไว้หลังการย้ายระบบ ไม่มีการสูญหายของข้อมูลเกิดขึ้นเมื่อกระบวนการดำเนินการอย่างถูกต้อง
การแปลงโมเดล Core Data ไปเป็น SwiftData
ขั้นตอนรูปธรรมแรกคือการแปลงเอนทิตี Core Data ให้เป็นคลาส SwiftData Xcode มีเครื่องมืออัตโนมัติให้ใช้ แต่ความเข้าใจกระบวนการแบบแมนนวลยังคงเป็นสิ่งสำคัญ
import SwiftData
// SwiftData equivalent of CDTask
@Model
final class Task {
// Properties with default values
var id: UUID = UUID()
var title: String = ""
var isCompleted: Bool = false
var createdAt: Date = Date()
var priority: Int = 0
// Optional relationship to Category
var category: Category?
// Explicit initializer recommended
init(
id: UUID = UUID(),
title: String,
isCompleted: Bool = false,
createdAt: Date = Date(),
priority: Int = 0,
category: Category? = nil
) {
self.id = id
self.title = title
self.isCompleted = isCompleted
self.createdAt = createdAt
self.priority = priority
self.category = category
}
}ความแตกต่างหลักจาก Core Data ได้แก่ การใช้แมโคร @Model แทน NSManagedObject รวมถึงประเภท Swift แบบเนทีฟแทนประเภท Objective-C
import SwiftData
@Model
final class Category {
var id: UUID = UUID()
var name: String = ""
var color: String = "blue"
// Inverse relationship with delete rule
@Relationship(deleteRule: .cascade, inverse: \Task.category)
var tasks: [Task] = []
init(id: UUID = UUID(), name: String, color: String = "blue") {
self.id = id
self.name = name
self.color = color
}
}ประเภทใน Core Data จะแปลงโดยตรง: Int16 กลายเป็น Int, NSSet กลายเป็น [Model] และ Date ยังคงเป็น Date แอตทริบิวต์ Transformable ต้องใช้การ adopt Codable
การกำหนดค่า ModelContainer
ModelContainer ของ SwiftData มาแทนที่ NSPersistentContainer ของ Core Data การกำหนดค่าจะระบุว่าข้อมูลถูกเก็บที่ใดและอย่างไร
import SwiftData
import SwiftUI
@main
struct TaskManagerApp: App {
// SwiftData container configuration
var sharedModelContainer: ModelContainer = {
// Schema including all models
let schema = Schema([
Task.self,
Category.self
])
// Configuration with storage options
let modelConfiguration = ModelConfiguration(
schema: schema,
isStoredInMemoryOnly: false,
// Use the same store as Core Data
url: URL.applicationSupportDirectory
.appending(path: "TaskManager.sqlite")
)
do {
return try ModelContainer(
for: schema,
configurations: [modelConfiguration]
)
} catch {
fatalError("Could not create ModelContainer: \(error)")
}
}()
var body: some Scene {
WindowGroup {
ContentView()
}
.modelContainer(sharedModelContainer)
}
}จุดสำคัญอยู่ที่ URL ของ store: การใช้ไฟล์ SQLite เดียวกันกับ Core Data ทำให้ SwiftData อ่านข้อมูลที่มีอยู่ได้
กลยุทธ์การอยู่ร่วมกันของ Core Data และ SwiftData
สำหรับแอปพลิเคชันที่ซับซ้อน การย้ายระบบแบบค่อยเป็นค่อยไปด้วยการอยู่ร่วมกันของทั้งสองเฟรมเวิร์กเป็นแนวทางที่ปลอดภัยที่สุด ทั้งสอง stack สามารถเข้าถึงไฟล์ SQLite เดียวกันได้
import CoreData
import SwiftData
// Configuration for coexistence
class PersistenceController {
static let shared = PersistenceController()
// Shared store between Core Data and SwiftData
private let storeURL: URL = {
let appSupport = FileManager.default
.urls(for: .applicationSupportDirectory, in: .userDomainMask)
.first!
return appSupport.appending(path: "TaskManager.sqlite")
}()
// MARK: - Core Data Stack (existing)
lazy var persistentContainer: NSPersistentContainer = {
let container = NSPersistentContainer(name: "TaskManager")
// Configure to use shared store
let description = NSPersistentStoreDescription(url: storeURL)
description.setOption(
true as NSNumber,
forKey: NSPersistentHistoryTrackingKey
)
container.persistentStoreDescriptions = [description]
container.loadPersistentStores { _, error in
if let error = error as NSError? {
fatalError("Core Data error: \(error)")
}
}
return container
}()
// MARK: - SwiftData Stack (new)
lazy var swiftDataContainer: ModelContainer = {
let schema = Schema([Task.self, Category.self])
let config = ModelConfiguration(
schema: schema,
url: storeURL,
// Disable automatic migrations in coexistence
allowsSave: true
)
do {
return try ModelContainer(for: schema, configurations: [config])
} catch {
fatalError("SwiftData error: \(error)")
}
}()
}ในโหมดอยู่ร่วมกัน การเปลี่ยนแปลงที่กระทำโดยเฟรมเวิร์กหนึ่งจะไม่ปรากฏให้เห็นทันทีในอีกเฟรมเวิร์ก อาจจำเป็นต้องโหลดใหม่อย่างชัดเจนหรือรีสตาร์ตแอป
การย้ายเควรี: จาก NSFetchRequest ไปยัง @Query
ความแตกต่างที่สำคัญที่สุดเกี่ยวข้องกับวิธีการดึงข้อมูล SwiftUI ใช้ property wrapper @Query แทน @FetchRequest
import SwiftUI
import SwiftData
// ❌ Old pattern with Core Data
struct OldTaskListView: View {
@FetchRequest(
sortDescriptors: [
NSSortDescriptor(keyPath: \CDTask.createdAt, ascending: false)
],
predicate: NSPredicate(format: "isCompleted == NO")
)
private var tasks: FetchedResults<CDTask>
var body: some View {
List(tasks) { task in
Text(task.title)
}
}
}
// ✅ New pattern with SwiftData
struct NewTaskListView: View {
// @Query with built-in sorting and filtering
@Query(
filter: #Predicate<Task> { !$0.isCompleted },
sort: \Task.createdAt,
order: .reverse
)
private var tasks: [Task]
var body: some View {
List(tasks) { task in
TaskRowView(task: task)
}
}
}
struct TaskRowView: View {
let task: Task
var body: some View {
HStack {
// Priority indicator
Circle()
.fill(priorityColor)
.frame(width: 8, height: 8)
VStack(alignment: .leading) {
Text(task.title)
.font(.headline)
if let category = task.category {
Text(category.name)
.font(.caption)
.foregroundStyle(.secondary)
}
}
Spacer()
// Date badge
Text(task.createdAt, style: .date)
.font(.caption2)
.foregroundStyle(.tertiary)
}
}
private var priorityColor: Color {
switch task.priority {
case 3: return .red
case 2: return .orange
case 1: return .yellow
default: return .gray
}
}
}พร้อมที่จะพิชิตการสัมภาษณ์ iOS แล้วหรือยังครับ?
ฝึกฝนด้วยตัวจำลองแบบโต้ตอบ, flashcards และแบบทดสอบเทคนิคครับ
การจัดการ Predicate แบบไดนามิก
ความท้าทายสำคัญของ SwiftData เกี่ยวข้องกับ predicate แบบไดนามิก ซึ่งแตกต่างจาก Core Data ที่สามารถแก้ไข predicate ได้ทันที @Query ต้องการแนวทางทางเลือก
import SwiftUI
import SwiftData
// Solution 1: Use @Query with custom init
struct FilteredTasksView: View {
@Query private var tasks: [Task]
// Create view with specific filter
init(showCompleted: Bool, categoryId: UUID?) {
// Build predicate based on parameters
var predicates: [Predicate<Task>] = []
if !showCompleted {
predicates.append(#Predicate { !$0.isCompleted })
}
if let categoryId {
predicates.append(#Predicate { task in
task.category?.id == categoryId
})
}
// Combine predicates
let combinedPredicate: Predicate<Task>?
if predicates.isEmpty {
combinedPredicate = nil
} else if predicates.count == 1 {
combinedPredicate = predicates[0]
} else {
// Manually combine for AND logic
combinedPredicate = #Predicate<Task> { task in
!task.isCompleted && task.category?.id == categoryId
}
}
_tasks = Query(
filter: combinedPredicate,
sort: \Task.createdAt,
order: .reverse
)
}
var body: some View {
List(tasks) { task in
TaskRowView(task: task)
}
}
}
// Solution 2: View-side filtering with all results
struct SmartTaskListView: View {
// Fetch all tasks
@Query(sort: \Task.createdAt, order: .reverse)
private var allTasks: [Task]
// Filter state
@State private var searchText = ""
@State private var showCompleted = false
@State private var selectedCategory: Category?
// Computed filtering
private var filteredTasks: [Task] {
allTasks.filter { task in
// Text filter
let matchesSearch = searchText.isEmpty ||
task.title.localizedCaseInsensitiveContains(searchText)
// Status filter
let matchesStatus = showCompleted || !task.isCompleted
// Category filter
let matchesCategory = selectedCategory == nil ||
task.category?.id == selectedCategory?.id
return matchesSearch && matchesStatus && matchesCategory
}
}
var body: some View {
NavigationStack {
List(filteredTasks) { task in
TaskRowView(task: task)
}
.searchable(text: $searchText)
.toolbar {
FilterMenu(
showCompleted: $showCompleted,
selectedCategory: $selectedCategory
)
}
}
}
}การย้ายสคีมาแบบมีเวอร์ชัน
เมื่อโมเดลข้อมูลพัฒนา SwiftData ใช้ VersionedSchema เพื่อจัดการการย้ายระบบที่ซับซ้อน
import SwiftData
// Version 1: Initial schema
enum TaskSchemaV1: VersionedSchema {
static var versionIdentifier = Schema.Version(1, 0, 0)
static var models: [any PersistentModel.Type] {
[Task.self, Category.self]
}
@Model
final class Task {
var id: UUID = UUID()
var title: String = ""
var isCompleted: Bool = false
var createdAt: Date = Date()
var category: Category?
init(title: String) {
self.title = title
}
}
@Model
final class Category {
var id: UUID = UUID()
var name: String = ""
@Relationship(deleteRule: .cascade, inverse: \Task.category)
var tasks: [Task] = []
init(name: String) {
self.name = name
}
}
}
// Version 2: Added priority and notes fields
enum TaskSchemaV2: VersionedSchema {
static var versionIdentifier = Schema.Version(2, 0, 0)
static var models: [any PersistentModel.Type] {
[Task.self, Category.self]
}
@Model
final class Task {
var id: UUID = UUID()
var title: String = ""
var isCompleted: Bool = false
var createdAt: Date = Date()
// New properties with default values
var priority: Int = 0
var notes: String = ""
var category: Category?
init(title: String, priority: Int = 0) {
self.title = title
self.priority = priority
}
}
@Model
final class Category {
var id: UUID = UUID()
var name: String = ""
// New property
var color: String = "blue"
@Relationship(deleteRule: .cascade, inverse: \Task.category)
var tasks: [Task] = []
init(name: String, color: String = "blue") {
self.name = name
self.color = color
}
}
}แผนการย้ายระบบกำหนดลำดับเวอร์ชันและการย้ายระบบแบบกำหนดเองที่อาจจำเป็น
import SwiftData
enum TaskMigrationPlan: SchemaMigrationPlan {
// Chronological order of schemas
static var schemas: [any VersionedSchema.Type] {
[TaskSchemaV1.self, TaskSchemaV2.self]
}
// Migration stages
static var stages: [MigrationStage] {
[migrateV1toV2]
}
// V1 → V2 migration: lightweight (properties with defaults)
static let migrateV1toV2 = MigrationStage.lightweight(
fromVersion: TaskSchemaV1.self,
toVersion: TaskSchemaV2.self
)
}
// Container configuration with migration
@main
struct TaskManagerApp: App {
var sharedModelContainer: ModelContainer = {
do {
return try ModelContainer(
for: TaskSchemaV2.Task.self, TaskSchemaV2.Category.self,
migrationPlan: TaskMigrationPlan.self
)
} catch {
fatalError("Migration failed: \(error)")
}
}()
var body: some Scene {
WindowGroup {
ContentView()
}
.modelContainer(sharedModelContainer)
}
}SwiftData จัดการการย้ายระบบแบบ lightweight โดยอัตโนมัติ (การเพิ่มคุณสมบัติพร้อมค่าเริ่มต้น การเปลี่ยนชื่อ การลบ) การย้ายระบบที่ซับซ้อนซึ่งต้องการการแปลงข้อมูลใช้ MigrationStage.custom
การแทนที่ NSFetchedResultsController
สำหรับรายการแบบมีส่วนหรือการสังเกตการเปลี่ยนแปลงอย่างละเอียด @Query ร่วมกับการสกัดข้อมูลแทนที่ NSFetchedResultsController
import SwiftUI
import SwiftData
struct SectionedTaskListView: View {
@Query(sort: \Task.createdAt, order: .reverse)
private var tasks: [Task]
// Grouping by category
private var tasksByCategory: [(Category?, [Task])] {
Dictionary(grouping: tasks) { $0.category }
.map { ($0.key, $0.value) }
.sorted { first, second in
// Tasks without category last
guard let firstName = first.0?.name else { return false }
guard let secondName = second.0?.name else { return true }
return firstName < secondName
}
}
var body: some View {
List {
ForEach(tasksByCategory, id: \.0?.id) { category, categoryTasks in
Section(header: SectionHeader(category: category)) {
ForEach(categoryTasks) { task in
TaskRowView(task: task)
}
}
}
}
}
}
struct SectionHeader: View {
let category: Category?
var body: some View {
HStack {
if let category {
Circle()
.fill(Color(category.color))
.frame(width: 12, height: 12)
Text(category.name)
} else {
Text("Uncategorized")
.foregroundStyle(.secondary)
}
}
}
}
// Alternative: Grouping by date
struct DateGroupedTasksView: View {
@Query(sort: \Task.createdAt, order: .reverse)
private var tasks: [Task]
private var tasksByDate: [(Date, [Task])] {
let calendar = Calendar.current
let grouped = Dictionary(grouping: tasks) { task in
calendar.startOfDay(for: task.createdAt)
}
return grouped
.map { ($0.key, $0.value) }
.sorted { $0.0 > $1.0 }
}
var body: some View {
List {
ForEach(tasksByDate, id: \.0) { date, dateTasks in
Section(header: Text(date, style: .date)) {
ForEach(dateTasks) { task in
TaskRowView(task: task)
}
}
}
}
}
}การดำเนินการ CRUD ด้วย ModelContext
ModelContext แทนที่ NSManagedObjectContext สำหรับการดำเนินการสร้าง อ่าน อัปเดต และลบทั้งหมด
import SwiftUI
import SwiftData
struct TaskManagementView: View {
@Environment(\.modelContext) private var modelContext
@Query private var tasks: [Task]
@Query private var categories: [Category]
@State private var newTaskTitle = ""
@State private var selectedCategory: Category?
var body: some View {
NavigationStack {
VStack {
// Add form
AddTaskForm(
title: $newTaskTitle,
category: $selectedCategory,
categories: categories,
onAdd: addTask
)
// Task list
List {
ForEach(tasks) { task in
TaskRowView(task: task)
.swipeActions(edge: .trailing) {
Button(role: .destructive) {
deleteTask(task)
} label: {
Label("Delete", systemImage: "trash")
}
}
.swipeActions(edge: .leading) {
Button {
toggleCompletion(task)
} label: {
Label(
task.isCompleted ? "Todo" : "Done",
systemImage: task.isCompleted ? "circle" : "checkmark"
)
}
.tint(task.isCompleted ? .orange : .green)
}
}
}
}
.navigationTitle("Tasks")
}
}
// CREATE
private func addTask() {
guard !newTaskTitle.isEmpty else { return }
let task = Task(
title: newTaskTitle,
category: selectedCategory
)
// Insert into context
modelContext.insert(task)
// Explicit save (optional - autosave enabled by default)
do {
try modelContext.save()
} catch {
print("Save error: \(error)")
}
// Reset form
newTaskTitle = ""
selectedCategory = nil
}
// UPDATE
private func toggleCompletion(_ task: Task) {
// Direct modification - SwiftData tracks automatically
task.isCompleted.toggle()
// Automatic save handles persistence
}
// DELETE
private func deleteTask(_ task: Task) {
modelContext.delete(task)
}
}
struct AddTaskForm: View {
@Binding var title: String
@Binding var category: Category?
let categories: [Category]
let onAdd: () -> Void
var body: some View {
VStack(spacing: 12) {
TextField("New task...", text: $title)
.textFieldStyle(.roundedBorder)
HStack {
Picker("Category", selection: $category) {
Text("None").tag(nil as Category?)
ForEach(categories) { cat in
Text(cat.name).tag(cat as Category?)
}
}
.pickerStyle(.menu)
Button("Add", action: onAdd)
.buttonStyle(.borderedProminent)
.disabled(title.isEmpty)
}
}
.padding()
}
}การทดสอบหน่วยด้วย SwiftData
กลยุทธ์การทดสอบที่แข็งแกร่งช่วยอำนวยความสะดวกในการตรวจสอบการย้ายระบบ SwiftData อนุญาตให้สร้างคอนเทนเนอร์ในหน่วยความจำสำหรับการทดสอบ
import XCTest
import SwiftData
@testable import TaskManager
final class TaskModelTests: XCTestCase {
var container: ModelContainer!
var context: ModelContext!
override func setUpWithError() throws {
// In-memory container for tests
let config = ModelConfiguration(isStoredInMemoryOnly: true)
container = try ModelContainer(
for: Task.self, Category.self,
configurations: config
)
context = ModelContext(container)
}
override func tearDownWithError() throws {
container = nil
context = nil
}
func testCreateTask() throws {
// Given
let task = Task(title: "Test Task")
// When
context.insert(task)
try context.save()
// Then
let descriptor = FetchDescriptor<Task>()
let tasks = try context.fetch(descriptor)
XCTAssertEqual(tasks.count, 1)
XCTAssertEqual(tasks.first?.title, "Test Task")
}
func testTaskCategoryRelationship() throws {
// Given
let category = Category(name: "Work", color: "blue")
let task = Task(title: "Meeting", category: category)
// When
context.insert(category)
context.insert(task)
try context.save()
// Then
XCTAssertEqual(task.category?.name, "Work")
XCTAssertTrue(category.tasks.contains(task))
}
func testDeleteCategoryCascade() throws {
// Given
let category = Category(name: "Personal")
let task1 = Task(title: "Task 1", category: category)
let task2 = Task(title: "Task 2", category: category)
context.insert(category)
context.insert(task1)
context.insert(task2)
try context.save()
// When
context.delete(category)
try context.save()
// Then - cascade delete should remove tasks
let descriptor = FetchDescriptor<Task>()
let remainingTasks = try context.fetch(descriptor)
XCTAssertEqual(remainingTasks.count, 0)
}
func testFilteredFetch() throws {
// Given
let task1 = Task(title: "Completed", isCompleted: true)
let task2 = Task(title: "Pending", isCompleted: false)
let task3 = Task(title: "Also Pending", isCompleted: false)
[task1, task2, task3].forEach { context.insert($0) }
try context.save()
// When
var descriptor = FetchDescriptor<Task>(
predicate: #Predicate { !$0.isCompleted }
)
let pendingTasks = try context.fetch(descriptor)
// Then
XCTAssertEqual(pendingTasks.count, 2)
}
}รายการตรวจสอบการย้ายระบบฉบับสมบูรณ์
ต่อไปนี้เป็นสรุปขั้นตอนสำหรับการย้ายระบบที่ประสบความสำเร็จ:
/*
PHASE 1: PREPARATION
□ Audit Core Data features in use
□ Identify features not supported by SwiftData
□ Create dedicated migration branch
□ Back up test data
PHASE 2: MODEL CONVERSION
□ Convert NSManagedObject entities to @Model
□ Adapt relationships with @Relationship
□ Configure appropriate delete rules
□ Add required default values
PHASE 3: CONFIGURATION
□ Create ModelContainer with existing store URL
□ Configure versioned schema if needed
□ Define migration plan
□ Test in coexistence mode if applicable
PHASE 4: CODE MIGRATION
□ Replace @FetchRequest with @Query
□ Adapt predicates to #Predicate
□ Migrate NSFetchedResultsController to manual grouping
□ Convert CRUD operations to ModelContext
PHASE 5: VALIDATION
□ Run all unit tests
□ Test migration with real data
□ Verify performance with Instruments
□ Validate CloudKit sync (if applicable)
PHASE 6: DEPLOYMENT
□ Document breaking changes
□ Prepare rollback plan
□ Deploy to TestFlight
□ Monitor post-deployment crashes
*/บทสรุป
การย้ายระบบจาก Core Data ไปยัง SwiftData แสดงถึงวิวัฒนาการตามธรรมชาติสำหรับแอปพลิเคชัน iOS สมัยใหม่ ความเข้ากันได้ในระดับ store ของ SQLite รับประกันการรักษาข้อมูลของผู้ใช้ ในขณะที่ไวยากรณ์ Swift แบบเนทีฟทำให้โค้ดเรียบง่ายขึ้นอย่างมาก
ประเด็นสำคัญ
- ✅ SwiftData และ Core Data ใช้เอนจิน SQLite เดียวกัน
- ✅ การอยู่ร่วมกันช่วยให้สามารถย้ายระบบทีละขั้นตอน
- ✅
@Queryแทนที่@FetchRequestด้วยไวยากรณ์ที่ง่ายกว่า - ✅ Predicate แบบไดนามิกต้องการรูปแบบทางเลือก
- ✅
VersionedSchemaจัดการวิวัฒนาการของสคีมา - ✅ การทดสอบในหน่วยความจำช่วยอำนวยความสะดวกในการตรวจสอบ
- ✅ iOS 26 นำเสนอการสนับสนุนการสืบทอดคลาส
- ✅ ควรเริ่มโปรเจกต์ใหม่ด้วย SwiftData ยกเว้นกรณีที่มีความต้องการเฉพาะของ Core Data
เริ่มฝึกซ้อมเลย!
ทดสอบความรู้ของคุณด้วยตัวจำลองสัมภาษณ์และแบบทดสอบเทคนิคครับ
แท็ก
แชร์
บทความที่เกี่ยวข้อง

Combine vs async/await ใน Swift: รูปแบบการย้ายระบบแบบค่อยเป็นค่อยไป
คู่มือฉบับสมบูรณ์สำหรับการย้ายจาก Combine ไปยัง async/await ใน Swift: กลยุทธ์แบบค่อยเป็นค่อยไป รูปแบบการเชื่อมโยง และการอยู่ร่วมกันของกระบวนทัศน์ในโค้ดเบส iOS

คำถามสัมภาษณ์การเข้าถึง iOS ในปี 2026: VoiceOver และ Dynamic Type
เตรียมตัวสัมภาษณ์ iOS ด้วยคำถามสำคัญเรื่องการเข้าถึง: VoiceOver, Dynamic Type, traits เชิงความหมาย และการตรวจสอบ.

Swift Macros: ตัวอย่างเชิงปฏิบัติของ metaprogramming
คู่มือฉบับสมบูรณ์ของ Swift Macros: การสร้างมาโคร freestanding และ attached, การจัดการ AST ด้วย swift-syntax และตัวอย่างเชิงปฏิบัติเพื่อกำจัดโค้ดซ้ำซาก