在编写易于维护的代码时,有几个概念对我很有用:
除了具体的模型对象(例如,如果从服务请求天气信息,您可能有一个Weather
表示特定天气报告的类,例如 a city
、 atemperature
和narrative
属性),我还将使用一个数据控制器对象来协调从缓存、持久存储或网络请求对象,视情况而定(通常按此顺序)。您要警惕仅依赖于 Web 服务的简单请求,因为缓存等在设计响应式 UI 时可能非常重要。
对我来说,关键是我很少希望视图或视图控制器处理这种细节级别(尽管我承认,在琐碎的情况下,我直接从控制器完成了请求)。理想情况下,视图控制器将启动我的数据控制器的异步请求,向它传递一个带有参数的完成块(a)正在检索的模型对象;(b) 一个NSError
。
我将在下面包含一个示例。
正如您可能从上面推断的那样,我经常也将 Web 请求的启动和将响应解析为它们自己的对象的细节抽象出来(因为数据控制器可能足够复杂,管理缓存、持久存储等) . 我通常将NSOperation
子类用于请求/解析任务(它适用于带有完成块的异步请求;如果 UI 移动到其他内容,则能够取消挂起的请求等)。因此,如果我的数据控制器断定缓存和/或持久存储不能满足请求,它将启动异步请求/解析操作。
您列出了分配给视图控制器的其他一些职责,但最好也将其抽象出来:
1.2) 处理手势 - 如果手势变得有点复杂(例如仅水平,从边缘滑动等),我实际上将继承手势处理程序,大大简化视图控制器自己与手势处理程序的交互。
3) 定制 UI - 如果 UI 需要大量定制,我也会经常将适当的视图子类化。特别是使用UITableViewCell
and UICollectionViewCell
,这可以极大地简化一个视图控制器的代码。任何需要任何材质定制的视图通常都可以在视图本身的子类中更优雅地完成。
示例数据控制器抽象
所以,我可能有一个这样定义的完成块:
typedef void(^WeatherRequestCompletion)(WeatherReport *weatherReport, NSError *error);
然后我的数据控制器中可能有一个方法,其接口如下:
- (WeatherRequest *)requestWeatherWithIdentifier:(CityIdentifier)cityIdentifier completion:(WeatherRequestCompletion)completion;
我的视图控制器会像这样使用它:
typeof(self) __weak weakSelf = self;
self.weatherRequest = [[WeatherModel sharedController] requestWeatherWithIdentifier:self.cityIdentifier completion:^(WeatherReport *weatherReport, NSError *error) {
if (error) {
// handle error
}
if (weatherReport) {
weakSelf.cityLabel.text = weatherReport.city;
weakSelf.tempLabel.text = [weatherReport.temperature stringValue];
weakSelf.narrativeLabel.text = weatherReport.narrative;
}
}];
视图控制器不应该担心请求的格式或响应的解析方式(这是我的网络请求NSOperation
子类的工作)。视图控制器也不应该过多地参与缓存和/或持久存储逻辑(这是我的数据控制器的工作)。因此,视图控制器应该被提炼成非常合乎逻辑且易于理解的东西。
请注意,您会注意到我的数据控制器正在返回一个请求对象(对我来说,这通常只是一个 typedef 到 a NSOperation
)。我将让我的视图控制器维护weak
对此的引用,以便我可以在需要时轻松取消请求,例如在dealloc
视图控制器中):
- (void)dealloc
{
[_weatherRequest cancel];
}