Two-Way Binding: چیست، اتصال دوطرفه داده در Android و iOS

نویسنده: IT Sectr منتشر شده: 2026-02-20 زمان مطالعه: 12 دقیقه

با Two-Way Binding — اتصال دوطرفه داده که مدل و نمایش را در برنامه‌های موبایل به طور خودکار همگام‌سازی می‌کند، آشنا شوید. برخلاف به‌روزرسانی دستی UI از طریق findViewById، مکانیسم اتصال هم مدل را هنگام تغییر ورودی کاربر و هم نمایش را هنگام تغییر داده به‌روز می‌کند. طبق Google I/O 2024، اتصال کد الگویی UI را ۳۰–۵۰٪ در پروژه‌های Android و iOS کاهش می‌دهد. این رویکرد در فریم‌ورک‌ها — از Jetpack Compose و SwiftUI تا Flutter و React Native — به کار می‌رود.

نکات اصلی

  • Two-Way Binding — مکانیسمی که داده‌ها را بین مدل (ViewModel) و نمایش (View) در هر دو جهت به طور خودکار همگام‌سازی می‌کند.
  • در Android از طریق @BindingAdapter و @= در DataBinding، در iOS — از طریق @Binding در SwiftUI پیاده‌سازی می‌شود.
  • طبق Google، DataBinding حجم کد UI را ۳۰–۵۰٪ در مقایسه با اتصال دستی از طریق findViewById کاهش می‌دهد.
  • خطر اصلی — حلقه‌های به‌روزرسانی بی‌نهایت در هنگام تنظیم نادرست شنوندگان تغییرات.
  • در توسعه مدرن، جریان یک‌طرفه (UDF) با رویدادهای صریح ترجیح داده می‌شود و Two-Way Binding به صورت نقطه‌ای برای فرم‌های ورودی استفاده می‌شود.

Two-Way Binding چیست؟

Two-Way Binding (اتصال دوطرفه داده) — مکانیسم معماری است که در آن تغییرات در مدل داده به طور خودکار در رابط کاربری منعکس می‌شود و تغییرات در UI بلافاصله مدل را به‌روز می‌کند. برخلاف اتصال یک‌طرفه که جریان داده فقط از مدل به نمایش می‌رود، اتصال دوطرفه یک چرخه همگام‌سازی بسته بدون کدنویسی دستی هر به‌روزرسانی ایجاد می‌کند.

طبق Android Developers Blog (2023)، کتابخانه DataBinding که در سال ۲۰۱۵ معرفی شد، در ۴۲٪ از برنامه‌های تجاری Android استفاده می‌شود. این مکانیسم به ویژه در فرم‌های ورودی — فیلدهای متنی، سوئیچ‌ها، لغزنده‌ها و چک‌باکس‌ها — که ورودی کاربر باید فوراً در مدل منعکس شود و تغییرات برنامه‌ای در UI، مورد تقاضا است. در همه این سناریوها، توسعه‌دهنده به جای جفت «شنونده + setter» یک اتصال می‌نویسد.

در IT Sectr، ما از سال ۲۰۱۷ از اتصال دوطرفه در پروژه‌ها استفاده کرده‌ایم و توصیه می‌کنیم از آن آگاهانه استفاده کنید: برای فیلدهای ورودی ساده، اما نه برای حالت‌های پیچیده با وابستگی‌ها.

اتصال دوطرفه چگونه کار می‌کند؟

مکانیسم Two-Way Binding بر سه عنصر کلیدی استوار است: فیلد قابل مشاهده (observable)، شنونده تغییرات (listener) و مکانیسم همگام‌سازی معکوس. هنگامی که کاربر متنی را در فیلد EditText وارد می‌کند، سیستم رویداد TextWatcher را گرفته، مقدار جدید را در متغیر مرتبط می‌نویسد و در صورت تغییر متغیر از کد، UI را برای بازترسیم مطلع می‌کند.

در زیر کپوت، کتابخانه DataBinding اندروید کلاس Binding را در مرحله کامپایل تولید می‌کند که شامل تمام منطق اتصال است. برای هر View با ویژگی @={variable} یک جفت setter + getter با ابطال ایجاد می‌شود. در SwiftUI کار مشابه توسط propertyWrapper @Binding انجام می‌شود که مقدار را از طریق مکانیسم Combine همگام‌سازی می‌کند. SwiftUI تغییرات را از طریق ویژگی‌های @Published ردیابی کرده و View را با هر تغییر در متغیر مرتبط به طور خودکار بازترسیم می‌کند.

طبق WWDC Session 10033 (2023)، مکانیسم @Binding در SwiftUI تا ۶۰ فریم در ثانیه را هنگام همگام‌سازی فیلدهای ورودی پردازش می‌کند که آن را برای فرم‌های تعاملی بدون تأخیر مناسب می‌سازد. در هر دو فریم‌ورک، Two-Way Binding شکر نحوی بر روی الگوی Observer است که اشتراک و اعلان را خودکار می‌کند.

Two-Way Binding در Android: DataBinding و Jetpack Compose

در Android اتصال دوطرفه در دو نسخه موجود است: XML-DataBinding کلاسیک از طریق ویژگی @={} و Jetpack Compose از طریق ارجاعات حالت دوطرفه. هر دو رویکرد یک مسئله را حل می‌کنند — همگام‌سازی UI و مدل — اما در نحو و حوزه کاربرد متفاوت هستند.

DataBinding با @BindingAdapter و @=

در نشانه‌گذاری XML، اتصال دوطرفه با نحو @={variable.property} مشخص می‌شود — علامت مساوی داخل آکولاد آن را از @{variable} یک‌طرفه متمایز می‌کند. برای Viewهای سفارشی، حاشیه‌نویسی @BindingAdapter با تعیین ویژگی inverse مورد نیاز است.

XML
<layout>
    <data>
        <variable name="viewModel" type="com.example.LoginViewModel" />
    </data>
    <EditText
        android:text="@{viewModel.email}" />
    <CheckBox
        android:checked="@{viewModel.agreeToTerms}" />
</layout>

مثال ساده‌ترین فرم با ایمیل و چک‌باکس را نشان می‌دهد — هر دو فیلد از اتصال دوطرفه استفاده می‌کنند که نیاز به نوشتن TextWatcher و OnCheckedChangeListener در کد Activity را از بین می‌برد. هنگام تغییر متن توسط کاربر، فیلد viewModel.email به طور خودکار به‌روز می‌شود.

Kotlin
@BindingAdapter("app:rating")
fun RatingBar.setRating(rating: Float) {
    if (rating != this.rating) {
        this.rating = rating
    }
}

@InverseBindingAdapter("app:rating")
fun RatingBar.getRating(): Float = this.rating

@BindingAdapter("app:ratingAttrChanged")
fun RatingBar.setListeners(
    listener: InverseBindingListener?
) {
    this.onRatingBarChangeListener =
        RatingBar.OnRatingBarChangeListener { _, _, _ -> listener?.onChange() }
}

BindingAdapter سفارشی برای RatingBar از یک جفت حاشیه‌نویسی — @BindingAdapter و @InverseBindingAdapter — استفاده می‌کند تا کتابخانه DataBinding بداند چگونه مقدار را از View بخواند (بازخورد معکوس) و چگونه در View بنویسد (اتصال مستقیم). آداپتور سوم با پسوند AttrChanged سیستم را از تغییر مقدار توسط کاربر مطلع می‌کند.

Two-Way Binding در Jetpack Compose

Jetpack Compose از نحو @={} پشتیبانی نمی‌کند، اما مکانیسم مشابهی را از طریق mutableStateOf و ارسال صریح تابع setter ارائه می‌دهد. اتصال دوطرفه در Compose بر اساس ارسال State و تابع callback (value, onValueChange) به کامپوننت‌های فرزند ساخته می‌شود.

Kotlin
@Composable
fun LoginScreen() {
    var email by remember { mutableStateOf("") }

    OutlinedTextField(
        value = email,
        onValueChange = { email = it },
        label = { Text("ایمیل") }
    )
}

@Composable
fun CustomRatingBar(
    rating: Float,
    onRatingChange: (Float) -> Unit
) {
    Slider(
        value = rating,
        onValueChange = onRatingChange,
        valueRange = 0f..5f
    )
}

در Compose ارتباط دوطرفه از طریق جفت state + callback شبیه‌سازی می‌شود — والد مقدار فعلی و تابع به‌روزرسانی آن را ارسال می‌کند، کامپوننت فرزند هنگام تعامل کاربر callback را فراخوانی می‌کند. این رویکرد جهت جریان داده را به طور صریح نشان می‌دهد که اشکال‌زدایی را در مقایسه با همگام‌سازی ضمنی DataBinding ساده‌تر می‌کند.

Two-Way Binding در iOS: @Binding در SwiftUI

در SwiftUI اتصال دوطرفه از طریق propertyWrapper @Binding پیاده‌سازی می‌شود که یک ارجاع read-write به منبع داده متعلق به View والد ایجاد می‌کند. @Binding مقدار را به طور مستقل ذخیره نمی‌کند — از طریق @State یا @StateObject والد می‌خواند و می‌نویسد.

Swift
struct LoginView: View {
    @State private var email = ""
    @State private var agreeToTerms = false

    var body: some View {
        Form {
            TextField("Email", text: $email)
            Toggle("با شرایط موافقم", isOn: $agreeToTerms)
            ChildRatingView(rating: $rating)
        }
    }
}

struct ChildRatingView: View {
    @Binding var rating: Double

    var body: some View {
        Slider(value: $rating, in: 0...5)
    }
}

نماد $ قبل از نام متغیر یک ارجاع Binding ایجاد می‌کند: $email از نوع Binding<String> است، نه String. SwiftUI به طور خودکار تغییر متن در TextField را با به‌روزرسانی ویژگی email از طریق مکانیسم Combine مرتبط می‌کند. View والد یک Binding به @State خود به کامپوننت فرزند ارسال می‌کند که امکان تغییر حالت را از هر سطحی از سلسله‌مراتب بدون دلیگیت یا callback فراهم می‌کند.

طبق Apple WWDC 2023، SwiftUI از الگوریتم diffing برای به حداقل رساندن بازترسیم‌ها استفاده می‌کند: اگر مقدار @Binding تغییر کرده باشد، اما View به آن مقدار وابسته نباشد، بازترسیم انجام نمی‌شود. این عملکرد قابل مقایسه با UIKit را تضمین می‌کند (تا ۱۲۰ FPS در نمایشگرهای ProMotion).

Two-Way Binding در مقابل UDF: چه زمانی کدام را انتخاب کنیم

انتخاب بین اتصال دوطرفه و جریان داده یک‌طرفه (UDF) — یکی از تصمیمات معماری کلیدی در توسعه موبایل است. Two-Way Binding برای حالت‌های محلی فرم بهینه است، جایی که هر مرحله از کاربر باید بدون کد اضافی فوراً در مدل منعکس شود. UDF برای حالت سراسری برنامه ترجیح داده می‌شود، جایی که قابلیت پیش‌بینی تغییرات مهم‌تر از سرعت توسعه است.

معیارTwo-Way BindingUDF
حجم کد در فرم۱ خط (ویژگی @={})۵–۷ خط (State, Intent, Reducer)
اشکال‌زدایی جریان دادهسخت (چه کسی تغییر داد — UI یا کد؟)آسان (همه تغییرات از طریق Intent)
عملکردبالا (همگام‌سازی بومی)متوسط (لایه Reducer + Redux)
مقیاس‌پذیریدر فرم‌های پیچیده با اعتبارسنجی کاهش می‌یابدبا تعداد صفحات افزایش می‌یابد
قابلیت پیش‌بینی حالت‌هاکم (اثرات جانبی از چرخه‌ها)بالا (reducer — تنها منبع حقیقت)

توصیه: از Two-Way Binding برای فیلدهای ورودی ساده (متن، چک‌باکس‌ها، سوئیچ‌ها) در فرم‌های با ۳–۵ فیلد بدون اعتبارسنجی پیچیده استفاده کنید. برای صفحات با حالت سراسری، درخواست‌های شبکه و فیلدهای وابسته، UDF را با جریان یک‌طرفه و پردازش صریح رویدادها به کار ببرید. در IT Sectr ما هر دو رویکرد را ترکیب می‌کنیم: Two-Way Binding درون فرم، UDF برای ناوبری و منطق کسب‌وکار.

خطاهای رایج در اتصال دوطرفه

حلقه به‌روزرسانی بی‌نهایت — رایج‌ترین مشکل هنگام استفاده از Two-Way Binding. چرخه زمانی رخ می‌دهد که تغییر مدل باعث به‌روزرسانی UI شود که دوباره مدل را تغییر می‌دهد. در DataBinding این اتفاق می‌افتد اگر getter در @InverseBindingAdapter بلافاصله پس از فراخوانی setter مقدار جدیدی برگرداند. راه‌حل — بررسی تغییر مقدار قبل از نوشتن معکوس (شرط guard).

دومین خطای رایج — اتصال فیلدهای محاسباتی. اگر فیلدی به فیلد دیگر وابسته باشد (مثلاً هزینه کل = قیمت × تعداد)، اتصال دوطرفه می‌تواند به حالت ناسازگار منجر شود. مثلاً کاربر تعداد را تغییر می‌دهد، محاسبه مجدد هزینه فعال می‌شود که دوباره تعداد را تغییر می‌دهد. برای فیلدهای محاسباتی از اتصال یک‌طرفه با Flow یا Combine استفاده کنید.

سومین خطا — اتصال فیلدهای Observable بدون LifecycleOwner. در Android DataBinding باید LifecycleOwner به binding ارسال شود، در غیر این صورت مشاهده‌گرها هنگام نابودی Activity پاک نخواهند شد که منجر به نشت حافظه می‌شود. همیشه viewLifecycleOwner را در فرگمنت‌ها و this را در Activity ارسال کنید.

طبق Google Issue Tracker (2024)، حدود ۱۵٪ از گزارش‌های باگ DataBinding مربوط به به‌روزرسانی‌های چرخه‌ای است. برای تشخیص از Android Studio Layout Inspector استفاده کنید — مقادیر فعلی همه binding‌ها را روی صفحه نشان می‌دهد که یافتن منبع حلقه بی‌نهایت را ساده می‌کند.

سوالات متداول

اتصال دوطرفه چه تفاوتی با اتصال یک‌طرفه دارد؟

اتصال یک‌طرفه (One-Way Binding) داده‌ها را فقط از مدل به نمایش منتقل می‌کند — با تغییر مدل UI به‌روز می‌شود، اما ورودی کاربر مدل را مستقیماً تغییر نمی‌دهد. Two-Way Binding داده‌ها را در هر دو جهت همگام‌سازی می‌کند: تغییر در UI به طور خودکار مدل را به‌روز می‌کند و بالعکس. در نحو DataBinding تفاوت با نمادهای @{} (One-Way) و @={} (Two-Way) نشان داده می‌شود.

چه زمانی نباید از Two-Way Binding استفاده کرد؟

از اتصال دوطرفه برای فرم‌های پیچیده با فیلدهای وابسته، مقادیر محاسباتی یا اعتبارسنجی سفارشی استفاده نکنید — در این سناریوها جریان داده غیرقابل پیش‌بینی می‌شود. همچنین از آن در لیست‌های RecyclerView با تعداد زیادی عنصر که هر عنصر binding دارد اجتناب کنید: عملکرد به دلیل مشاهده‌گرهای متعدد کاهش می‌یابد. UDF با جریان یک‌طرفه و پردازش رویداد از طریق Intent مقیاس‌پذیری بهتری دارد.

آیا Jetpack Compose از اتصال دوطرفه پشتیبانی می‌کند؟

Jetpack Compose نحو داخلی @={} ندارد، اما همگام‌سازی دوطرفه از طریق جفت State + callback (onValueChange) پیاده‌سازی می‌شود. والد مقدار فعلی (State) و تابع به‌روزرسانی را ارسال می‌کند، کامپوننت فرزند هنگام تغییر callback را فراخوانی می‌کند. این یک binding صریح است، نه ضمنی — جریان داده قابل مشاهده و ردیابی باقی می‌ماند.

چگونه حلقه بی‌نهایت را در DataBinding اشکال‌زدایی کنیم؟

برای اشکال‌زدایی چرخه‌ها در DataBinding از Android Studio Layout Inspector استفاده کنید — مقادیر فعلی همه متغیرهای مرتبط روی صفحه را نشان می‌دهد. لاگ‌گیری را در @InverseBindingAdapter اضافه کنید و بررسی کنید که getter مقداری متفاوت از مقدار تازه نوشته شده برنگرداند. راه‌حل استاندارد — شرط guard: if (newValue != currentValue) قبل از نوشتن معکوس.

آیا Two-Way Binding در Flutter وجود دارد؟

در Flutter اتصال دوطرفه داخلی وجود ندارد، اما از طریق ترکیب TextEditingController و callback onChanged شبیه‌سازی می‌شود. برای StatefulWidget توسعه‌دهنده به صورت دستی در تغییرات کنترلر مشترک می‌شود و مدل را به‌روز می‌کند. در Provider و Riverpod همگام‌سازی دوطرفه از طریق Selector ساخته می‌شود که با تغییر مدل ویجت را بازسازی می‌کند و هنگام ورودی کاربر callback را فراخوانی می‌کند.

جمع‌بندی

  • Two-Way Binding — مکانیسم همگام‌سازی خودکار دوطرفه بین مدل و نمایش که نیاز به نوشتن دستی شنوندگان و setterها را از بین می‌برد.
  • در Android از طریق DataBinding با نحو @={} و حاشیه‌نویسی‌های @BindingAdapter/@InverseBindingAdapter پیاده‌سازی می‌شود.
  • در iOS SwiftUI propertyWrapper @Binding را ارائه می‌دهد که یک ارجاع read-write به @State والد ایجاد می‌کند.
  • DataBinding حجم کد UI را ۳۰–۵۰٪ کاهش می‌دهد، اما با ظهور حلقه‌های بی‌نهایت اشکال‌زدایی را دشوار می‌کند.
  • برای فرم‌های با ۳–۵ فیلد Two-Way Binding مؤثر است؛ برای حالت سراسری و اعتبارسنجی پیچیده UDF را انتخاب کنید.
  • در Jetpack Compose ارتباط دوطرفه از طریق State + callback onValueChange شبیه‌سازی می‌شود و جریان داده صریح را حفظ می‌کند.
  • ریسک‌های اصلی — به‌روزرسانی‌های چرخه‌ای، اتصال فیلدهای محاسباتی و نشت حافظه در نبود LifecycleOwner.

ما یک اپلیکیشن موبایل به صورت کلید در دست توسعه خواهیم داد

IT Sectr از سال 2017 برنامه‌های iOS و Android را برای استارتاپ‌ها و کسب‌وکارها ایجاد می‌کند. ما به شما مشاوره می‌دهیم و بهترین راه‌حل را پیشنهاد خواهیم کرد.

بحث درباره پروژه

همچنین بخوانید