Swift: Closure

Opublikowano: 07.09.2023 - tagi: Swift Funkcje

Czym jest Closure w Swift?

Closure w Swift to funkcja, która nie posiada nazwy.

let myFunction = {
	print("Hello!")
}

myFunction() // Hello!

Składnia Closure

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

{ (parameters) -> returnType in
   ...
}

Tak jak w "zwykłej" funkcji, ta typu Closure może posiadać parametry i możesz określić zwracany typ danych.

let calculate = { (a: Int, b: Int) -> Int in
	return a + b
}

print(calculate(4, 3)) // 7

Zauważ, że w momencie wywołania funkcji typu Closure nie musimy podawać nazw parametrów, tak ja w "zwykłej" funkcji:

func substract(a: Int, b: Int) -> Int {
	return a - b
}

let add = { (a: Int, b: Int) -> Int in
	return a + b
}

print(add(4, 3)) // 7
print(substract(a: 10, b: 3)) // 7

print(substract(10, 3)) // błąd!

Closure jako parametr funkcji

Możesz przekazać do funkcji inną funkcję, która jest typu Closure:

let substract = { (a: Int, b: Int) -> Int in
	return a - b
}

let add = { (a: Int, b: Int) -> Int in
	return a + b
}

func calculate(a: Int, b: Int, operate: (_ a: Int, _ b: Int) -> (Int)) -> Int {
  return operate(a, b);
}

print(calculate(a: 5, b: 5, operate: add)) // 10
print(calculate(a: 5, b: 5, operate: substract)) // 0

Trailing Closure

Jeśli funkcja przyjmuje kilka parametrów i ostatnim z nich jest Closure można wywołać ją w inny sposób.

"Standardowa" wersja:

func calculate(a: Int, b: Int, operate: (_ a: Int, _ b: Int) -> (Int)) -> Int {
  return operate(a, b);
}

let result = calculate(a: 5, b: 5, operate: { (a: Int, b: Int) -> Int in
  return a + b
})

print(result) // 10

Można wywołać to też tak:

let result = calculate(a: 5, b: 5) { (a, b) in
  return a + b
}

print(result) // 10

Takie wywołanie nazwy się trailing closure.

Uproszczona składnia

Poniżej znajdziesz kilka sposobów wywołania Closure.

func calculate(a: Int, b: Int, operate: (_ a: Int, _ b: Int) -> (Int)) -> Int {
  return operate(a, b);
}

Przykłady wywołania Closure:

calculate(a: 5, b: 5, operate: { (a: Int, b: Int) -> Int in
  return a + b
})

Można krócej:

calculate(a: 5, b: 5) { (a, b) in
  return a + b
}

Inna wersja:

calculate(a: 5, b: 5) {
  a, b in a + b
}

Jeszcze prostsza wersja:

calculate(a: 5, b: 5) {
  $0 + $1
}

$0 i $1 to referencje do parametrów Closure.

Uwaga! Można jeszcze to zapisać jeszcze inaczej:

calculate(a: 5, b: 5, operate: +)

React Testing Library: Jak zasymulować wpisanie danych do kontrolki?

Opublikowano: 05.09.2023 - tagi: JavaScript React Testowanie Komponent Pisanie Formularz

Testowanie wprowadzenia danych

Kiedy potrzebujesz stestować komponent, w którym zachodzą interakcje z użytkownikiem, warto napisać test, który zasymuluje cały proces, tak jakby robił to użytkownik. Możesz to zrobić za pomocą biblioteki user-event

Przykład

Komponent:

export function MyComponent() {
    const [name, setName] = useState('');

    const onInputChange = e => {
        setName(e.target.value);
    }

    return (
        <>
            { name &&
                <div>
                    Hello {name}! How are you?
                </div>
            }
            <div>
                <label htmlFor="name">Your name:</label>
                <input value={name} id="name" name="name" onChange={onInputChange} />
            </div>
        </>
    )
}

Kiedy użytkownik wpisze jakieś dane w pole tekstowe, na ekranie wyświetlony zostanie tekst.

Jak to przetestować?

import {render, screen} from '@testing-library/react';
import userEvent from '@testing-library/user-event';

const { getByLabelText, getByText } = screen;

test('should display greeting', async () => {
    // given
    render(<MyComponent />);
    const inputName = getByLabelText('Your name:');

    // when
    await userEvent.type(inputName, 'Kacper');

    // then
    expect(getByText('Hello Kacper! How are you?')).toBeInTheDocument();
});

Za pomocą getByLabelText i getByText pobieramy referencje do pola tekstowego i miejsca, gdzie tekst ma się wyświetlić po wpisaniu danych.

Za pomocą type z biblioteki user-event wprowadzany jest tekst do pola tekstowego. Pierwszy argument to referencja do kontrolki, dla której wpisujemy dane. Drugi argument to tekst, który chcemy przekazać.


Podsumowanie: Sierpień 2023

Opublikowano: 31.08.2023 - tagi: Podsumowanie Sierpień Blog 2023

W sierpniu opublikowałem 6 wpisów:


React

  1. React Testing Library: Jak pobrać referencję do elementu w komponencie?
  2. React Testing Library: Jak zasymulować kliknięcie w element?

Swift

  1. Optional
  2. Funkcje
  3. Range

MongoDB

  1. Jak sprawdzić status?

Przeczytałem dwie książki:

  1. Ikigai - Francesc Miralles Contijoch, Hector Garcia Piugcerver
  2. The Art and Business of Online Writing - Nicolas Cole

Przesłuchałem cztery audiobooki:

  1. Studnia wstąpienia - Brandon Sanderson
  2. Oko Jelenia. Drewniana twierdza - Andrzej Pilipiuk
  3. Mindset - Dr Carol Dweck
  4. Cyberpunk 2077: Bez przypadku - Rafał Kosik

Swift: Range

Opublikowano: 29.08.2023 - tagi: Swift Zakres

Do czego służy Range?

Swift pozwala na tworzenie zakresu liczbowego od...do za pomocą operatora: ....

Przykład:

var range = 1...10

W tym przypadku stworzony został zakres od 1 do 10.

Zakres można wykorzystać na przykład w pętli:

for i in 1...10 {
	print(i)
}

Swift daje do ręki programiście trzy typy zakresu:

  1. Zamknięty
  2. Półotwarty
  3. Jednostronny

Zakres zamknięty

Ten typ zakresu określa jego początek i koniec. Wystarczy podać te dwie wartości:

a...b

Za pomocą first i last możesz dostać informację odpowiednio o początku i końcu zakresu:

var range = 1...10
print(range.first!) // 1
print(range.last!) // 10

Dzięki zakresowi możesz pobrać część danych ze zbioru danych:

let users = ["Greg", "Jane", "Spox", "Blue"]
let range  = 0...2
print(users[range]) // ["Greg", "Jane", "Spox"]

Zakres półotwarty

Zakres półotwarty określa zakres, który zawiera dane od a do b, ale bez b.

Składnia:

a..<b

Przykład:

var range = 1..<10
print(range.first!) // 1
print(range.last!) // 9

Zakres jednostronny

Jednostronny zakres definiuje wartość tylko z jednej strony zakresu:

a... lub ...b

Jeśli zdefiniujesz zakres tak:

let range = 10...

To zaczyna się od 10 i kończy na... plus nieskończoności (+∞)

Z kolei:

let range = ...10

Zaczyna się na minus nieskończoności (-∞) do 10.

Gdzie można taki zakres wykorzystać?

Na przykład w tablicach.

Załóżmy, że potrzebujesz pobrać wszystkie elementy tablicy, zaczynając od drugiego elementu:

let users = ["Greg", "Jane", "Spox", "Blue"]
print(users[1...]) // ["Jane", "Spox", "Blue"]

Albo, tylko pierwsze dwa elementy tablicy:

let users = ["Greg", "Jane", "Spox", "Blue"]
print(users[...1]) // ["Greg", "Jane"]

Swift: Funkcje

Opublikowano: 24.08.2023 - tagi: Swift Funkcje

Funkcje w Swift

Składania funkcji w Swift wygląda następująco:

func name() {
	...
}

Najpierw słowo kluczowe func, a następnie podajesz jej nazwę.

Wywołanie funkcji:

func helloWorld() {
	print("Hello World!)
}

helloWorld() // Hello World!

Parametry

Parametry funkcji są stałymi. To znaczy, że jeśli spróbujesz zmienić ich wartość, zostanie zgłoszony błąd.

Możesz zdefiniować parametry funkcji na różne sposoby:

Parametry z etykietą

func sayHello(name) {
	print("Hello \(name)!")
}

Jeśli tak deklarujesz parametry funkcji, musisz podać nazwę etykiety argumentu przy wywołaniu:

sayHello(name: "Anna")

Nie możesz napisać tak:

sayHello("Anna")

Parametry i typ danych

Możesz określić typ danych parametrów:

func calculate(a: Int, b: Int) {
	print(a + b)
}

Parametry bez etykiet

Jeśli nie chcesz używać etykiet dla argumentów, to przed nazwą parametru użyj _:

func sayHello(_ name: String) {
	print("Hello \(name)!")
}

sayHello("Anna") // Hello Anna!

Domyślne wartości parametrów

Do funkcji można przekazywać domyślne wartości:

func calculate(a: Int = 1, b: Int = 2) {
	print(a + b)
}

Wywołanie:

calculate() // 3

calculate(a: 9, b: 3) // 12

calculate(a: 8) // 10

Parametry typu in-out

Jak już wyżej wspomniałem: nie można zmieniać wartości parametrów w ciele funkcji. Jeśli spróbujesz to zrobić, zostanie zgłoszony błąd.

Jeśli jednak koniecznie potrzebujesz zmienić wartość parametru, oznacz go jako: inout. Dodatkowo w momencie wywołania funkcji przed nazwą argumentu należy użyć: &.

Przykład:

func increment(_ counter: inout Int) {
	counter += 1
}

var counter = 0

print(counter) // 0

increment(&counter)

print(counter) // 1

Parametr funkcja

Możesz do funkcji przekazać inną funkcję

func sum(_ a: Int, _ b: Int) {
	print(a + b)
}

func substract(_ a: Int, _ b: Int) {
	print(a - b)
}

func calculate(_ operation: (Int, Int) -> Void, _ a: Int, _ b: Int) {
	operation(a, b)
}

calculate(sum, 1, 1) // 2
calculate(substract, 1, 1) // 0

Zwracanie danych

Możesz określić typ danych, jaki zwraca funkcja za pomocą: return. Dzięki -> określasz typ zwracanych danych.

Zwracania jednej wartości

func calculate(a: Int, b: Int) -> Int {
	return a + b
}

let result: Int = calculate(a: 5, b: 7)

print(result) // 12

Zwracanie wielu wartości

Swift pozwala na zwrócenie wielu wartości. Taka funkcja zwróci krotkę (ang.: Tuple).

func prepare(_ name: String) -> (greet: String, goodbye: String) {
	let greet: String = "Hello \(name)!"
	let goodbye: String = "Bye \(name)!"
	
	return (greet, goodbye)
}

let result = prepare("Marcin")

print(result.greet) // Hello Marcin!
print(result.goodbye) // Bye Marcin!

Zwracanie funkcji

Możesz zwrócić funkcję jako wynik działania innej funkcji.

func sum(_ a: Int, _ b: Int) -> Int {
	return a + b
}

func substract(_ a: Int, _ b: Int) -> Int {
	return a - b
}
	
func defaultOperate(_ a: Int, _ b: Int) -> Int {
	return 0
}
	
func prepare(_ op: String) -> (Int, Int) -> Int {
	if op == "+" {
	    return sum
	} else if op == "-" {
	    return substract
	}
	
	return defaultOperate
}

let sumOperate = prepare("+")
print(sumOperate(0, 1)) // 1

let substractOperate = prepare("-")
print(substractOperate(0, 1)) // -1

Zagnieżdżone funkcje

Swift pozwala także na zagnieżdżone funkcje:

func calculate(_ a: Int, _ b: Int, _ op: String) -> Int {
  func sum(_ a: Int, _ b: Int) -> Int {
	  return a + b
  }

  func substract(_ a: Int, _ b: Int) -> Int {
	  return a - b
  }
	
  if op == "+" {
   return sum(a, b)
  } else if op == "-" {
    return substract(a, b)
  }
	
  return 0
}

print(calculate(0, 1, "+")) // 1

print(calculate(0, 1, "-")) // -1