Czym jest Swift jako „@synchronized” w Objective-C?


231

Przeszukałem książkę Swift, ale nie mogę znaleźć wersji Swift @synchronized. Jak mogę dokonać wzajemnego wykluczenia w Swift?


1
Użyłbym bariery wysyłkowej. Bariery zapewniają bardzo tanią synchronizację. dispatch_barrier_async (). itp.
Frederick C. Lee,

@ FrederickC.Lee, a jeśli potrzebujesz synchronizacji zapisu , na przykład podczas tworzenia opakowania removeFirst()?
ScottyBlades

Odpowiedzi:


183

Możesz użyć GCD. Jest trochę bardziej szczegółowy @synchronized, ale działa jako zamiennik:

let serialQueue = DispatchQueue(label: "com.test.mySerialQueue")
serialQueue.sync {
    // code
}

12
To świetnie, ale brakuje możliwości ponownego wejścia z @synchronized.
Michael Waterfall

9
Przy takim podejściu musisz być ostrożny. Twój blok może zostać wykonany w innym wątku. Dokumenty API mówią: „Jako optymalizacja ta funkcja wywołuje blok bieżącego wątku, gdy jest to możliwe”.
biografia

20
Świetny artykuł Matta Gallaghera na ten temat: cocoawithlove.com/blog/2016/06/02/threads-and-mutexes.html
wuf810

4
Nie, powoduje to czasami impasy.
Tom Kraina

70
Nie, nie i nie. Niezła próba, ale działa niedoskonale. Czemu? Niezbędna lektura (kompleksowe porównanie alternatyw, przestrogi) i świetny program narzędziowy autorstwa Matta Gallaghera, tutaj: cocoawithlove.com/blog/2016/06/02/threads-and-mutexes.html @ wuf810 wspominał o tej pierwszej (HT), ale zaniżone, jak dobre są te artykuły. Wszyscy powinni przeczytać. (Prosimy o poparcie tego minimum, aby było początkowo widoczne, ale nie więcej.)
t0rst

181

Szukałem tego sam i doszedłem do wniosku, że nie ma jeszcze natywnej konstrukcji w swift.

Zrobiłem tę małą funkcję pomocnika na podstawie kodu, który widziałem od Matta Bridgesa i innych.

func synced(_ lock: Any, closure: () -> ()) {
    objc_sync_enter(lock)
    closure()
    objc_sync_exit(lock)
}

Użycie jest dość proste

synced(self) {
    println("This is a synchronized closure")
}

Znalazłem z tym jeden problem. Przekazywanie tablicy jako argumentu blokady wydaje się w tym momencie powodować bardzo tępy błąd kompilatora. W przeciwnym razie wydaje się, że działa zgodnie z oczekiwaniami.

Bitcast requires both operands to be pointer or neither
  %26 = bitcast i64 %25 to %objc_object*, !dbg !378
LLVM ERROR: Broken function found, compilation aborted!

Miły! Proszę zgłosić błąd, jeśli nadal występuje problem w wersji 1.0
MattD

14
Jest to bardzo przydatne i @synchronizedładnie zachowuje składnię bloku, ale należy pamiętać, że nie jest on identyczny z rzeczywistą wbudowaną instrukcją bloku, taką jak @synchronizedblok w Objective-C, ponieważ instrukcje returni breaknie działają już tak, aby wyskakiwały z otaczającej funkcji / pętli jak byłoby, gdyby było to zwykłe stwierdzenie.
newacct

3
Prawdopodobnie
wystąpił

9
Byłoby to prawdopodobnie świetne miejsce na użycie nowego defersłowa kluczowego, aby zapewnić, że objc_sync_exitzostanie wywołany, nawet jeśli closurewyrzuca.
devios1

3
@ t0rst Nazywanie tej odpowiedzi „błędną” na podstawie linku do artykułu jest nieprawidłowe. Artykuł mówi, że ta metoda jest „nieco wolniejsza niż idealna” i „jest ograniczona do platform Apple”. To nie sprawia, że ​​„wada” z długiego strzału.
RenniePet,

150

Podobają mi się i używam wielu odpowiedzi tutaj, więc wybiorę tę, która najbardziej Ci odpowiada. To powiedziawszy, metoda, którą wolę, gdy potrzebuję czegoś takiego jak cel-c, @synchronizedużywa deferinstrukcji wprowadzonej w swift 2.

{ 
    objc_sync_enter(lock)
    defer { objc_sync_exit(lock) }

    //
    // code of critical section goes here
    //

} // <-- lock released when this block is exited

Zaletą tej metody jest to, że krytyczna sekcja może opuścić blok zawierający w jakikolwiek sposób pożądany (np return, break, continue, throw) oraz „sprawozdanie w zestawieniu odroczyć są realizowane bez względu na to w jaki sposób program kontroli jest przekazywana”. 1


Myślę, że jest to prawdopodobnie najbardziej eleganckie rozwiązanie tutaj dostarczone. Dziękuję za twój komentarz.
Scott D

3
Co to jest lock? Jak jest lockinicjowany?
Van Du Tran,

6
lockjest dowolnym obiektem celu-c.
ɲeuroburɳ

1
Doskonały! Napisałem kilka metod pomocniczych, kiedy wprowadzono Swift 1 i od dawna ich nie odwiedzałem. Zupełnie zapomniałem o odroczeniu; To jest odpowiednie rozwiązanie!
Randy,

Podoba mi się to, ale pojawia się błąd kompilatora „Usztywniony blok instrukcji to nieużywane zamknięcie” w Xcode 8. Ach, rozumiem, to tylko nawiasy klamrowe - za długo, aby znaleźć link referencyjny „1” - dzięki!
Duncan Groenewald

83

Można wstawiać instrukcje między objc_sync_enter(obj: AnyObject?)i objc_sync_exit(obj: AnyObject?). Słowo kluczowe @synchronized używa tych metod pod przykryciem. to znaczy

objc_sync_enter(self)
... synchronized code ...
objc_sync_exit(self)

3
Czy będzie to uważane za użycie prywatnego interfejsu API przez Apple?
Drux

2
Nie, objc_sync_enteri objc_sync_exitsą metodami zdefiniowanymi w Objc-sync.h i są open source: opensource.apple.com/source/objc4/objc4-371.2/runtime/…
bontoJR

Co się stanie, jeśli wiele wątków spróbuje uzyskać dostęp do tego samego zasobu, czy drugi z nich czeka, próbuje ponownie lub ulega awarii?
TruMan1

Dodając do tego, co powiedział @bontoJR, objc_sync_enter(…)i objc_sync_exit(…)są nagłówkami publicznymi udostępnianymi przez iOS / macOS / etc. Interfejsy API (wygląda na to, że znajdują się ….sdkna ścieżce usr/include/objc/objc-sync.h) . Najłatwiejszym sposobem sprawdzenia, czy coś jest publicznym interfejsem API, jest (w Xcode) wpisanie nazwy funkcji (np. objc_sync_enter()Argumenty nie muszą być określone dla funkcji C) , a następnie spróbuj kliknąć ją. Jeśli pokazuje plik nagłówka dla tego interfejsu API, oznacza to, że jesteś dobry (ponieważ nie byłby on w stanie zobaczyć nagłówka, gdyby nie był publiczny) .
Slipp D. Thompson

75

Analogicznie do @synchronizeddyrektywy z Objective-C może mieć dowolny typ zwrotu i ładne rethrowszachowanie w Swift.

// Swift 3
func synchronized<T>(_ lock: AnyObject, _ body: () throws -> T) rethrows -> T {
    objc_sync_enter(lock)
    defer { objc_sync_exit(lock) }
    return try body()
}

Zastosowanie deferinstrukcji pozwala bezpośrednio zwrócić wartość bez wprowadzania zmiennej tymczasowej.


W Swift 2 dodaj @noescapeatrybut do zamknięcia, aby umożliwić więcej optymalizacji:

// Swift 2
func synchronized<T>(lock: AnyObject, @noescape _ body: () throws -> T) rethrows -> T {
    objc_sync_enter(lock)
    defer { objc_sync_exit(lock) }
    return try body()
}

Na podstawie odpowiedzi z GNewc [1] (gdzie lubię dowolny typ zwrotu) i Tod Cunningham [2] (gdzie lubię defer).


Xcode mówi mi, że @noescape jest teraz domyślny i jest przestarzały w Swift 3.
RenniePet

Zgadza się, kod w tej odpowiedzi dotyczy Swift 2 i wymaga pewnej adaptacji do Swift 3. Zaktualizuję go, kiedy będę miał czas.
werediver

1
Czy możesz wyjaśnić użycie? Może z przykładem .. dzięki z góry! W moim przypadku mam zestaw, który muszę zsynchronizować, ponieważ manipuluję jego zawartością w DispatchQueue.
sancho

@sancho Wolę zachować zwięzłość tego postu. Wydaje się, że pytasz o ogólne wytyczne dotyczące programowania równoległego, to szerokie pytanie. Spróbuj zadać to jako osobne pytanie!
werediver

41

SWIFT 4

W Swift 4 możesz używać kolejek dyspozytorskich GCD do blokowania zasobów.

class MyObject {
    private var internalState: Int = 0
    private let internalQueue: DispatchQueue = DispatchQueue(label:"LockingQueue") // Serial by default

    var state: Int {
        get {
            return internalQueue.sync { internalState }
        }

        set (newState) {
            internalQueue.sync { internalState = newState }
        }
    }
} 

To nie wydaje się działać z XCode8.1. .serialwydaje się być niedostępny. Ale .concurrentjest dostępny. : /
Travis Griggs,

2
domyślnie jest to .serial
Duncan Groenewald

2
Zauważ, że ten wzór nie chroni właściwie przed najczęstszymi problemami z wieloma wątkami. Na przykład, jeśli myObject.state = myObject.state + 1działałbyś jednocześnie, nie policzyłby wszystkich operacji, ale zamiast tego dałby wartość niedeterministyczną. Aby rozwiązać ten problem, kod wywołujący powinien być zawinięty w kolejkę szeregową, aby zarówno odczyt, jak i zapis odbywały się atomowo. Oczywiście Obj-c @synchronisedma ten sam problem, więc pod tym względem Twoja implementacja jest poprawna.
Berik

1
Tak, myObject.state += 1jest kombinacją operacji odczytu, a następnie operacji zapisu. Niektóre inne wątki wciąż mogą pojawiać się między nimi, aby ustawić / zapisać wartość. Zgodnie z objc.io/blog/2018/12/18/atomic-variables łatwiej byłoby uruchomić setblok synchronizacji / zamknięcia, a nie pod samą zmienną.
CyberMew

23

Aby dodać funkcję zwrotu, możesz to zrobić:

func synchronize<T>(lockObj: AnyObject!, closure: ()->T) -> T
{
  objc_sync_enter(lockObj)
  var retVal: T = closure()
  objc_sync_exit(lockObj)
  return retVal
}

Następnie możesz zadzwonić za pomocą:

func importantMethod(...) -> Bool {
  return synchronize(self) {
    if(feelLikeReturningTrue) { return true }
    // do other things
    if(feelLikeReturningTrueNow) { return true }
    // more things
    return whatIFeelLike ? true : false
  }
}

23

Korzystając z odpowiedzi Bryana McLemore'a, rozszerzyłem ją, aby obsługiwała obiekty rzucające się do bezpiecznej posiadłości dzięki zdolności odroczenia Swift 2.0.

func synchronized( lock:AnyObject, block:() throws -> Void ) rethrows
{
    objc_sync_enter(lock)
    defer {
        objc_sync_exit(lock)
    }

    try block()
}

Lepiej byłoby użyć, rethrowsaby uprościć korzystanie z zamknięciami nie rzucającymi (nie trzeba używać try), jak pokazano w mojej odpowiedzi .
werediver

10

Szybki 3

Ten kod ma możliwość ponownego wprowadzania i może współpracować z wywołaniami funkcji asynchronicznych. W tym kodzie, po wywołaniu someAsyncFunc (), inne zamknięcie funkcji w kolejce szeregowej będzie przetwarzane, ale będzie blokowane przez semaphore.wait (), dopóki nie zostanie wywołane signal (). InternalQueue.sync nie powinien być używany, ponieważ zablokuje główny wątek, jeśli się nie mylę.

let internalQueue = DispatchQueue(label: "serialQueue")
let semaphore = DispatchSemaphore(value: 1)

internalQueue.async {

    self.semaphore.wait()

    // Critical section

    someAsyncFunc() {

        // Do some work here

        self.semaphore.signal()
    }
}

objc_sync_enter / objc_sync_exit nie jest dobrym pomysłem bez obsługi błędów.


Jaka obsługa błędów? Kompilator nie zezwala na nic, co rzuca. Z drugiej strony, nie używając objc_sync_enter / exit, rezygnujesz z pewnych znacznych wzrostów wydajności.
gnasher729,

8

W sesji „Zrozumienie awarii i dzienników awarii” 414 WWDC w 2018 r. Pokazują one następujący sposób przy użyciu DispatchQueues z synchronizacją.

W swift 4 powinno być coś takiego:

class ImageCache {
    private let queue = DispatchQueue(label: "sync queue")
    private var storage: [String: UIImage] = [:]
    public subscript(key: String) -> UIImage? {
        get {
          return queue.sync {
            return storage[key]
          }
        }
        set {
          queue.sync {
            storage[key] = newValue
          }
        }
    }
}

W każdym razie możesz także przyspieszyć odczytywanie, używając równoległych kolejek z barierami. Odczyty synchronizacji i asynchroniczne są wykonywane jednocześnie, a zapisanie nowej wartości czeka na zakończenie poprzednich operacji.

class ImageCache {
    private let queue = DispatchQueue(label: "with barriers", attributes: .concurrent)
    private var storage: [String: UIImage] = [:]

    func get(_ key: String) -> UIImage? {
        return queue.sync { [weak self] in
            guard let self = self else { return nil }
            return self.storage[key]
        }
    }

    func set(_ image: UIImage, for key: String) {
        queue.async(flags: .barrier) { [weak self] in
            guard let self = self else { return }
            self.storage[key] = image
        }
    }
}

prawdopodobnie nie musisz blokować odczytów i spowalniać kolejki za pomocą synchronizacji. Możesz po prostu użyć synchronizacji do seryjnego zapisu.
Basheer_CAD,

6

Użyj NSLock w Swift4:

let lock = NSLock()
lock.lock()
if isRunning == true {
        print("Service IS running ==> please wait")
        return
} else {
    print("Service not running")
}
isRunning = true
lock.unlock()

Ostrzeżenie Klasa NSLock wykorzystuje wątki POSIX do implementacji swojego zachowania blokującego. Podczas wysyłania wiadomości odblokowującej do obiektu NSLock należy upewnić się, że wiadomość została wysłana z tego samego wątku, który wysłał początkową wiadomość blokującą. Odblokowanie blokady z innego wątku może spowodować niezdefiniowane zachowanie.



6

W nowoczesnym Swift 5 z funkcją powrotu:

/**
Makes sure no other thread reenters the closure before the one running has not returned
*/
@discardableResult
public func synchronized<T>(_ lock: AnyObject, closure:() -> T) -> T {
    objc_sync_enter(lock)
    defer { objc_sync_exit(lock) }

    return closure()
}

Użyj go w ten sposób, aby skorzystać z możliwości zwracania wartości:

let returnedValue = synchronized(self) { 
     // Your code here
     return yourCode()
}

Lub tak inaczej:

synchronized(self) { 
     // Your code here
    yourCode()
}

2
To jest poprawna odpowiedź, a nie zaakceptowana i wysoko oceniona (zależy od GCD). Wydaje się, że w zasadzie nikt nie używa ani nie rozumie, jak używać Thread. Jestem z tego bardzo zadowolony - podczas gdy GCDjest on pełen gotch i ograniczeń.
javadba

4

Wypróbuj: NSRecursiveLock

Blokada, którą można wielokrotnie nabyć przez ten sam wątek bez powodowania impasu.

let lock = NSRecursiveLock()

func f() {
    lock.lock()
    //Your Code
    lock.unlock()
}

func f2() {
    lock.lock()
    defer {
        lock.unlock()
    }
    //Your Code
}

2

Rysunek: Opublikuję moją implementację Swift 5, opartą na wcześniejszych odpowiedziach. Dzięki chłopaki! Uważam, że warto mieć taką, która zwraca również wartość, więc mam dwie metody.

Oto prosta klasa do zrobienia jako pierwsza:

import Foundation
class Sync {
public class func synced(_ lock: Any, closure: () -> ()) {
        objc_sync_enter(lock)
        defer { objc_sync_exit(lock) }
        closure()
    }
    public class func syncedReturn(_ lock: Any, closure: () -> (Any?)) -> Any? {
        objc_sync_enter(lock)
        defer { objc_sync_exit(lock) }
        return closure()
    }
}

Następnie użyj go tak, jeśli potrzebujesz wartości zwracanej:

return Sync.syncedReturn(self, closure: {
    // some code here
    return "hello world"
})

Lub:

Sync.synced(self, closure: {
    // do some work synchronously
})

Wypróbuj public class func synced<T>(_ lock: Any, closure: () -> T), działa zarówno na void, jak i na inne typy. Są też rzeczy odrastające.
hnh

@hnh, co masz na myśli przez rzeczy odrastające? Również, jeśli zechcesz udostępnić przykładowe wywołanie metody ogólnej o typie <T>, które pomogłoby mi zaktualizować odpowiedź - podoba mi się to, dokąd zmierzasz.
TheJeff

rethrows, not notrow, srz
hnh

1

Detale

xCode 8.3.1, szybki 3.1

Zadanie

Odczytaj wartość zapisu z różnych wątków (asynchronicznie).

Kod

class AsyncObject<T>:CustomStringConvertible {
    private var _value: T
    public private(set) var dispatchQueueName: String

    let dispatchQueue: DispatchQueue

    init (value: T, dispatchQueueName: String) {
        _value = value
        self.dispatchQueueName = dispatchQueueName
        dispatchQueue = DispatchQueue(label: dispatchQueueName)
    }

    func setValue(with closure: @escaping (_ currentValue: T)->(T) ) {
        dispatchQueue.sync { [weak self] in
            if let _self = self {
                _self._value = closure(_self._value)
            }
        }
    }

    func getValue(with closure: @escaping (_ currentValue: T)->() ) {
        dispatchQueue.sync { [weak self] in
            if let _self = self {
                closure(_self._value)
            }
        }
    }


    var value: T {
        get {
            return dispatchQueue.sync { _value }
        }

        set (newValue) {
            dispatchQueue.sync { _value = newValue }
        }
    }

    var description: String {
        return "\(_value)"
    }
}

Stosowanie

print("Single read/write action")
// Use it when when you need to make single action
let obj = AsyncObject<Int>(value: 0, dispatchQueueName: "Dispatch0")
obj.value = 100
let x = obj.value
print(x)

print("Write action in block")
// Use it when when you need to make many action
obj.setValue{ (current) -> (Int) in
    let newValue = current*2
    print("previous: \(current), new: \(newValue)")
    return newValue
}

Pełna próbka

rozszerzenie DispatchGroup

extension DispatchGroup {

    class func loop(repeatNumber: Int, action: @escaping (_ index: Int)->(), completion: @escaping ()->()) {
        let group = DispatchGroup()
        for index in 0...repeatNumber {
            group.enter()
            DispatchQueue.global(qos: .utility).async {
                action(index)
                group.leave()
            }
        }

        group.notify(queue: DispatchQueue.global(qos: .userInitiated)) {
            completion()
        }
    }
}

klasa ViewController

import UIKit

class ViewController: UIViewController {

    override func viewDidLoad() {
        super.viewDidLoad()

        //sample1()
        sample2()
    }

    func sample1() {
        print("=================================================\nsample with variable")

        let obj = AsyncObject<Int>(value: 0, dispatchQueueName: "Dispatch1")

        DispatchGroup.loop(repeatNumber: 5, action: { index in
            obj.value = index
        }) {
            print("\(obj.value)")
        }
    }

    func sample2() {
        print("\n=================================================\nsample with array")
        let arr = AsyncObject<[Int]>(value: [], dispatchQueueName: "Dispatch2")
        DispatchGroup.loop(repeatNumber: 15, action: { index in
            arr.setValue{ (current) -> ([Int]) in
                var array = current
                array.append(index*index)
                print("index: \(index), value \(array[array.count-1])")
                return array
            }
        }) {
            print("\(arr.value)")
        }
    }
}

1

W przypadku opakowań właściwości Swift używam teraz:

@propertyWrapper public struct NCCSerialized<Wrapped> {
    private let queue = DispatchQueue(label: "com.nuclearcyborg.NCCSerialized_\(UUID().uuidString)")

    private var _wrappedValue: Wrapped
    public var wrappedValue: Wrapped {
        get { queue.sync { _wrappedValue } }
        set { queue.sync { _wrappedValue = newValue } }
    }

    public init(wrappedValue: Wrapped) {
        self._wrappedValue = wrappedValue
    }
}

Następnie możesz po prostu zrobić:

@NCCSerialized var foo: Int = 10

lub

@NCCSerialized var myData: [SomeStruct] = []

Następnie uzyskaj dostęp do zmiennej w normalny sposób.


1
Podoba mi się to rozwiązanie, ale byłem ciekawy kosztu ludzi @Dekorowanie, ponieważ powoduje to efekt uboczny tworzenia DispatchQueueukrytego przed użytkownikiem. Znalazłem to odniesienie SO, aby się uspokoić: stackoverflow.com/a/35022486/1060314
Adam Venturella

Samo opakowanie właściwości jest dość lekkie - po prostu struktura, więc jedna z najlżejszych rzeczy, jakie możesz zrobić. Dzięki za link na DispatchQueue. Miałem w głowie, aby przeprowadzić testy wydajności na pakowaniu kolejki.sync w porównaniu z innymi rozwiązaniami (i bez kolejki), ale tego nie zrobiłem.
drewster

1

Podsumowując, tutaj podaj bardziej powszechny sposób, który obejmuje wartość zwracaną lub void i rzucaj

import Foundation

extension NSObject {


    func synchronized<T>(lockObj: AnyObject!, closure: () throws -> T) rethrows ->  T
    {
        objc_sync_enter(lockObj)
        defer {
            objc_sync_exit(lockObj)
        }

        return try closure()
    }


}

0

Dlaczego to utrudnia i kłopot z zamkami? Użyj Barier Wysyłkowych.

Bariera wysyłki tworzy punkt synchronizacji w równoległej kolejce.

Podczas działania żaden inny blok w kolejce nie może działać, nawet jeśli jest równoległy i dostępne są inne rdzenie.

Jeśli to brzmi jak wyłączna blokada (zapis), to właśnie tak. Bloki bez barier można traktować jako współdzielone (czytane) zamki.

Tak długo, jak cały dostęp do zasobu odbywa się przez kolejkę, bariery zapewniają bardzo tanią synchronizację.


2
Mam na myśli, że zakładasz użycie kolejki GCD do synchronizacji dostępu, ale nie zostało to wspomniane w pierwotnym pytaniu. Bariera jest konieczna tylko przy równoczesnej kolejce - możesz po prostu użyć kolejki szeregowej, aby ustawić w kolejce wykluczone wzajemnie bloki i emulować blokadę.
Bill

Moje pytanie, po co emulować zamek? Z tego, co przeczytałem, zamki są odradzane ze względu na obciążenie ogólne lub barierę w kolejce.
Frederick C. Lee,

0

W oparciu o „eurobur” przetestuj przypadek podklasy

class Foo: NSObject {
    func test() {
        print("1")
        objc_sync_enter(self)
        defer {
            objc_sync_exit(self)
            print("3")
        }

        print("2")
    }
}


class Foo2: Foo {
    override func test() {
        super.test()

        print("11")
        objc_sync_enter(self)
        defer {
            print("33")
            objc_sync_exit(self)
        }

        print("22")
    }
}

let test = Foo2()
test.test()

Wynik:

1
2
3
11
22
33

0

dispatch_barrier_async jest lepszym sposobem, nie blokując bieżącego wątku.

dispatch_barrier_async (accessQueue, {dictionary [object.ID] = object})


-5

Inną metodą jest utworzenie nadklasy, a następnie jej odziedziczenie. W ten sposób możesz używać GCD bardziej bezpośrednio

class Lockable {
    let lockableQ:dispatch_queue_t

    init() {
        lockableQ = dispatch_queue_create("com.blah.blah.\(self.dynamicType)", DISPATCH_QUEUE_SERIAL)
    }

    func lock(closure: () -> ()) {
        dispatch_sync(lockableQ, closure)
    }
}


class Foo: Lockable {

    func boo() {
        lock {
            ....... do something
        }
    }

10
-1 Dziedziczenie daje polimorfizm podtypu w zamian za zwiększenie sprzężenia. Unikaj później, jeśli nie potrzebujesz tego pierwszego. Nie bądź leniwy. Preferuj kompozycję do ponownego użycia kodu.
Jano,
Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.