هناك نوعان من واجهات برمجة التطبيقات التي تستخدم نمط الأوامر. كما قال sharplet ، ربما تم إحضارها من ReactiveObjC ، على سبيل المثال button.rac_command
.
extension Reactive where Host: UIButton {
public var pressed: MutableProperty<CocoaAction> { ... }
}
extension Reactive where Host: UIBarButtonItem {
public var pressed: MutableProperty<CocoaAction> { ... }
}
من الخطأ أن يكون لديك طريقة عرض تمتلك Action
بمرجع قوي. يجب ترك ملكية Action
(بشكل غير مباشر عبر CocoaAction
) لطبقته الأصلية (سواء كانت VC أو VM).
لذلك ، بعد مناقشة مطولة مع liscio و sharplet ، يُقترح الحصول على واجهة جديدة مصممة خصيصًا لـ Action
مع UIControl
و NSControl
s. المفهوم مثل:
extension Reactive where Base: UIControl {
/// Create a trigger signal for a specific set of control events.
public func trigger(for controlEvents: UIControlEvents) -> Signal<(), NoError>
/// The action is weakly referenced internally.
public func setAction<Input, Output, Error>(_ action: Action<Input, Output, Error>, for controlEvents: UIControlEvents, inputTransform: (Self, UIControlEvents) -> Input)
public func removeAction()
/// ... some convenience overloads of `setAction(_:for:inputTransform:)`.
}
باستخدام واجهة برمجة التطبيقات هذه ، سنزيل جميع أهداف الربط Action
من الفئتين الفرعيتين UIControl و NSControl ، حيث كان من المفترض أن تغطي واجهة برمجة التطبيقات كل منهم.
بمعنى أكبر ، سنقوم بتقليد نمط الإجراء المستهدف خلف @IBAction
و @IBOutlet
.
في Interface Builder ، قد نقوم بربط _Sent Action_ لزر UIButton بإجراء _Received Action_ (طريقة @IBAction
) من UIViewController ، و UIButton يرسل رسالة إلى الهدف ذي الإشارة الضعيفة.
في حالتنا ، سنكشف عن مجموعة من إشارات الأحداث على عناصر التحكم. مع Action
s كأهداف ملزمة ، قد يكون لـ Action
دور مشابه لـ @IBAction
. فمثلا:
// Received Action <~ Sent Action
commitAction <~ confirmButton.touchUpInside
// The reverse `isEnabled` binding is now explicit & optional.
confirmButton.isEnabled <~ commitAction.isEnabled
// OR
confirmButton.setAction(commitAction, for: .touchUpInside)
التي تعادل
confirmButton.rac_pressed.value = CocoaAction(commitAction)
ولكن مع ملكية منفصلة عن طريق دلالات الربط من الضعيف إلى الضعيف.
لا تنس أننا سنحتاج أيضًا إلى هذا للحصول على وظائف مكافئة:
confirmButton.rac.isEnabled <~ commitAction.isEnabled
حسنًا ، لقد تجاهلت ذلك. يبدو أننا يجب أن ندخل في كل شيء مع Action
بعد ذلك.
andersio لست متأكدًا مما
لمعالجة ما يعرضهsharplet ، أعتقد أنه من الجيد فصل انتشار القيم إلى المدخل isEnabled
على زر.
إن إخفاء هذه الحقيقة بطريقة / نمط ملائم لا يجعلنا نشعر بالكثير ، في رأيي.
تم التنفيذ في https://github.com/ReactiveCocoa/ReactiveCocoa/pull/3210/commits/f983a746f915a9a517e9e076c19581724075c562. دعونا نرى كيف يفكر الآخرون. 😆
من الخطأ أن يكون لديك وجهة نظر تمتلك الإجراء مع مرجع قوي. يجب ترك ملكية الإجراء (بشكل غير مباشر عبر CocoaAction) لطبقته الأصلية (سواء كانت VC أو VM).
لا أعتقد أنني أوافق. لماذا يشعر هذا خطأ بالنسبة لك؟
المرجع الضعيف عبارة عن أجزاء صغيرة من واجهة برمجة التطبيقات المقترحة ، والتي تدفع مجموعة واحدة من طرق UIControl
التي تغطي جميع عناصر التحكم باستخدام نمط الأوامر (عبر Action
).
في Rex ، كان حصريًا لـ UIButton
مع تنفيذ محدد له.
AFAIK ، يمثل عادةً Action
أمرًا ، ويمتلكه نموذج عرض ، ومن المحتمل أن يكون له تبعيات داخلية وخارجية من / على حالته. مثل نمط عمل Cocoa المستهدف ، لا أفهم لماذا يجب أن يكون مرجعًا قويًا عندما يكون له مالك واضح.
من المحتمل أن تكون واجهة برمجة تطبيقات Rex التي تستخدم مرجعًا قويًا ناتجة عن كيفية تصرف CocoaAction
، وهو أمر مطلوب للاحتفاظ به بحيث يمكن نشر القيم إلى Action
المغلف.
بدلاً من ذلك ، تأخذ واجهة برمجة التطبيقات المقترحة هنا Action
مباشرة.
ريكس 0.12:
extension UIButton {
public var rex_pressed: MutableProperty<CocoaAction> { get }
// This one would become `BindingTarget`. Irrelevant to this issue.
public var rex_title: MutableProperty<String> { get }
}
مقترح (يعمل على جميع UIControl
s):
extension Reactive where Base: UIControl {
/// The action is weakly referenced internally.
public func setAction<Input, Output, Error>(
_ action: Action<Input, Output, Error>,
for controlEvents: UIControlEvents,
inputTransform: (Self, UIControlEvents) -> Input
)
public func removeAction()
/// ... some convenience overloads of `setAction(_:for:inputTransform:)`.
}
بشكل عام ، لدينا ثلاث فئات من حالات واجهة المستخدم + فئة خاصة بنمط الأوامر مع Action
:
UIControl.isEnabled
، UILabel.text
، UIView.isHidden
.نموذج كـ BindingTarget
s.
UIControl.trigger(for: .valueChanged)
، UITableViewCell.trigger(for: #selector(prepareForReuse))
نموذج كـ Signal
s.
UITextField.text
، NSTextField.text
، UISwitch.isOn
# 3229
Action
بعنصر تحكم. UIButton.rac_command
_هذه المسألة. _
andersio أعتقد أنه لسوء الحظ ، removeAction()
أيضًا تحديد أحداث التحكم التي تزيل الإجراء من أجلها ، _ أو _ بالنسبة لـ 1.0 فقط ، سأعيد تسميته removeAllActions()
وأضف ملاحظة إلى setAction()
للإشارة إلى أن عنصر التحكم لا يتعامل حاليًا إلا مع إجراء واحد يتم ربطه باستخدام "واجهة برمجة التطبيقات السهلة" هذه لأن هذه هي الطريقة التي يستخدمها 99.9٪ من الأشخاص ، على أي حال.
إنه قابل للنقاش. قد لا تكون إجراءات IMO المتعددة منطقية ، لأنه في هذه الحالة يمكن لإجراءات متعددة معالجة isEnabled
بشكل متزامن (= غير محدد نوعًا ما).
لماذا لا تضيف خصائص (Swift) التي تحدد الإجراء المحدد مباشرة؟
extension Reactive where Base: UIButton {
var pressed: CocoaAction {
get { … }
set { … }
}
}
أو ربما تم تضمين ذلك في قسم some convenience overloads
الخاص بك؟
أعتقد أنه من الجيد إذا أردنا إضافة واجهة برمجة تطبيقات عامة ، لكنني أعتقد أن واجهات برمجة التطبيقات الملائمة أكثر أهمية. هذه هي واجهة برمجة التطبيقات التي سأتباهى بها للعالم. أعتقد أن شيئًا كهذا مقنع للغاية:
button.reactive.pressed = CocoaAction(viewModel.action)
طالما أننا نذهب مع setAction(…)
، فمن المحتمل أن تتم الإزالة عن طريق تعيين إجراء nil
. لا حاجة لواجهة برمجة تطبيقات منفصلة.
هذا بلا شك يبدو أفضل ، نعم. يجب بالتأكيد الاحتفاظ بهذه. ما زلت أقترح جعل مرجع CocoaAction
ضعيفًا إلى Action
بالرغم من ذلك.
setAction
( 790b3e8 ) ، pressed
( 790b3e8 ).
extension Reactive where Base: UIButton {
public var pressed: CocoaAction<Base> { get nonmutating set }
}
extension Reactive where Base: UIControl {
public var action: CocoaAction<Base>? { get }
public var controlEventsForAction: UIControlEvents? { get }
/// `CocoaAction` is retained by `self`, but `CocoaAction` weakly references `Action`.
public func setAction(
_ action: CocoaAction<Base>?,
for controlEvents: UIControlEvents
)
}
أعتقد أن API ستحتاج إلى دعم إجراءات متعددة. لذلك يجب أن يكون هذا صحيحًا:
button.reactive.setAction(action1, for: .touchUpInside)
button.reactive.setAction(action2, for: .touchUpOutside)
// button now has 2 actions set for 2 different events
لماذا يشير CocoaAction
ضعيف إلى Action
؟
تضمين التغريدة
// button now has 2 actions set for 2 different events
هذا يعني أن حالة التمكين button
مرتبطة بكل من action1.isEnabled
و action2.isEnabled
. لست متأكدًا مما إذا كان هذا هو ما نريده. كيف ينبغي تحديد حالة التمكين؟ هل AND
من كل حالات التمكين الخاصة بـ Action
s؟
لماذا يشير CocoaAction بشكل ضعيف إلى Action؟
تم إرجاعه. أعترف أنه لا يسبب مشكلة في الوقت الحالي (وخلال تاريخ ريكس).
هذا يعني أن حالة الزر التمكينية مرتبطة بكل من action1.isEnabled و action2.isEnabled. لست متأكدًا مما إذا كان هذا هو ما نريده. كيف ينبغي تحديد حالة التمكين؟ هل هي حالة تمكين لجميع الإجراءات "و"؟
و لا يبدو مناسبًا لي.
فيما يلي خياراتنا:
.pressed
، وما إلى ذلك وليس في setAction(:for:)
مباشرة(5) يبدو أنه أفضل حل بالنسبة لي. الحالة الشائعة سهلة ومريحة. إذا كنت تريد أن تفعل شيئًا أكثر تعقيدًا ، فلا يزال بإمكانك ذلك.
(5) يعني أن setAction
لن يكون له أي اختلاف عن عمل actionN <~ reactive.trigger(for: .specificEvent)
. علاوة على ذلك ، ما الذي يجب أن يحدث إذا احتوى عنصر التحكم على عدة فتحات أوامر ملائمة؟
لا يزال الأمر مختلفًا في ذلك:
CocoaAction
وإلا فإنهم متماثلون تقريبًا ، نعم. لكن أعتقد أن هذا جيد.
إذا كانت الخطة تدعم أكثر من إجراء ، فمن المحتمل أن نعيد تسميتها إلى addAction
.
لنفترض أنه إذا أضفنا وسيطة propagateEnablingState
إلى setAction
، فلا يزال يتعين علينا تحديد كيفية التعامل مع المتجه isEnabled
. (قابل للتكوين؟ على سبيل المثال و / أو / غير محدد؟)
لست متأكدًا من أنه يستحق دعم> إجراء واحد في "خاصية الملاءمة" التي تتم مناقشتها هنا ، حيث تركنا الاستخدام البديل (تشغيل الإجراءات بشكل فردي مع trigger(for:)
) للأشخاص الذين يحتاجون إلى أكثر من واحد.
لا يمكنني رؤية سبب وجيه لإضافة أكثر من إجراء واحد إلى زر بهذه الطريقة بالنظر إلى البديل سهل الاستخدام (يحدث هذا أيضًا لمطابقة استخدام عناصر Signal / BindingTarget الأخرى الخاصة بنا.)
بشكل أساسي ، إذا كنت تريد "واجهة برمجة تطبيقات بسيطة" ، فأنت تستخدم خاصية action
كما هو موضح هنا. تنعكس حالة التمكين الخاصة به في الزر.
إذا كنت تريد إرفاق زر بـ> إجراء واحد ، فأنت تستخدم trigger
s الخاص بهم ، ثم قم بتوصيل map
خاص بك للربط بـ isEnabled
BindingTarget
. هل هذا يبدو معقولا؟
بشكل أساسي ، إذا كنت تريد "واجهة برمجة تطبيقات بسيطة" ، يمكنك استخدام خاصية الإجراء كما هو موضح هنا. تنعكس حالة التمكين الخاصة به في الزر.
إذا كنت تريد إرفاق زر بـ> إجراء واحد ، فأنت تستخدم المشغلات الفردية الخاصة بهم ، ثم اربط خريطة خاصة بك لربطها بـ isEnabled BindingTarget. هل هذا يبدو معقولا؟
هذا يبدو معقولا بالنسبة لي.
فهل نحتاج إلى setAction(:for:)
على الإطلاق؟
سأخفيه بعد ذلك. أبقيها بسيطة.
التعليق الأكثر فائدة
سأخفيه بعد ذلك. أبقيها بسيطة.