środa, 10 grudnia 2014

iOS, a co to jest - odcinek czwarty: Swift cz.2

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 kompilatora

                  super.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: