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 linkها و اعلانهای push) باشد.
سه رویکرد به Router — Router به عنوان سرویس (Navigator-singleton سراسری)، Router به عنوان جزء ماژول (رویکرد VIPER)، Router به عنوان پروتکل (انتزاع برای Coordinator). Router سراسری — سادگی (یک نمونه برای کل برنامه) — در پروژههای کوچک محبوب است. Router ماژولار — هر ویژگی Router مخصوص خود را دارد — استاندارد VIPER و Clean Architecture. Router پروتکلی — در Coordinator استفاده میشود: RouterProtocol با متدهای push/present/pop/dismiss.
| رویکرد | ساختار | زمان استفاده |
|---|---|---|
| Navigator سراسری | Singleton با متدهای انتقال | پروژههای کوچک، نمونههای اولیه |
| Router ماژولار | Router درون هر ماژول | VIPER, Clean Architecture |
| Router پروتکلی | Protocol + پیادهسازی برای 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 میتواند ارائههای modal (present/dismiss)، push/pop در پشته ناوبری، showDetail برای Split View Controller و ارائههای تمامصفحه (iOS 13+) را مدیریت کند. Router ViewController ایجاد نمیکند — نمونه آماده دریافت میکند و آن را نمایش میدهد. ایجاد ViewController توسط کارخانه (Assembly، DI-container) یا 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 که از سال 2018 توسط Google توصیه میشود. ناوبری در گراف XML (nav_graph.xml) تعریف میشود: گرهها (fragments, activities, destinations) و یالها (actions). NavController انتقالها را مدیریت میکند: FragmentManager.replace/add را فراخوانی کرده و back stack را مدیریت میکند. Navigation Component از deep linkها، آرگومانها، انیمیشنهای انتقال و Safe Args برای انتقال نوعامن داده پشتیبانی میکند.
// 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 — آرگومانهای نوعامن
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 linkهای صریح (PendingIntent + NavDeepLinkBuilder) و ضمنی (از طریق intent-filter در مانیفست) پشتیبانی میکند. Deep link میتواند به هر گره nav_graph منتهی شود. پردازش اعلانهای push: Notification یک PendingIntent با NavDeepLinkBuilder ایجاد میکند که back stack را بازیابی کرده و صفحه مورد نظر را باز میکند. Navigation Component به طور خودکار دکمههای Up/Back را مدیریت کرده و کاربر را به صفحه قبلی بازمیگرداند.
URL Routing — مسیریابی بر اساس طرحهای URL، deep linkها و universal linkها. برنامه یک طرح (myapp://profile/42) یا universal link (https://example.com/profile/42) را ثبت میکند. Router URL را تجزیه میکند، پارامترها را استخراج میکند (userId=42) و صفحه مربوطه را باز میکند. URL Routing امکان باز کردن برنامه از خارج را فراهم میکند: از طریق لینک وبسایت، ایمیل، اعلان push، کد 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 — تطبیق URL با مسیرها
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 routing در عمل — برنامه باید deep link را در سه حالت پردازش کند: اجرا نشده (راهاندازی با deep link)، در پسزمینه (بازیابی از پسزمینه)، فعال (onNewIntent/SceneDelegate). URL Router باید back stack را به درستی بازیابی کند: در deep link به صفحه پروفایل، کاربر باید بتواند Back را فشار داده و به صفحه قبلی بازگردد. iOS و Android back stack را متفاوت مدیریت میکنند — Router باید ویژگیهای پلتفرم را در نظر بگیرد.
Router vs Coordinator — Router به سوال "چگونه صفحه را نمایش دهیم؟" پاسخ میدهد (push/present)، Coordinator — "کدام صفحه را نمایش دهیم؟" (کدام flow را اجرا کنیم). Router ابزار ناوبری است، Coordinator سازماندهنده flow. Router توسط Coordinator استفاده میشود. در VIPER، Router هر دو عملکرد را انجام میدهد: تصمیم میگیرد به کجا برود و چگونه نمایش دهد. در MVVM-C، Router — یک پروتکل جداگانه است که توسط Coordinator استفاده میشود. الگوها یکدیگر را حذف نمیکنند، بلکه مکمل هم هستند.
| ویژگی | Router | Coordinator | Navigator |
|---|---|---|---|
| تمرکز | مکانیک ناوبری | Flow ناوبری | دسترسی سراسری |
| سطح | UI-framework (push/present) | منطق کسب و کار (flow) | زیرساخت |
| ایجاد صفحات | 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 که flow را مدیریت میکند (کدام صفحه بعدی نمایش داده شود). Router مسئول مکانیک است، Coordinator — مسئول منطق ناوبری. Coordinator از Router برای انجام انتقالها استفاده میکند. در VIPER، Router هر دو عملکرد را ترکیب میکند، در MVVM-C — جدا شدهاند.
خیر. برای برنامههای با 3-5 صفحه، فراخوانی مستقیم navigationController.pushViewController یک راهحل عادی است. Router با 10+ صفحه، deep linkها، اعلانهای push و معماری ماژولار مفید میشود. Router آزمایش ناوبری را ساده میکند و امکان پردازش متمرکز انتقالها از هر منبع (دکمه، deep link، push) را فراهم میکند.
URL Router — extension Router که deep linkها و universal linkها را تجزیه میکند. URL Router تطابق بین URL و صفحه برنامه را تعیین میکند. به عنوان مثال، route myapp://profile/42 → ProfileViewController(userId: 42). URL Router پردازش deep linkها را از منابع مختلف متمرکز میکند: اعلانهای push، کدهای 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 از سال 2017 برنامههای iOS و Android را برای استارتاپها و کسبوکارها ایجاد میکند. ما به شما مشاوره میدهیم و بهترین راهحل را پیشنهاد خواهیم کرد.
همچنین بخوانید