Codableyapılarla JSON okumak ve yazmakURLSessionveasync/awaitile bir ağ isteği yazmak ve hataları ele almak- Yüklemeyi
.taskve.refreshableile ekrana bağlamak - SwiftData'da
@Model,@QueryvemodelContextile çalışmak
Elvin uçakta Educora'yı açıyor. İnternet yok, ama dün yazdığı notlar yerinde duruyor; iniş yapınca da uygulama yeni dersleri sunucudan sessizce yüklüyor. Bunun için iki beceri gerekir: ağdan veri almak (JSON, URLSession, async/await) ve onu cihazda saklamak (SwiftData). Bu derste ikisini de adım adım kuracağız.
Codable: JSON'dan Swift'e ve geri
Sunucular veriyi genellikle JSON metni olarak gönderir. Bir yapıyı Codable protokolüne uyan olarak tanımlarsan Swift onu JSON'dan okumayı (JSONDecoder) ve JSON'a yazmayı (JSONEncoder) kendisi öğrenir; tek satır fazladan kod gerekmez. Özellik adları JSON'daki anahtarlarla aynı olmalıdır; JSON'daki fazla anahtarlar yalnızca atlanır.
import Foundation
struct Lesson: Codable {
let id: Int
let title: String
let minutes: Int
}
let json = """
[{"id": 1, "title": "Variables", "minutes": 10},
{"id": 2, "title": "Loops", "minutes": 15, "level": "beginner"}]
"""
let lessons = try JSONDecoder().decode([Lesson].self, from: Data(json.utf8))
for lesson in lessons {
print("\(lesson.id). \(lesson.title) - \(lesson.minutes) min")
}
let encoder = JSONEncoder()
encoder.outputFormatting = .sortedKeys
let data = try encoder.encode(lessons[0])
print(String(decoding: data, as: UTF8.self))1. Variables - 10 min
2. Loops - 15 min
{"id":1,"minutes":10,"title":"Variables"}level anahtarı yapıda olmadığı için atlanır; .sortedKeys anahtarları alfabetik sırayla yazar.URLSession ve async/await
URLSession.shared.data(from:) bir async fonksiyondur: await sözcüğü cevabı beklediği yeri işaretler, ama bu sırada ana iş parçacığı bloke olmaz; ekran tepki vermeye devam eder. Fonksiyon bir (Data, URLResponse) ikilisi döndürür. Ağ kesilirse hata fırlatır; bu yüzden çağrının önüne try da yazılır. Örnekte yapılacaklar, test için herkese açık bir servis olan JSONPlaceholder'dan yüklenir.
struct Todo: Codable, Identifiable {
let id: Int
let title: String
let completed: Bool
}
enum APIError: Error {
case badStatus(Int)
}
func fetchTodos() async throws -> [Todo] {
let url = URL(string: "https://jsonplaceholder.typicode.com/todos")!
let (data, response) = try await URLSession.shared.data(from: url)
if let http = response as? HTTPURLResponse, !(200...299).contains(http.statusCode) {
throw APIError.badStatus(http.statusCode)
}
return try JSONDecoder().decode([Todo].self, from: data)
}decode hata fırlatır.func fetchTodos(completion: @escaping (Result<[Todo], Error>) -> Void) {
let url = URL(string: "https://jsonplaceholder.typicode.com/todos")!
URLSession.shared.dataTask(with: url) { data, _, error in
if let error {
completion(.failure(error))
return
}
do {
let todos = try JSONDecoder().decode([Todo].self, from: data ?? Data())
completion(.success(todos))
} catch {
completion(.failure(error))
}
}.resume()
}func fetchTodos() async throws -> [Todo] {
let url = URL(string: "https://jsonplaceholder.typicode.com/todos")!
let (data, _) = try await URLSession.shared.data(from: url)
return try JSONDecoder().decode([Todo].self, from: data)
}.resume() unutulursa istek hiç gönderilmez; bir completion çağrısını atlamak da ekranı sonsuza dek “yükleniyor” durumunda bırakır. Sağda kod yukarıdan aşağı okunur, hatalar da throws ile kendiliğinden yukarı iletilir.@MainActor
@Observable
final class TodosModel {
var todos: [Todo] = []
var isLoading = false
var errorMessage: String?
func load() async {
isLoading = true
defer { isLoading = false }
do {
todos = try await fetchTodos()
errorMessage = nil
} catch {
errorMessage = "Could not load the list"
}
}
}@MainActor, modelin özelliklerinin yalnızca ana iş parçacığında değişmesini garanti eder; arayüz de tam orada güncellenir.struct TodosView: View {
@State private var model = TodosModel()
var body: some View {
List(model.todos) { todo in
Label(todo.title, systemImage: todo.completed ? "checkmark.circle.fill" : "circle")
}
.overlay {
if model.isLoading { ProgressView() }
}
.task { await model.load() }
.refreshable { await model.load() }
}
}defer bloğu, fonksiyon nasıl biterse bitsin (başarıyla ya da hatayla) en sonda çalışır; bu yüzden gösterge ekranda asla “takılı” kalmaz. errorMessage dolduğunda listenin yerine iOS 17'nin hazır boş durum görünümünü göstermek kullanışlıdır: ContentUnavailableView("No connection", systemImage: "wifi.slash"). Altına load()'u yeniden çağıran bir “Try again” düğmesi koyabilirsin.
SwiftData: cihazda saklamak
SwiftData (iOS 17+), bir Swift sınıfını veritabanında saklamanın en kısa yoludur. Sınıfın önüne @Model yazarsın, uygulamada .modelContainer(for:) ile bir depo oluşturursun, görünümde de veriyi @Query ile okursun. Yeni nesne modelContext.insert(...) ile eklenir, delete ile silinir; değişiklikler kendiliğinden kaydedilir ve @Query listeyi kendisi günceller.
import SwiftData
import SwiftUI
@Model
final class Note {
var text: String
var createdAt: Date
init(text: String, createdAt: Date = .now) {
self.text = text
self.createdAt = createdAt
}
}
@main
struct EducoraNotesApp: App {
var body: some Scene {
WindowGroup {
NotesView()
}
.modelContainer(for: Note.self)
}
}struct NotesView: View {
@Environment(\.modelContext) private var context
@Query(sort: \Note.createdAt, order: .reverse) private var notes: [Note]
var body: some View {
NavigationStack {
List {
ForEach(notes) { note in
Text(note.text)
}
.onDelete { offsets in
for index in offsets { context.delete(notes[index]) }
}
}
.navigationTitle("Notes")
.toolbar {
Button("Add", systemImage: "plus") {
context.insert(Note(text: "New note"))
}
}
}
}
}+ yeni not ekler, sola kaydırmak siler. Uygulamayı kapatıp açınca notlar yerinde kalır.| Ne saklanır | Nerede |
|---|---|
| küçük ayarlar: tema, günlük hedef | @AppStorage (UserDefaults) |
| notlar, sonuçlar, aranan listeler | SwiftData |
| parolalar ve giriş belirteçleri | Keychain |
| tüm cihazlarda aynı olması gereken ilerleme | sunucu |
@AppStorage("dailyGoal") private var dailyGoal = 15, değeri uygulama kapandıktan sonra da saklar.- 1SwiftData'lı bir proje
Yeni proje oluştururken
StoragealanındaSwiftData'yı seç; Xcode örnek birItemmodeli,.modelContainerve bir liste ekranı oluşturur. - 2Önizleme için bellekte depo
Önizleme gerçek veritabanına dokunmasın diye şunu yaz:
#Preview { NotesView().modelContainer(for: Note.self, inMemory: true) }. - 3Hata ayıklayıcıyla adım adım ilerle
load()içinde bir kesme noktası koy, uygulamayı çalıştır veF6ile satır satır ilerle:todosdizisinin nasıl dolduğunu görürsün.
Önemli noktalar
Codablebir yapı JSON'danJSONDecoderile okunur,JSONEncoderile yazılır; fazla anahtarlar atlanır.try await URLSession.shared.data(from:)(Data, URLResponse)döndürür ve ekranı bloke etmez.- Model
@MainActor @Observableolur;.taskyüklemeyi başlatır ve kendini iptal eder,.refreshableyenileme hareketi ekler. - SwiftData: bir
@Modelsınıf, uygulamada.modelContainer(for:), görünümde@QueryvemodelContext.insert/delete. - Küçük ayarlar
@AppStorage'da, parolalar Keychain'de tutulur; yalnızca HTTPS kullan.
Kendini test et
10 soru. Her doğru cevap XP kazandırır.
try await URLSession.shared.data(from: url) ne döndürür?