Trong Swift, bất kỳ đối tượng nào có phương thức callAsFunction đều có thể được gọi giống như một hàm. Các đối tượng như vậy được đặt tên là callable.

Cú pháp gọi hàm chuyển tiếp các đối số tới phương thức callAsFunction tương ứng [SE-0253]:

struct WannabeFunction {
    func callAsFunction() {
        print("Hi there")
    }
}

let f = WannabeFunction()
f() // Hi there

Có ba cách để gọi callAsFunction() với f:

let f = WannabeFunction()

f() // Callable sugar
f.callAsFunction() // Member function sugar
WannabeFunction.callAsFunction(f)() // No sugar

Phương thức callAsFunction() tuân theo tất cả các quy tắc của hàm trong Swift. Nó có thể được khai báo trong một phần mở rộng:

struct WannabeFunction {}

extension WannabeFunction {
    func callAsFunction() { ... }
}

Và ghi đè:

struct WannabeFunction {
    func callAsFunction() { print(#function) }

    // Argument label overloading
    func callAsFunction(x: Int) { print(#function, x) }
    func callAsFunction(y: Int) { print(#function, y) }

    // Argument type overloading
    func callAsFunction(x: String) { print(#function, x) }

    // Generic type constraint overloading
    func callAsFunction<T>(value: T) where T: Numeric { print(#function, value) }
    func callAsFunction<T>(value: T) where T: StringProtocol { print(#function, value) }
}

let f = WannabeFunction()
f() // callAsFunction()
f(x: 1) // callAsFunction(x:) 1
f(y: 2) // callAsFunction(y:) 2
f(value: 3) // callAsFunction(value:) 3
f(value: "str") // callAsFunction(value:) str
f([1, 2, 3]) // ❌ Error: Type of expression is ambiguous without more context

Hoặc gán:

let foo = f.callAsFunction(y:)
foo(10) // callAsFunction(y:) 10

CallAsFunction() có mang lại cho Swift bất cứ điều gì hơn là một cú pháp khác không? Hãy cùng tìm hiểu bằng cách kiểm tra các trường hợp sử dụng bên dưới.

Types representing functions (Đại diện chức năng)

Các loại đại diện cho các hàm, chẳng hạn như các phép toán hoặc machine learning, thường chứa một phương thức chính duy nhất, phương thức này có thể được tạo đường thuận tiện bằng cách sử dụng cú pháp callable.

Một ví dụ nổi bật là TensorFlow. Giao thức Layer, đại diện cho một lớp mạng thần kinh, chứa yêu cầu phương thức callAsFunction (_ :).

Một ví dụ khác là bộ giảm thiểu từ kiến ​​trúc Redux. Một ví dụ thúc đẩy có thể được tìm thấy trong Kiến trúc Composable.

Trình phân tích cú pháp, máy tính, lệnh shell thuộc loại này.

Passing generic functions (Truyền các chức năng chung)

Việc tham chiếu và chuyển các hàm chung chung sẽ không thể thực hiện được nếu không có cú pháp callable. Lỗi trình biên dịch xuất hiện nếu cố gắng tham chiếu đến một hàm chung:

func bar<T>(_ x: T) { print(x) }
let f = bar<Int>() // ❌ Error: Cannot explicitly specialize a generic function

Tuy nhiên, chúng ta có thể đạt được điều này bằng cách sử dụng callAsFunction():

struct Bar<T> {
    var f: (T) -> Void
    func callAsFunction(_ x: T) { f(x) }
}

let f = Bar<Int> { print($0) }
f(1)

Mặc dù trên thực tế f là một đối tượng callable, nhưng nó có thể được coi là một hàm chung tại một điểm sử dụng.

Function identity (Chức năng nhận dạng)

Hai chức năng được coi là bằng nhau nếu đối với mỗi đầu vào, chúng tạo ra cùng một đầu ra. Cho rằng một số danh mục là vô hạn, cách duy nhất có thể để đạt được điều này là gói các hàm vào một loại danh nghĩa (norminal type).

Trường hợp sử dụng tiếp theo cho thấy cách chúng ta có thể thêm IdentifiableHashable, và Equatable vào một hàm bằng cách gói nó thành một kiểu norminal type. 

Phương thức callAsFuntion() cho phép chúng ta gọi trình bao bọc bằng cú pháp giống như hàm:

struct Function<Input, Output>: Identifiable {
    let id: UUID
    let f: (Input) -> Output

    init(id: UUID = UUID(), f: @escaping (Input) -> Output) {
        self.id = id
        self.f = f
    }

    func callAsFunction(_ input: Input) -> Output {
        f(input)
    }
}

extension Function: Equatable {
    static func == (lhs: Function<Input, Output>, rhs: Function<Input, Output>) -> Bool {
        lhs.id == rhs.id
    }
}

extension Function: Hashable {
    func hash(into hasher: inout Hasher) {
        hasher.combine(id)
    }
}

Bound functions (Chức năng ràng buộc)

Ứng dụng từng phần và các liên kết đóng có thể được mô hình hóa bằng cú pháp callable.

Partial Application (Ứng dụng từng phần) là một kỹ thuật lập trình hàm có nghĩa là ràng buộc một số đối số vào một hàm mà không đánh giá đầy đủ nó.

Đây là một hàm add() sau đó được áp dụng một phần:

func add(_ x: Int) -> (_ y: Int) -> Int {
    { y in return x + y }
}

let addTwo = add(2)
print(addTwo(3)) // 5

Kỹ thuật tương tự có thể được mô hình hóa bằng cú pháp callable:

// 1.
struct Sum {
    var x: Int
    func callAsFunction(_ y: Int) -> Int { x + y }
}

// 2.
let addTwo = Sum(x: 2)
print(addTwo(3)) // 5

Dưới đây là những điểm nổi bật chính:

  1. Khai báo kiểu Sum, cho phép tính toán lại phép cộng số học.
  2. Đối số 2 được lưu trữ trong thể hiện Struct, về cơ bản giới hạn 2 với phương thức callAsFunction().

Delegation (Sự uỷ nhiệm)

Kiểu delegation auto-weak, sử dụng callAsFunction() để cải thiện khả năng đọc tại một trang web:

class Delegate<Input, Output> {
    init() {}

    private var block: ((Input) -> Output?)?
    func delegate<T: AnyObject>(on target: T, block: ((T, Input) -> Output)?) {
        self.block = { [weak target] input in
            guard let target = target else { return nil }
            return block?(target, input)
        }
    }

    func call(_ input: Input) -> Output? {
        return block?(input)
    }

    func callAsFunction(_ input: Input) -> Output? {
        return call(input)
    }
}

Giả sử có một DataLoader tìm nạp dữ liệu và thông báo khi hoàn tất:

class DataLoader {
    let onLoad = Delegate<(), Void>()
    func loadData() {
        onLoad(())
    }
}

Sau đó, trong lệnh gọi lại onLoad, chúng tôi nhận được weakself và không cần phải viết [weak self] mỗi lần:

class ViewController: UIViewController {
    let loader = DataLoader()

    override func viewDidLoad() {
        super.viewDidLoad()
        loader.loadData()
        loader.onLoad.delegate(on: self) { (self, _) in
            self.setupLoadedUI()
        }
    }

    func setupLoadedUI() {}
}

Nguồn bài viết!