Property

Property gán giá trị với 1 class, structure hoặc enumeration cụ thể. Stored property lưu giá trị hằng và biến như là 1 phần của đối tượng, khi computed property tính giá trị. Computed property được cung cấp bởi class, structure và enumeration. Stored property được cung cấp bởi class và structure.

Stored và computed property thường gắn với đối tượng của một type cụ thể nhưng cũng có thể gán với type đó 1 cách trực tiếp (type property).

Hơn nữa, bạn có thể định nghĩa property observer để theo dõi thay đổi trong giá trị của property, thứ bạn có thể phản hồi bằng lệnh. Property observer có thể được thêm vào stored property bạn định nghĩa, hoặc vào property mà 1 subclass kế thừa từ 1 superclass.

Stored Property

Ở dạng đơn giản nhất, stored property là một hằng hoặc biến được lưu như là 1 phần của đối tượng của 1 class hoặc structure cụ thể. Stored property có thể là variable stored property (khai báo bằng từ var) hoăc constant stored property (khai báo bằng từ let).

Có thể cấp giá trị mặc định cho 1 stored property trong phần định nghĩa nó, như được tả trong Default Property Values . Có thể gán và sửa giá trị đầu của 1 stored property khi khởi tạo. Điều này đúng kể cả với constant stored property, như được nhắc đến trong Assigning Constant Properties During Initialization.

Ví dụ sau định nghĩa structure FixedLengthRange, nó tả 1 khoảng các số nguyên có độ dài bất biến sau khi được khởi tạo:

struct FixedLengthRange {
var firstValue: Int
let length: Int
}
var rangeOfThreeItems = FixedLengthRange(firstValue: 0, length: 3)
// the range represents integer values 0, 1, and 2
rangeOfThreeItems.firstValue = 6
// the range now represents integer values 6, 7, and 8

Các đối tượng của FixedLengthRange có 1 variable stored property là firstValue và 1 constant stored property là length. Ở ví dụ trên, length được khởi tạo khi 1 khoảng mới được tạo ra và không thể thay đổi sau đó, vì nó là constant property.

Stored Property của đối tượng của structure được đặt làm hằng

Nếu tạo ra 1 đối tượng của 1 structure và gán đối tượng đó với 1 hằng thì không thể thay đổi property của nó, kể cả khi chúng được khai báo là variable property:

let rangeOfFourItems = FixedLengthRange(firstValue: 0, length: 4)
// this range represents integer values 0, 1, 2, and 3
rangeOfFourItems.firstValue = 6
// this will report an error, even though firstValue is a variable property

Vì rangeOfFourItems được khai báo là hằng bằng từ let nên không thể sửa firstValue của nó, kể cả khi firstValue là variable property. Việc này là do structure là kiểu giá trị. Khi 1 đối tượng của kiểu giá trị được đánh dấu là 1 hằng thì mọi property của nó cũng là hằng. Điều này không đúng với class (kiểu đối chiếu). Nếu khai báo 1 đối tượng của kiểu đối chiếu là 1 hằng thì vẫn sửa được property của nó.

 

Lazy Stored Property

Lazy stored property là property mà giá trị đầu không được tính cho đến khi nó được dùng lần đầu. Chỉ ra lazy stored property bằng từ lazy trước khai báo của nó.

NOTE

Luôn khai báo lazy stored property bằng var vì giá trị đầu có thể không được lấy về cho đến khi khởi tạo đối tượng. Constant property phải có giá trị trước khi việc khởi tạo hoàn tất nên không thể thuộc loại lazy.

Lazy property hữu dụng khi giá trị đầu của 1 property phụ thuộc yếu tố bên ngoài chưa rõ giá trị cho đến khi 1 đối tượng được khởi tạo xong. Lazy property còn hữu dụng khi giá trị đầu của 1 property cần setup phức tạp không nên thực hiện cho đến khi cần thiết.

Ví dụ dưới dùng lazy stored property để tránh khởi tạo không cần thiết của 1 class phức tạp. Ví dụ có 2 class là DataImporter và DataManager, cả 2 đều không hiện ra đủ:

class DataImporter {
/*
DataImporter is a class to import data from an external file.
The class is assumed to take a nontrivial amount of time to initialize.
*/
var filename = "data.txt"
// the DataImporter class would provide data importing functionality here
}

class DataManager {
lazy var importer = DataImporter()
var data = [String]()
// the DataManager class would provide data management functionality here
}

let manager = DataManager()
manager.data.append("Some data")
manager.data.append("Some more data")
// the DataImporter instance for the importer property has not yet been created

Class DataManager có 1 stored property là data (array rỗng của các string). Dù phần còn lại của DataManager không hiển thị nhưng mục đích của class này là quản lý và cấp quyền truy cập vào data.

Một phần chức năng của class DataManager là khả năng lấy dữ liệu từ file. Chức năng này có từ class DataImporter (class mất nhiều thời gian để khởi tạo). Điều này có thể là do đối tượng của DataImporter cần mở file và đọc nội dung rồi ghi nhớ khi đối tượng này được khởi tạo.

Đối tượng của DataManager có thể quản lý dữ liệu của nó mà không cần lấy dữ liệu từ file, vậy nên không cần tạo ra đối tượng DataImporter mới khi DataManager được khởi tạo. Sẽ hợp lý hơn nếu tạo đối tượng DataImporter khi cần nó.

Vì được đánh dấu là lazy nên đối tượng DataImporter chỉ được tạo khi property importer được truy cập lần đầu, ví dụ như property filename cần được kiểm tra:

print(manager.importer.filename)
// the DataImporter instance for the importer property has now been created
// Prints "data.txt"

 

NOTE

Nếu 1 lazy property được truy cập bởi nhiều luồng cùng lúc và property này chưa được khởi tạo thì không thể chắc chắn property này sẽ chỉ được khởi tạo 1 lần.

 

Stored Property và Instance Variable

Nếu bạn có kinh nghiệm với Objective-C, bạn có thể biết nó cho 2 cách lưu giá trị và sự đối chiếu như 1 phần của đối tượng của class. Ngoài property, bạn có thể dùng instance variable như 1 chỗ lưu trữ dự phòng cho giá trị được lưu trong 1 property.

Swift gộp các cách này thành 1 khai báo property. 1 Swift property không có 1 instance variable tương ứng, và chỗ lưu dự phòng cho 1 property không được truy cập trực tiếp. Hướng tiếp cận này tránh sự khó hiểu về cách giá trị được truy cập trong nhiều hoàn cảnh và đơn giản hoá việc khai báo đối tượng thành 1 câu dứt khoát. Mọi thông tin về property (tên, loại, tính chất quản lý bộ nhớ) được định nghĩa ở 1 nơi duy nhất như 1 phần của định nghĩa loại.

Computed Property

Ngoài stored property; class, structure và enumeration còn định nghĩa computed property, thứ không thực sự lưu giá trị. Chúng cung cấp getter và có thể cả setter để lấy và xử lý các property và giá trị khác 1 cách gián tiếp.

struct Point {
var x = 0.0, y = 0.0
}
struct Size {
var width = 0.0, height = 0.0
}
struct Rect {
var origin = Point()
var size = Size()
var center: Point {
get {
let centerX = origin.x + (size.width / 2)
let centerY = origin.y + (size.height / 2)
return Point(x: centerX, y: centerY)
}
set(newCenter) {
origin.x = newCenter.x - (size.width / 2)
origin.y = newCenter.y - (size.height / 2)
}
}
}
var square = Rect(origin: Point(x: 0.0, y: 0.0),
size: Size(width: 10.0, height: 10.0))
let initialSquareCenter = square.center
square.center = Point(x: 15.0, y: 15.0)
print("square.origin is now at (\(square.origin.x), \(square.origin.y))")
// Prints "square.origin is now at (10.0, 10.0)"

Ví dụ định nghĩa 3 structure để làm việc với hình:

  • Point bao gồm toạ độ x và y của 1 điểm.
  • Size bao gồm chiều rộng và cao.
  • Rect định nghĩa 1 hình chữ nhật bằng 1 điểm gốc và kích cỡ.

Structure Rect còn cung cấp 1 computed property là center. Vị trí tâm hiện tại của 1 đối tượng của Rect luôn tính được từ origin và size của nó, nên không cần lưu vị trí tâm như 1 giá trị Point. Rect định nghĩa 1 custom getter và setter cho 1 biến tính là center, giúp bạn làm việc với tâm hình như là 1 stored property thật.

Ví dụ trên tạo 1 biến Rect mới gọi là square. Biến square được khởi tạo với gốc là (0, 0), rộng và cao bằng 10. Hình vuông này được thể hiện ở hình dưới.

Biến center của square sau đó được truy cập qua cú pháp dấu "." (square.center). Việc này gọi  getter cho center để lấy giá trị hiện tại của property. Getter không trả về giá trị cũ mà tính ra và trả về 1 Point mới đại diện cho tâm hình vuông. Như có thể thấy ở trên, getter trả về đúng tâm (5, 5).

Property center sau đó được đặt giá trị (15, 15). Việc này dịch hình vuông lên và sang phải, đến vị trí mới thể hiện bằng màu cam trên hình dưới. Việc đặt giá trị cho property center gọi setter cho center, thứ chỉnh lại x và y của property origin và dịch hình đến chỗ mới.

../_images/computedProperties_2x.png
 

Cú pháp khai báo setter thu gọn

Nếu 1 setter của computed property không nêu ra tên cho giá trị mới thì tên mặc định là newValue. Sau đây là phiên bản khác của structure Rect sử dụng cú pháp thu gọn này:

struct AlternativeRect {
var origin = Point()
var size = Size()
var center: Point {
get {
let centerX = origin.x + (size.width / 2)
let centerY = origin.y + (size.height / 2)
return Point(x: centerX, y: centerY)
}
set {
origin.x = newValue.x - (size.width / 2)
origin.y = newValue.y - (size.height / 2)
}
}
}
 

Read-Only Computed Property

Computed property có getter mà không có setter gọi là read-only computed property. Read-only computed property luôn trả về giá trị và có thể truy cập qua cú pháp dấu ".", nhưng không thể gán với giá trị khác.

NOTE

Phải khai báo computed property—bao gồm read-only computed property—là biến bằng từ var vì giá trị của chúng có thể thay đổi. Từ let chỉ dùng với constant property để chỉ rằng giá trị của chúng là hằng.

Có thể đơn giản hoá việc khai báo read-only computed property bằng cách bỏ từ get và {} của nó: 

struct Cuboid {
var width = 0.0, height = 0.0, depth = 0.0
var volume: Double {
return width * height * depth
}
}
let fourByFiveByTwo = Cuboid(width: 4.0, height: 5.0, depth: 2.0)
print("the volume of fourByFiveByTwo is \(fourByFiveByTwo.volume)")
// Prints "the volume of fourByFiveByTwo is 40.0"

Ví dụ này có 1 structure là Cuboid đại diện cho hình hộp chữ nhật có 3 property: width, height, depth. Structure này còn có read-only computed property là volume, thứ tính thể tích hiện tại của hình. Thật không có ý nghĩa nếu volume có thể được gán giá trị vì sẽ không rõ giá trị cho width, height và depth trong trường hợp này. Dù vậy, sẽ có ích nếu Cuboid có read-only computed property giúp người dùng biết thể tích hiện tại của hình.

Property Observer

Property observer theo dõi và phản hồi lại các thay đổi trong giá trị của property. Property observer được gọi đến mỗi khi 1 property được đặt giá trị, kể cả khi giá trị mới giống giá trị hiện tại.

Có thể thêm property observer vào mọi stored property bạn đặt (trừ lazy stored property). Cũng có thể thêm property observer vào mọi property được thừa kế (cả stored và computed) bằng cách ghi đè lên property trong 1 subclass. Không cần đặt property observer cho computed property không bị ghi đè vì có thể theo dõi và phản hồi lại các thay đổi trong giá trị bằng setter. Ghi đè property được tả trong Overriding.

Có thể đặt 1 hoặc 2 loại observer cho 1 property:

  • willSet được gọi trước khi giá trị được lưu.
  • didSet được gọi ngay sau khi giá trị mới được lưu.

Nếu gọi đến willSet thì nó truyền giá trị property mới như 1 tham chiếu hằng. Có thể nêu cụ thể tên cho tham chiếu này trong phần gọi đến willSet. Nếu không viết tên tham chiếu trong 2 dấu ngoặc tròn thì tên mặc định là newValue.

Tương tự, nếu gọi đến didSet thì nó truyền 1 tham chiếu hằng chứa giá trị property cũ. Có thể đặt tên cho tham chiếu này hoặc dùng tên mặc định là oldValue. Nếu gán giá trị cho property trong didSet của nó thì giá trị mới được gán thay thế cho giá trị cũ.

NOTE

willSet và didSet của property của superclass được gọi đến khi 1 property được đặt trong khởi tạo của 1 subclass, sau khi superclass khởi tạo. Chúng không được gọi đến khi 1 class đang đặt property của chính nó, trước khi khởi tạo superclass.

Để có thêm thông tin về uỷ quyền khởi tạo, xem Initializer Delegation for Value Types and Initializer Delegation for Class Types.

Đây là ví dụ cho willSet và didSet. Ví dụ định nghĩa 1 class mới tên StepCounter (theo dõi số bước 1 người đi). Class này có thể được dùng với dữ liệu từ máy đếm bước để theo dõi quá trình tập luyện hằng ngày. 

class StepCounter {
var totalSteps: Int = 0 {
willSet(newTotalSteps) {
print("About to set totalSteps to \(newTotalSteps)")
}
didSet {
if totalSteps > oldValue {
print("Added \(totalSteps - oldValue) steps")
}
}
}
}
let stepCounter = StepCounter()
stepCounter.totalSteps = 200
// About to set totalSteps to 200
// Added 200 steps
stepCounter.totalSteps = 360
// About to set totalSteps to 360
// Added 160 steps
stepCounter.totalSteps = 896
// About to set totalSteps to 896
// Added 536 steps

Class StepCounter có property totalSteps nhận giá trị Int. Đây là stored property với willSet và didSet observer.

willSet và didSet của totalSteps được gọi đến khi property này được gán giá trị mới (kể cả khi giá trị mới giống giá trị cũ).

willSet của ví dụ này dùng tên tham chiếu tự đặt là newTotalSteps cho giá trị mới. Nó chỉ in ra giá trị sắp được đặt.

didSet được gọi sau khi giá trị của totalSteps được cập nhật. Nó so sánh giá trị cũ và mới. Nếu tổng số bước tăng thì tin nhắn được in ra với nội dung đã thêm bao nhiêu bước. didSet không có tên tự đặt cho giá trị cũ nên dùng tên mặc định là oldValue.

NOTE

Nếu truyền đi property có observer đến đến 1 hàm như 1 tham chiếu in-out, willSet và didSet luôn được gọi đến. Điều này là do mô hình ghi nhớ copy-in copy-out cho tham chiếu in-out: giá trị luôn được viết lại về property lúc cuối hàm. Để biết thêm thông tin chi tiết về hành vi của tham chiếu in-out, xem In-Out Parameters.

Biến global and local

Khả năng theo dõi và tính property được tả trên cũng có bởi biến global và biến local. Biến global là biến định nghĩa ngoài hàm, method, closure, hoặc type context. Biến local là biến định nghĩa trong hàm, method, hoặc closure context.

Biến global và local gặp ở các chương trước đều là stored variable. Stored variable, cũng như stored property, cung cấp bộ nhớ cho giá trị thuộc 1 loại xác định và cho phép giá trị đó được đặt và lấy.

Cũng có thể định nghĩa computed variable và định nghĩa observer cho cho stored variable, trong phạm vi global hoặc local. Computed variable  tính giá trị chứ không lưu, và chúng được viết giống computed property.

NOTE

Hằng và biến global luôn được tính kiểu lazy, giống như Lazy Stored Properties. Khác lazy stored property, hằng và biến global không cần đánh dấu bằng từ lazy.

Hằng và biến local không bao giờ được tính kiểu lazy.

Type Property

Instance property là property thuộc về đối tượng của một kiểu xác định. Mỗi khi tạo 1 đối tượng mới của kiểu đó, nó có bộ giá trị property tách biệt với các đối tượng khác.

Cũng có thể định nghĩa property thuộc về kiểu chứ không phải đối tượng. Sẽ luôn chỉ có 1 copy của các property này bất kể số đối tượng được tạo ra. Các property kiểu này là type property.

Type property hữu dụng khi cần tạo các giá trị chung cho mọi đối tượng của 1 kiểu xác định, ví dụ như constant property mà mọi đối tượng có thể dùng (giống static constant trong C) hay variable property lưu giá trị chung cho mọi đối tượng của kiểu đó (giống static variable trong C).

Stored type property có thể là hằng hay biến. Computed type property luôn là biến, giống như với computed instance property.

NOTE

Không như stored instance property, phải cho stored type property giá trị mặc định. Điều này là vì kiểu bản thân nó không có khởi tạo có khả năng gán giá trị cho stored type property vào thời điểm khởi tạo.

Stored type property được khởi tạo kiểu lazy lần truy cập đầu. Chúng chắc chắn chỉ được khởi tạo 1 lần duy nhất, kể cả khi được truy cập bởi nhiều luồng cùng lúc, và cũng không cần đánh dấu bằng từ lazy.

Cú pháp của type property

Trong C và Objective-C, định nghĩa hằng và biến static gắn với kiểu là global static variable. Trong Swift, type property được viết như 1 phần của định nghĩa kiểu (trong ngoặc nhọn ngoài) và mỗi type property có phạm vi trong type nó hỗ trợ.

Định nghĩa type property bằng từ static. Với computed property của kiểu class, có thể dùng từ class để cho phép subclass ghi đè lên quy trình chạy superclass. Sau đây là ví dụ của cú pháp cho stored và computed type property: 

struct SomeStructure {
static var storedTypeProperty = "Some value."
static var computedTypeProperty: Int {
return 1
}
}
enum SomeEnumeration {
static var storedTypeProperty = "Some value."
static var computedTypeProperty: Int {
return 6
}
}
class SomeClass {
static var storedTypeProperty = "Some value."
static var computedTypeProperty: Int {
return 27
}
class var overrideableComputedTypeProperty: Int {
return 107
}
}

NOTE

Các ví dụ computed property của kiểu ở trên là cho read-only computed type property, nhưng có thể định nghĩa read-write computed type property bằng cú pháp tương tự như với computed instance property.

 

Truy cập và đặt type property

Type property được truy cập và đặt với cú pháp dấu ".", giống instance property. Tuy vậy, type property được được truy cập và đặt trên kiểu chứ không phải trên đối tượng của kiểu. Ví dụ:

print(SomeStructure.storedTypeProperty)
// Prints "Some value."
SomeStructure.storedTypeProperty = "Another value."
print(SomeStructure.storedTypeProperty)
// Prints "Another value."
print(SomeEnumeration.computedTypeProperty)
// Prints "6"
print(SomeClass.computedTypeProperty)
// Prints "27"

Ví dụ tiếp theo dùng 2 stored type property trong 1 structure mô phỏng thước đo mức âm cho 1 số kênh âm thanh. Mỗi kênh có 1 mức âm là số nguyên từ 0 đến 10.

Hình dưới minh hoạ cách các cặp kênh này có thể kết hợp để mô phỏng thước đo mức âm cho 1 cái đài. Khi mức âm của 1 kênh là 0, không đèn nào của kênh đó sáng. Khi mức âm là 10, mọi đèn của kênh đó sáng. Trong hình, kênh bên trái có mức âm 9, kênh bên phải là 7:

../_images/staticPropertiesVUMeter_2x.png
 

Kênh âm thanh tả trên được đại diện bởi đối tượng của structure AudioChannel:

struct AudioChannel {
static let thresholdLevel = 10
static var maxInputLevelForAllChannels = 0
var currentLevel: Int = 0 {
didSet {
if currentLevel > AudioChannel.thresholdLevel {
// cap the new audio level to the threshold level
currentLevel = AudioChannel.thresholdLevel
}
if currentLevel > AudioChannel.maxInputLevelForAllChannels {
// store this as the new overall maximum input level
AudioChannel.maxInputLevelForAllChannels = currentLevel
}
}
}
}

Structure AudioChannel định nghĩa 2 stored type property để hỗ trợ chức năng của nó. Cái đầu, thresholdLevel, định nghĩa ngưỡng trên mà mức âm có thể đến. Nó là hằng số 10 cho mọi đối tượng của structure này. Nếu tín hiệu đến với giá trị lớn hơn 10, nó sẽ bị chặn ở mức 10.

Type property thứ 2 là variable stored property maxInputLevelForAllChannels. Property này theo dõi giá trị đầu vào tối đa mà bất kì đối tượng nào của structure thu được. Nó bắt đầu với giá trị 0.

Structure này còn định nghĩa 1 stored instance property là currentLevel, thứ đại diện cho mức âm hiện tại của kênh (từ 1 đến 10).

currentLevel có didSet để kiểm tra giá trị mỗi khi nó được đặt. Observer này thực hiện 2 kiểm tra:

  • Nếu giá trị mới lớn hơn thresholdLevel, chặn currentLevel tại thresholdLevel.
  • Nếu giá trị mới (sau khi chặn nếu có) lớn hơn mọi giá trị trước đó đối tượng nhận, lưu giá trị mới làm maxInputLevelForAllChannels.

NOTE

Trong kiểm tra đầu, didSet đặt cho currentLevel giá trị mới nhưng không làm cho observer bị gọi lại.

Có thể dùng AudioChannel để tạo 2 kênh âm thanh mới là leftChannel và rightChannel, để đại diện 2 mức âm của 1 cái đài:

var leftChannel = AudioChannel()
var rightChannel = AudioChannel()

Nếu đặt currentLevel của leftChannel là 7, có thể thấy maxInputLevelForAllChannels được cập nhật thành 7:

leftChannel.currentLevel = 7
print(leftChannel.currentLevel)
// Prints "7"
print(AudioChannel.maxInputLevelForAllChannels)
// Prints "7"

Nếu đặt currentLevel của rightChannel là 11, có thể thấy currentLevel này bị chặn tại max là 10 và maxInputLevelForAllChannels được cập nhật thành 10:

rightChannel.currentLevel = 11
print(rightChannel.currentLevel)
// Prints "10"
print(AudioChannel.maxInputLevelForAllChannels)
// Prints "10"