Məzmuna keç
Educora
İrəli25 dəq13 / 14

Şəbəkə və məlumatın saxlanması

Codable ilə JSON-u Swift tiplərinə çevir, URLSession və async/await ilə serverdən məlumat yüklə, nəticəni ekranda göstər və qeydləri SwiftData ilə cihazda saxla.

Özünü yoxla
Bu dərsdə öyrənəcəksən
  • Codable strukturları ilə JSON-u oxumaq və yazmaq
  • URLSession və async/await ilə şəbəkə sorğusu yazmaq və xətaları emal etmək
  • Yükləməni .task və .refreshable ilə ekrana bağlamaq
  • SwiftData-da @Model, @Query və modelContext ilə işləmək

Elvin təyyarədə Educora-nı açır. İnternet yoxdur, amma dünən yazdığı qeydlər yerindədir, yerə enəndə isə tətbiq yeni dərsləri serverdən sakitcə yükləyir. Bunun üçün iki bacarıq lazımdır: şəbəkədən məlumat almaq (JSON, URLSession, async/await) və onu cihazda saxlamaq (SwiftData). Bu dərsdə hər ikisini addım-addım quracağıq.

Codable: JSON-dan Swift-ə və geri

Serverlər məlumatı adətən JSON mətni kimi göndərir. Strukturu Codable protokoluna uyğun elan etsən, Swift onu JSON-dan oxumağı (JSONDecoder) və JSON-a yazmağı (JSONEncoder) özü öyrənir — bir sətir də əlavə kod lazım deyil. Xassələrin adları JSON-dakı açarlarla eyni olmalıdır; JSON-da artıq açarlar varsa, onlar sadəcə buraxılır.

Swift
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))
Gözlənilən nəticə
1. Variables - 10 min
2. Loops - 15 min
{"id":1,"minutes":10,"title":"Variables"}
Swift Playgrounds-da və ya Mac-də işləyir. level açarı strukturda olmadığı üçün buraxılır; .sortedKeys açarları əlifba sırası ilə yazır.

URLSession və async/await

URLSession.shared.data(from:) async funksiyadır: await sözü onun cavabı gözlədiyi yeri göstərir, amma əsas axın bu vaxt bloklanmır — ekran cavab verməyə davam edir. Funksiya (Data, URLResponse) cütü qaytarır. Şəbəkə kəsiləndə o xəta atır, ona görə çağırışın qarşısında try da yazılır. Nümunədə sınaq üçün açıq olan JSONPlaceholder xidmətindən tapşırıqlar yüklənir.

Swift
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)
}
Server 200–299 aralığından kənar status qaytararsa, öz xətamızı atırıq; JSON uyğun gəlməsə, decode xəta atacaq.
Köhnə: completion handler
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()
}
Yeni: async/await
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)
}
Solda .resume() unudulsa, sorğu heç vaxt göndərilməz, bir completion çağırışını buraxmaq isə ekranı əbədi «yüklənir» vəziyyətində saxlayar. Sağda kod yuxarıdan aşağı oxunur, xətalar throws ilə özü yuxarı ötürülür.
Swift
@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 xassələrinin yalnız əsas axında dəyişməsinə zəmanət verir — interfeys məhz orada yenilənir.
Swift
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() }
    }
}
Ekranda: əvvəlcə fırlanan indikator, sonra işarəli və boş dairəli tapşırıqlar siyahısı; siyahını aşağı dartanda yenidən yüklənir.

defer bloku funksiyadan necə çıxılmasından asılı olmayaraq — uğurla və ya xəta ilə — sonda icra olunur, ona görə indikator heç vaxt ekranda «ilişib» qalmır. errorMessage dolanda siyahının yerinə iOS 17-dəki hazır boş vəziyyət görünüşünü göstərmək rahatdır: ContentUnavailableView("No connection", systemImage: "wifi.slash"). Onun altına «Try again» düyməsi qoyub yenidən load() çağırmaq olar.

SwiftData: cihazda saxlamaq

SwiftData (iOS 17+) Swift sinfini verilənlər bazasında saxlamağın ən qısa yoludur. Sinfin qarşısına @Model yazırsan, tətbiqdə .modelContainer(for:) ilə anbar yaradırsan, görünüşdə isə @Query ilə məlumatı oxuyursan. Yeni obyekt modelContext.insert(...) ilə əlavə olunur, delete ilə silinir; dəyişikliklər avtomatik yadda saxlanır, @Query isə siyahını özü yeniləyir.

Swift
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)
    }
}
Swift
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"))
                }
            }
        }
    }
}
Ekranda: «Notes» siyahısı, ən yenisi yuxarıda; + yeni qeyd əlavə edir, sola sürüşdürmək silir. Tətbiqi bağlayıb açanda qeydlər yerində qalır.
Nə saxlanırHarada
kiçik parametrlər: tema, gündəlik məqsəd@AppStorage (UserDefaults)
qeydlər, nəticələr, axtarılan siyahılarSwiftData
parollar və giriş tokenləriKeychain
bütün cihazlarda eyni olmalı olan irəliləyişserver
Məsələn: @AppStorage("dailyGoal") private var dailyGoal = 15 qiyməti tətbiq bağlanandan sonra da saxlayır.
  1. 1
    SwiftData ilə layihə

    Yeni layihə yaradarkən Storage sahəsində SwiftData seç — Xcode nümunə Item modeli, .modelContainer və siyahı ekranını özü yaradacaq.

  2. 2
    Önizləmə üçün yaddaşda anbar

    Önizləmə real bazanı korlamasın deyə yaz: #Preview { NotesView().modelContainer(for: Note.self, inMemory: true) }.

  3. 3
    Sazlayıcı ilə addım-addım irəlilə

    load() funksiyasının içində dayanma nöqtəsi qoy, tətbiqi işə sal və F6 ilə sətir-sətir irəlilə: todos massivinin necə dolduğunu görəcəksən.

sazlayıcıda növbəti sətrə keçmək — Step OverF6
çağırılan funksiyanın içinə girmək — Step IntoF7
dayanmadan sonra icranı davam etdirməkCtrl+Cmd+Y
bütün layihədə axtarmaqCmd+Shift+F

Əsas fikirlər

  • Codable struktur JSON-dan JSONDecoder ilə oxunur və JSONEncoder ilə yazılır; artıq açarlar buraxılır.
  • try await URLSession.shared.data(from:) (Data, URLResponse) qaytarır və ekranı bloklamır.
  • Model @MainActor @Observable olur; .task yükləməni başladır və özü ləğv edir, .refreshable yeniləmə jesti verir.
  • SwiftData: @Model sinif, tətbiqdə .modelContainer(for:), görünüşdə @Query və modelContext.insert/delete.
  • Kiçik parametrlər @AppStorage-də, parollar Keychain-də saxlanır; yalnız HTTPS-dən istifadə et.

Özünü yoxla

10 sual. Hər düzgün cavab XP qazandırır.

1 / 10
try await URLSession.shared.data(from: url) nə qaytarır?