Swift: Protokoły

Opublikowano: 09.11.2023 - tagi: Swift Klasa Struktura

Protokoły w Swift

W Swift protokoły działają w podobny sposób, jak interfejsy w innych językach programowania.

Za pomocą protokołu możesz rozszerzyć swoją klasę, strukturę lub enumerację o nowe właściwości i metody.

W przypadku klasy jest to przydatne, ponieważ klasę w Swift można dziedziczyć, tylko po jednej klasie.

Jeśli chodzi o struktury, tutaj jest większy problem. Struktury nie można rozszerzyć poprzez dziedziczenie. W tym przypadku ten problem rozwiązują protokoły.

Definicja protokołu

Protokół stworzysz za pomocą słowa kluczowego protocol:

protocol Fly {
    var maximumSpeed: Int { get }
	
    func fly()
}

Ponieważ protokół jest typem, jego nazwa musi zaczynać się od dużej litery.

W protokole umieszcza tylko się definicje właściwości i metod.

Składnia deklaracji właściwości wygląda tak:

var someProperty: Type { get set }

get i set służą do poinformowania, że dana zmienna jest tylko do: odczytu (get), zapisu (set) lub zarówno do odczytu jak i zapisu (get set).

Jeśli oznaczysz właściwość jako get i set:

protocol Fly {
  var maximumSpeed: Int { get set }
}

I zastosujesz ten protokół, w taki sposób, że ta właściwość będzie computed property, zostanie zgłoszony błąd:

struct Bird: Fly {
    var energy: Int = 100
    var maximumSpeed: Int {  // Błąd!
        return energy + 50
    }
}

Jest tak dlatego, bo maximumSpeed zostało oznaczone jako set, a do computed property nie można przypisać wartości.

Implementacja protokołu

Zastosowanie protokołu wygląda tak samo, jak dziedziczenie:

class Bird: Fly {
	var maximumSpeed: Int = 50
	
	func fly() {
		print("Bird is flying with maximum speed: \(maximumSpeed)")
	}
}

class Plane: Fly {
	var maximumSpeed: Int = 1000
	
	func fly() {
		print("Plane is flying with maximum speed: \(maximumSpeed)")
	}
}

var bird = Bird()
bird.fly() // Bird is flying with maximum speed: 50

var plane = Plane()
plane.fly() // Plane is flying with maximum speed: 1000

Wiele protokołów

Swift pozwalana na zastosowanie wielu protokołów:

protocol Fly {	
	func fly()
}

protocol Eat {
	func eat()
}

class Bird: Fly, Eat {	
	func fly() {
		print("Bird is flying")
	}
	
	func eat() {
		print("Bird is eating")
	}
}

var bird = Bird()
bird.fly() // Bird is flying 
bird.eat() // Bird is eating

Dziedziczenie protokołów

Możesz także dziedziczyć po... innym protokole:

protocol A {
	func doA()
}

protocol SomeProtocol: A {
	func doSomething()
}

class MyClass: SomeProtocol {
	func doA() {
		print("Call doA")
	}
	
	func doSomething() {
		print("Call doSomething")
	}
}

Istnieje możliwość dziedziczenia po wielu protokołach:

protocol A {
	func doA()
}

protocol SomeProtocol: A {
	func doSomething()
}

class MyClass: SomeProtocol {
	func doA() {
		print("Call doA")
	}
	
	func doSomething() {
		print("Call doSomething")
	}
}

Struktury i protokoły

Jeśli zamierzasz zastosować protokół do struktury, a ten posiada metodę, której implementacja w strukturze będzie działać na zasadzie zmiany stanu struktury, musisz użyć dla tej metody słowa kluczowego mutating.

Przykład:

protocol Counter {
    var counter: Int { get }
	mutating func increment()
	mutating func decrement()
}
struct MyCounter: Counter {
	var counter = 0
	
	mutating func increment() {
		counter += 1
	}
	
	mutating func decrement() {
	    counter -= 1
	}
}
var myCounter = MyCounter()
myCounter.increment()
myCounter.increment()
myCounter.decrement()

print("Counter: \(myCounter.counter)")

Comics: Coding rules

Opublikowano: 04.11.2023 - tagi: Komiks Rysowanie Kod

Coding rules

Swift: Klasy

Opublikowano: 02.11.2023 - tagi: Swift Klasa

Składnia

Klasę w Swift tworzy się za pomocą słowa kluczowego class:

class MyClass {
	var property1: String = ""
	var property2: Int = 0
}

Tworzenie obiektów

Stworzenie instancji klasy wygląda tak:

var myObject = MyClass()

Dostęp do właściwości

Za pomocą . możesz dostać się do właściwości obiektów:

myObject.property1 = "Hello"
myObject.property2 = 7

print(myObject.property1) // Hello
print(myObject.property2) // 7

Metody

Do klasy można dodać także funkcje:

class User {
	var firstName: String = ""
	var lastName: String = ""
	
	func getFullName() -> String {
	  return "\(firstName)  \(lastName)"
    }
}

var user = User()
user.firstName = "Jan"
user.lastName = "Kowalski"

print(user.getFullName()) // Jan Kowalski

Metody statyczne

Możesz dodać do klasy metodę statyczną:

class MyClass {
	static func sumUp(_ data: [Int]) -> Int {
	  return data.reduce(0, +)
	}
}

let result: Int = MyClass.sumUp([1, 2, 3])

print(result) // 6

Do metody statycznej można odwołać się tylko za pomocą nazwy klasy.

Konstruktor

Tak samo jak w strukturach możesz za pomocą init przekazać dane dla tworzonej instancji:

class User {
	var firstName: String
	var lastName: String
	
	init(firstName: String, lastName: String) {
	  self.firstName = firstName
	  self.lastName = lastName
	}
	
	func getFullName() -> String {
	  return "\(firstName)  \(lastName)"
	}
}

var user = User(firstName: "Jan", lastName: "Kowalski")

print(user.getFullName()) // Jan Kowalski

Dziedziczenie

Swift pozwala także na dziedziczenie z innej klasy.

class BaseClass {
	var propA: Int = 0
	
	func doSomething1() {
		print("Call doSomething1 from BaseClass")
	}
}

class SubClass: BaseClass {
	var propB: String = ""
	
	func doSomething2() {
		print("Call doSomething1 from SubClass")
	}
}

var object = SubClass()

object.propA = 7
object.propB = "Some data"

object.doSomething1() // Call doSomething1 from BaseClass
object.doSomething2() // Call doSomething1 from SubClass

Klasa pochodna dziedziczy wszystkie właściwości i metody po klasie bazowej.

W Swift dziedziczyć można tylko po jednej klasie.

Nadpisanie metody rodzica

Jeśli klasa potomna używa takiej samej metody jak klasa bazowa (rodzic), to musisz użyć słowa kluczowego override.

class Shape {
	func area() -> Double {
	  return 0.0
	}
}

class Rectangle: Shape {
    var a: Double = 0.0
    var b: Double = 0.0

    init(a: Double, b: Double) {
        self.a = a
        self.b = b
    }
    
	
    override func area() -> Double {
        return a * b
    }
}

class Square: Shape {
    var a: Double = 0.0
	
    init(a: Double) {
        self.a = a
    }

	override func area() -> Double {
	  return a * a
	}
}

var rect = Rectangle(a: 7.0, b: 5.0)
print(rect.area()) // 

var square = Square(a: 10.0)
print(square.area())

Nadpisanie właściwości klasy

Jeśli klasa bazowa ma computed property możesz nadpisać ją w klasie potomnej.

class Product {
	var price: Double {
	  return 0.0
	}
}

class ProductA: Product {
	override var price: Double {
	  return 500.0
	}
}

class ProductB: Product {
	override var price: Double {
	  return 1000.0
	}
}

var productA = ProductA()
var productB = ProductB()

print(productA.price) // 500.0
print(productB.price)// 1000.0

Możesz nadpisać także property observers.

Uwaga: Nie można nadpisać "zwykłych" właściwości klasy:

class A {
  var counter = 1
}

class B: A {
  override var counter = 2    // Błąd!
}

Wywołanie metody rodzica

Jeśli potrzebujesz wywołać w metodzie podklasy metodę należącą do rodzica, użyj słowa kluczowego super.

class ParentClass {
  func doSomething() {
    print("ParentClass doSomething")
  }
}

class ChildClass: ParentClass {
  override func doSomething() {
    super.doSomething()
    print("ChildClass doSomething")
  }
}

var childObject =  ChildClass()
childObject.doSomething()
// ParentClass doSomething
// ChildClass doSomething

Za pomocą super można też dostać się do właściwości klasy.

Zapobieganie nadpisaniu

Jeżeli nie chcesz, żeby właściwość lub metoda klasy były nadpisane użyj słowa kluczowego final.

class ParentClass {
  final func doSomething() {
    print("ParentClass doSomething")
  }
}

class ChildClass: ParentClass {
  override func doSomething() {
    super.doSomething()
    print("ChildClass doSomething")
  }
}

var childObject =  ChildClass()
childObject.doSomething()

Przy takiej próbie zostanie zgłoszony błąd. final możesz użyć także do właściwości klasy.

Klasa to typ referencyjny

Każdy obiekt klasy to typ referencyjny. To znaczy, że zmienna, do której przypisany jest obiekt, zawiera referencję do instancji, a nie samą instancję.

Przykład:

class User {
	var firstName: String
	var lastName: String
	
	init(firstName: String, lastName: String) {
	  self.firstName = firstName
	  self.lastName = lastName
	}
	
	func getFullName() -> String {
	  return "\(firstName)  \(lastName)"
    }
}

var user1 = User(firstName: "Jan", lastName: "Kowalski")

print(user1.getFullName()) // Jan Kowalski

var user2 = user1

print(user2.getFullName()) // Jan Kowalski

user1.firstName = "Grażyna"
user1.lastName = "Kowalska"

print(user2.getFullName()) // Grażyna Kowalska

Do zmiennej user2 przypisany został obiekt ze zmiennej user1. A tak naprawdę do user2 została przypisana referencja do obiektu, którą przechowuje user1. Więc zmiana danych poprzez user1 spowoduje, że user2 będzie wskazywać na te same dane.


Podsumowanie: Październik 2023

Opublikowano: 31.10.2023 - tagi: Blog Podsumowanie Październik 2023

We październiku opublikowałem 7 wpisów:


React

  1. React Testing Library: Jak mockować requesty?
  2. React Testing Library: Within
  3. React Testing Library: Jak sprawdzić kolejność wyświetlanych elementów na liście?
  4. React Testing Library: Jak pisać łatwe w utrzymaniu testy?

JavaScript

  1. Jak pobrać n pierwszych elementów tablicy?

Swfit

  1. Computed property
  2. Property Observers

Przeczytałem trzy książki:

  1. Facet jak młody bóg — Tadeusz Oleszczuk
  2. Outpost — Dmitry Glukhovsky
  3. Wtorki z Morriem — Mitch Albom

Przesłuchałem dwa audiobooki:

  1. Strach — Jozef Karika
  2. Bohater wieków — Brandon Sanderson

Swift: Property Observers

Opublikowano: 26.10.2023 - tagi: Swift Atrybut Zmienna

Czym są Property Observers?

Swift pozwala na obserwowanie ewentualnych zmian przypisywanych do zmiennej. Dzięki property observers możesz zareagować na przypisanie wartości zmiennej tuż przed lub zaraz *po.

Możesz użyć:

  1. willSet — Zostanie wywołane zaraz przed przypisanie nowej wartości
  2. didSet — Zostanie wywołane tuż po przypisaniu wartości

Składania prezentuje się następująco:

var someProperty: Int = 0 {
    willSet {
        print("New value: \(newValue)")
    }
    didSet {
       print("Old value: \(oldValue)")
    }
}

Zarówno willSet jak i didSet przyjmują domyślnie parametry:

  1. Dla willSet jest to newValue — jest to wartość, która ma zostać przypisana do zmiennej.
  2. Dla didSet jest to oldValue — jest to poprzednia wartość, którą zawierała ta zmienna.

Możesz też sam określić nazwy tych parametrów:

var someProperty: Int = 0 {
    willSet(valueToSet) {
        print("New value: \(valueToSet)")
    }
    didSet(previousValue) {
       print("Old value: \(previousValue)")
    }
}

Przykład

struct CaloriesCounter {
    var totalCalories: Int = 0 {
        willSet(newTotalCalories) {
            print("Total calories is \(newTotalCalories)")
        }
        didSet {
            if totalCalories > oldValue  {
                print("Added \(totalCalories - oldValue) calories")
            }
        }
    }
}

Warto wiedzieć

Jeśli oznaczysz zmienną jako property observer:

  1. Nie może to być stała let, tylko var.
  2. Musi zawierać wartość domyślną. Jeśli nie wiesz, co przypisać przypisz nil. Możesz też oznaczyć zmienną jako optional — wtedy domyślnie zostanie przypisane nil do zmiennej.
  3. Nie możesz użyć tego wspólnie z computed property. Ponieważ computed property nie przechowuje wartości.
  4. Nie musisz używać willSet i didSet razem. Jeśli potrzebujesz tylko jednej z nich, użyj tylko tej.