poniedziałek, 8 grudnia 2014

iOS, a co to jest - odcinek trzeci: Swift cz.1

Tym razem też uciekniemy w awangardę, ale przyszłości. Przyznam się, że jak usłyszałem “nowy język, z duchem czasu” parę miesięcy temu, to nie zrobiło to na mnie już większego wrażenia. Ot, wyobraziłem sobie mieszankę czegoś pomiędzy TypeScript a C#, no może jeszcze okraszoną jakimiś dziwactwami z Objective-C, którego mało co wtedy kojarzyłem. 

No bo, co jeszcze można wymyśleć na tym świecie?  Po co wymyślać kolejny nowy język, kiedy Javascript przeżywa swój renesans  i pozwala pisać w zasadzie już sporą większość aplikacji?  Apple jednak nie poddał się zwątpieniu i zaproponował na swoją platformę zupełnie nowy język.  Strzał w kolano w erze Web?  Niekoniecznie. Jakby już nikt niczego nie proponował, wszyscy płynęli zgodnie już tylko w mainstreamie JavaScript-owym byłoby zwyczajnie po prostu nudno, a uleganie pewnej zgodności wstecz obniżałoby w pewnych obszarach kreatywność.  Może to kwestia polityki, by nie bratać się z mainstreamem i pokazać, że można wymyśleć coś lepszego swojego?  Taka pewna alienacja, podkreślenie elitarności, unikalności.  Apple przez tyle lat wytrwale trzymało się Objective-C nie ulegając tendencjom światowym. Swift pozwala wywoływać kod Objective-C i vice versa, to z pewnością było istotne dla twórców.   

Pomijając już te z lekka filozoficzne rozważania muszę przyznać, że Swift mnie … zaskoczył. Nie zapoznałem się z nim jeszcze nawet w połowie, a zobaczyłem oprócz adaptacji pewnych idei z TypeScript czy C# całkiem sporo świeżych, nowych pomysłów, by na elementy używane powszechnie w programowaniu spojrzeć w jeszcze bardziej otwarty, elastyczny sposób i uprościć sobie dodatkowo pracę. Swift stara się być maksymalnie nieprzegadany, wymaga na ogół jeszcze mniej pisania niż C# czy JavaScript, a kompilator ma olbrzymią inteligencję, że mysz się nie przeciśnie. Swift jest też zaprzeczeniem dynamiczności, zawsze typ jest znany, nie jest to język skryptowy. Przejdźmy do bardziej konkretnych wrażeń

  • wnioskowanie typów - jak na razie kojarzy mi się bardziej z C# niż TypeScript
  • operatory zakresu - czemu współczesne języki o tym zapomniały?  proponowana forma jest całkiem wygodna i do pętli for jak ulał
  • interpolacja stringów - analogiczna do C# 6, tylko znaczki nieco inne
  • stałe jako stałe i elementy read-only
  • propercje klasy jak definicje stałych i zmiennych
  • brak ;
  • tworzenie nowego obiektu bez słowa new
  • elementy opcjonalne - dość popularny ? na końcu typu, jakby typ nullowalny w C#, ale bez ograniczenia typu,  typ z wykrzyknikiem zawsze wypakowuje prostą wartość z siebie automatycznie
  • warunki if bez nawiasów
  • warunkowe przypisanie do zmiennej i operacje na niej - kojarzy się odrobinę z operatorem ? w C# 6, C# 6 robi to jednak jeszcze oszczędniej
  • funkcje także poza klasami - jakby ukłon w kierunku C/C++/JavaScript, ale i C# 6 poprzez import klasy statycznej jakby też chciał zrobić ukłon w tę stronę
  • słowo func i strzałka w definicji typu zwracanego przez funkcję pewnym udziwnieniem, a może chodziło a nawiązanie formą do modnych lambda expressions?  Ale nadal to są funkcje, nie lambda expressions
  • słowo class przy funkcji w klasie?  To metoda klasowa (czyli statyczna). Podobnie jak w TypeScript mogą istnieć obok siebie niezależnie metoda instancyjna i statyczna o tych samych nazwach.
  • wymyślił ktoś wcześniej definiowanie parametrów funkcji z nazwami widzianymi na zewnątrz i wewnątrz ?
  • klauzule - jakby takie lambda expressions jak dla mnie, tylko to słowo in trochę może niepotrzebne, natomiast warianty skracania składni schodzą do jeszcze większego skrótu niż w C#. Żeby bardziej zamieszać:  jeśli klauzula jest ostatnim parametrem funkcji, to może być wyciągnięta poza jej listę argumentów przy wywoływaniu
  • parametry z domyślną wartością - pomysł z C# i TypeScript
  • struktury kojarzą się ze strukturami C#, ale niespodzianką jest fakt, że Int, Bool, String, Array, Dictionary są strukturami (zwłaszcza te dwie ostatnie pozycje mogą zaskakiwać kopiowaniem)
  • w strukturach jest używane słowo static dla elementów statycznych
  • typy wyliczeniowe - śmiała rewolucja w porównaniu do innych języków, tutaj nie dość, że mają dziwny zapis w definicji kojarzący się z blokiem switch case, to mogą mieć w sobie metody i właściwości!  Zamiast teraz pisać wszędzie bloki switch case możemy niektóre logiki, konwersje, tworzenie zamknąć w implementacji danego typu.  Jeśli chodzi o mapowanie na wartości proste znowu mamy liberalizm, nie musi to być tylko Int, ale także inne typy np. String z podanymi przez nas wartościami.
  • tuples - typowe dla współczesnych języków takich jak C#, a ostatnio także JavaScript / TypeScript, formą bardziej kojarzą mi się z tymi drugimi
  • protokoły - jak interfejsy, mogą zawierać elementy opcjonalne jak TypeScript i Objective-C, słowo optional przy składowej oraz wymóg stosowania wtedy oznaczenia @objc przy całym protokole  burzą trochę spójność i nowoczesność języka, ja dałbym tylko przy składowej ? jak w TypeScript
  • wywołanie metody opcjonalnie zaimplementowanej z ? wywoła ją tylko wtedy, jeśli została zaimplementowana. Czy nie jest to piękne?  Może trochę kojarzyć się z C# 6, gdzie operator ? wywołuje składową obiektu, jeśli ta nie jest nullem. Dodatkowo możemy wymusić wywołanie metody i uśpić kompilator stosując znak !
  • rozszerzenia - są jakby nawiązaniem do extension methods z C#, ale są znacznie czymś więcej, kolejną rewolucją w tej dziedzinie. Otóż możemy rozszerzyć istniejącą już definicję obiektu nie tylko o metody, ale także o wyliczane właściwości,  definicje typów zagnieżdżonych czy implementację wskazanego protokołu (interfejsu). Sami twórcy w swoich definicjach mocno tego używają np. w przypadku String.

Jeśli zainteresowały Cię moje zdania o Swift powyżej, możesz przeczytać notatki o tym języku poniżej lub samemu zacząć go rozpoznawać.  W Swift odnajdziemy z jednej strony pomysły z innych języków z ostatnich lat oraz te najnowsze, z drugiej  strony sporo nowych, czasami wręcz śmiałych propozycji, które mogą z kolei zainspirować twórców C# czy JavaScript.

 

Intro

Swift

  • przyszłość na platformie Apple
  • zerwanie z C
  • połączenie możliwości nowoczesnych języków
  • zwięzły
  • ścisle typowany
  • nie jest językiem skryptowym
  • nie jest dynamicznie typowany
  • nie wszystkie API zostanie przepisane
  • kodu z Objective-C nie można konwertować do Swift, ale można wywoływać z jego poziomu i na odwrót
  • w jednym projekcie można łączyć oba języki

Obietnice Swift

  • bezpieczeństwo
  • elastyczność
  • produktywność
  • nowoczesność
  • szybkość
  • łatwość nauki

Swift REPL

xcrun swift

 

import Foundation

let df = NSDateFormatter()    //jak alloc

df.dateStyle = .MediumStyle   // NSDateFormatterMediumStyle

df.stringFromDate(NSDate())

 

^+P lub strzałka do góry – poprzednia komenda

:quit lub CTRL+D – wyjście

Można też zapisać komendy do pliku *.swift  i go wykonać:

xcrun swift plik.swift  //kompiluje plik i uruchamia plik wykonywalny

println(df.stringFromDate(NSDate()))

 

XCode playground - wyniki po prawej stronie okna

import UIKit

var str = “abc”

var x = 1    // Swift wnioskuje typy tam, gdzie jest to możliwe

for i in 1…10 {    // 1…10 - operator zakresu

        x = i * 2

        println(“x = \(x)”)   //interpolacja stringów, przypomina tą z C# 6 tylko że ma () zamiast {}

}

Można też testować elementy wizualne

let label = UILabel(frame: CGRect(x: 0, y: 0, width: 120, height: 50))

label.backgroundColor = UIColor.redColor()

label.sizeToFit()

 

Typy danych

W każdym momencie typ obiektu jest znany

Podstawowe elementy

  • klasy
  • protokoły
  • rozszerzenia
  • struktury
  • krotki (tuples)
  • funkcje
  • closures
  • wyliczenia

let - stała, bez możliwości ponownego przypisania czy modyfikacji (tablice)

var - zmienna, możliwość zmiany

Objective-C przez typ:  NSArray, NSMutableArray

var array = [“Kapitan Bomba”,  “Piesek Leszek”,  “Koń Rafał”]

array.append(“Miś”)

 

class A {

         let x: String

         var y: String

         init(x: String, y: String)  {

                 self.x = x;

                 self.y = y;

         }

}

var a = A(x: “Lepsza Statystyka”, y: “Układ zamknięty”)

a.x = “Czarny czwartek”

Elementy opcjonalne

var x: String?   //String lub nil, typ jest cały czas widziany jako opcjonalny string   {Some “wartość”} /  Optional(“wartość”), nie jako string

var x: String!    //specjalny przypadek pozwalający na nil, niejawne wypakowanie wartości

 

Opcjonalne bindowanie

Objective-C:

NSFileManager *fm = [NSFileManager defaultManager];

NSData *data = [fm contentsAtPath: @”/Users/marcin/plik.txt” ];

if  (data)  {

}

Swift:

let fm = NSFileManager.defaultManager()

if let data = fm.contentsAtPath(“/Users/marcin/plik.txt”)  {

}

 

if x != nil  {

}

x!  - wymuszenie wypakowania wartości z typu opcjonalnego

let z = (x ?? “brak”)   //wypakowanie zawartości jak inna wartość niż nil

Każdy typ może być opcjonalny

 

Typy wartości i referencji

Typy referencyjne

  • klasy
  • funkcje

func  printAlbum(a: Album)  -> Void  {   //strzałkę z Void można pominąć

       println(“\(a.artist)  \(a.title)”)

}

Typy wartości

  • struktury
  • wyliczenia
  • krotki

 

Klasy i obiekty

  • pojedyncze dziedziczenie
  • polimorfizm
  • klasowe i instancyjne metody i propercje

class A {

         let x: String

         var y: String

         init(x: String, y: String)  {

                 self.x = x;

                 self.y = y;

         }

         func f(message: String) {

         }

         class func f(message: String) {  //metoda klasowa

         }

}

let a = A(x: “Lepsza Statystyka”, y: “Układ zamknięty”)

a.f(“Przepraszam, czy tu biją?”)

A.f(“Miś”)

 

Funkcje i klauzule

let array = [“Kapitan Bomba”,  “Piesek Leszek”,  “Koń Rafał”]

//klauzula

array.filter({(name: String)  -> Bool in

          return name.hasPrefix(“K”)

})

func f(name n: String, age a: Int) - wariant z zewnętrzymi i wewnętrznymi nazwami parametrów, pierwsze są nazwy widziane na zewnątrz

func f(#name: String,  #age: Int)  - zewnętrzne i wewnętrzne nazwy parametrów są takie same, trzeba wtedy w wywołaniu funkcji jawnie użyć nazw parametrów

f(name: “Zuch Kozioł”, age:  32)

Przeładowania funkcji na podstawie listy argumentów

Uproszczenia klauzul

array.filter({(name)  -> Bool in

          return name.hasPrefix(“K”)

}

array.filter({ name  -> Bool in

          return name.hasPrefix(“K”)

}

array.filter({ name  -> Bool in

          return name.hasPrefix(“K”)

}

array.filter({ name  -> Bool in

          name.hasPrefix(“K”)

}

array.filter({ $0.hasPrefix(“K”)}

Jeśli klauzula jest ostatnim parametrem funkcji, to może być wyciągnięta poza jej listę argumentów

array.filter() { (name)  -> Bool in

          return name.hasPrefix(“K”)

}

Taka składnia jest możliwa dla wszystkich czterech wariantów definiowania klauzul.

Zamiast definiowania klauzuli można do filter przekazać nazwę funkcji o odpowiedniej sygnaturze.

Funkcja może zwracać klauzulę:

func nameWithMaxLength(max: Int = 3) –> (String)  -> Bool {

         return {  (name:  String) in

                  return name.lengthOfBytesUsingEncoding(NSUTF8StringEncoding)  <  max

         }

}

Przy argumentach z domyślnymi wartościami, jeśli decydujemy się określić w wywołaniu funkcji ich wartości, należy podać także  ich nazwy

array.filter(nameWithMaxLength(max: 5))

Różnice między metodą a funkcją:

metoda

  • dostępna przez self
  • pierwszy argument - domyślna lokalna nazwa
  • kolejne argumenty - lokalne i zewnętrzne nazwy

 

Struktury

  • właściwości
  • metody
  • dostęp przez indeksy
  • inicjalizatory
  • rozszerzalność
  • zgodność z protokołem

Klasy

  • to, co struktury
  • dziedziczenie
  • polimorfizm
  • deinicjalizatory
  • liczenie referencji

struct Point {

        var x: Int

        var y: Int

}

Domyślny inicjalizator

var p = Point(x: 10, y: 20)

Strukturami są:

  • Array<T>
  • Dictionary<Key: Hashable, Value>
  • Bool
  • String
  • Int

struct X {

       static var x: String { get } //propercja statyczna, tylko do odczytu

}

 

Wyliczenia

Objective-C: makro

typedef NS_ENUM(NSInteger, Xyz) {

         XyzOption1,

         XyzOption2,

         XyzOption3

};

Swift:

enum Xyz: Int {   //: Int można pominąć

         case Option1

         case Option2

         case Option3

}

Wyliczenia mogą zawierać:

  • metody
  • właściwości
  • surowe wartości

UIView.animationCurve = UIViewAnimationCurve.EaseInOut

lub krócej:

UIView.animationCurve = .EaseInOut

 

enum Xyz: String {

         case Option1  = “Option1”

         case Option2  = “Option2”

         case Option3  = “Option3”

 

         private func regularExpression() –> NSRegularExpression  {

                 switch self  {

                         case .Option1:

                                   return NSRegularExpression(pattern: “…”, options: nil, error: nil)

                         case .Option2:

                                   …

                         case .Option3:

                                   …

                 }

         }

         func isValidFor(xyz: String)  -> Bool  {

         }

         static func from(xyz:  String)  -> Xyz {

         }

}

Xyz.from(“Option1”).toRaw()   //konwersja z napisu na enum, a potem enum na wartość

 

0..< 10 - w połowie domknięty operator zakresu, nie zawiera 10

 

Tuples

var x = (code: 500, message: “Error”)    //para (Int, String)

x.code  lub x.0

x.message lub x.1

 

(Int?, String?)

(Int, String) ?

 

Protokoły

protocol A {

          var x: Double { get }

          func f()         

}

class B:  A  {

          var x: Double = 100

          func () {

                 …

          }

}

let array: [A] = [b, c]

 

@objc protocol A {   //@objc - interoperabilność z Objective-C, w przypadku definiowania opcjonalnych wymagane

          var x: Double { get }

          func f()         

          optional func g(h: Double)    //funkcje opcjonalne w protokołach jak w Objective-C

}

a.g?(20)  //wywołanie opcjonalnej metody, jeśli została zaimplementowana

a.g!(20)  //wymuszenie (błąd w run-time)

 

Rozszerzenia

  • wyliczane właściwości
  • nowe metody
  • definicja typów zagnieżdżonych
  • zgodność z protokołem
  • istniejące zachowania nie mogą być nadpisane

extension String  {

        func reverse() –> String {

               let chars = Array(self).reverse()

                …

        }

}

let song = “Karinga”

song.reverse()

 

extension String: CollectionType  {

        struct Index: … {

                 …

        }

        //implementacja metod

}

Brak komentarzy: