أهم أخطاء مطوري Swift المبتدئين: المستوى المتقدم
عندما تنتقل من كتابة شاشات بسيطة باستخدام SwiftUI إلى إنشاء تطبيقات معقدة متعددة الخيوط بهندسة مخصصة، يزداد عدد المزالق بشكل كبير. العديد من المطورين، بعد إتقان بناء الجملة الأساسي لـ Swift، يعتقدون أنهم جاهزون للإنتاج، لكنهم يرتكبون أخطاءً حرجة تؤدي إلى تسرب الذاكرة، وعدم استقرار العمل، وأخطاء يصعب اكتشافها. في هذه المقالة، سنقوم بتحليل أربع فئات من الأخطاء التي تظهر غالبًا لدى المطورين من المستوى المتوسط.
1. تسرب الذاكرة في الإغلاقات: ليس فقط [weak self]
الخطأ الأكثر شيوعًا هو استخدام [weak self] دون فهم متى يكون ذلك ضروريًا حقًا. العديد من المطورين يضعون weak self في كل مكان يوجد فيه إغلاق، مما يؤدي إلى تدمير الكائنات قبل الأوان. أو على العكس، ينسون دورات المرجع القوية في الأماكن غير الواضحة.
الخطأ النموذجي: إنشاء مرجع قوي لكائن داخل إغلاق يقوم هذا الكائن بتخزينه.
class NetworkManager { var onCompletion: (() -> Void)? func fetchData() { // خطأ: الإغلاق يلتقط self بقوة onCompletion = { self.updateUI() } } func updateUI() { print("تم تحديث الواجهة") }}الحل الصحيح: استخدام [weak self] أو [unowned self] اعتمادًا على عمر الكائنات.
class NetworkManager { var onCompletion: (() -> Void)? func fetchData() { onCompletion = { [weak self] in guard let self = self else { return } self.updateUI() } } func updateUI() { print("تم تحديث الواجهة") }}فارق دقيق متقدم: كن حذرًا مع [unowned self]. إذا تم تحرير الكائن وتم استدعاء الإغلاق، سيتعطل التطبيق. استخدم unowned فقط عندما تكون متأكدًا بنسبة 100% أن الكائن سيعيش أكثر من الإغلاق.
2. العمل غير الصحيح مع async/await و Actors
مع ظهور Structured Concurrency في Swift 5.5، بدأ العديد من المطورين في استخدام async/await بنشاط، لكنهم ينسون MainActor و Sendable. يؤدي هذا إلى حالات السباق (race conditions) وتحديث الواجهة من خيط الخلفية.
الخطأ النموذجي: استدعاء دالة غير متزامنة يجب أن تنفذ على الخيط الرئيسي دون تحديد @MainActor بشكل صريح.
class ViewModel: ObservableObject { @Published var data: [String] = [] // خطأ: الدالة غير موسومة بـ @MainActor func loadData() async { let result = await fetchFromNetwork() // قد يتم تنفيذ هذا على خيط الخلفية! self.data = result } func fetchFromNetwork() async -> [String] { try? await Task.sleep(nanoseconds: 1_000_000_000) return ["Item1", "Item2"] }}الحل الصحيح: استخدام @MainActor للدوال التي تقوم بتحديث الواجهة.
class ViewModel: ObservableObject { @Published var data: [String] = [] @MainActor // الآن يتم التنفيذ بشكل مضمون على الخيط الرئيسي func loadData() async { let result = await fetchFromNetwork() self.data = result } func fetchFromNetwork() async -> [String] { try? await Task.sleep(nanoseconds: 1_000_000_000) return ["Item1", "Item2"] }}فارق دقيق متقدم: لا تنسَ nonisolated و actor. إذا كنت تستخدم Actors، فإن جميع الطرق العامة تكون غير متزامنة افتراضيًا. قد يؤدي هذا إلى تأخيرات غير متوقعة إذا لم تخطط للعزل بشكل جيد.