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 –). 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”.
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ć.