VIPER: المفاهيم الأساسية، نمط View-Interactor-Presenter-Entity-Router

المؤلف: IT Sectr نُشر: 2026-02-16 وقت القراءة: 9 دق

VIPER (View-Interactor-Presenter-Entity-Router) — عمارة معيارية طورتها شركة Mutual Mobile لتطبيقات iOS. يقسم VIPER التطبيق إلى خمس طبقات: View مسؤولة عن العرض، Interactor عن منطق الأعمال، Presenter عن إعداد البيانات، Entity عن نماذج البيانات، Router عن التنقل بين الوحدات. VIPER هو التنفيذ الأكثر تفصيلاً لمبدأ المسؤولية الفردية بين المعماريات المحمولة. اقرأ المزيد في المقال على objc.io.

الخلاصة

  • VIPER — خمسة مكونات: View وInteractor وPresenter وEntity وRouter بحدود مسؤولية واضحة
  • النمطية — كل شاشة (وحدة) معزولة، التواصل عبر البروتوكولات
  • Router — يخرج التنقل من Presenter، مما يحل مشكلة التنقل في iOS
  • Interactor — يحتوي على منطق الأعمال ولا يعتمد على UIKit، يتم اختباره باختبارات الوحدة
  • iOS-native — تم إنشاء VIPER لـ UIKit قبل ظهور SwiftUI ولا يزال المعيار للمشاريع الكبيرة لنظام iOS

ما هو VIPER: خمسة مكونات للعمارة المعيارية

VIPER (View-Interactor-Presenter-Entity-Router) — نمط معماري تم تطويره في 2013–2014 في شركة Mutual Mobile للمشاريع الكبيرة لنظام iOS. كل شاشة تطبيق هي وحدة منفصلة من خمسة مكونات بمسؤوليات محددة بدقة. VIPER هو التنفيذ الأكثر صرامة لمبدأ المسؤولية الفردية في تطوير التطبيقات المحمولة: لا مكون يفعل ما يمكن لمكون آخر فعله.

View مكون سلبي مسؤول فقط عن عرض البيانات المرسلة من Presenter. لا يحتوي View على منطق أعمال، ولا يعالج التنقل، ولا يقوم بطلبات الشبكة. في iOS — UIViewController مع ViewProtocol. Interactor هو طبقة منطق الأعمال التي تعمل مع Entity والخدمات (الشبكة، قاعدة البيانات، GPS). Interactor لا يستورد UIKit. Presenter هو الوسيط بين View وInteractor: يستقبل البيانات من Interactor، ويصيغها للعرض، ويمررها إلى View. Presenter أيضًا لا يستورد UIKit. Entity — نماذج البيانات (struct, class). Router — يدير التنقل: ينشئ الوحدات، يفتح الشاشات، يمرر البيانات بين الوحدات.

المكونالمسؤوليةالتبعيات
Viewالعرض، الرسوم المتحركة، الإيماءاتUIKit (View فقط)
Interactorمنطق الأعمال، الشبكة، قاعدة البياناتEntity، الخدمات
Presenterتنسيق البيانات، أوامر ViewViewProtocol، Interactor
Entityنماذج البياناتلا يوجد
Routerالتنقل، إنشاء الوحداتUIViewController (للانتقالات)

العلاقات بين المكونات توصف بواسطة البروتوكولات. ViewProtocol يحدد طرق العرض، InteractorProtocol يحدد طرق منطق الأعمال، PresenterProtocol يحدد طرق معالجة الأحداث، RouterProtocol يحدد طرق التنقل. كل مكون يتواصل مع آخر فقط عبر بروتوكول، مما يسمح باستبدال التطبيقات بسهولة والاختبار المعزول. في المتوسط، وحدة VIPER لشاشة واحدة تحتوي على 5 بروتوكولات + 5 فئات + 1 Builder/Assembler = 11 ملفًا لكل شاشة.

VIPER في Swift: الوحدة وRouter وPresenter

بناء وحدة VIPER يتم في Builder (أو Assembler)، الذي ينشئ جميع المكونات الخمسة ويصلها عبر البروتوكولات. Builder هو المكان الوحيد الذي تعرف فيه المكونات الأنواع الملموسة لبعضها البعض. بعد التجميع، يتم إرجاع View للخارج للعرض، وبقية السلسلة نظيفة ويتم اختبارها بشكل معزول.

swift
// بروتوكول — View
protocol UserViewProtocol: AnyObject {
    func display(name: String)
    func display(email: String)
    func showLoading()
    func hideLoading()
}

// بروتوكول — Interactor
protocol UserInteractorProtocol {
    func fetchUser(id: Int, completion: @escaping (Result<User, Error>) -> Void)
}

// بروتوكول — Router
protocol UserRouterProtocol {
    func navigateToProfile(userId: Int)
}

// Interactor — منطق الأعمال
final class UserInteractor: UserInteractorProtocol {
    private let service: UserService

    init(service: UserService) { self.service = service }

    func fetchUser(id: Int, completion: @escaping (Result<User, Error>) -> Void) {
        service.fetchUser(id: id, completion: completion)
    }
}

// Presenter — إعداد البيانات
final class UserPresenter {
    private weak var view: UserViewProtocol?
    private let interactor: UserInteractorProtocol
    private let router: UserRouterProtocol

    init(interactor: UserInteractorProtocol, router: UserRouterProtocol) {
        self.interactor = interactor
        self.router = router
    }

    func setView(_ view: UserViewProtocol) {
        self.view = view
    }

    func viewDidLoad() {
        view?.showLoading()
        interactor.fetchUser(id: 42) { [weak self] result in
            guard let self else { return }
            self.view?.hideLoading()
            switch result {
            case .success(let user):
                self.view?.display(name: user.name)
                self.view?.display(email: user.email)
            case .failure(let error):
                // معالجة الخطأ
            }
        }
    }
}

// Router — التنقل
final class UserRouter: UserRouterProtocol {
    private weak var viewController: UIViewController?

    func setViewController(_ vc: UIViewController) {
        viewController = vc
    }

    func navigateToProfile(userId: Int) {
        let profileModule = ProfileModuleBuilder.build(userId: userId)
        viewController?.navigationController?.pushViewController(profileModule, animated: true)
    }
}

// Builder — تجميع الوحدة
enum UserModuleBuilder {
    static func build(userId: Int) -> UIViewController {
        let service = UserService()
        let interactor = UserInteractor(service: service)
        let router = UserRouter()
        let presenter = UserPresenter(interactor: interactor, router: router)
        let viewController = UserViewController(presenter: presenter)
        presenter.setView(viewController)
        router.setViewController(viewController)
        return viewController
    }
}

Builder/Assembler هو عنصر رئيسي في VIPER، ينفذ حقن التبعيات يدويًا. حقن التبعيات عبر المنشئ (constructor injection) يضمن أنه لا يمكن إنشاء مكون دون تبعياته. في VIPER الحديث، يمكن لـ Builder استخدام Swinject (حاوية DI)، لكن التجميع اليدوي يبقى أكثر شفافية للاختبار. في IT Sectr، نطبق VIPER مع التجميع اليدوي للوحدات ذات المنطق المعقد — وهذا يبسط قراءة الكود للمطورين الجدد.

Mapper (Formatter) هو مكون سادس اختياري في VIPER. Mapper يحول Entity (نماذج قاعدة البيانات/الخادم) إلى ViewModel (نماذج العرض). Entity يحتوي على UserDTO مع الحقول id, first_name, last_name, email. ViewModel — UserDisplayItem مع name (first_name + last_name) وemail. Mapper يتم تنفيذه في Presenter. إذا كان تعيين البيانات معقدًا (Entity متعددة → ViewModel واحد)، يتم استخراج Mapper في فئة منفصلة للاختبار.

التواصل بين وحدات VIPER

وحدات VIPER معزولة ولا تعرف عن بعضها البعض. يحدث التواصل بين الوحدات عبر Router. عندما ينقر المستخدم على زر "الملف الشخصي" في شاشة المستخدم، يستدعي Presenter الدالة router.navigateToProfile(userId: 42). يقوم Router بإنشاء وحدة جديدة عبر ProfileModuleBuilder.build(userId: 42) ويفتحها عبر navigationController.push. تدفق البيانات: الوحدة A → Router A → Builder الوحدة B → يتم إنشاء الوحدة B وفتحها.

إعادة البيانات (مثلاً، اختيار مدينة في شاشة الاختيار → العودة إلى شاشة تحرير الملف الشخصي) في VIPER يتم تنفيذها عبر المفوضين أو الإغلاقات. الوحدة B تحدد بروتوكول ModuleBDelegate بطريقة didSelectCity(_ city: City). الوحدة A تنفذ هذا البروتوكول. Router A يمرر المفوض إلى Builder الوحدة B. عند اختيار مدينة، الوحدة B تستدعي delegate?.didSelectCity(city). هذه ممارسة قياسية في iOS، مألوفة لأي مطور UIKit.

السيناريوالآليةمثال
التنقل إلى الأمامRouter → BuildernavigateToProfile(userId:)
إعادة البياناتDelegatedidSelectCity(_:)
إشعار النظامNotificationCenterUserDidLogout
حدث من InteractorPresenter → Viewرسالة WebSocket

NotificationCenter يستخدم لأحداث النظام (تسجيل الخروج، تغيير الخطة، الإشعارات الفورية) التي تؤثر على وحدات متعددة في نفس الوقت. Router أو AppDelegate يشترك في Notification، وينشئ الوحدة المطلوبة أو يحدث الحالة. VIPER لا يمنع NotificationCenter — المهم أنه يستخدم فقط لأحداث 1-to-many، بينما للتواصل 1-to-1 تستخدم المفوضون أو الإغلاقات.

مقارنة VIPER مع MVVM وClean Architecture

VIPER مقابل MVVM — VIPER يتطلب 2–3 أضعاف الكود لكل شاشة لكنه يوفر عزلًا مطلقًا للمكونات. MVVM مع ViewModel + SwiftUI أبسط وأسرع لكنه أقل قابلية للتوسع مع فرق 5+ مطورين. VIPER يحدد بدقة من المسؤول عن ماذا: Interactor — منطق الأعمال فقط، Presenter — التنسيق، Router — التنقل. في MVVM، ViewModel غالبًا ما ينمو، مستحوذًا على التنقل ومنطق الأعمال.

VIPER مقابل Clean Architecture — VIPER هو حالة خاصة من Clean Architecture مُكيَّفة لـ iOS UIKit. Interactor = Use Case، Entity = Domain Model، Presenter = Presentation، Router = Controller بمصطلحات روبرت مارتن. Clean Architecture تضيف طبقة Gateway/Repository بين Interactor والبيانات، والتي عادة لا تُفصل في VIPER. للمشاريع الحديثة على SwiftUI، معظم الفرق تختار Clean Architecture (The Composable Architecture) أو MVVM، تاركة VIPER للأنظمة القديمة على UIKit.

متى تختار VIPER — فرق 5+ مطورين، مشاريع UIKit بـ 50+ شاشة، متطلبات اختبار أعلى من 80%، iOS فقط (VIPER غير قابل للنقل إلى Android دون إعادة كتابة). VIPER يوفر هيكلًا يمكن التنبؤ به: مطور جديد يفهم الوحدة في 15 دقيقة. لكن سرعة التطوير أقل بنسبة 20–30% مقارنة بـ MVVM بسبب كثرة الملفات. في IT Sectr، نستخدم VIPER للمشاريع المؤسسية UIKit مع فرق 3+ أشخاص ونفضل Clean Architecture للمشاريع الجديدة على SwiftUI.

اختبار وحدات VIPER

VIPER مصمم للاختبار — كل مكون يختبر بشكل معزول عبر البروتوكولات. Interactor يختبر مع خدمات وهمية: يتم التحقق من أن fetchUser تم استدعاؤه بالمعرف الصحيح وأن النتيجة مرت إلى Presenter. Presenter يختبر مع كائنات وهمية لـ View وInteractor. Router يختبر مع تنقل وهمي: يتم التحقق من أن navigateToProfile تم استدعاؤه بـ userId الصحيح وأن الوحدة الصحيحة تم إنشاؤها. View يختبر باختبارات UI (XCUITest).

swift
import XCTest

final class UserPresenterTests: XCTestCase {
    func testViewDidLoad_callsFetchUserAndUpdatesView() {
        // Given
        let view = MockUserView()
        let interactor = MockUserInteractor()
        let router = MockUserRouter()
        let presenter = UserPresenter(interactor: interactor, router: router)
        presenter.setView(view)
        let expectedUser = User(id: 42, name: "John", email: "john@test.com")
        interactor.result = .success(expectedUser)

        // When
        presenter.viewDidLoad()

        // Then
        XCTAssertEqual(interactor.capturedUserId, 42)
        XCTAssertEqual(view.displayedName, "John")
        XCTAssertTrue(view.didShowLoading)
    }
}

الكائنات الوهمية لـ VIPER تُنشأ يدويًا (فئة بخصائص التقاط مخزنة) أو عبر مكتبات مثل Cuckoo / Mockingbird. الفئات الوهمية اليدوية أبسط وأوضح، خاصة لتدريب المطورين الجدد. كل كائن وهمي يخزن القيم الملتقطة (capturedUserId, displayedName) وأعلام الاستدعاء (didShowLoading). في نهاية الاختبار، لا يتم التحقق فقط من أن الطريقة تم استدعاؤها، ولكن أيضًا بالمعلمات التي تم استدعاؤها — وهذا يعطي ثقة في صحة تدفق البيانات.

تغطية الكود في مشاريع VIPER في IT Sectr تصل إلى 85–95% لـ Interactor، 90–95% لـ Presenter، 70–80% لـ Router، 30–50% لـ View (عبر اختبارات UI). View يختبر باختبارات اللقطات (SnapshotTesting، 1.5K نجمة) — هذا أسرع من XCUITest ويغطي حالات أكثر. التغطية الإجمالية لمشروع VIPER عادة 70–80%، وهي أعلى من مشروع MVVM (50–65%)، لكنها تتطلب وقتًا أطول لكتابة الاختبارات (30–40% من وقت التطوير مقابل 20–25% في MVVM).

الأسئلة الشائعة

كم عدد الملفات في وحدة VIPER واحدة؟

11 ملفًا على الأقل: 5 بروتوكولات (ViewProtocol, InteractorProtocol, PresenterProtocol, RouterProtocol, Entity)، 5 تطبيقات (ViewController, Interactor, Presenter, Router, Entity) وBuilder/Assembler. مع Mapper (Formatter) — 12–13. لمشروع 50 شاشة، هذا 550–650 ملفًا من وحدات VIPER فقط. MVVM يتطلب 3 ملفات لكل شاشة (ViewModel, View, Model) — 150 ملفًا لـ 50 شاشة.

هل يمكن استخدام VIPER على Android؟

نعم، نظريًا VIPER قابل للنقل إلى Android، لكن عمليًا لا يُستخدم — Google توصي بـ MVVM مع Jetpack. تم إنشاء VIPER لـ iOS UIKit، حيث ViewController يصعب اختباره بسبب دورة حياته. على Android، Jetpack ViewModel يحل مشكلة الاختبار دون عزل VIPER. المكافئ لـ VIPER على Android هو Clean Architecture مع تقسيم إلى وحدات/ميزات.

ما الفرق بين VIPER وClean Architecture؟

VIPER هو تنفيذ خاص بنظام iOS لـ Clean Architecture. Interactor يقابل Use Case، Entity — Domain Model، Presenter — طبقة Presentation. Clean Architecture تضيف Repository/Gateway بين Interactor والبيانات، والتي في VIPER عادة تُنفذ داخل Interactor. Clean Architecture لا تفرض Router — التنقل متروك للتنفيذ.

هل نحتاج VIPER لمشاريع SwiftUI؟

لا — SwiftUI مصمم لـ MVVM + Combine. VIPER في SwiftUI زائد عن الحاجة: خمسة مكونات لكل شاشة مع UI تصريحي هو عبء دون فائدة. لـ SwiftUI، اختر MVVM أو TCA (The Composable Architecture). VIPER يبقى مناسبًا للأنظمة القديمة UIKit والمشاريع حيث iOS 12 وما دونه هو الإصدار الأدنى.

كيف نمرر البيانات بين وحدات VIPER؟

عبر Router. الوحدة A تستدعي router.navigateToProfile(userId: id). Router A ينشئ الوحدة B عبر Builder، ويمرر userId. التواصل العكسي — عبر مفوض: الوحدة B تحدد بروتوكول ModuleBDelegate، الوحدة A تنفذه وتمرره عبر Router. أحداث النظام (تسجيل الخروج) — عبر NotificationCenter.

الملخص

  • VIPER — خمسة مكونات بفصل صارم: View، Interactor، Presenter، Entity، Router
  • النمطية — كل شاشة معزولة، Builder يجمع التبعيات عبر حقن المنشئ
  • Router — يخرج التنقل من Presenter، مما يحل مشكلة التنقل على iOS
  • Interactor — منطق أعمال نظيف دون UIKit، يختبر باختبارات الوحدة
  • حجم الكود — 11+ ملفًا لكل شاشة، التطوير أبطأ بنسبة 20–30% من MVVM
  • الاختبار — تغطية 70–80%، Interactor وPresenter يختبران عبر كائنات وهمية
  • SwiftUI مقابل UIKit — VIPER لـ UIKit (الأنظمة القديمة)، MVVM/TCA لـ SwiftUI

سنقوم بتطوير تطبيق جوال جاهز

تقدم IT Sectr تطبيقات iOS وAndroid للشركات الناشئة والشركات منذ عام 2017. سوف نقدم لك النصح ونقترح أفضل حل.

مناقشة المشروع

اقرأ أيضًا