czwartek, 7 stycznia 2016

iOS - Swift 2.0

Jeśli szukasz w zimowy wieczór odskoczni od C# czy JavaScript, polecam zgłębienie aktualnej wersji Swift-a. Po zapoznaniu się z nią mam dwie główne uwagi. Wiedza ze Swift 1 generalnie w całości się przydaje (patrz: iOS, a co to jest - odcinek trzeci- Swift cz.1, iOS, a co to jest - odcinek czwarty- Swift cz.2iOS, a co to jest - odcinek piąty- Swift cz.3).  Dwójka ma jeszcze większy power (m.in nowa obsługa błędów, adaptatywny kod do wersji systemu, rozszerzenia protokołów, Open Source).

Nie widziałem jeszcze takiego języka jak Swift.  Mamy wygodę C# plus masę nowych wymyślnych udogodnień, lekkość JavaScript-u (a także znane z niego programowanie funkcyjne za pomocą klauzul), udogodnienia Objective-C, których nie było w innych językach i dużą konfigurowalność. Co prawda w C# dzięki Roslyn możemy dowolnie rozszerzać język, ale trzeba przyznać, że ileś pomysłów Swifta przydałoby się zinkorporować do przyszłej wersji C#. Wyliczenia i rozszerzenia biją wszystko! 

Dla równowagi dodam, że język ten w różnych miejscach może okazać się zachować inaczej niż byśmy się mogli spodziewać. Czasami odchodzi od dobrze znanych konstrukcji z C# czy Java (np. obsługa błędów). Automatyczne zliczanie referencji (w Objective-C od iOS 5 pojawiła się taka opcja) jest dość wygodne, jednak czasami możemy doprowadzić do wycieków pamięci, jak nie oznaczymy jakiejś składowej klasy odpowiednim słowem kluczowym (uważać m.in przy wzajemnej zależności jednej klasy od drugiej). Kontrola dostępu na poziomie pliku, a nie klasy (i brak trybu protected) wydaje się jakąś pomyłką z dawnej epoki (a może jest w tym właśnie jakaś głębsza myśl? czy jest sens coś zabezpieczać, jak jest w tym samym pliku?). Pewne kontrowersje może budzić zastosowanie etykiet do wychodzenia z pętli lub warunków, choć może to dawać swobodę przy zagnieżdżeniach.  Prowizorką wydaje się obejście na definiowanie protokołów generycznych.

 

Xcode 7

 

Operatory

kod adaptatywny

if #available(iOS 8.0, OSX 10.10, *) {

}

@available(iOS 8.0, OSX 10.10, *)

func f() { … }

niedozwolone:  a = b = c

operatory dopuszczające overflow

a &+ b     a &- b

a &* b     a  &/ b

operatory zakresu

1…3      1..<3

let i = 5

if 1..10 ~= i { … }

 

Typy

UTF8  // 16, 32, 64

Float80

typealias Unsigned = UInt16

 

let anInt = 10

let aDouble = 1.0

let x = aDouble + Double(anInt)

 

var str1: String = “abc”

var str2: NSString = “def”

str2 = str1

str1 = str2 as String

b.characters.count()

for ch in s { … }

b.utf8

s[s.startIndex]  - pierwszy znak

s[s.startIndex.successor()]  - drugi znak

s[s.endIndex.predecessor()]  - ostatni znak

s[advance(s.startIndex, 1)]  - drugi znak

s[advance(s.endIndex, –2)] - przedostatni znak

s.insert(“|”, atIndex: advance(s.startIndex, 4)) - na 5-tej pozycji

let range = 1 ..< 3

s.removeRange(range)

 

var x: Int?

x = Int(“xxx”)

if  x != nil  {

let num = x!  //wypakowanie, jeśli ma jakąś wartość

}

var y: Int!

y = Int(“yyy”)  //błąd jeśli nie ma wartości

 

if let a = optionalValue  { /* bezpieczne użycie a */ }

if let a = optionalValue,   //jeśli a ma wartość, wylicz b, jeśli b ma wartość  sprawdź a < b, jeśli a < b policz c, jeśli c ma wartość

b = bar()  where a < b,

let c = cow()   { …}

 

var array = [String]()

var array2 = [String](count:3, repeatedValue:””)

array.removeLast()

var twoD: [[String]] = [  [“a”, “b”, “c”],  [“d”, “e”, “f”] ]

twoD[0] = [“A”, “B”, “C”]

twoD[0][0] = “x”

 

var mySet: Set<String> = [“A”, “B”, “C”]

var c = Set<String>( [“A”, “B”, “C”])

insert, remove, removeAll, isEmpty, count, contains, isDisjointWith, isSupersetOf, isSubSetOf, isStrictSupersetOf, isStrictSubsetOf

a.exclusiveOr(b)    a.intersect(b)     a.substract(b)      a.union(b)

a.exlusiveOrInPlace(b)   itd.

 

var httpStatus:  [Int: String] = [:]   //słownik

for (code, message) in httpStatus  {  … }

 

Klasy i struktury

class Circle {

var center = Point()

var radius = 0.0

init(c: Point, r: Point) {

      center = c

      radius = r

}

//nie można nadpisać

final func moveTo(center: Point) {

      self.center = center

}

static var offset = getDefault()

class func offset(here: Point) { … }  //static nie pozwala nadpisać, class pozwala

}

let c1 = Circle()

let c2 = Circle(c: Point(), r: 5.0)

 

struct Circle {

mutating func moveTo(center: Point)  { … }  //modyfikacja wartości

mutating func reset() {  self = Circle() }

}

 

class Node {

var optDict:  [String:String]?

var next: Node?

func successor() –> Node?  { return next }

}

if let a = node.next?.optDict?[“k”] ?.isEmpty { … }

 

Tuples

let http404Error = (404, “Not Found”)

let (status, desc) = http404Error

print(“\(status):  \(desc)”)

let (status, _)  = http404Error  //interesuje nas tylko pierwsze pole

func fetch() –> (code: Int, desc: String) {

return (200, “OK”)

}

 

Wyliczenia

enum CompassPoint {

case North,

case South,

case East, West

}

switch direction {

case .East:    …

default:  …

}

mega konstrukcja!

enum PostalCode {

case US(Int, Int)

case UK(String)

case CA(code: String)

}

var p1 = PostalCode.US(94707, 2625)

switch s {

case .UK (let s):  …

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

case .CA:  break;

}

let x = CompassPoint.South.rawValue

if  let c = CompassPoint(rawValue: 5)  {  … }

enum Dimension {

case DISTANCE (Int)

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

func value() –> Int {

       switch self  {

              case .DISTANCE  (let value) : return value

       }

}

}

enum ConnectionState {

case closed, opening, open, closing

mutating func next()  {

       switch self  {

               case closed:  self = opening

               case opening:  self = open

               …

        }

}

}

 

Kontrola przepływu

for element in list  {  … }

for i in 0 ..< list.count  {

print(“\(list[i])”)

}

for var i=0; i < list.count; ++i  { 

}

for _ in 1…5  {

print(“hello”)

}

forever

for  ;;  {

print(“hello”)

}

repeat  {

}   while condition

break i continue

whileLabel:  while c1 {

if c2 { continue whileLabel }

if  c3 { break whileLabel }

}

outerLabel:  if c1 {

innerLabel:  if c2 {

         if c3  { break outerLabel }

         if c4   { break innerLabel }

}

}

let ch: Character = “e”

switch ch {

case “a”, “e”, “i”:   …

case  “a”…”z”:  fallthrough

default:  …

}

let str = “wilk”

switch str {

case “anoda” …”antena”

          …

case “kajman”…”prosto”

         …

default:

         …

}

let p = (1.0, 2.0)

switch p {

case (let x, 2.0):  …

case (1.0, let y):  …

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

case let(x,y):  …  //lub default:

}

switch rgbaColor {

case (1.0, 1.0, 1.0, 1.0:  …

case let (r, g, b, 1.0) where r==g && g ==b: …

case (0.0, 0.5…1.0, let b, _):  …

default: break

}

let r = 1.0, g = 1.0, b = 1.0, a = 1.0

switch (r, g, b, a) {

//tak samo jak wcześniej przy tuple

}

enum Status {

case OK(status: Int)

case Error(code: Int, message: String)

}

switch status {

case .OK(0):  …

case .Error(0, _): …

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

case .Error(let code, let msg): …

}

enum może mieć w swoim paametrze inny enum, obsługuje to także switch

switch connection {

case .Down(.Error(0,_)):  …

case .Down(.Error(let status, _)) where status==1 || status > 5:  …

case .Down:  …

}

var opt: Int? = 10

switch opt {

case let val?  where val > 0:  …

case nil:  …

}

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

var array: [Int?] = [1, nil, 3]

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

 

obsługa błędów

assert(warunek, “Message”)

assertionFailure(“Message”)

precondition(warunek, “Message”)   //zawsze aktywne, także w wersji produkcyjnej

preconditionFailure(“Message”)

fatalError(“Message”)  //zatrzymuje program

“wyjątki”

class MyFile {

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

init ( _ path: String) throws {

         if !MyFile.exists(path)  {

                  throw Error.NoSuchFile(path)

          }

         func readLine() throws –> String {

                  if !isOpen {  throw Error.NotOpen }

                  return “xxx”

         }

}

}

assert(MyFile.exists(“fff”))

let file = try!  MyFile(“fff”)   //każda metoda mogąca wyrzucać poprzedzona try!, nie ma typowego bloku try i catch

defer { file.close() }  //blok wywoływany bez względu na wszystko

blok do catch

do {

let file2 = try MyFile(“yyy”)

defer { file2.close() }

try file2.readLine()

}

catch let MyFile.Error.NoSuchFile(path) where path==”/x” {    //catch podobny do case w switch

}

catch {  … }

 

var optX: Int? = 10, optY: Int? = nil

if let x=optX, max=optY where 0 < x && x < max  {

let op = doSomething(x)

if let o = op

        something(o)

inaczej:  guard (wykonywanie jak coś pójdzie nie tak)

guard let x=opX, max=optY where 0 < x && x < max  else { return }

let op = doSomething(x)

guard let o = op else { return }

doSomething(o)

zmienna w bloku if jest widoczna tylko w tym bloku, nie jest widoczna w else ani na zewnątrz

zmienna w guard jest widoczna na zewnątrz

 

Funkcje i klauzule

func noReturn() –> () { … }

func printName(given first: String, family last: String) {

print(“\(first) \(last)”)

}

printName(given: “Alibaba”, family: “Matheo”)

func printName( _ first: String, _ last: String) { …}

printName(“Alibaba”, “Matheo”)  //przy opuszczeniu zewnętrznych _ w wyołaniu można w ogóle pominąć nazwy

domyślnie zewnętrzna nazwa pierwszego argumentu jest _, w kolejnym jest to jego wewnętrzna nazwa

przeładowanie funkcji (typ parametru, zestaw parametru, zwracany rodzaj, przy podaniu zewnętrznych nazw parametrów przeładowania dla różnych nazw parametru tego samego typu)

parametr z domyślną wartością

func concatenate( strings: [String], delimiter: String = “,”) –> String { … }

func sub(a: Int = 0, b:Int = 0) –> Int { … }

sub()

sub(a: 10)

sub(b: 10)

func sub(a a: Int = 0, b: Int = 0) –> Int { … }

sub(a: 20, b: 10)

sub(b: 10, a : 20)  //w tym przypadku można podać argumenty w innej kolejności

dowolna ilość parametrów

func sum(numbers: Int…) –>Int {

for number in numbers {

      …

}

}

sum (1, 2, 3, 4)

przekazanie przez referencje

func swap( inout a: Int, inout _b: Int ) {

let t = a

a = b

b = t

}

var left = “L”

var right = “R”

swap(&left, &right)

 

funkcja jest obiektem

func hello() { print(“hello”) }

var talk: () –> () = hello

talk()

funkcja może być zdefiniowana wewnątrz innej funkcji i zwracana jako wynik

func F(value: Int) –> (Int) –>(Int) {

func up (input: Int) –> Int { return input + 1 }

func down (input: Int) –> Int { return input - 1 }

return (value < 0 ? up : down)

}

 

klauzule / funkcje lambda

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

let ref: (Int) –> Double = { Double($0) } // nie musimy w definicji klauzuli powtarzać sygnatury, bo jest już wnioskowana z typu ref

var names = [ “Borixon”, “Alibaba”, “Popek” ]

names.sort( { (s1: String, s2: String) –> Bool in return s1 < s2 } )

names.sort() { $0 < $1 }  //gdy jedynym parametrem jest klauzula można ją wyciągnąć na zewnątrz  //trailing closure

names.sort { $0 < $1 }

func adder(increment: Int) –> (Int) –> Int {

return { increment + $0 }

}

var add = adder(10)

add(20)  //30

func getIncrementers(amount: Int) –> (increment: ()->Int, decrement: ()->Int) {

var total = 0

return ( { total += amount; return total}, {total –= amount; return total} )

}

let (up5, down5) = getIncrementers(5)

up5()  //5

up5()  //10

let (up1, down1) = getIncrementers(1)

up5()  //5

up1() // 1

up5() // 10

let str: String = { … }()  //wywołanie klauzuli w miejscu jej definicji

 

Propecje

field/member == stored property

property (get/set) == computed property

class Person {

var firstName = “Alibaba”

var fullName: String {

          set {

                …  newValue

          }

          get {

               return …

          }

}

//read-only

var email: String {   return … }

}

nadpisywanie

class Employee: Person {

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

override var email: String { … }

override var firstName: String { set{…} get{…} }  //nadpisanie propercji stored propercją computed!

override var lastName: String {

       willSet(newVal) { … }

       didSet(oldVal) { …;  lastName = lastName.uppercaseString } 

//można zmieniać propercję w obserwatorze didSet, ale w klasie pochodnej prowadzi do wpadniecia w nieskończoną pętlę wywołań obserwatorów

}

}

w klasie bazowej takiego problemu nie będzie

class Person {

var lastName: String = “Alibaba” {

         willSet(newVal) { … }

        didSet(oldVal) { …; lastName = lastName.uppercaseString }

}

}

 

w klasie pochodnej inicjalizatory convenience mogą wywoływać tylko inicjalizator designated lub inne convenience z tej samej klasy

jeśli zdefiniujemy wszystkie inicjalizatory designated w klasie pochodnej to możemy użyć inicjalizatora convenience z bazowej

inicjalizator, który może zawieść

struct Failable {

init?(_ x: String) {

         if x.isEmpty {

                  return nil

         }

}

}

if let f = Failable(“xxx”) { … }

wymuszenie konieczności nadpisania inicjalizatora w klasie pochodnej

class Super {

required init() { … }

}

class Sub: Super {

required init() {

       …

}

}

 

dostęp na poziomie pliku, nie klasy

public

internal

private  (w tym samym pliku)

nie ma protected

generalnie w pliku powinna być jedna klasa

wszystkie pola w klasie i metody pomocnicze powinny być prywatne

public class Super {

public func doSomething() { }

}

public class Sub: Super {

override public func doSomething() { … }  //nadpisująca metoda nie może zmienić public na private, ale private na public już tak !!! (pod warunkiem że typy parametrów są publiczne)

}

w klasie można umieścić definicje innej klasy

private (set)

public var x: Int { set { … } get { return 0 } }

 

rzutowanie

is  as!  as?

0.0 as Double

AnyObject

Any

 

generyki - przy wywołaniu metody nie podajemy typów w <>

 

rozszerzenia

extension String {

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

}

 

c === self   //ten sam obiekt

 

class HTMLElement {

lazy var asHTML: () –>String = {

         [unowned self] in

         return self.content == nil

               ? “<\(self.tag) />”

               : “<\(self.tag)>\(self.content!)</\(self.tag)>”

}

}

 

Protokoły

protocol P {

func representation(asType: Format) –> String

init(asType: Format, contents: String)

}

class E: P {

func representation(asType: Format)  -> String  {

      …

}

required init(asType: Format, contents: String)  {   //przy implementacji inicjalizatora wymaganego przez interfejs dodajemy required

      …

}

 

protocol Cacheable {

}

class CacheableEmployee: Employee, Cacheable {   //Employee: Representable

}

func f(x: protocol<Representable, Cacheable>) {

}

 

protocol Cacheable:  Representable {

static var versionID:  Double { get set }

            var objectID:  String { get }

func flush() –> String

mutating func load(flushId:  String)

static func f(a: Double, b: Double)

}

class CacheableEmployee: Employee, Cacheable {

static var versionID = 1.0  //nie musi być get i set

required init() {

        super.init(asType: Format.JSON, contents:””)

}

required init(asType: Format, contents: String) {

       super.init(asType: asType, conents: contents)

}

func load(flushId: String) { … }   //nie ma mutating w class

static  func f(a: Double, b:  Double)  { … }

}

 

class C<T: Cloneable> { … }

class C<T where T: Cloneable, T: Representable> { … }

 

protocol SupportsReplace {

func replaceWith(other: Self)  //Self – klasa implementująca ten interfejs

}

class ReplaceableEmployee:  Employee, SupportsReplace {

func replaceWith(other:  ReplaceableEmployee) {

         …

}

}

w Swift będzie nielegalna metoda:

func overwrite(original: SupportsReplace, with: SupportsReplace) {

original.replaceWith(with)

}

ale można tak:

func overwrite<T: SupportsReplace>(original: T, with: T) {

original.replaceWith(with)

}

 

protocol Cloneable: class {

func clone() –> Self

}

final class CloneableEmployee:  Employee, Cloneable {

// func clone() –> Self {

//        return self

//}

func clone() –> CloneableEmployee {

      return CloneableEmployee(“xxx”)

}

}

protocol Cloneable: class {

func clone() –> Cloneable

}

class CloneableEmployee: Employee, Cloneable {

func clone() –> Cloneable {

        return CloneableEmployee(“xxx”)

}

}

 

nie ma protokołów generycznych, ale można osiągnąć coś podobnego za pomocą typealias

protocol Container {

typealias T

mutating func append(item: T)

}

class Queue: Container {

typealias T = String  //można pominąć

func append(item: String) { … }

}

func allItemsMatch <C1: Container, C2: Container where C1.T==C2.T, C1.T: Equatable> (first: C1, second: C2) –> Bool {  … }

 

@objc enum ObjcEnum: Int { case X, Y }

@objc protocol ObjcProtocol { // AnyObject, ObjcEnum

func f (x: Int)

func >= (left: Int, right: Int) –> Bool  //przeładowanie operatora

}

class ObjcSubclass: ObjcProtocol {

@objc func f(x: Int ){}

}

class ObjcSubclass: NSObject, ObjcProtocol {

func f(x: Int) {}

}

 

@objc protocol P {

optional var optVar: Int   { get }

optional func optMethod() –> Int

func f()

}

class MyClass: P {

@objc var optVar: Int  { … }   //nie musi być

@objc func optMethod()->Int { … }  //nie musi być

}

opt.optMethod?()

opt.optVar

 

protocol TextRepresentable {

func asText() –> String

}

class Person {

}

extension Person: TextRepresentable {    //rozszerzenie dodające implementację protokołu

func asText() –> String { return “Alibaba” }

}

 

rozszerzenie protokołu

public protocol MyCollection {

typealias T

init( _ args: T… )

func elements() –> [T]

}

public class IntCollection: MyCollection { … }

let obj = IntCollection(1,2,3)

public extension MyCollection where T: SignedIntegerType {

public func sum() –> T { … }  // jeśli klasa sama nie zaimplementuje tej metody, zostanie użyta domyślna implementacja z rozszerzenia

}

obj.sum()

 

Customizacja

public class Employee { … }

extension Employee: Equatable {}  //Equatable wymaga publicznej funkcji ==

public func == (lhs:Employee, rhs: Employee) –> Bool {

return lhs.name == rhs.name

}

podobnie porównania z rozszerzeniem Comparable

do słownika:

extension Employee: Hashable {

public var hashValue: Int { return name.hashValue }

}

 

inicjalizacja stringa liczbą

let number:String = 1234

extension String: IntegerLiteralConvertible {

public init(integerLiteral value: Int) {

          self = “\(value)”

}

}

var s = “0123456789”

s[2]

s[1…3]

s[2] = “b”

s[7..9] = “XYZ”

public extension String {

public subscript(index: Int) –> String {

        get {

                return self.substringWithRange(

                        Range(start: advance(self.startIndex, index),

                                      end: advance(self.startIndex, index+1))

                )

        }

        set {

               self.replaceRange(

                      Range(start: advance(self.startIndex, index),

                       end: advance(self.startIndex, index+1)),

                      with: newValue

               )

        }

}

public substrict(r:Range<Int>) –>String {

        get {

                return self.substringWithRange(

                         Range(start: advance(self.startIndex, r.startIndex),

                                        end:  advance(self.startIndex, r.endIndex)))

        }

        set {

             //podobnie jak set wyżej, tylko zakresy z tego gettera

        }

}

}

wyrażenia regularne

let isALetter = try! Regex(“[a-z]+”)

isALetter.matches(“abcdef”)

public class Regex {

private let pattern: String

private let expression: NSRegularExpression

private let noOptions = NSRegularExpressionOptions(rawValue:0)

public init(_ pattern: String) throws {

        self.pattern = pattern

       try! expression = NSRegularExpression(pattern: pattern, options: noOptions)

}

public func matches(input: String) –> Bool {

      let result = expression.matchesInString(

            input, options: NSMatchingOptions(rawValue:0), range: NSMakeRange(0, input.count))

     return result.count > 0

}

}

użycie w case:

case try! Regex(“[0-9]+”)

public ~= (inCase: Regex, inSwitch: String) –> Bool {

         return inCase.matches(inSwitch)

}

można ładniej:

case ~”[0-9]+”:

prefix operator ~ {}   //zupełnie nowy operator

public prefix func ~ (operand: String) –> Regex {

do { return try Regex(operand) }

catch {

          preconditionFailure(“xxx”)

}

}

isALetter ~ “abc”

“abc” ~ isALetter

isALetter !~ “012”

“012” !~ isALetter

“abc” ~~”[a-z]+”

infix operator ~ {associativity left precedence 130}

infix operator !~ {associativity left precendence 130}

public func ~ (lhs: Regex, rhs: String) –> Bool { return lhs ~= rhs }

public func ~ (lhs: String, rhs: Regex) –> Bool { return rhs ~=lhs }

własny inicjalizator

let q:PriorityQueue<Int, Int> = [ (item:0, priority:0), (1,1) ]

public class PriorityQueue <I, P:Comparable>:  ArrayLiteralConvertible {

public init() {

}

public required init(arrayLiteral elements: (item: I, priority:P)…) {

         for element in elements  {

                 append(element.item, priority: element.priority)

         }

}

}

jeszcze ładniej:

var q:PriorityQueue<Int, Int> = [ 2:2, 3:3 ]

public class PriorityQueue <I, P:Comparable>: ArrayLiteralConvertible, DictionaryLiteralConvertible {

public required init( doctionaryLiteral elements: (I,P)… ) {

          for (item, priority) in elements {  … }        

}

}

własna iteracja

q4 = [ 0:0, 1:1, 2:2 ]

for item in q4 { … }

extension PriorityQueue: SequenceType {

public func generate() –> _PriorityQueueIterator<I> {

        return _PriorityQueueIterator<I>(items: ….)  

}

}

public struct _PriorityQueueIterator <I>: GeneratorType {

private let items: [I]

private var index = 0

      init( items: [I] ) {

      self.items = items

}

public mutating func next() –> I? {

       return index >= items.count ? nil : items[index++]

}

}

1 komentarz:

Wojciech Roszkowski pisze...

Jestem pod wrażeniem. Bardzo fajny wpis.