Scope

Khái niệm về scope hay còn gọi là phạm vi cho biết rằng, nếu bạn đã khai báo một biến hoặc một hàm trong phạm vi đó, bạn không thể sử dụng nó ở nơi khác. 

Chúng ta sẽ bắt đầu với một ví dụ sau:

func getAge() -> Int
{
    var age = 42
    age += 1

    return age
}

var age = 99
var anotherAge = getAge()
anotherAge += 1

print(age)
print(anotherAge)

Trong ví dụ trên, có 2 scope: local scope (phạm vi cục bộ) và global scope (phạm vi toàn cầu / toàn cục):

  • Phạm vi cục bộ trong hàm getAge(), đó là lý do tại sao nó thường được gọi là phạm vi hàm. Biến age, được khai báo bên trong hàm, không thể được truy cập từ bên ngoài.
  • Phạm vi toàn cầu hiện diện ở khắp mọi nơi - đó là lý do tại sao nó được gọi là toàn cầu. Các biến được xác định ở cấp cao nhất của mã, tức là bên ngoài các hàm (function), lớp (class), v.v., có thể được sử dụng ở mọi nơi. (Tuy nhiên, vẫn có những ngoại lệ.)

Bây giờ, hãy xem lại mã đó. Độ tuổi của biến được xác định bên trong hàm getAge(). Chúng ta không thể truy cập cùng một biến đó bên ngoài hàm.

  1. Một biến tuổi được định nghĩa bên trong hàm getAge(), với var age = 42, trên dòng đầu tiên của hàm.
  2. Một biến tuổi được định nghĩa bên ngoài (bên dưới) hàm getAge(), với var age = 99. 

Hai biến này có cùng tên, nhưng chúng được khai báo trong các phạm vi khác nhau. Các vùng mã của chúng - phạm vi của chúng - không xung đột với nhau. Đó là lý do tại sao chúng ta có thể sử dụng cả hai, cùng một tên, riêng biệt!

Khi mã chạy, một biến age được khởi tạo với giá trị 99. Sau đó, khởi tạo một biến có tên anotherAge với giá trị được trả về từ hàm getAge(), là 43. Sau đó, giá trị đó được tăng lên một.

Cuối cùng, in ra các giá trị của các biến này. Giá trị của age là 99, bởi vì nó không thay đổi. Giá trị của anotherAge là 44. Nó được khởi tạo là 42, tăng dần bên trong hàm và tăng bên ngoài hàm. Mặc dù 2 trong số 3 biến này có cùng tên nhưng chúng không xung đột với nhau, vì chúng được khai báo trong các phạm vi khác nhau.

Global, local, function & class scope

Ở trên, chúng ta mới chỉ xem xét đến phạm vi toàn cầu (global scope) và phạm vi địa phương (local scope). Ngoài ra còn có một thứ gọi là phạm vi hàm (scope function) và các lớp (class) cũng có phạm vi. 

Hãy bắt đầu với class đơn giản:

class Product
{

}

 Theo như những gì chúng ta thấy, lớp này được định nghĩa trong phạm vi toàn cục. Bây giờ chúng ta sẽ thêm một kiểu liệt kê vào lớp này, dưới dạng một kiểu lồng nhau. Như thế này:

class Product
{
    var kind:Kind = .thing

    enum Kind {
        case food
        case thing
    }
}

Trong đoạn mã trên, chúng ta đã xác định một enum được gọi là Kind. Nó có hai trường hợp là food (có thể ăn được) hoặc thing (không thể ăn). Và một biến kind, có giá trị ban đầu là .thing

  1. Phạm vi của lớp Product là toàn cầu. Nó được xác định trên toàn cầu, vì vậy có thể tạo các đối tượng Product ở bất kỳ đâu trong mã.
  2. Phạm vi của Kind enum được giới hạn trong lớp. Chúng ta có thể sử dụng loại Kind bên trong lớp chứ không phải bên ngoài nó.
  3. Phạm vi của thuộc tính kind cũng được giới hạn trong lớp. Chúng ta có thể sử dụng thuộc tính đó bên trong lớp.

Hãy thêm một hàm canEat() vào lớp Product:

class Product
{
    var kind:Kind = .thing

    enum Kind {
        case food
        case thing
    }

    func canEat() -> Bool {
        return kind == .food
    }
}

Bây giờ bài toán đang giải quyết 3 cấp phạm vi ở đây:

  1. Phạm vi toàn cầu, trong đó lớp Product được xác định
  2. Phạm vi lớp, trong đó thuộc tính kind, Kind enum và hàm canEat() được xác định
  3. Phạm vi hàm, bên trong hàm canEat()

Lớp Product được xác định trong phạm vi toàn cầu, vì vậy có thể sử dụng lớp đó ở bất kỳ đâu trong mô-đun của ứng dụng. Thuộc tính kind được định nghĩa trong phạm vi lớp, vì vậy có thể sử dụng thuộc tính đó trong lớp Product. Tương tự với Kind enum và hàm canEat().

Kết luận:

  • Mọi vùng trong mã của bạn, những thứ nằm giữa dấu ngoặc nhọn, đều có phạm vi: phạm vi toàn cục, phạm vi lớp, phạm vi chức năng, v.v.
  • Chúng ta hường phân biệt giữa phạm vi cục bộ và phạm vi toàn cầu, để thể hiện liệu chúng ta có quyền truy cập vào một biến thể, thuộc tính hoặc loại nhất định hay không
  • Phạm vi được phân cấp, có nghĩa là chúng ta có thể truy cập thuộc tính lớp bên trong một hàm lớp, vì hàm có quyền truy cập vào phạm vi lớp

Phạm vi phát triển trong iOS thực tế

Phạm vi ở khắp mọi nơi trong quá trình phát triển iOS thực tế hàng ngày. Khi bạn đang di chuyển các giá trị trong ứng dụng của mình, việc theo dõi các biến, loại, v.v. mà bạn có quyền truy cập, là một hoạt động liên tục.

Một trường hợp thú vị về phạm vi là các closures.

Như bạn có thể biết, bao đóng là một khối mã có thể được chuyển xung quanh mã của bạn. Nó tương tự như một hàm, ngoại trừ bản thân mã là giá trị. Bạn có thể gán một bao đóng cho một biến, chuyển nó vào một hàm, sau đó nó sẽ kết thúc trong một phần khác của chương trình của.

Bao đóng thường được sử dụng như cái gọi là trình xử lý hoàn thành. Giả sử chúng ta đang tải xuống một hình ảnh không đồng bộ từ internet. Khi quá trình tải xuống hoàn tất, tại một thời điểm trong tương lai, chúng ta muốn thực thi một số mã để hiển thị hình ảnh. Chúng ta xác định mã này trong một bao đóng, chuyển nó cho hàm tải xuống hình ảnh. Sau đó, hàm này thực hiện quá trình đóng khi quá trình tải xuống hoàn tất.

Tại đây, hãy kiểm tra điều này:

class DetailViewController: UIViewController
{
    @IBOutlet weak var imageView:UIImageView?

    func viewDidLoad()
    {
        network.downloadImage(url, completionHandler: { image in
            imageView?.image = image
        })
    }
}

 Trong đoạn mã trên đã tạo một lớp bộ điều khiển chế độ xem với thuộc tính imageView. Bên trong hàm gọi một hàm downloadImage (_: completeHandler :) giả định. Tham số thứ hai, trình xử lý hoàn thành, giữa các dấu ngoặc nhọn trong cùng, là một bao đóng. 

Khi quá trình tải xuống hình ảnh hoàn tất, chúng ta sẽ gán giá trị hình ảnh đã tải xuống cho thuộc tính hình ảnh của imageView, thuộc tính này sẽ hiển thị hình ảnh.

Cảm ơn đã theo dõi và hẹn gặp lại!