Router (الموجه) — نمط معماري يركز منطق التنقل بين شاشات التطبيق. يحدد Router إلى أين وكيف ينتقل عند حدوث حدث. في iOS، يقوم Router بتجريد UINavigationController وsegue؛ في Android — FragmentManager وNavController من Jetpack Navigation. يمكن أن يعمل Router كنمط مستقل أو كمكون من Coordinator (Router-per-Coordinator) وVIPER (Router-per-module). المزيد من التفاصيل — في Android Navigation Component Guide.
النقاط الرئيسية
Router — نمط يغلف منطق الانتقال بين الشاشات. الفكرة الرئيسية: ViewController (أو Presenter/ViewModel) لا يستدعي navigationController.pushViewController مباشرة، بل يخطر Router بنية التنقل. يقرر Router أي ViewController سينشئ وكيف يعرضه. يمكن أن يكون Router بسيطًا (مجموعة من طرق التنقل) أو معقدًا (مع التوجيه بمخططات URL وdeep links وإشعارات الدفع).
ثلاث طرق لـ Router — Router كخدمة (Navigator singleton عام)، Router كمكون وحدة (طريقة VIPER)، Router كبروتوكول (تجريد لـ Coordinator). Router العام — البساطة (مثيل واحد لكل تطبيق) — شائع في المشاريع الصغيرة. Router المعياري — كل feature لها Router خاص بها — معيار VIPER وClean Architecture. Router البروتوكولي — يُستخدم في Coordinator: RouterProtocol مع طرق push/present/pop/dismiss.
| الطريقة | الهيكل | متى يُستخدم |
|---|---|---|
| Navigator العام | Singleton مع طرق تنقل | المشاريع الصغيرة، النماذج الأولية |
| Router المعياري | Router داخل كل وحدة | VIPER, Clean Architecture |
| Router البروتوكولي | بروتوكول + تنفيذ لـ Coordinator | MVVM-C, Coordinator pattern |
تاريخ النمط — ظهر Router في أطر الويب (URL Router — Rails Routes, Express.js) وتم تكييفه لتطوير الأجهزة المحمولة. في iOS، أصبح Router شائعًا مع صعود VIPER (2015-2017). في Android، Router جزء من Jetpack Navigation (2018)، حل Google القياسي للتنقل. اليوم، Router جزء لا يتجزأ من أي بنية معيارية لتطبيقات الأجهزة المحمولة.
iOS Router — بروتوكول مع طرق لأنواع مختلفة من التنقل. التنفيذ الأساسي يغلف UINavigationController. يمكن لـ Router التعامل مع العروض التقديمية المشروطة (present/dismiss)، وpush/pop في مكدس التنقل، وshowDetail لـ Split View Controller، والعروض التقديمية بملء الشاشة (iOS 13+). Router لا ينشئ ViewControllers — يتلقى مثيلاً جاهزًا ويعرضه. إنشاء ViewControllers يتم بواسطة مصنع (Assembly, حاوية DI) أو Coordinator.
// RouterProtocol لنظام iOS
protocol RouterProtocol {
func push(_ viewController: UIViewController, animated: Bool)
func pop(animated: Bool)
func popToRoot(animated: Bool)
func present(_ viewController: UIViewController, animated: Bool)
func dismiss(animated: Bool)
func setViewControllers(_: [UIViewController], animated: Bool)
}
// تنفيذ Router عبر UINavigationController
class NavigationRouter: RouterProtocol {
private let navigationController: UINavigationController
init(navigationController: UINavigationController) {
self.navigationController = navigationController
}
func push(_ vc: UIViewController, animated: Bool) {
if navigationController.presentedViewController {
navigationController.dismiss(animated: false)
}
navigationController.pushViewController(vc, animated: animated)
}
func pop(animated: Bool) {
navigationController.popViewController(animated: animated)
}
func popToRoot(animated: Bool) {
navigationController.popToRootViewController(animated: animated)
}
func present(_ vc: UIViewController, animated: Bool) {
navigationController.present(vc, animated: animated)
}
func dismiss(animated: Bool) {
navigationController.dismiss(animated: animated)
}
func setViewControllers(_ vcs: [UIViewController], animated: Bool) {
navigationController.setViewControllers(vcs, animated: animated)
}
}
// Assembly — إنشاء وحدة مع Router
protocol ProfileModuleFactory {
func makeProfileView() -> UIViewController
}
Router في VIPER — كل وحدة VIPER لها Router خاص بها، يعرف أي الشاشات يفتح من الوحدة الحالية. ينشئ Router ViewController للوحدة التالية من خلال مصنع ويسلم التحكم. Router في VIPER هو المكون الوحيد الذي يحتوي على import UIKit (باستثناء View). هذا يبسط اختبار Presenter وInteractor: لا يعتمدان على UIKit.
Jetpack Navigation Component — جهاز التوجيه القياسي لـ Android، الذي أوصت به Google منذ 2018. يتم تعريف التنقل في رسم بياني XML (nav_graph.xml): العقد (fragments, activities, destinations) والحواف (actions). يدير NavController الانتقالات: يستدعي FragmentManager.replace/add ويدير مكدس الرجوع. يدعم Navigation Component deep links والوسائط ورسوم الانتقالات وSafe Args لنقل البيانات بشكل type-safe.
// nav_graph.xml — رسم بياني للتنقل
<!-- @xml/nav_graph.xml -->
<navigation xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:id="@+id/nav_graph"
app:startDestination="@id/loginFragment">
<fragment android:id="@+id/loginFragment"
android:name=".ui.login.LoginFragment">
<action android:id="@+id/to_home"
app:destination="@id/homeFragment" />
</fragment>
<fragment android:id="@+id/homeFragment"
android:name=".ui.home.HomeFragment">
<argument android:name="userId"
android:defaultValue="0"
app:argType="integer" />
<deepLink app:uri="myapp://home/{userId}" />
</fragment>
</navigation>
// Kotlin — التنقل عبر NavController
class LoginFragment : Fragment() {
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
binding.loginButton.setOnClickListener {
val action = LoginFragmentDirections.toHome(userId = 42)
findNavController().navigate(action)
}
}
}
// Safe Args — وسائط type-safe
class HomeFragmentArgs : NavArgs {
val userId: Int get() = arguments?.getInt("userId") ?: 0
}
// NavigationUI — التكامل مع Toolbar وBottomNav وDrawer
class MainActivity : AppCompatActivity() {
private lateinit var navController: NavController
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
val navHost = supportFragmentManager
.findFragmentById(R.id.nav_host_fragment) as NavHostFragment
navController = navHost.navController
NavigationUI.setupActionBarWithNavController(this, navController)
}
}
Deep links في Android Navigation — يدعم Navigation Component deep links الصريحة (PendingIntent + NavDeepLinkBuilder) والضمنية (عبر intent-filter في البيان). يمكن أن يؤدي deep link إلى أي عقدة في nav_graph. معالجة إشعارات الدفع: ينشئ Notification PendingIntent مع NavDeepLinkBuilder، الذي يستعيد مكدس الرجوع ويفتح الشاشة المطلوبة. يتعامل Navigation Component تلقائيًا مع أزرار Up/Back ويعيد المستخدم إلى الشاشة السابقة.
URL Routing — التوجيه بناءً على مخططات URL وdeep links وuniversal links. يسجل التطبيق مخططًا (myapp://profile/42) أو universal link (https://example.com/profile/42). يحلل Router عنوان URL، ويستخرج المعلمات (userId=42)، ويفتح الشاشة المقابلة. يتيح URL Routing فتح التطبيق من الخارج: عبر رابط من موقع ويب أو بريد إلكتروني أو إشعار دفع أو رمز QR. يستخدم iOS NSUserActivity + universal link، Android — intent-filter + deep link.
// URL Router — تحليل deep link والتنقل
protocol URLRoute {
var pattern: String { get }
func navigate(parameters: [String: String], router: RouterProtocol)
}
struct ProfileRoute: URLRoute {
let pattern = "myapp://profile/{userId}"
func navigate(parameters: [String: String], router: RouterProtocol) {
guard let userId = parameters["userId"] else { return }
let profileVC = ProfileViewController(userId: userId)
router.push(profileVC, animated: true)
}
}
// URL Router — مطابقة URLs مع المسارات
class URLRouter {
private var routes: [URLRoute] = []
func handle(_ url: URL, router: RouterProtocol) {
for route in routes {
if let params = matchPattern(route.pattern, url: url) {
route.navigate(parameters: params, router: router)
return
}
}
}
}
توجيه deep link عمليًا — يجب أن يعالج التطبيق deep links في ثلاث حالات: غير مشغل (بدء التشغيل مع deep link)، في الخلفية (الاستعادة من الخلفية)، نشط (onNewIntent/SceneDelegate). يجب على URL Router استعادة مكدس الرجوع بشكل صحيح: عند deep link إلى شاشة ملف شخصي، يجب أن يتمكن المستخدم من الضغط على رجوع والعودة إلى الشاشة السابقة. iOS وAndroid يتعاملان مع مكدسات الرجوع بشكل مختلف — يجب على Router مراعاة خصائص المنصة.
Router vs Coordinator — Router يجيب على السؤال "كيفية عرض شاشة؟" (push/present)، Coordinator — "أي شاشة عرض؟" (أي تدفق يبدأ). Router أداة تنقل، Coordinator منظم تدفق. يستخدم Coordinator Router. في VIPER، يؤدي Router كلا الوظيفتين: يقرر إلى أين يذهب وكيف يعرضها. في MVVM-C، Router بروتوكول منفصل يستخدمه Coordinator. الأنماط لا تستبعد بل تكمل بعضها البعض.
| الخاصية | Router | Coordinator | Navigator |
|---|---|---|---|
| التركيز | آلية التنقل | تدفق التنقل | وصول عام |
| المستوى | إطار UI (push/present) | منطق الأعمال (تدفق) | بنية تحتية |
| إنشاء الشاشات | يستقبل VC جاهزًا | ينشئ VC عبر مصنع | ينشئ VC |
| دورة الحياة | واحد لكل تنقل | childCoordinators | Singleton |
Router vs Navigator — Navigator هو اسم أقدم لـ Router في مجتمع iOS. في التطبيقات المبكرة، كان Navigator فئة Singleton مع طرق تنقل. Navigator هو Router singleton عام. Navigator أبسط لكنه يخلق تبعيات خفية. Router القائم على البروتوكول أكثر قابلية للاختبار ونمطية. Navigator مناسب للمشاريع بدون بنية صارمة، Router — لـ Clean Architecture وVIPER.
الأسئلة الشائعة
Router هو تجريد فوق UINavigationController (push/present/pop). Coordinator هو طبقة فوق Router تدير التدفق (أي شاشة تظهر بعد ذلك). Router يتعامل مع الآلية، Coordinator يتعامل مع منطق التنقل. Coordinator يستخدم Router لتنفيذ الانتقالات. في VIPER، يجمع Router بين الوظيفتين؛ في MVVM-C، منفصلتان.
لا. للتطبيقات ذات 3-5 شاشات، الاستدعاءات المباشرة لـ navigationController.pushViewController هي حل مقبول تمامًا. يصبح Router مفيدًا مع 10+ شاشات وdeep links وإشعارات الدفع والبنية المعيارية. Router يبسط اختبار التنقل ويسمح بمعالجة مركزية للانتقالات من أي مصدر (زر، deep link، إشعار دفع).
URL Router هو امتداد لـ Router يحلل deep links وuniversal links. يحدد URL Router التطابق بين عناوين URL وشاشات التطبيق. على سبيل المثال، مسار myapp://profile/42 → ProfileViewController(userId: 42). URL Router يركز معالجة deep links من مصادر مختلفة: إشعارات الدفع ورموز QR وروابط البريد الإلكتروني والروابط العامة.
نعم. يمكن لـ Router العمل بشكل مستقل — كـ Navigator عام أو كمكون وحدة VIPER. Router بدون Coordinator هو ببساطة تغليف UINavigationController خلف بروتوكول. Coordinator بدون Router أكثر تعقيدًا (ViewController يستدعي push/present مباشرة)، لكنه ممكن. أفضل ممارسة هي Router + Coordinator معًا.
وظيفيًا — نعم، كلاهما يركزان التنقل. التنفيذ مختلف: iOS Router — بروتوكول + فئة حول UINavigationController؛ Android — Jetpack Navigation Component مع رسم بياني XML وNavController. Android Router مدمج في الإطار (Navigation Component)، iOS Router هو تنفيذ طرف ثالث. Android Router يدعم Safe Args، iOS Router لا (يحتاج generics أو Codable).
الملخص
سنقوم بتطوير تطبيق جوال جاهز
تقدم IT Sectr تطبيقات iOS وAndroid للشركات الناشئة والشركات منذ عام 2017. سوف نقدم لك النصح ونقترح أفضل حل.
اقرأ أيضًا