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.2 i iOS, 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++]
}
}