ملاحظةدقيقتا قراءة

الـ typealias يقبل وسيطًا نوعيًّا

تسمّي معالج الإكمال مرةً واحدة ثم تكتبه Handler<Post> في كل موضع. يعطيك ذلك وضوحًا وموضعًا واحدًا تغيّر فيه التوقيع. ولا يعطيك أمانًا نوعيًّا.

الـ‏typealias يفعل شيئًا واحدًا: يعطي نوعًا موجودًا اسمًا ثانيًا. و‏TimeInterval عند آبل واحدٌ منها، اسمٌ لـ‏Double. يقول ما الذي يعنيه هذا العدد، ويترك المجال لأن يتغيّر النوع تحته دون أن يتغيّر كل موضعٍ يذكره.

والصيغة التي أستعملها أكثر من غيرها تأخذ وسيطًا نوعيًّا:

struct Service {
  typealias Handler<Response> = (Result<Response, APIError>) -> Void

  func fetchPost(id: Int, _ completion: @escaping Handler<Post>) { ... }
  func fetchPosts(_ completion: @escaping Handler<[Post]>) { ... }
  func fetchBooks(_ completion: @escaping Handler<[Book]>) { ... }
}

فبدونه يحمل كلُّ توقيعٍ من هذه (Result<Post, APIError>) -> Void كاملةً، وتصير هيئة الواجهة — أنّ كل نداءٍ يعود بـ‏Result بنوع الخطأ نفسه — شيئًا على القارئ أن يجمعه من ثلاثة أسطرٍ يختلف ظاهرها قليلًا. ومعه تُقال الهيئة مرةً واحدة، ولا يتغيّر إلا نوع الاستجابة.

وهو يمنحك كذلك موضعًا واحدًا للتغيير: فحين ينتقل نوع الخطأ من APIError إلى غيره، ينتقل في سطرٍ واحد لا في كل توقيعٍ يذكره.

والاستعمال الآخر تقصيرُ اسمٍ لم تختره أنت:

final class Service {
  typealias Book = SLBSomeLibraryServiceBookModel

  func loadBooks() -> [Book] { ... }
}

وهذا آمنٌ داخل نوع، حيث يكون الاسم محصورًا ويجد القارئ موضع تعريفه. أما في نطاق الملف فيحتاج إلى حذرٍ أكثر: فـ‏Book من ثلاث وحداتٍ ثلاثةُ أشياء.

والذي لا يمنحك إياه شيءٌ من هذا هو الأمان النوعيّ. فـ‏Handler<Post> و‏(Result<Post, APIError>) -> Void نوعٌ واحد، لا نوعان اتّفق أن كُتبا كتابتين — لا فرق يُلزمك به المترجم، ولا اختيار دالّةٍ يختلف بسببه. فإن أردت أن يعلم المترجم أنّ إكمال هذه الخدمة ليس أيَّ closure، فالاسم المستعار أداةٌ خطأ، والصواب نوعٌ يلفّها.

هو توثيقٌ يفحص المترجم هجاءه. وليس نوعًا.

كتب Antoine van der Lee مقالةً أطول عن الـ‏typealias تستحقّ عشر دقائق.

المواضيع