为什么我推荐在网络请求回调处理分成两部分

《Effective 2.0》这本书中第 39 条:用 handler 块降低代码分散程度,建议“使用同一个块来处理成功与失败情况”,在实习的阶段刚好遇到这个情况,所以浅显的论述一下自己的观点,希望能有个抛砖引玉的效果。

# 本书中对两种回调处理的分析

# 成功情况和失败情况用两个块分别处理

API 如下:

EOCNetworkFetcher *fetcher = [[EOCNetworkFetcher alloc] initWithURL:url];
[fetcher startWithCompletionHander:^(NSData *data) {
      // Handler success
}
            failureHandler:^(NSError *error) {
     // Handler failure
}];

优点:

  • 不同情况分开写,代码更易读懂
  • 还可以根据具体需要,把处理失败情况或者成功情况所用的代码省略

# 成功情况和失败情况处理全部放在一个块

API 如下:

EOCNetworkFetcher *fetcher = [[EOCNetworkFetcher alloc] initWithURL:url];
[fetcher startWithCompletionHander:^(NSData *data, NSError *error) {
      if (error) {
          // Handle failure
      } else {
         // Handle success
      }
}];

优点:

  • 在传入错误信息时,可以把数据也传进来
  • 可以根据问题作适当处理,并利用已经获得的数据做些事情
  • 调用 API 的代码可能会在处理成功响应的过程中发现错误

根据自己的具体使用经验来分析我为什么推荐前者:

# 分离网络请求成功和失败处理

先给出我司在网络请求回调的处理方式(大概如下):

^(DQKDataModel *dataModel, DQKErrorModel *errorModel, NSError *error) {
       // Handle …
}

Response 的 JSON 数据大概如下:

{
    data =     {
            topic  = “天气不错”;
    };
    respcd = “00”;
    respmsg = “数据获取成功”;
}

回调的代码块就是上面的样子,分别是成功返回数据信息(data)、业务逻辑信息(respcd ,respmsg)、网络请求错误信息。一个代码块,数据可以统一处理。 我们一般不会去在 GET POST 中,获得很大的数据的 Response ,所以作者提到的这种数据一半就没了的情况基本是不会有的,我们从业务处理网络处理两个情况来分析。

这里业务逻辑信息是指如登录密码错误,没有权限发帖等逻辑。

从网络请求层面分析,如果网络有问题,或者服务器处理出现错误等等,这个时候可能会出现错误 404 ,500 之类的情况,此时就不会有 JSON 数据的返回,那也就不会有返回数据信息,业务逻辑信息,因为这两个是同时在 JSON 中一起返回的。网络回调的层次应该如下所示:

成功返回数据信息业务逻辑信息
网络请求错误信息

二者并没有什么可联系的关系,有数据一般就没有网络错误信息,没有数据一般就没有网络错误信息。

# 执行成功的代码就必定不执行失败的代码,反之亦然

两个块的时候,一个块专注处理成功情况,一个块专注网络错误,不需要再去判断有那个数据,没有那个数据,会处理成功的代码块就一定是有数据的,会处理错误信息的代码就一定是网络请求中出现了某些问题。

同时这里还减少了一层鞭尸金字塔,更方便代码的维护和 Review 。

这里说的鞭尸金字塔就是类似这样的代码:

if (condition) {
        if (condition) {
            if (condition) {
                if (condition) {
                    statements
                } else if (expression) {
                    statements
                } else {
                    statements
                }
            }
        } else if (expression) {
            statements
        } else {
            statements
        }
    } else {
        statements
    }

# 业务失败放在数据中

那么我们该怎么解决这种既需要处理网络请求错误,也需要处理业务逻辑错误呢?

我的方法如下:

^(DQKDataModel *dataModel) {
       if (dataModel.error) {
        // Handler data error
       } else if (dataModel.data) {
       // Handler data
       }
} failureHandler:^(NSError *error) {
     // Handler failure
}

对返回的数据处理生成两个属性,一个是业务错误信息,一个是数据成功信息,相比之前的处理可能会清晰一点。

如果您对网络请求的封装的有更好的想法、理解,欢迎大家一起讨论。 补充(2015-8-24):和 @画渣程序猿mmoaay (opens new window) 简单讨论了下,可以考虑这样的处理,在封装处理中把 self(UIViewController) 传进去,然后错误处理(以及停止下拉刷新等操作)就可以都封装在里面处理,外部 API 只留一个成功返回的块。