ReactiveX在Xamarin.iOS中订阅者无法监听后续变更求助
看起来你遇到的问题是Rx流首次正常工作,但后续文本变更时订阅者收不到数据——虽然文本变更的事件确实触发了。我帮你分析几个可能的原因,以及对应的修复方案:
1. 最可能的原因:未处理异常导致订阅终止
Rx的核心规则之一是:当Observable抛出未捕获的异常时,订阅会自动终止,后续的任何事件都不会再被处理。你的订阅代码没有错误处理分支,很可能后续的搜索请求抛出了异常(比如网络波动、API返回格式错误等),导致订阅被静默终止。
修复方案:给订阅添加错误处理回调,同时在流中捕获可能的异常,避免单个请求的异常终止整个流:
// 给订阅添加错误处理 dataStream.Subscribe( o => { suggestions.Add(o); TableView.Source = new ATableSource(suggestions); TableView.ReloadData(); }, ex => { // 打印异常信息排查问题 Console.WriteLine($"搜索请求出错:{ex.Message}\n{ex.StackTrace}"); // 可选:如果想让订阅继续,可在这里做错误恢复逻辑 }); // 在流中捕获单个请求的异常 .Select(t => { try { var lookup = new Lookup(prefix: t); smartyStreetsClient.Send(lookup); var data = new List<Suggestion>(lookup.Result); return data.ToObservable(); } catch (Exception ex) { Console.WriteLine($"处理请求「{t}」时出错:{ex.Message}"); return Observable.Empty<Suggestion>(); // 返回空流,避免终止整个订阅 } })
2. 同步网络请求阻塞UI线程
你的代码在UI线程上直接执行smartyStreetsClient.Send(lookup)同步网络请求,这会阻塞UI线程,导致后续的文本变更事件处理延迟,甚至可能因为UI线程卡死而出现调度异常。虽然你说每次文本变更都能执行,但阻塞UI线程很可能导致Rx流的内部调度出现问题。
修复方案:将网络请求移到后台线程执行,完成后切回UI线程更新UI,同时加上节流避免频繁请求:
var dataStream = Observable .FromEventPattern<UISearchBarTextChangedEventArgs>( ev => searchController.SearchBar.TextChanged += ev, ev => searchController.SearchBar.TextChanged -= ev) .Select(o => o.EventArgs.SearchText) .DistinctUntilChanged() .Throttle(TimeSpan.FromMilliseconds(300)) // 节流,避免用户快速输入时频繁触发API .ObserveOn(RxApp.TaskpoolScheduler) // 切换到后台线程执行网络请求 .Select(t => { var lookup = new Lookup(prefix: t); smartyStreetsClient.Send(lookup); return new List<Suggestion>(lookup.Result); }) .Catch((Exception ex) => { Console.WriteLine($"流异常:{ex.Message}"); return Observable.Return(new List<Suggestion>()); }) .ObserveOn(SynchronizationContext.Current) // 切回UI线程处理UI更新 .SelectMany(list => list.ToObservable()); // 将列表拆分为单个元素发射
3. Switch()与内部Observable的潜在问题
你的代码在Select中返回data.ToObservable().ObserveOn(SynchronizationContext.Current),然后用Switch()切换到最新的流。如果后续的lookup.Result为空,那么data.ToObservable()不会发射任何元素,订阅者自然收不到数据。
验证与修复:在Select中添加日志,确认lookup.Result是否有数据:
.Select(t => { var lookup = new Lookup(prefix: t); smartyStreetsClient.Send(lookup); var data = new List<Suggestion>(lookup.Result); Console.WriteLine($"搜索文本「{t}」返回{data.Count}条结果"); // 打印结果数量 return data.ToObservable(); })
如果结果数量为0,你需要检查SmartyStreets的API请求参数或返回逻辑;如果有数据但订阅者没收到,可去掉内部的ObserveOn,将UI线程调度移到整个流的最后(如方案2所示)。
4. UI更新逻辑的优化
另外,你的订阅中每次收到一个Suggestion就重新设置TableView的Source并ReloadData,这会导致频繁的UI刷新。更好的做法是直接发射完整的搜索结果列表,一次性更新UI:
// 修改流,直接发射完整列表 var dataStream = Observable // ... 前面的逻辑保持不变,去掉最后面的SelectMany .ObserveOn(SynchronizationContext.Current); // 订阅时一次性更新UI dataStream.Subscribe( suggestionsList => { suggestions.Clear(); suggestions.AddRange(suggestionsList); TableView.Source = new ATableSource(suggestions); TableView.ReloadData(); }, ex => Console.WriteLine($"错误:{ex.Message}"));
内容的提问来源于stack exchange,提问作者panthor314

