Swift – Properties

Po kratší odmlce další díl. Tentokrát na téma properties. Žádné ivary, properties u struktur, výčtů, … Spousta novinek.

Máme stored properties, computed properties, které existují per instance. Potom máme type stored properties, type computed properties, které existují v jedné kopii bez ohledu na počet instancí a jsou svázány s typem (něco jako static  v C). Můžeme je definovat u tříd, struktur a výčtů. Ale u properties je to zatím taky mírně nekonzistentní, protože ne všechny kombinace jsou možné.

Stored properties – pouze třídy a struktury, nelze u výčtů.

Computed properties – třídy, struktury a výčty.

Type stored properties – struktury a výčty, nelze u tříd (bude).

Type computed properties – třídy, struktury a výčty.

Jinak řečeno, všechno jde až na:

  • type stored properties nelze definovat u tříd,
  • stored properties nelze definovat u výčtů.

Co jednotlivé typy properties znamenají se dočtete níže.

Stored properties

Převezmeme jednoduchý příklad z knížky.

struct FixedLengthRange {
  var location: Int // promenna
  let length: Int   // konstanta
}

var range = FixedLengthRange( location: 0, length: 2 )

range.location = 2
// Lze zmenit, jde o promennou

range.length = 3
// Chyba 'Cannot assign', jde o konstantu

Pokud celou strukturu přiřadíme do konstanty, není možné modifikovat její properties, přestože jsou to proměnné.

let constRange = range

constRange.location = 3
// Chyba 'Cannot assign', prestoze je location promenna, cela struktura je nyni
// konstanta a proto nejdou menit ani promenne, ktere jsou jeji soucasti

Toto chování neplatí pro třídy. Pouze pro struktury.

Lazy stored properties

Musí být vždy proměnná a ne konstanta. Property se inicializuje až před prvním použitím.

import Foundation

class DateFormatter {
  @lazy var foundationFormatter: NSDateFormatter = NSDateFormatter( dateFormat: "%Y-%m", allowNaturalLanguage: false )

  func formattedDate( date: NSDate? ) -> String {
    return self.foundationFormatter.stringFromDate( date )
  }
}

var df = DateFormatter()

// v tuhle chvili neni df.foundationFormatter property
// inicializovana, protoze jeste nebyla pouzita

df.formattedDate( NSDate.date() ) // "2014-06"

// ted uz je, protoze property foundationFormatter byla pouzita ve funkci
// formattedDate

Globální proměnné a konstanty jsou by default inicializovány stejně jako @lazy properties, tj. až při prvním použití.

Klíčové slovo @lazy  lze použít pouze ve strukturách a třídách.

Stored properties & ivar

V Objective-C jste mohli definovat property, k tomu svoji ivar, která držela reálnou hodnotu / referenci pro property. Na to teď zapomeňte, Swiftu to neumožňuje, všechno je sjednoceno.

Computed properties

Poměrně často se mi stává, že potřebuji další property jejíž hodnota je vypočítána z jiných properties. K tomu slouží computed properties. Tzn. hodnota není nikde uložená a je závislá na jiných. To ale neznamená, že nemá setter a nemůže nic modifikovat. Typickým příkladem je struktura Frame  a property center .

struct Point {
  var x: Float = 0       // stored
  var y: Float = 0       // stored
}

struct Size {
  var width: Float = 0   // stored
  var height: Float = 0  // stored
}

struct Frame {
  var origin: Point      // stored
  var size: Size         // stored

  var center: Point {    // computed
    get {
      let centerX = self.origin.x + self.size.width / 2
      let centerY = self.origin.y + self.size.height / 2
      return Point( x: centerX, y: centerY )
    }
    set( newCenter ) {
      let centerX = newCenter.x - self.size.width / 2
      let centerY = newCenter.y - self.size.height / 2
      self.origin = Point( x: centerX, y: centerY )
    }
  }
}

Setter se dá ještě zkrátit. Pokud neuvedeme název, by default je nová hodnota v newValue .

set {
  let centerX = newValue.x - self.size.width / 2
  let centerY = newValue.y - self.size.height / 2
  self.origin = Point( x: centerX, y: centerY )
}

Setter je optional. Pokud není definován, jedná se o read only property. Všechny computed properties (i read only) musí být proměnné (var ). A pokud máme jenom read only computed property, dá se zkrátit na:

var center: Point {    // computed
  let centerX = self.origin.x + self.size.width / 2
  let centerY = self.origin.y + self.size.height / 2
  return Point( x: centerX, y: centerY )
}

Observers

Zapomeňte na KVO, KVC, … Swift je beta, neumí všechno co známe z Objective-C, a tak jedinou cestou (zatím) je.

struct Person {
  var name: String {
    willSet( newName ) {
      println( "Will set 'name' to '(newName)'" )
    }
    didSet( oldName ) {
      println( "Did change 'name' value '(oldName)' to '(name)'" )
    }
  }
}

var p = Person( name: "Robert" )
p.name = "Pepa"

U observerů můžeme vynechat názvy a potom se kód změní na.

struct Person {
  var name: String {
    willSet {
      println( "Will set 'name' to '(newValue)'" )
    }
    didSet {
      println( "Did change 'name' value '(oldValue)' to '(name)'" )
    }
  }
}

Tj. newName  -> newValue , oldName  -> oldValue .

Hodnotu property můžete změnit i v jejím didSet  observeru. Následující příklad ukazuje jak kontroluji hodnotu, kterou chci mít uloženou v property progress .

class ProgressBar {
  var progress: Float = 0 {
    didSet {
      if progress > 1.0 {
        progress = 1.0
      } else if progress < 0.0 {
        progress = 0.0
      }
    }
  }
}

var pb = ProgressBar()

pb.progress         // 0.0
pb.progress = 2.0   // 1.0
pb.progress = -1.0  // 0.0
pb.progress = 0.5   // 0.5

Globální a lokální proměnné

Globální znamená, že je definovaná mimo funkci, strukturu, třídu, closure a typ. Stejně tak jako máme computed properties, můžeme mít i computed variables. A definovat u nich observery. Ať už pro globální, tak pro lokální proměnné.

var price: Double = 0 {
  willSet {
    "New price will be (newValue) CZK"
  }
  didSet {
    "Price was set to (price) CZK"
  }
}

let halerPriceRatio: Double = 100.0

var halerPrice: Int {
  get {
    return Int( price * halerPriceRatio )
  }
  set {
    price = Double( newValue ) / halerPriceRatio
  }
}

halerPrice                      // 0
price                           // 0.0
price = 100                     // 100
// New price will be 100.0 CZK
// Price was set to 1.0 CZK
halerPrice                      // 10 000
halerPrice = 100                // 100
// New price will be 1.0 CZK
// Price was set to 1.0 CZK
price                           // 1.0

Jak jsem už zmínil, globální proměnné a konstanty jsou inicializovány by default jako @lazy . Lokální konstanty a proměnné nejsou @lazy .

class Greeting {
  func greet() {
    println( "Hallo" )
  }
}

let g = Greeting()

// g = porad nil
g.greet()
// prvni pouziti g, pred zavolanim funkce greet()
// probehla lazy inicializace

Type properties

Všechny výše uvedené ukázky properties se týkají instancí. Jinak řečeno, každá instance má svoje oddělené properties. Ve Swiftu je možné definovat tzv. type properties. Něco jako statické konstanty / proměnné v C. Type properties jsou sdílené, existuje jenom jeden exemplář bez ohledu na počet instancí a přistupuje se k nim přes jednotlivé typy.

Type stored property musí mít výchozí hodnotu, protože typy nemají inicializátor.

Type properties se definují stejně, jen se přidává klíčové slovo class  pro třídy a static  pro ostatní typy (value type). Třídy podporují pouze computed properties (zatím, budou) a struktury & výčty podporují computed i stored properties.

class Person {
  class var className: String {
    return "Person"
  }
}

Person.className   // "Person"

Struktury:

struct Example {
  static var sharedStoredString      = "S"
  static let sharedStoredStringConst = "SC"

  static var computedReadOnlySharedString: String {
    return sharedStoredString
  }

  static var computedSharedStringAlias: String {
    get {
      return sharedStoredString
    }
    set {
      sharedStoredString = newValue
    }
  }

  var name: String
}

Example.sharedStoredString                    // "S"
Example.sharedStoredStringConst               // "SC"
Example.computedSharedStringAlias = "SSSS"
Example.sharedStoredString                    // "SSSS"

Výčty:

enum Names {
  static var sharedStoredString      = "E"
  static let sharedStoredStringConst = "EC"

  static var computedReadOnlySharedString: String {
    return sharedStoredString
  }

  static var computedSharedStringAlias: String {
    get {
      return sharedStoredString
    }
    set {
      sharedStoredString = newValue
    }
  }

  case Pepa, Robert, Franta
}

Names.sharedStoredString                    // "E"
Names.sharedStoredStringConst               // "EC"
Names.computedSharedStringAlias = "Names"
Names.sharedStoredString                    // "Names"

Závěrem, pokud vám něco vadí, reportujte to Applu v Radaru v kategorii Developer Tools. Apple je u Swiftu nezvykle vstřícný (co mu zbývá 🙂 a hodně poslouchá. Reportujte i duplikáty. Čím více stejných bug reportů, feature requestů, tím dříve se toho dočkáme. Apple podle toho prioritizuje co bude v oblasti Swiftu dělat. Příště mrknem na metody.

P.S. Všechny tyhle články jsou takový crash course, takže případné chyby komentujte, opravím. A nezapomínejte, že detailnější informace získáte v knížce The Swift Programming Language. Free na iBookstore.