CodeGym /Kurslar /Swift SELF /Optional chaining ?. ve nil-coalescing ??

Optional chaining ?. ve nil-coalescing ??

Swift SELF
Səviyyə, Dərs
Mövcuddur

1. Optional chaining ?.

Reel koddə optional çox vaxt proqramçının darixdiği ve heyatini çətinləşdirmek istədiyi üçün yox, dünya məlumatı təmin etmediyi üçün yaranır. İstifadəçi setr daxil etmemiş ola biler. Lüğətdə acar olmaya biler. Siyahi bos ola biler. Məhz burada böyle bir gündelik vazife yaranır: “men xususiəti goturmek ve ya metodu çağırmaq isteyirem, amma yalnız deyer doğrudan varsa”.

Optional chaining ?. — çox nəzakətli bir muhafizəçi kimidir: “zəncirboyu yalnız doğrudan gələnleri buraxacağam”. Sol terefdə nil varsa, zəncir qırılmır, qışqırmır ve fatalError-u çağırmır, sadəcə sakitcə nil qaytarır.

Ən çox ?. o hallarda istifadə olunur ki, nil üçün ayri böyük mentiq yazmağa ehtiyac olmur. Bizə kifayet edir ki, netice də nil olsun ve biz ya boşluğu göstərək, ya da defoltu ?? ilə yerləşdirək.

Optional daxilində xassələr ve metodlar

Optional chaining sadə bir şeydən başlayır: bizdə optional var ve onun xassəsinə və ya metoduna muraciət etmək isteyirik. if let yaza bilərdik, amma bəzən bu, “bir stəkan su tökmək üçün çadır qurmaq” kimi hiss olunur. Böyle kiçik əməliyyatlarda ?. kodu qısaldır və onu təhlükeli !-ə çevirmir.

Məsəl: optional daxilində xassə

import Foundation

let maybeText: String? = "Swift 6.2"

let length: Int? = maybeText?.count
print(length as Any) // Optional(9)

Burada maybeText — optional-dir. Adi sətirdə count xassəsinin tipi Int-dir, amma ?. səbəbindən netice Int? olur. Bu vacib məqamdir: biz “deyeri zorla goturmuruq”, deyirik: “əgər setr varsa — uzunluğu ver, yoxdursa — nil qaytar”.

Məsəl: optional daxilində metod çağırışı

import Foundation

let maybeName: String? = "alice"

let upper: String? = maybeName?.uppercased()
print(upper as Any) // Optional("ALICE")

Əgər maybeName == nil-dirsə, onda upper sadəcə nil olacaq. Metod ümumiyyətlə çağırılmır — sanki siz heç cəhd etmemisiniz.

Bir neçe addimli zəncirlər ve domino effekti

Tədris nümunələrində optional çox vaxt “bir dəyişən, bir əməl” kimi görünür. Həyatda isə daha maraqlıdır: məlumatlar iç-içədir, obyektin özü də optional olan bir sahəsi var ve sonra klassik “mən sadəcə istifadəçinin şeherini göstərmək istəyirdim, amma 5 səviyyə nil oldu” hekayəsi başlayır.

Optional chaining məhz belə hallarda çox yaxşı işləyir: siz “addım-addım” zəncir qurursunuz ve haradasa nil baş verərsə, bütün zəncir səliqəli şəkildə nil-ə çevrilir. Partlayış yoxdur. Dram yoxdur.

Məsəl: iç-içə strukturlar

import Foundation

struct Publisher {
    let name: String
}

struct Book {
    let title: String
    let publisher: Publisher?
}

let book = Book(title: "Swift for people", publisher: Publisher(name: "Acme Books"))

let publisherNameUpper: String? = book.publisher?.name.uppercased()
print(publisherNameUpper as Any) // Optional("ACME BOOKS")

Burada publisher optional-dir. Biz book.publisher?.name edirik ve yalnız sonra uppercased()-i çağırırıq. Əgər publisher == nil-dirsə, onda name-i oxumuruq ve uppercased() çağırılmır.

Qisa düşüncə sxemi

flowchart TD
    A[book.publisher] -->|əgər .some| B[name]
    A -->|əgər nil| N[nil]
    B --> C["uppercased()"]
    C --> R[nəticə String?]
    N --> R

Fikir sadədir: ?. “panikasiz qollanma” kimidir. Heç bir “unexpectedly found nil” yoxdur, sadece nəticənin proqnozlaşdırılan şəkildə nil-ə çevrilməsi var.

Lüğətlərlə optional chaining

Lüğətlər Swift-də optional-un əsas mənbələrindən biridir, çünki acar üzrə muraciət (dict[key]) Value? qaytarır: element olmaya biler. Bu, “Swift-in narahatçılığı” yox, reallığın düz modelidir.

Məhz burada optional chaining çox praktik olur: biz deyer varsa, dərhal onun üzərində nəsə edə bilərik.

Məsəl: başlığı götür və çevir

import Foundation

let headers: [String: String] = ["Content-Type": "application/json"]

let ctUpper: String? = headers["Content-Type"]?.uppercased()
print(ctUpper as Any) // Optional("APPLICATION/JSON")

Əgər acar yoxdursa, headers["Content-Type"] nil verəcək, zəncir dayanacaq, ctUpper nil olacaq.

Məsəl: lüğətdəki deyeri ?. ilə dəyişmək

Diqqətlə baxın: bu, gözəl görünür, amma “səssizcə heç nə etməyə bilər”.

import Foundation

var tagsByBookID: [Int: [String]] = [:]

tagsByBookID[10]?.append("swift")
print(tagsByBookID) // [:]

Niyə lüğət boş qaldı? Çünki tagsByBookID[10] nil qaytardı (acar yoxdur) ve ?.append(...) sadəcə çağırılmadı. Bu davranış lüğətdə optional chaining vasitəsilə deyerin dəyişdirilməsinə çalışan nümune ilə çox oxşardır.

Böyle kod qəza vermir (və bu, üstünlükdür), amma mentiq tələsinə çevrilə bilər: siz “tag əlavə edirəm” deyə düşünürdünüz, əslində isə “əgər massiv artıq varsa — əlavə edirəm, yoxdursa — səssizcə ignore edirəm”.

?. üçün tip qaydasının əsası

Optional chaining ilə bir şeyi “əlifba kimi” öyrənmək yaxşıdır, yoxsa IDE göstərişlərində tiplərə görə tez-tez təəccüblənəcəksiniz. Qayda belə səslənir: əgər istifadə etmisinizsə ?., ifadənin nəticəsi optional olur.

Hətta sağdakı xassənin/metodun tipi optional olmasa da (məsələn, count: Int), chaining nəticəni optional ilə “örtür”, çünki əməliyyatın özü baş verməyə bilər.

Bununla belə vacib bir incəlik var: optional chaining sağ tərəfdə onsuz da optional alınan zaman “artıq iç-içəlik” yaratmamağa çalışır. Başlanğıc səviyyədə praktik olaraq bunu yadda saxlamaq kifayətdir: ?. demək olar ki, həmişə nəticənin ...? olduğunu bildirir

2. ?? operatoru

Optional chaining çox vaxt “necə yıxılmamaq” sualına cavab verir, amma “sonra nə etmək lazımdır” sualına cavab vermir. Çünki sonda sizdə hələ də optional qalır. Məhz burada ?? — nil-coalescing operator — ortaya çıxır.

O, insan dilində belə oxunur: “sol tərəfdə deyer varsa — onu götür, əks halda sağdakı defoltdan istifadə et”.

Məsəl: default sətir

import Foundation

let maybeCity: String? = nil

let city: String = maybeCity ?? "Unknown"
print(city) // Unknown

Nəticənin tipi optional deyil, çünki biz defolt ilə deyerə zəmanət verdik.

?? ilə bağlı vacib tip qaydası

??-nin dəmir tələbi var: sağ tərəfdəki tip soldakı “açılmış” tipe uyğun olmalıdır.

Yəni solda Int? varsa, sağda Int olmalıdır, "0" yox, 0.0 da yox.

import Foundation

let maybeCount: Int? = nil
let count = maybeCount ?? 0
print(count) // 0

Əgər başqa tipdə defolt qoysanız, kompilyator sizi dayandıracaq (və düz də edəcək).

3. ?. + ?? kombosu

Ən tipik “günün kombinasiyası” belə görünür: biz yıxılmamaq üçün chaining edirik, sonra isə işləmək üçün rahat normal (yəni optional olmayan) deyer almaqdan ötrü ?? qoyuruq.

Bu, gündəlik vəziyyətə bənzəyir: mağazanın açıq olub-olmadığını yoxladınız (?.), əgər bağlı idisə, başqasına getdiniz (??). Sonda yenə çörəklə qayıdırsınız, sadəcə bəzən başqa yerdən.

Məsəl: istifadəçi daxil etməsini artıq if-siz oxuyuruq

readLine() String? qaytarır. Deməli, sətrin istənilən işlənməsini ?. ilə edə bilərik, sonra isə defoltla bağlaya bilərik.

import Foundation

let raw: String? = readLine()

let name: String = raw?.trimmingCharacters(in: .whitespacesAndNewlines) ?? "Qonaq"
print("Salam, \(name)!") // məsələn: Salam, Qonaq!

Əgər readLine() nil qaytardısa, trimmingCharacters çağırılmayacaq və biz "Qonaq" yerləşdirəcəyik.

Məsəl: uzunluğu götürürük, amma Int? yox, Int istəyirik

import Foundation

let maybeText: String? = nil

let length: Int = maybeText?.count ?? 0
print(length) // 0

count xassəsi Int qaytarır, amma chaining Int? verir və ?? 0-ı bunu adi Int-ə çevirir.

Məsəl: lüğətlə

import Foundation

let headers: [String: String] = [:]

let contentType: String = headers["Content-Type"]?.uppercased() ?? "-"
print(contentType) // -

Oxunaqlı alındı: “Content-Type-ı götür, böyük hərfə çevir, alınmadısa — defolt qoy”.

4. Oxunaqlılıq

Optional chaining asanlıqla “18 naqilli elektrik xətti”nə çevrilə bilər, xüsusən də hər şeyi bir sətirdə etməyə çalışdığınızda. İlk vaxtlar bu hətta xoş görünür: “baxın, Swift nə qədər bacarıqlıdır!”. Sonrakı mərhələdə kodu başqa insanlar oxuyur və onlarda çox insani bir sual yaranır: “bunu... bir az sadələşdirmək olar?”

Yaxşı qayda budur: əgər zəncirdə iki-üçdən çox addım varsa, onu aralıq dəyişənlərə bölməyi düşünün. Bu, kodu daha az “Swift” etmir, onu daha oxunaqlı edir.

Bir sətirdə çox uzundur

import Foundation

let result: String = readLine()?
    .trimmingCharacters(in: .whitespacesAndNewlines)
    .lowercased()
    .replacingOccurrences(of: " ", with: "_")
    ?? "empty"

print(result) // məsələn: "hello_world" və ya "empty"

Bunu yazmaq olar, amma başlanğıc səviyyədə olan biri üçün (və yorğun insan üçün) chaining-in harada bitdiyini və defoltun harada başladığını gözlə tutmaq çətindir.

Addımlara bölürük

import Foundation

let raw = readLine()
let trimmed = raw?.trimmingCharacters(in: .whitespacesAndNewlines)
let normalized = trimmed?.lowercased()

let result = normalized ?? "empty"
print(result)

Bəli, sətrlər daha çoxdur. Amma hər addımı yoxlamaq daha asandır və debug etmək də rahatdır.

Bir də vacib məqam: əgər nil-ə reaksiya “defolt yerləşdir” yox, “istifadəçiyə səhv göstər, funksiyadan çıx, alternativ ssenari qur” isə, chaining narahat ola bilər. Böyle hallarda yenidən if let / guard let-ə qayıdırıq: bunlar “boru transformasiyası”ndan çox “şaxələnmə”ni daha yaxşı ifadə edir.

5. Nümunə: kitab kartının səliqəli çıxışı

?. və ?? “operator xatirinə operator” kimi qalmasın deyə, onları kitabxana haqqında konsol tətbiqimizin kiçik hissəsində tətbiq edək. Biz mürəkkəb komanda parse-i etməyəcəyik, sadəcə bir məqsədə fokuslanacağıq: sahələrin bir hissəsi yoxdursa belə, məlumatı gözəl göstərmək.

Təsəvvür edək ki, bizdə Book modeli var, burada müəllif ve il məlum olmaya bilər.

import Foundation

struct Book {
    let title: String
    let author: String?
    let year: Int?
}

func printBookCard(_ book: Book) {
    let author = book.author ?? "Müəllif məlum deyil"
    let yearText = book.year.map { String($0) } ?? "İl məlum deyil"
    print("— \(book.title) | \(author) | \(yearText)")
}

Burada map istifadə olunduğunu gördünüzsə, haqlısınız: bu, optional üzərində çevirmələr mövzusuna keçid körpüsüdür. Amma indi belə əsas fikir vacibdir: ?? defoltu istifadə yerinə yaxın qoymağa imkan verir, optional isə bütün yol boyunca təhlükəsiz qalır.

Əgər map hələ “çox ağıllı” görünürsə, bu mərhələdə ümumi prinsipi kifayət saymaq olar: chaining (?.) — təhlükəsiz muraciət üçün, ?? — defoltlar üçün, daha mürəkkəb çevirmələri isə müvəqqəti olaraq if let ilə etmək olar (və ya növbəti mövzuya saxlamaq olar).

6. ?. və ?? ilə işləyərkən tipik səhvlər

Səhv №1: ?.-dən sonra non-optional alınacağını gözləmək.
Çox yayılmış vəziyyətdir: tələbə let n: Int = text?.count yazır ve kompilyator xətasına təəccüblənir. Burada dilin mentiqi sərtdir: əgər solda nil mümkündürsə, nəticə də mümkün olaraq nil olur. Buna görə ya nəticəni Int? kimi saxlayırıq, ya da ?? ilə defolt bağlayırıq.

Səhv №2: ??-də “başqa məna” daşıyan defolt yerləşdirmək, nil ilə boşluğu qarışdırmaq.
Bəzən hər yerdə let name = maybeName ?? "" yazmaq istənilir. Amma "" — “deyer var, sadəcə boş sətirdir”, nil isə — “deyer yoxdur” deməkdir. Əgər bu halları qarışdırırsınızsa, tətbiqin mentiqi dumanlı olur: artıq “istifadəçi heç nə daxil etmədi” ilə “istifadəçi boş sətir daxil etdi”ni ayırd edə bilmirsiniz. Bəzən bu normaldır, amma çox vaxt bu, səssiz bir mentiq səhvidir.

Səhv №3: dict[key]?.append(...) istifadə edib açarın özü-özünə yaranacağını gözləmək.
Lüğətdə optional chaining mutasiyanı çox kompakt edir, amma bu yalnız deyer artıq mövcud olduqda işləyir. Gördüyümüz kimi, tagsByBookID[10]?.append("swift") ümumiyyətlə heç nə etməyə bilər. Bu, Swift-də bug deyil, ?. qaydasına hərfi əməl etməkdir: “əgər solda nil varsa — sus”. Əgər sizə “yoxdursa yarat” lazımdırsa, vəziyyəti açıq şəkildə işləməlisiniz (məsələn, artıq bu API-dən keçmisinizsə, dict[key, default: []].append(...) vasitəsilə).

Səhv №4: gözəllik xatirinə uzun zəncirlər qurmaq və sonra onların içində itmək.
?. uzun ifadələr yazmağa imkan verir, amma bu, həmişə belə etməli olduğunuz demək deyil. Zəncir vizual olaraq ağırlaşarsa, onu addımlara bölüb aralıq dəyərlərə ad vermək daha yaxşıdır. IDE və gələcəkdəki siz buna görə təşəkkür edəcəksiniz.

Səhv №5: guard let-i ?? ilə əvəz etməyə çalışmaq, halbuki lazımi şey məhz erkən çıxışdır.
?? defolt doğrudan da normal olduqda yaxşıdır. Amma nil ssenari xətasıdırsa (“istifadəçisiz davam etmək olmaz”, “id-siz qeyd yenilənə bilməz”), o zaman defolt yerləşdirmək problemi gizli bug-a çevirir. Böyle yerlərdə funksiyadan guard let vasitəsilə dürüstcə çıxmaq və “heç nə olmamış kimi” davam etməmək daha düzgündür.

1
Tapşırıq
Swift SELF, səviyyə, dərs
Bağlanıb
Simvol sayıcı
Simvol sayıcı
1
Tapşırıq
Swift SELF, səviyyə, dərs
Bağlanıb
Sorğu başlığı
Sorğu başlığı
1
Tapşırıq
Swift SELF, səviyyə, dərs
Bağlanıb
Sifarişdə ünvan
Sifarişdə ünvan
1
Tapşırıq
Swift SELF, səviyyə, dərs
Bağlanıb
Kitablara görə teqlər
Kitablara görə teqlər
Şərhlər
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION