Dziś kontynuacja podróży ze Swiftem. Kolejna porcja równie awangardowych rozwiązań, co poprzednio, wśród których chciałbym przede wszystkim zwrócić uwagę na:
- inicjalizatory - konstruktory, kontrowersyjne moim zdaniem rozróżnienie w składni na inicjalizatory desygnowane (inicjalizujące wszystkie nieopcjonalne propercje) oraz convenience (muszą wywoływać inicjalizator desygnowany lub inny convenience). Powoduje to restrykcje kompilatora na wywoływanie pewnych inicjalizatorów z poziomu innych, są też odpowiednie reguły podczas dziedziczenia. W C# takich ograniczeń nie ma, zastanawiam się na ile uporządkowano w ten sposób tworzenie i używanie inicjalizatorów, a na ile jest to już pewien przerost formy nad treścią
- właściwości - mówi się o propercjach stored (takie auto) oraz wyliczanych (takie w miarę typowe z getterem i setterem). Propercje związane z typem, nie instancją oznacza się słowem class (klasy) lub static (struktury, wyliczenia). Największą petardą są jednak tutaj obserwatory propercji na poziomie składni języka! Operując blokami willSet i didSet możemy obserwować moment przed zmianą wartości propercji lub po jej zmianie. Mi to kojarzy się trochę z handlerem do zmian dependency property we frameworkach XAML czy watchem na elemencie w scopie Angular. Równie interesujące jest słowo lazy, które powoduje, że propercja inicjalizuje się w momencie pierwszego jej użycia. Idea lazy loadingu jest popularna, ale tutaj trafiła wprost do języka! W temacie inicjalizacji tablic należy wspomnieć, że można je w deklaracji inicjalizować nie tylko zestawem określonych wartości, ale także za pomocą kawałka kodu.
- nadpisywanie w dziedziczeniu - tylko w klasach, dotyczy metod, inicjalizatorów, deinicjalizatorów oraz propercji wyliczanych, w stylu Javy mamy override i final.
- polimorfizm - operatory is (podobnie jak w C#, obecnie wspierane są tylko klasy bez struktur i wyliczeń, w przypadku protokołów muszą być oznaczone @objc, co jest pewnym ograniczeniem i zaburzeniem czystości języka), as! (konwersja się dokonuje tylko wtedy, kiedy może) i as (wymuszenie konwersji)
- referencje - zwalnianie obiektów przez automatyczne zliczanie referencji (jak w Objective-C). Na poziomie języka mamy trzy typy referencji: normalne, weak (musi być typ opcjonalny, po zdealokowaniu obiektu staje się nilem) oraz onowned (typ nieopcjonalny, po zdealokowaniu obiektu wartość niepoprawna)
- generyki – podobne do tego, co znamy w C#, warte podkreślenia jest wnioskowanie typu tablicy na podstawie zawartości jej elementów oraz możliwość definiowania złożonych warunków na typy generyka (trochę awangardowo warunki są umieszczane także w <>)
Wiele inicjalizatorów
inicjator desygnowany – inicjalizacja wszystkich obowiązkowych propercji, może być więcej niż 1
convenience init(name: String, object: AnyObject?) //każdy convenience musi wywołać albo designated albo inny convenience
class Album { //błąd zgłaszany przez kompilator, nie ma desygnowanego inicjalizatora ustawiającego x i y
let title: String
let artist: String
}
class Album { //kompilator już nie protestuje
let title: String = “TPN 25”
let artist: String = “Słabo?!”
}
let a = Album()
class Album { //kompilator też nie protestuje
let title: String?
let artist: String?
}
class Album {
let title: String
let artist: String
init (title: String, artist: String) {
self.title = title
self.artist = artist
}
convenience init() {
self.init(title: “Zakażone piosenki”, artist: “Zuch Kazik”)
}
}
Można też mieć init z parametrami o domyślnych wartościach
W klasie pochodnej
- inicjalizator designated może wywołać designated z klasy bazowej
- każdy convenience może wywołać designated
class SuperAlbum: Album {
let discCount: Int
init (title: String, artist: String, discCount: Int) {
self.discCount = discCount
super.init(title: title, artist: artist) //jeśli przeniesiemy linijkę wyżej, kompilator zgłosi błąd
}
init(discCount: Int) {
self.discCount = discCount
super.init()//błąd kompilatorasuper.init(title: “Zakażone piosenki”, artist: “Zuch Kazik”) //najlepiej by w klasie bazowej init() był designated zamiast convenience
}
}
Aby domyślny bezparametrowy inicjalizator był dostępny wszystkie propercje muszą mieć domyślne wartości.
Dziedziczenie inicjalizatorów
- Desygnowane inicjalizatory są dziedziczone, jeśli klasa pochodna nie definiuje żadnego swojego
- Inicjalizatory convenience są dziedziczone, jeśli klasa pochodna ma wszystkie desygnowane inicjalizatory klasy bazowej
Deinicjalizatory
- deinit()
- wywoływane przed dealokacją
- niewymagane
- nieprzeładowywane
- tylko w klasach
class X: NSObject {
deinit {
let nc = NSNotificationCenter.defaultCenter()
nc.removeObserver(self, name: “Xyz”, object: nil)
}
override init() {
super.init()
let nc = NSNotificationCenter.defaultCenter()
nc.addObserver(self, selector: “xxx”, name: “Xyz”, object: nil)
}
func xxx(notification: NSNotification) {
}
}
Propercje
class X {
let a: Int //stored
var b { //computed
get { return … }
set { … = newValue }
}
}
Typ:
- static: struct, enum
- class: class
Obserwatorzy propercji (odpalani nie przez inicjalizatory, jedynie przy zmianach)
class X {
var a: String {
willSet {
println(“A changing from \(a) to \(newValue)”)
}
didSet {
println(“A changed from \(oldValue) to \(a)”)
}
}
var b: String {
willSet(newB) {
println(“A changing from \(b) to \(newB)”)
}
didSet(oldB) {
println(“A changed from \(oldB) to \(b)”)
}
}
}
Propercje lazy (inicjalizacja przy pierwszym użyciu)
class Datastore {
lazy var dbConnection: Connection = Connection()
}
Inicjalizacja tablicy przy pomocy kawałka kodu.
class Calendar {
var months: [String] = {
let df = NSDataFormatter()
df.locale = NSLocale(localeIdentifier: “pl-PL”)
return df.monthSymbols.map() { $0 as String }
}()
}
Nadpisywanie
- tylko w klasach, nie w strukturach i wyliczeniach
- metody
- inicjalizatory
- wyliczane propercje (nie można nadpisać store property, ale obserwator już tak)
- deinicjalizatory (automatycznie wywoływany łańcuch deinicjalizatorów z klas bazowych)
class Album {
let title: String
let artist: String
var description: String {
get {
return “\(artist) \(title)”
}
}
init (title: String, artist: String) {
self.title = title
self.artist = artist
}
}
class SuperAlbum: Album {
override var description: String {
get {
return “\(artist) \(title) - edycja limitowana”
}
}
}
final - element nie może zostać nadpisany
Polimorfizm
- is - sprawdzenie zgodności z protokołem lub typem klasy (protokół musi być wtedy poprzedzony @objc, is nie może być stosowany do struktur i wyliczeń, ale można nimi i obiektami o danym protokole zainicjować tablicę)
- as? - warunkowe rzutowanie
- as - wymusza rzutowanie
if let guitar = thing as? Guitar {
}
Referencje
Automatic Reference Counting (podobnie jak w Objective-C)
strong:
var a1: Album
weak:
weak a2: Album? //musi być typ opcjonalny, jest nilem po zdealokowaniu obiektu
unowned:
unowned a3: Album //typ nieopcjonalny, po zdealokowaniu obiektu wartość nie jest poprawna i przy dostępie do niej wystąpi bład
modyfikacja typu referencji
let closure = [unowned self]() –>Void {
println(“Jestem \(self.name)”)
}
Zwalnianie obiektów wzajemnie wskazujących na siebie
class A {
var b: B?
}
class B {
var a: A?
}
Nie działa z automatu, gdy poustawiamy pola a i b zamiast domyślnych nil.
Samo przypisanie głównych zmiennych nil też nic nie da, pomoże jawne wyczyszczenie pól z referencjami.
Lepiej
class B {
weak var a: A?
}
Unowned zachowuje się przy dealokacji podobnie, ale przy złej kolejności sypnie wyjątkiem
Generyki
struct Array<T> { …}
Wnioskowanie typów
let names = [“Gdy naród do boju”, “Czterdzieści lat minęło”, “Dzień zwycięstwa”]
let str = names[1].uppercaseString
Definicje
struct Array<T>: MutableCollectionType, Sliceable {
var first: T? { get }
…
}
func map<U>(transform: (T) –> U) –> [U]
Warunki
struct Dictionary<Key: Hashable, Value> {
}
func f<T1: A, T2: A
where T1.x == T2.x, T1.x: C>
(item1: T1, item2: T2) -> Bool {
return true
}
Brak komentarzy:
Prześlij komentarz