piątek, 29 kwietnia 2016

[DSP2016] iOS - ciężkie intro (Swift 2 reedukacja w pigułce, Xcode + hello light organ + github, sprzęt)

Mamy już rozwiązanie na Android i Raspberry Pi, teraz pora rozprawić się z iOS.  Przecież ktoś mający urządzenie z jabłkiem też może chcieć cieszyć się kolorowymi błyskami świateł! Dziś kilka słów wstępu na ten temat.

App-kę będziemy pisać w Swift w Xcode (aby nie było za lekko). Z tej okazji przeszedłem reedukację ze Swift by później lepiej mi się tworzyło (kiedyś poznawałem ten język, ale na sucho, teraz trzeba było sobie przypomnieć  linie harmoniczne się jak muzyk orientalny instrument przed wejściem do studia –Winking smile). Mogę polecić szkolenie Swift in Depth. Oczywiście warto też korzystać z oficjalnej dokumentacji Apple oraz oficjalnego bloga Swift.

Zapiski z mojej powtórki tego języka znajdziecie poniżej:

 

Podstawy podstaw

Metoda w klasie:  func, typ zwracany po strzałce

func f (x: Int) –> String {  … }

tworzenie obiektu klasy: bez słowa new

let a = A()

nie ma średników, chyba że damy dwie komendy po sobie w tej samej linii

if #available(iOS 8.0, *)

if-y bez nawiasów

a = b = c  nielegalne (a = b nie produkuje wartości)

operatory zakresu:    1…3    1..<3     if  1…10 ~= i  { … }

var x = 10   var zmienna, interferencja typu

var x: Int   zmienna o typie

let s = “abc”   let stała

let s: String

s = “def”

Double(1)  - konwersja na Int na Double

String (swift) a NSString (API):     objcStr = swiftStr      ale    swiftStr = objcStr as String

var y: Int?   typ opcjonalny   rozpakowanie:   let x = y!     gdy nie ma wartości Int y ma wartość nil

jeśli użyjemy typu Int! to wystarczy let x = y  (automatyczne wypakowanie)

bezpieczna składnia:   if   let a = optionalValue  {  … }

if   let a = optionalValue,  //jeśli nie nil to przejdź dalej

            b = func1()  where a < b,   //jeśli b nie nil, to sprawdź warunek

            let c = func2()   {    //jeśli warunek ok i c nie nil

     …

}

var array:  [String] = [“a”, “b”, “c”]

var array2 = [String] (count: 3, repeatedValue: “”)  //inicjalizacja powtarzalną wartością

var x = array [1…2]

 

Klasa / struktura/ enum

class C {

var x = 0

var y = 0

//inicjalizator

init(a: Int, b: Int) { … }

//deinicjalizator

deinit { … }

//metoda

func f (d: Int) {

     self.x = d

}

final func g(d: Int) { … }  //nie do nadpisania

lazy var offset = getDefault()  //leniwa inicjalizacja

//statyczne pole

static var k = 2

//statyczna metoda z możliwością nadpisania

class func h(d: Int) { … }

//statyczna metoda nie do nadpisania

static func m(d: Int) { … }

}

let c = C(a: 2, b: 3)

typ opcjonalny C?

struct C {

       //zmienne

       //init

       //nie ma deinit

      //metoda modyfikująca strukturę

       mutating func f (d: Init) {  self.x = d }

       //możliwość wyczyszczenia wszystkiego

      mutating func clear() { self = C() }

}

Opcjonalny łańcuch wywołania (jak w C# 6):  

obj.next?.next  (drugie next wywoła się jeśli pierwsze nie będzie nil)

Krotki (w stylu C#7, TS)

let error = ( 500, “Internal error”)

dostęp:    error.0  error.1

let (status, desc) = error //odpowiednia inicjalizacja status i desc

let (status, _ ) = error //drugie pole ignoruj

let error = (code: 500, desc: “Internal error”) //pola nazwane

error.code   error.desc

Interpolacja stringa (podobnie jak  w C# 6):   “\ (status)”

Enum

enum Direction {

case North

case South

}

var dir = Direction.North

dir = .South  //skrótowy zapis

różne dane:

enum PostalCode {

case US (Int, Int)

case UK(String)

}

var p1 = PostalCode.US(12345, 6789)

switch p {

case .US (let loc, var route): …

case .US (1111, 2222): …

case .US (1..<100, _): …

}

enum ASCIIControls: Character {

case Newline = “\n”

case Tab = “\t”

}

let str = ASCIIControls.Tab.rawValue

enum D {

case DISTANCE ( Int )

init (distance: Int) { self = DISTANCE(distance) }

func value() –> Int {

        switch self  {

               case .DISTANCE (let value):  return value

        }

}

//mutating func też może być

}

 

Kontrola przepływu

for:

for element in list { … }

for i in 0..< list.count { … }

for _ in 1…5 { … }

for ;; { … }  //nieskończenie wiele razy

while typowa

do while:

repeat {

}  while warunek

switch:

let p = (1, 2)

switch p {

case (let x, 2):  …

case let(x, y) where x>0 & y > 0: …

}

var opt: Int? = 10

switch opt {

case let val? where val > 0:  …

case nil:  …

}

//case w if-ie

if case let .UK(val) = p { … }

//case w for  np. tablica Int?

for case let n? in array where n < 3 {  … }

wyjątki:

//nie obiekty, tylko enum-y!

class M {

enum Error: ErrorType { case NoSuchFile(String), NotOpen }

init (_ path: String) throws {

        …  throw Error.NoSuchFile(path)

}

func readLine() throws –> String { 

       …

       throw Error.NotOpen

}

}

let f = try!  M(“abc”) 

//etykieta try zamiast bloku, przy każdej metodzie wyrzucającej wyjątek, try! wiem, ale nic z tym nie robię (tam gdzie wiemy że nie będzie wyjątku)

defer { f.close() }  //wykonanie niezależnie od wszystkiego, coś jak finally

do {

let f = try M(“abc”)

defer {  f.close() }

try f.readLine()

}

catch let M.Error.NoSuchFile(path) where path==”/x” {  … }

guard - wykonanie jeśli nie

guard let x = a, y = b where 0 < x && x < b else { … }

 

Funkcje i klauzule

func f() –> () { … }  gdy nic nie zwracamy (opcjonalny zapis)

func p(first: String, last: String) { … }

p(“abc”, last: “xyz”)  //etykiety w dalszych parametrach podobnie jak w Objective-C

func p(given first: String,  family last: String) { … }  //etykiety zewnętrzne

p(given: “abc”, family: “xyz”)

func p(_ first: String, _ last: String) { … }  //bez nazwanych etykiet

p(“abc”, “xyz”)

różne nazwy etykiet – różne przeładowania funkcji

wartości domyślne atrybutów jak w C#

func sum(numbers: Int…) –> Int { … }     f(1,2,3,4)  //wiele argumentów typu Int

func f( inout a: Int, inout _ b: Int)  { … }  //parametry dwukierunkowe np. zamiana a z b

var l = “L”     var r = “R”   f(&l, &r)

var talk: () –> () = hello  // funkcja hello     talk()   //referencja na funkcję

klauzule (funkcje lambda):

let ref = { (x: Int) –> Double in return Double(x) }

let ref: (Int) –> Double = {  x in return Double(x) }

let ref: (Int) –> Double = {  Double($0) }

names.sort() { $0 < $1 } //można wyciągnąć poza, jeśli jedynym argumentem jest klauzula (trailing closure)

names.sort { $0 < $1 }

{   

      let x = $0 + 1

      return “\(x)”

}

 

Dziedziczenie

class A {

func f() { … }

}

class B: A {

override func f() { … }

}

pole == stored property  

właściwość (get/set) == computed property

class P {

      var firstName = “Marcin”

var fullName:  String {

        set {

           … newValue 

       }

       get {

          return …

      }

}

//read-only

var mail: String {  return … }

}

class E: P {

override var fullName: String { get { … }   set { … } }

//nadpisanie także prostych

override var firstName:  String { set { … }  get { … } }

//obserwatory

override var lastName:  String = “Kapitan”  {

        willSet(newVal) {  … newVal  }

        didSet(oldVal) { … oldVal }

}

}

Inicjalizatory

Może być ich wiele w jednej klasie

class S {

var d: Double

//inicjalizator designated - inicjalizuje wszystkie składowe + wywołuje odpowiedni inicjalizator z klasy bazowej

init (d: Double)  {

       self.d = d

}

//inicjalizator convenience - wywołuje designated lub inny convenience

convenience init(i: Int)  {

      self.init(d: …)

}

}

class SS:  S {

      override init(d: Double) {    //designated pochodnej musi odwoływać się do designated bazowej

            super.init(d: d)

      }

}

convenience w pochodnej nie może odwoływać się do convenience ani designated w bazowej, może tylko do innego convenience / designated w swojej klasie 

jeśli klasa pochodna nie ma inicjalizatorów –> dziedziczy wszystkie designated z bazowej

jeśli klasa pochodna implementuje wszystkie inicjalizatory designated bazowej –> dziedziczy wtedy inicjalizatory convenient

inicjalizator, który może zawieść

struct F {

init?(_ x: String) {

       if x.isEmpty {

              return nil

       }  …

}

}

Inicjalizator wymagający nadpisania w klasie pochodnej:

class A {

required init() { … }

}

class B: A {

required init() { … }

}

 

Dostęp, rzutowania, generyki, rozszerzenia

Dostęp na poziomie plików, nie klas  (public, internal – app/framework/module, private - tylko z tego pliku)

public class A {

private func b() { … }

}

public class B: A {

override public func b() { … }

}

Rzutowanie

if p is S  {

var s:S   =  p as!  S  (as - gwarantowane przez kompilator, as! - wymuszenie, może być błąd w runtime)

}

if let s2 = p as? S  { … }

AnyObject,  Any

case  0.0  as Double:

case is Double:

case let d as Double where d > 0:

Generyki podobnie jak w C#

funkcje, klasy, enum-y

Rozszerzenia

extension String  {

var length: Int { return self.characters.count }

}

extension Double {

var km: Double {  return self * 1_000 }

}

12.3.km

===  czy to ten sam obiekt

 

Pamięć

class C {

var array: [D] = []

}

class D {

weak var myC: C?

}

class D {

unowned var c: C  //ten sam czas życia co C, C nie ma wartości nil

}

 

Protokoły (interfejsy)

protocol R {

func f(a: Int) –> String

init(a: Int, b: String) 

}

class A: R {

func f(a: Int) –> String { … }

required init(a: Int, b: String)  { … }

}

x: protocol<Representable, Cacheable>  // implementuje oba

protocol Cacheable: Representable {  //dziedziczenie protokołów

static var version: Int { get set }

var id: String { get }

mutating func load (data: String)  //dla struktur, w klasie zwykła funkcja

func replaceWith(other: Self)   //Self - klasa implementująca

}

protocol P: class { … }  //implementować go mogą tylko klasy

Nie ma generycznych, ale można to obejść

@objc protocol ObjcProtocol {

func f (x: Int)   //typy argumentów:  proste (np. Int, String), AnyObject (nie Any), enum z ObjC, tablice, słowniki, protokoły, klauzule, operatory…  Nie można typealias, opcjonalnych, klas z Swift, inout, krotek, Self ….

       optional var x: Int { get }

optional func g() –> Int    //wywołanie  obiekt.g?()

class ObjcSubclass:  ObjcProtocol {

@objc func f(x: Int)  {  … }   //nie potrzebne @objc gdy dziedziczymy po NSObject

}

extension Person: TextRepresentable { … }  //extension może implementować interfejs

rozszerzenie protokołu, łatwe dodanie np. implementacji nowej funkcji do wszystkich klas implementujących protokół

 

Zobaczymy jak pójdzie pisanie w praktyce. Najnowszy Xcode 7.3  ma w sobie Swift w wersji 2.2 i taki też założyłem projekt, który można już znaleźć na github. Jak dodałem plik .gitignore?  Zrobiłem podobnie jak w filmiku Adding a .gitignore file to a Swift project in Xcode. Do wbitek używam podobnie jak w Windows programu GitHub Desktop. Aplikacja na razie jeszcze nic nie robi, tylko wyświetla napis “Hello Light Organ”.

WP_20160429_22_40_54_Pro

Najtańszym sposobem na pozyskanie środowiska dla Xcode (poza Hackintoshem czy maszyną wirtualną) jest coś takiego jak Mac Mini, które w wersji 8GB pamięci RAM nie jest może demonem szybkości, ale działa zadawalająco. Generalnie nie ma się co bać OS X. Wystarczy zwykła nawet stara klawiatura i mysz od Windows, monitor z HDMI i… wszystko działa bez problemów.  Warto zaktualizować sobie system do najnowszej wersji, aktualnie jest to El Capitan 10.11.4. Wszelkie takie przesądy w stylu, że “nie wiadomo co i jak” można między bajki włożyć.

1 komentarz:

Karolina Zarębska pisze...

Jestem pod wrażeniem. Bardzo ciekawie napisany artykuł.