يُبقي حساب Swift المؤجّل مالكه حيًا عندما تلتقط مهمته ذلك المالك بمرجع قوي. استخدم التقاطًا ضعيفًا للسماح باختفاء المالك أثناء الانتظار، أو التقط لقطة مستقلة للقيم إذا كان ينبغي للحساب أن يكتمل من دونه.
بالنسبة إلى مطوّر Mac يبني معاينة حسابية صغيرة لسطح المكتب، تتمثل المهمة العملية في اختيار مدة بقاء الكائن عمدًا. ينتظر هذا المثال قليلًا، ويجمع القيم الاصطناعية [4, 8, 12]، ثم ينسّق الإجمالي. يجري كل شيء محليًا في الذاكرة.
ما مدة البقاء التي ينبغي أن تكون لحسابك؟
تتيح لك قوائم الالتقاط في Swift تحديد ما يحتفظ به الإغلاق. يحتفظ الالتقاط القوي بالمثيل المشار إليه؛ أما المرجع الضعيف فيسمح بإلغاء تخصيصه، وتصبح قيمته nil بعد ذلك. هذه سلوكيات موثقة في اللغة، ولا تعتمد على مدة انتظار الحساب. العدّ المرجعي التلقائي
حدّد السلوك المطلوب قبل اختيار صياغة الالتقاط. يطبّق جدول القرار التالي قواعد اللغة هذه على معاينة سطح مكتب مؤجّلة:
| السلوك المطلوب | استراتيجية الالتقاط | ماذا يحدث بعد أن يحرر المستدعي مالكه؟ |
|---|---|---|
| الإكمال باستخدام المالك نفسه | [self] |
تُبقي المهمة المالك متاحًا لإنجاز عملها. |
| المتابعة فقط إذا ظل المالك متاحًا | [weak self] |
تتحقق المهمة بعد الانتظار مما إذا كان المالك لا يزال موجودًا. |
| الإكمال باستخدام القيم المحددة عند الجدولة | [samples = self.samples] |
تحتفظ المهمة بالقيم الملتقطة من دون الاحتفاظ بهذا المالك. |
هناك شرط أساسي في صف اللقطة: يلتقط هذا المثال مصفوفة من الأعداد الصحيحة. تُقيَّم تعبيرات قائمة الالتقاط عند إنشاء الإغلاق. أما التقاط مثيل فئة بدلًا من ذلك فسيحتفظ بمرجع إلى ذلك المثيل؛ ولن ينشئ نسخة مستقلة من محتوياته القابلة للتغيير. التعبيرات
وهناك أيضًا مسألة منفصلة تتعلق بالقيم. يقرأ الإصداران القوي والضعيف أدناه قيم المالك بعد التأخير. أما إصدار اللقطة فيستخدم القيم الموجودة وقت الجدولة. إذا كان من الممكن أن تتغير مدخلات المعاينة أثناء الانتظار، فحدّد أي تفسير ينبغي أن تمثله النتيجة.
شغّل مقارنة محلية كاملة على Mac
استخدم macOS 13 أو إصدارًا أحدث، وسلسلة أدوات Swift 6 أو أحدث مثبّتة. إن التحميل الزائد Task.sleep(for:) القائم على المدة والمستخدم هنا موثّق للإصدارات بدءًا من macOS 13. يعلّق المهمة من دون حظر الخيط الأساسي، ويرمي CancellationError إذا حدث الإلغاء قبل انتهاء الانتظار. sleep(for:tolerance:clock:)
احفظ البرنامج الكامل التالي باسم CaptureDemo.swift. تعمل الحاسبة وإغلاقات المهام والعرض التوضيحي على @MainActor، ما يجعل الوصول إلى حالتها متسلسلًا. توثّق Swift كلًا من عزل الفئة على الممثل الرئيسي والتعليق الصريح على الإغلاق المستخدم هنا. التزامن
import Foundation
enum CapturePolicy: String {
case strong, weak, snapshot
}
@MainActor
func formatTotal(_ samples: [Int]) -> String {
let total = samples.reduce(0, +)
let formatter = NumberFormatter()
formatter.numberStyle = .decimal
formatter.locale = Locale(identifier: "en_US_POSIX")
formatter.usesGroupingSeparator = false
formatter.maximumFractionDigits = 0
guard let text = formatter.string(from: NSNumber(value: total)) else {
return "Formatting unavailable"
}
return "Total: \(text)"
}
@MainActor
final class Calculator {
var samples = [4, 8, 12]
func start(_ policy: CapturePolicy) -> Task<String, Error> {
switch policy {
case .strong:
return Task { @MainActor [self] in
try await Task.sleep(for: .milliseconds(300))
try Task.checkCancellation()
return formatTotal(self.samples)
}
case .weak:
return Task { @MainActor [weak self] in
try await Task.sleep(for: .milliseconds(300))
try Task.checkCancellation()
guard let self else {
return "Owner unavailable"
}
return formatTotal(self.samples)
}
case .snapshot:
return Task { @MainActor [samples = self.samples] in
try await Task.sleep(for: .milliseconds(300))
try Task.checkCancellation()
return formatTotal(samples)
}
}
}
}
@main
struct CaptureDemo {
@MainActor
static func demonstrate(
_ policy: CapturePolicy,
cancel: Bool = false
) async {
var owner: Calculator? = Calculator()
weak var probe = owner
let task = owner!.start(policy)
owner = nil
let label = cancel ? "\(policy.rawValue), cancelled" : policy.rawValue
print("\(label) retains owner: \(probe != nil)")
if cancel {
task.cancel()
}
do {
let result = try await task.value
print(result)
} catch is CancellationError {
print("Cancelled")
} catch {
print("Calculation failed: \(error)")
}
}
@MainActor
static func main() async {
await demonstrate(.strong)
await demonstrate(.weak)
await demonstrate(.snapshot)
await demonstrate(.snapshot, cancel: true)
}
}
تستخدم العمليات الحسابية reduce(0, +) من المكتبة القياسية لدمج الأعداد الصحيحة في مجموع واحد. يحوّل NumberFormatter من Foundation ذلك المجموع إلى نص للعرض؛ وتعيد دالته string(from:) سلسلة اختيارية، لذا يبلّغ البرنامج عن إخفاق التنسيق صراحةً. مجموع العينة الثابتة هو 24. reduce(::)، NumberFormatter، string(from:)
في Terminal، انتقل إلى المجلد الذي يحتوي على الملف وشغّل هذه الأوامر. يحدد الهدف macOS 13 لمعمارية معالج Mac. يتيح الخيار -parse-as-library استخدام نقطة الدخول الصريحة @main في الملف. يقبل مترجم Swift الملفات المصدرية وخيارات سطر الأوامر؛ ويسرد swiftc -help الخيارات المثبّتة مع سلسلة أدواتك. دليل سطر أوامر Swift
swift --version
swiftc -parse-as-library -swift-version 6 -target "$(uname -m)-apple-macos13.0" CaptureDemo.swift -o CaptureDemo
./CaptureDemo
يظهر أدناه ناتج التطبيق المتوقع. وهو مستنتج من قواعد الالتقاط والإلغاء؛ فلم تتوفر بيئة بحث هذه المخطوطة على مترجم Swift، لذا فُحص البرنامج لكن لم يُنفّذ هنا.
strong retains owner: true
Total: 24
weak retains owner: false
Owner unavailable
snapshot retains owner: false
Total: 24
snapshot, cancelled retains owner: false
Cancelled
يتيح probe الضعيف ملاحظة الملكية من دون إضافة مرجع قوي آخر. يمسح كل عرض توضيحي مرجعه القوي الخاص قبل انتظار المهمة. وبما أن العرض التوضيحي وإغلاقات المهام تستخدم الممثل نفسه، فلا يمكن للإغلاق المنشأ حديثًا أن يتداخل مع الإعداد المتزامن. وهذا يجعل المقارنة مستقلة عن تخمين مدى سرعة بدء المهمة. التزامن
لماذا يهم موضع فحص المرجع الضعيف؟
في الفرع الضعيف، يظهر guard let self بعد التأخير. لذا يمكن للمهمة أن تنتظر من دون الاحتفاظ بالمالك بمرجع قوي. وإذا اختفى المالك، تنهي النتيجة الصريحة Owner unavailable ذلك الحساب.
يؤدي نقل فك التغليف إلى ما قبل التأخير إلى تغيير مدة البقاء. الجزء التالي توضيحي ويحل محل جسم إغلاق الفرع الضعيف؛ وليس برنامجًا مستقلًا:
// Inside the existing [weak self] task closure:
guard let self else {
return "Owner unavailable"
}
try await Task.sleep(for: .milliseconds(300))
try Task.checkCancellation()
return formatTotal(self.samples)
هنا، ينشئ الربط الاختياري الناجح مرجعًا محليًا قويًا يحتاج إليه التنفيذ مرة أخرى بعد التعليق. وبالتالي يظل المالك متاحًا أثناء الانتظار بعد نجاح ذلك الربط. وينبع هذا من قواعد المراجع القوية في Swift: يحدد الالتقاط الضعيف كيفية وصول الإغلاق إلى المالك في البداية، بينما يحدد المتغير المحلي الذي جرى فك تغليفه كيفية احتفاظ التعليمات اللاحقة به. العدّ المرجعي التلقائي
للسلوك «الانتظار من دون الاحتفاظ بالمالك، ثم إجراء الحساب فقط إذا ظل موجودًا»، أبقِ فك التغليف بعد الانتظار ونفّذ الحساب الصغير مباشرةً بعده. وتتطلب نقاط التعليق الإضافية بعد فك التغليف قرارًا آخر بشأن مدة البقاء.
يتطلب التقاط unowned بقاء المثيل المشار إليه صالحًا كلما جرى الوصول إليه. وهو لا يوفر فحص الغياب الاختياري الذي يحتاج إليه هذا المثال، حيث يكون تحرير المالك أثناء التأخير مقصودًا. العدّ المرجعي التلقائي
هل يؤدي تحرير مقبض المهمة إلى إيقاف الحساب؟
تواصل المهمة عملها حتى لو تخلّصت من مقبضها. يتيح لك الاحتفاظ بالمقبض انتظار نتيجتها أو طلب إلغائها. وتوثّق Apple أيضًا أن المهمة المكتملة تحرر فورًا إغلاق العملية والمراجع التي احتفظ بها ذلك الإغلاق. لذا يطيل الالتقاط القوي المؤقت مدة بقاء المالك إلى حين انتهاء العمل من دون أن يؤدي بالضرورة إلى دورة مراجع مستمرة. Task
الإلغاء سلوك مستقل عن الملكية. يطلب العرض التوضيحي الأخير الإلغاء عبر المقبض. يوفر الانتظار الذي يرمي خطأ وTask.checkCancellation() اللاحق مواضع يمكن فيها إيقاف العملية، ويبلّغ المستدعي عن تلك النتيجة. الإلغاء في Swift تعاوني: فطلبه لا يوقف قسرًا شيفرة حساب عشوائية. Task
لا تستبدل الانتظار الذي يرمي خطأ بـ try? ثم تتابع بلا شرط إذا كان ينبغي للإلغاء منع ظهور نتيجة. فكبت ذلك الخطأ سيسمح للتنفيذ بالوصول إلى الحساب ما لم يوقفه فحص آخر للإلغاء. وبالمثل، فإن إسناد nil إلى متغير المالك ليس طلبًا للإلغاء.
تمنحك هذه الفروق ثلاثة عناصر تحكم مفيدة لمعاينة سطح المكتب: ما إذا كان المالك سيظل مخصّصًا، والقيم المدخلة التي سيستخدمها الحساب، وما إذا كان ينبغي إيقاف العملية. حدّد كلًا منها صراحةً بدلًا من توقع أن يحقق الالتقاط الضعيف الأهداف الثلاثة كلها.
ما الذي ينبغي تغييره لمعاينة سطح المكتب؟
استخدم الإصدار الضعيف عندما لا تكون للمعاينة المعلّقة قيمة إلا ما دام مالكها متاحًا. واستخدم إصدار اللقطة عندما ينبغي للمدخلات الاصطناعية المحددة أن تنتج نتيجة مستقلة. واستخدم الإصدار القوي عندما يتطلب إكمال العملية وجود المالك فعلًا.
تختلف مدة بقاء الكائن أيضًا عن ظهور الواجهة. فإغلاق نافذة لا يثبت بحد ذاته إزالة كل مرجع قوي إلى الحاسبة. إذا كان ينبغي للتطبيق إيقاف العمل المعلّق عند إغلاق المعاينة، فاربط حدث التطبيق هذا بطريقة cancel() في مقبض المهمة المحتفَظ به؛ إذ يظل على الحساب أن يتعاون مع الإلغاء. Task
أبقِ عبء العمل في هذا المثال صغيرًا. يجعل العزل على الممثل الرئيسي الوصول إلى الحالة مباشرًا، كما أن الانتظار يعلّق المهمة، لكن الجمع المتزامن يظل يُنفَّذ على ذلك الممثل. يشرح دليل التزامن في Swift أن الممثل الرئيسي يتولى أيضًا عمل الواجهة. ويحتاج الحساب الأكبر بكثير إلى تصميم تنفيذ منفصل يمرر قيمًا مناسبة إلى العامل. التزامن
في هذا الحساب المحلي، الاختبار الحاسم بسيط: أزل مرجع المستدعي قبل تشغيل العملية المؤجلة ولاحظ النتيجة. يحافظ الالتقاط القوي على المالك، ويسمح الالتقاط الضعيف بنتيجة صريحة تفيد بعدم توفره، وتحافظ لقطة مصفوفة الأعداد الصحيحة على المدخلات اللازمة للإكمال فحسب.
المصادر
- العدّ المرجعي التلقائي — Swift.org
- التعبيرات — Swift.org
- التزامن — Swift.org
- Task — Apple Developer
- sleep(for:tolerance:clock:) — Apple Developer
- reduce(::) — Apple Developer
- NumberFormatter — Apple Developer
- string(from:) — Apple Developer
- دليل سطر أوامر Swift — مشروع Swift