Η Swift είναι η γλώσσα προγραμματισμού της Apple που συνδυάζει τη δύναμη της Objective-C με την ασφάλεια των σύγχρονων γλωσσών. Σε αντίθεση με πολλές άλλες γλώσσες, η Swift προσφέρει μοναδικές κατασκευές: τύποι τιμής με αυτόματη αντιγραφή, Actors με απομόνωση σε επίπεδο μεταγλωττιστή, Property Wrappers για επαναχρησιμοποίηση λογικής ιδιοτήτων και Προγραμματισμός Προσανατολισμένος σε Πρωτόκολλα ως εναλλακτική της κλασικής κληρονομικότητας. Σύμφωνα με τα Έγγραφα Apple Swift, αυτές οι δυνατότητες καθιστούν τον κώδικα ασφαλέστερο και αποδοτικότερο.
Βασικά Σημεία
Στη Swift, το struct είναι τύπος τιμής, ενώ το class είναι τύπος αναφοράς. Αυτή η θεμελιώδης διαφορά καθορίζει τη συμπεριφορά κατά την ανάθεση, τη μεταβίβαση σε συναρτήσεις και τη διαχείριση μνήμης. Το struct αντιγράφεται εξ ολοκλήρου σε κάθε ανάθεση, το class μεταβιβάζεται μέσω αναφοράς — αντιγράφεται μόνο ο δείκτης προς το αντικείμενο στο σωρό.
Το struct υλοποιεί αυτόματα σημασιολογία τιμής — κάθε στιγμιότυπο είναι ανεξάρτητο. Η μεταβολή μιας ιδιότητας struct είναι δυνατή μόνο μέσω var και οι μέθοδοι που τροποποιούν ιδιότητες πρέπει να επισημαίνονται ως mutating. Το struct δεν υποστηρίζει κληρονομικότητα αλλά μπορεί να συμμορφώνεται σε πρωτόκολλα. Η τυπική βιβλιοθήκη της Swift χρησιμοποιεί struct για String, Array, Dictionary, Int, Bool — όλοι οι θεμελιώδεις τύποι είναι τύποι τιμής.
struct User {
let id: Int
var name: String
mutating func updateName(_ newName: String) {
name = newName
}
}
var user1 = User(id: 1, name: "Alice")
var user2 = user1
user2.updateName("Bob")
// user1.name === "Alice", user2.name === "Bob"
// Πλήρες αντίγραφο — ανεξάρτητα στιγμιότυπα
Το Class αποθηκεύεται στο σωρό και υποστηρίζει ισότητα αναφοράς. Πολλές μεταβλητές μπορούν να αναφέρονται στο ίδιο αντικείμενο, οι αλλαγές μέσω μιας αναφοράς είναι ορατές μέσω όλων των άλλων. Το Class υποστηρίζει κληρονομικότητα, επιτρέπει την παράκαμψη μεθόδων (override), έχει deinit για καθαρισμό πόρων. Στη Swift, το class χρησιμοποιείται λιγότερο συχνά από το struct — η Apple συνιστά να ξεκινάτε με struct και να μεταβαίνετε σε class μόνο όταν χρειάζεται κληρονομικότητα ή σημασιολογία αναφοράς.
class Car {
let model: String
var speed: Int
init(model: String, speed: Int) {
self.model = model
self.speed = speed
}
func accelerate(_ amount: Int) {
speed += amount
}
}
let car1 = Car(model: "Tesla", speed: 0)
let car2 = car1
car2.accelerate(50)
// car1.speed === 50, car2.speed === 50
// Ίδια αναφορά — οι αλλαγές ορατές παντού
Η επιλογή μεταξύ struct και class εξαρτάται από τη σημασιολογία των δεδομένων. Για διαμορφώσεις, μοντέλα δεδομένων και καταστάσεις χρησιμοποιήστε struct — η αντιγραφή είναι ασφαλέστερη και πιο προβλέψιμη. Για αντικείμενα με ταυτότητα (συνεδρία χρήστη, σύνδεση βάσης δεδομένων) και όταν χρειάζεται κληρονομικότητα, χρησιμοποιήστε class.
Actor είναι ένας τύπος αναφοράς που προστατεύει τη μεταβλητή του κατάσταση από ταυτόχρονη πρόσβαση. Ο μεταγλωττιστής Swift εγγυάται ότι οι μεταβλητές actor είναι προσβάσιμες μόνο από το ίδιο περιβάλλον actor — οποιαδήποτε εξωτερική πρόσβαση προκαλεί σφάλμα μεταγλώττισης. Αυτό αποτρέπει τις συνθήκες ανταγωνισμού σε επίπεδο γλώσσας, χωρίς χειροκίνητο κλείδωμα.
Όλες οι ιδιότητες και μέθοδοι του actor είναι απομονωμένες από προεπιλογή. Η κλήση μιας μεθόδου actor από έξω απαιτεί await — η Swift αλλάζει την εκτέλεση στον actor και πίσω. Ο actor συγχρονίζει την πρόσβαση σειριακά: τα αιτήματα από διαφορετικές εργασίες μπαίνουν σε ουρά. Αυτό εγγυάται συνεπή κατάσταση εντός του actor. Οι μη απομονωμένες μέθοδοι (nonisolated) μπορούν να κληθούν χωρίς await αλλά δεν μπορούν να έχουν πρόσβαση σε μεταβλητές ιδιότητες.
actor BankAccount {
private var balance: Double
init(initialBalance: Double) {
balance = initialBalance
}
func deposit(amount: Double) {
balance += amount
}
func withdraw(amount: Double) throws {
guard balance >= amount else {
throw BankError.insufficientFunds
}
balance -= amount
}
nonisolated func accountInfo() -> String {
return "Τραπεζικός Λογαριασμός"
}
}
let account = BankAccount(initialBalance: 1000)
await account.deposit(amount: 500)
// balance = 1500 — ατομικά, χωρίς συνθήκη ανταγωνισμού
Ο Actor είναι χρήσιμος για διαχειριστές κατάστασης, προσωρινές μνήμες και υπηρεσίες δικτύου όπου πολλές εργασίες διαβάζουν και γράφουν δεδομένα ταυτόχρονα. Σε αντίθεση με τον χειροκίνητο συγχρονισμό μέσω NSLock ή DispatchQueue, ο actor ελέγχεται κατά τη μεταγλώττιση — είναι αδύνατο να γίνει λάθος. Τα σφάλματα απομόνωσης εμφανίζονται κατά τη μεταγλώττιση, όχι κατά την εκτέλεση.
Property Wrapper είναι ένας σχολιασμός που προσθέτει συμπεριφορά σε μια ιδιότητα χωρίς να αλλάζει τη δήλωσή της. Το SwiftUI και το Combine παρέχουν ενσωματωμένους περιτυλιγμένους για τη διαχείριση κατάστασης προβολών. Ο μεταγλωττιστής παράγει κώδικα που τυλίγει την πρόσβαση στην ιδιότητα, προσθέτοντας λογική παρατήρησης, συγχρονισμού ή μετασχηματισμού.
@Published χρησιμοποιείται σε κλάσεις που συμμορφώνονται στο ObservableObject για αυτόματη δημοσίευση αλλαγών. Οι συνδρομητές Combine λαμβάνουν ειδοποιήσεις σε κάθε ενημέρωση. @State είναι η τοπική κατάσταση μιας προβολής SwiftUI. Η SwiftUI ανασχεδιάζει αυτόματα την προβολή όταν αλλάξει το @State. @Binding δημιουργεί μια αμφίδρομη σύνδεση μεταξύ μιας γονικής και θυγατρικής προβολής χωρίς να κατέχει τα δεδομένα.
import SwiftUI
import Combine
@main
struct CounterApp: App {
var body: some Scene {
WindowGroup {
ContentView()
}
}
}
class CounterViewModel: ObservableObject {
@Published var count: Int = 0
func increment() {
count += 1
}
}
struct ContentView: View {
@State private var greeting: String = "Γεια"
@StateObject private var viewModel = CounterViewModel()
var body: some View {
VStack {
CounterDisplay(count: $viewModel.count)
Text(greeting)
Button("Αύξηση", action: viewModel.increment)
}
}
}
struct CounterDisplay: View {
@Binding var count: Int
var body: some View {
Text("Μέτρηση: \(count)")
}
}
Οι προσαρμοσμένοι Property Wrappers δημιουργούνται χρησιμοποιώντας @propertyWrapper και μια δομή με πεδίο wrappedValue. Αυτό επιτρέπει την επαναχρησιμοποίηση λογικής: επικύρωση, κανονικοποίηση, αποθήκευση UserDefaults. Οι Property Wrappers μειώνουν σημαντικά τον επαναλαμβανόμενο κώδικα και καθιστούν την πρόθεση του προγραμματιστή ρητή μέσω σχολιασμού.
@propertyWrapper
struct Clamped<T: Comparable> {
private var value: T
let range: ClosedRange<T>
init(wrappedValue: T, range: ClosedRange<T>) {
self.range = range
self.value = min(max(wrappedValue, range.lowerBound), range.upperBound)
}
var wrappedValue: T {
get { value }
set { value = min(max(newValue, range.lowerBound), range.upperBound) }
}
}
struct Settings {
@Clamped(range: 0...100) var volume: Int = 50
}
var s = Settings()
s.volume = 150
// s.volume === 100 — η τιμή περιορίστηκε αυτόματα
Generics είναι ένας ισχυρός μηχανισμός της Swift για τη σύνταξη ευέλικτου κώδικα χωρίς απώλεια ασφάλειας τύπων. Οι γενικές συναρτήσεις και τύποι λειτουργούν με οποιονδήποτε τύπο που ικανοποιεί τους περιορισμούς. Ο Προγραμματισμός Προσανατολισμένος σε Πρωτόκολλα (POP) είναι ένα παράδειγμα Swift όπου το protocol + extension αντικαθιστούν την κλασική κληρονομικότητα κλάσεων, παρέχοντας σύνθεση συμπεριφοράς.
Η Swift επιτρέπει τον περιορισμό γενικών παραμέτρων με πρωτόκολλα (ρήτρες where). Πρωτόκολλο με συσχετιζόμενο τύπο είναι το γενικό ισοδύναμο για πρωτόκολλα: ο συγκεκριμένος τύπος καθορίζεται τη στιγμή της συμμόρφωσης. Αυτή είναι η βάση για ασφαλείς τύπων συλλογές και αλγόριθμους. Η τυπική βιβλιοθήκη Swift χρησιμοποιεί ενεργά τα Generics: Array, Dictionary, Optional — όλοι είναι γενικοί τύποι.
protocol Cacheable {
associatedtype Key: Hashable
associatedtype Value
func get(_ key: Key) -> Value?
mutating func set(_ value: Value, for key: Key)
}
struct MemoryCache<K: Hashable, V>: Cacheable {
private var storage: [K: V] = [:]
typealias Key = K
typealias Value = V
func get(_ key: K) -> V? {
return storage[key]
}
mutating func set(_ value: V, for key: K) {
storage[key] = value
}
}
func firstMatch<T: Equatable>(in array: [T], target: T) -> Int? {
for (index, item) in array.enumerated() {
if item == target {
return index
}
}
return nil
}
let numbers = [10, 20, 30, 40]
let index = firstMatch(in: numbers, target: 30)
// index === 2 — μία γενική μέθοδος για όλους τους τύπους Equatable
Η επέκταση πρωτοκόλλου είναι ένας βασικός μηχανισμός του POP. Σε μια επέκταση μπορεί να παρέχεται προεπιλεγμένη υλοποίηση των μεθόδων του πρωτοκόλλου. Οι τύποι που συμμορφώνονται στο πρωτόκολλο λαμβάνουν αυτήν την υλοποίηση δωρεάν και μπορούν να την παρακάμψουν εάν χρειαστεί. Αυτό επιτρέπει την κατασκευή ιεραρχιών συμπεριφοράς χωρίς κληρονομικότητα κλάσεων — μια εναλλακτική στην πολλαπλή κληρονομικότητα, που δεν υπάρχει στη Swift.
protocol Drivable {
var speed: Double { get set }
func drive()
}
extension Drivable {
func drive() {
if speed > 0 {
print("Οδήγηση με \(speed) km/h")
}
}
}
struct Bicycle: Drivable {
var speed: Double = 15
}
struct Plane: Drivable {
var speed: Double = 900
func drive() {
print("Πτήση με \(speed) km/h")
}
}
let bike = Bicycle()
bike.drive() // "Οδήγηση με 15.0 km/h" — προεπιλεγμένη
let plane = Plane()
plane.drive() // "Πτήση με 900.0 km/h" — παρακαμφθείσα
Ο POP με επέκταση επιτρέπει σύνθεση μέσω πολλαπλών πρωτοκόλλων — ένας τύπος μπορεί να συμμορφώνεται σε πολλά πρωτόκολλα, λαμβάνοντας τις υλοποιήσεις τους. Αυτό είναι πιο ευέλικτο από την απλή κληρονομικότητα κλάσεων όπου ολόκληρη η ιεραρχία είναι σταθερή. Η τυπική βιβλιοθήκη Swift είναι χτισμένη πάνω στο POP: CustomStringConvertible, Equatable, Hashable, Codable — όλα είναι πρωτόκολλα με επεκτάσεις.
Enum στη Swift είναι μια σημαντικά πιο ισχυρή κατασκευή από ό,τι σε άλλες γλώσσες. Κάθε περίπτωση μπορεί να έχει συσχετιζόμενες τιμές — ένα αυθαίρετο σύνολο δεδομένων οποιουδήποτε τύπου. Σε συνδυασμό με επέκταση και αντιστοίχιση προτύπων, το enum γίνεται η βάση για τη μοντελοποίηση καταστάσεων, αποτελεσμάτων λειτουργιών και πεπερασμένων μηχανών καταστάσεων.
Οι συσχετιζόμενες τιμές επιτρέπουν σε κάθε περίπτωση να αποθηκεύει μοναδικά δεδομένα. Για παράδειγμα, η περίπτωση loading δεν περιέχει δεδομένα, η success περιέχει το αποτέλεσμα, η failure περιέχει ένα σφάλμα. Η αντιστοίχιση προτύπων μέσω switch εξάγει τις συσχετιζόμενες τιμές και επεξεργάζεται κάθε περίπτωση. Ο μεταγλωττιστής ελέγχει την πληρότητα (exhaustive switch) — αυτό εξαλείφει τις ξεχασμένες καταστάσεις.
enum NetworkResult<T> {
case loading
case success(T)
case failure(Error)
}
enum MediaFile {
case image(url: URL, width: Int, height: Int)
case video(url: URL, duration: Double)
case audio(url: URL, bitrate: Int)
}
func handle(_ result: NetworkResult<String>) {
switch result {
case .loading:
print("Φόρτωση...")
case .success(let data):
print("Λήφθηκε: \(data)")
case .failure(let error):
print("Σφάλμα: \(error.localizedDescription)")
}
}
let file = MediaFile.video(url: URL(string: "https://example.com/video.mp4")!, duration: 120)
if case .video(let url, let duration) = file {
print("Βίντεο \(url) διάρκεια \(duration)s")
}
Η Swift επιτρέπει την προσθήκη μεθόδων, υπολογισμένων ιδιοτήτων και συμμόρφωσης σε πρωτόκολλα μέσω επέκτασης για enum. Αυτό δεν απαιτεί αλλαγή της αρχικής δήλωσης — ο κώδικας παραμένει καθαρός και αρθρωτός. Οι μέθοδοι στην επέκταση μπορούν να λειτουργούν με συσχετιζόμενες τιμές, να υπολογίζουν παράγωγα δεδομένα και να υλοποιούν επιχειρηματική λογική.
extension MediaFile {
var fileName: String {
switch self {
case .image(let url, _, _),
.video(let url, _),
.audio(let url, _):
return url.lastPathComponent
}
}
var description: String {
switch self {
case .image(_, let w, let h):
return "Εικόνα \(w)x\(h)"
case .video(_, let d):
return "Βίντεο \(d)s"
case .audio(_, let b):
return "Ήχος \(b) kbps"
}
}
}
extension MediaFile: Equatable {
static func == (lhs: MediaFile, rhs: MediaFile) -> Bool {
return lhs.fileName == rhs.fileName
}
}
let files: [MediaFile] = [file]
files.forEach { print($0.description) }
// "Βίντεο 120.0s"
Το Enum με συσχετιζόμενες τιμές + επέκταση είναι το υποκατάστατο των sealed κλάσεων από την Kotlin ή των διακριτικών ενώσεων από άλλες γλώσσες. Η Swift χρησιμοποιεί αυτόν τον συνδυασμό για Result (ένας τυπικός τύπος στην τυπική βιβλιοθήκη), Optional (το Optional είναι ένα enum με .none και .some) και χειρισμό κατάστασης στο SwiftUI. Η αντιστοίχιση προτύπων εγγυάται ότι όλες οι καταστάσεις έχουν υποστεί επεξεργασία — τα σφάλματα χρόνου εκτέλεσης είναι αδύνατα.
Συχνές Ερωτήσεις
Struct — τύπος τιμής, αντιγράφεται κατά την ανάθεση, δεν υποστηρίζει κληρονομικότητα. Class — τύπος αναφοράς, μεταβιβάζεται μέσω αναφοράς, υποστηρίζει κληρονομικότητα, deinit και έλεγχο ταυτότητας (===). Η Apple συνιστά να ξεκινάτε με struct και να μεταβαίνετε σε class μόνο όταν χρειάζεται σημασιολογία αναφοράς.
Actor — τύπος αναφοράς ασφαλής για νήματα. Ο μεταγλωττιστής απαγορεύει την ταυτόχρονη πρόσβαση σε μεταβλητή κατάσταση από διαφορετικά περιβάλλοντα. Χρησιμοποιήστε Actor για διαχειριστές κατάστασης, προσωρινές μνήμες και υπηρεσίες που προσπελαύνονται από πολλές εργασίες. Η κλήση μεθόδων actor απαιτεί await.
Κύρια: @State — τοπική κατάσταση προβολής; @Binding — σύνδεση δεδομένων χωρίς ιδιοκτησία; @Published — δημοσίευση αλλαγών στο ObservableObject; @StateObject — ιδιοκτησία ObservableObject; @EnvironmentObject — εξάρτηση από το περιβάλλον. Οι προσαρμοσμένοι περιτυλιγμένοι δημιουργούνται μέσω @propertyWrapper.
POP — ένα παράδειγμα όπου το protocol + extension καθορίζουν τη συμπεριφορά αντί για την κληρονομικότητα κλάσεων. Ένας τύπος συμμορφώνεται σε πολλά πρωτόκολλα, λαμβάνοντας τις υλοποιήσεις τους δωρεάν. Αυτή είναι σύνθεση αντί για κληρονομικότητα — πιο ευέλικτη και ασφαλής. Η τυπική βιβλιοθήκη Swift (Equatable, Codable) είναι χτισμένη πάνω στο POP.
Το enum στη Swift υποστηρίζει συσχετιζόμενες τιμές — κάθε περίπτωση μπορεί να αποθηκεύει αυθαίρετα δεδομένα διαφορετικών τύπων. Σε συνδυασμό με αντιστοίχιση προτύπων (switch), παρέχει ένα ισχυρό μοντέλο για καταστάσεις, σφάλματα και Σχεδιασμό Βασισμένο σε Τομέα. Το Optional και το Result είναι τυπικά enum της Swift με συσχετιζόμενες τιμές.
Περίληψη
Θα αναπτύξουμε μια εφαρμογή για κινητά έτοιμη για χρήση
Η IT Sectr δημιουργεί εφαρμογές iOS και Android για νεοφυείς επιχειρήσεις και επιχειρήσεις από το 2017. Θα σας συμβουλεύσουμε και θα προτείνουμε την καλύτερη λύση.