查阅快应用开发文档,可以知道官方提供了数据请求接口;对于如何使用,文档中也给出了简单的说明和代码示例,但很显然,这在实际项目中,不够科学优雅、且更不高效,所以需要对其进行再封装,使得可以大幅提升开发效率,同时也令整个代码优雅,以便于维护。所以在此篇文章的存在,旨在于讨论下如何优雅处理「快应用」数据请求。

Based on the examples provided with in the RxSwift GitHub
repo
I managed to do it.
Basically, I’m using a recursive function that returns my stream of
PaginatedList items, it calls itself with the loadNextPage trigger for
the next page. Here the code I used in my API manager:

原文转自:
http://www.jianshu.com/p/c695d20d95cb
学习大神写的,麻麻再也不用担心我的数据请求。。

有些后台写的不好,返回数据的字段偶尔有NULL类型存在,如果不直接赋值给其他对象就会造成崩溃。解决办法的主要思路就是将其中的NULL对象转换为@”“空字符串。以下是封装好的方法创建一个字典的分类
.h文件#import@interface NSDictionary (SetNilStr)

处理网络时,经常会遇到网络请求相互依赖的情况,如B请求的参数依赖A请求的回调结果,直接在A请求的回调中请求B,会导致回调嵌套,这样导致请求之间紧张耦合。这里介绍一个较优雅的方法:利用NSOperation的操作依赖。

金沙国际官网 1如何优雅处理快应用数据请求

class func films(withTitle title: String, startingAtPage page: Int = 0, loadNextPageTrigger trigger: Observable<Void> = Observable.empty()) -> Observable<[Film]> {
    let parameters: FilmSearchParameters = FilmSearchParameters(query: title, atPage: page)
    return TMDbAPI.instance.films(fromList: [], with: parameters, loadNextPageTrigger: trigger)
}

fileprivate func films(fromList currentList: [Film], with parameters: FilmSearchParameters, loadNextPageTrigger trigger: Observable<Void>) -> Observable<[Film]> {

    return self.films(with: parameters).flatMap { (paginatedList) -> Observable<[Film]> in
        let newList = currentList + paginatedList.results
        if let _ = paginatedList.nextPage {
            return [
                Observable.just(newList),
                Observable.never().takeUntil(trigger),
                self.films(fromList: newList, with: parameters.nextPage, loadNextPageTrigger: trigger)
            ].concat()
        } else { return Observable.just(newList) }
    }
}

fileprivate func films(with parameters: FilmSearchParameters) -> Observable<PaginatedList<Film>> {
    guard !parameters.query.isEmpty else { return Observable.just(PaginatedList.Empty()) }
    return Observable<PaginatedList<Film>>.create { (observer) -> Disposable in
        let request = Alamofire
            .request(Router.searchFilms(parameters: parameters))
            .validate()
            .responsePaginatedFilms(queue: nil, completionHandler: { (response) in
                switch response.result {
                case .success(let paginatedList):
                    observer.onNext(paginatedList)
                    observer.onCompleted()
                case .failure(let error):
                    observer.onError(error)
                }
            })
        return Disposables.create { request.cancel() }
    }
}

金沙国际官网 2

+(id)changeType:(id)myObj;

  • 本例尝试下载两张图片,假设图片2总是在图片1显示后再显示。
  • 下载框架选用AFNetworking3

备注: 本文最先发布于,基于 Ghost 构建的最新博客: 静轩之别苑。

为保证文中的代码,是直接拷贝就可以运行的,特有引用网络开源接口;需要备注说明的是,在快应用使用接口,需要在
manifest.json
中,对所使用的接口进行声明;使用数据请求,就需要注入下面的声明:

In ViewModel

fileprivate func setupFilms() -> Observable<[Film]> {

    let trigger = self.nextPageTrigger.asObservable().debounce(0.2, scheduler: MainScheduler.instance)

    return self.textSearchTrigger
        .asObservable()
        .debounce(0.3, scheduler: MainScheduler.instance)
        .distinctUntilChanged()
        .flatMapLatest { (query) -> Observable<[Film]> in
            return TMDbAPI.films(withTitle: query, loadNextPageTrigger: trigger)
        }
        .shareReplay(1)
}

2016-12-20 15_41_27.gif

@end

要使用NSOperation的操作依赖首先需要一个NSOperationQueue:

{ "name": "system.fetch" }

import $fetch from '@system.fetch'$fetch.fetch({ url: 'https://api.apiopen.top/singlePoetry', responseType: 'text', success: function  { const result = response.data console.log(`success response, code: ${result.code}, data: ${result.data}`) }, fail: function (data, code) { console.log(`fetch handling fail, code = ${code}`) }, complete: funtion (data, code) { console.log(`fetch handling complete, code = ${code}`) }})

包括获取手机上联系人的列表进行排序。
这里我按照大神写的敲了一遍。很强。
地址:https://github.com/fengfengaima/RequestCache.git

.m文件

NSOperationQueue *queue = [[NSOperationQueue alloc] init];queue.name = @"SessionQueue";

以上就是官方文档提供的上古 jQuery
时代的写法,冗长的代码,古老的回调式操作,以及对请求没有做任何必要的处理,如果这在项目中使用,对代码复用度无疑是零,从长期维护角度看,这样的代码就是导致痛苦的根源。虽然,示例代码这样写道,也无可厚非;但先入为主的模范作用,将对于诸多经验不够充足的开发者,起到错误的误导性引领。

#import “NSDictionary+SetNilStr.h”

由于AFNetworking3中的AFHTTPSessionManager不是NSOperation的子类,这与AFNetworking2有很大不同,不能直接加入NSOperationQueue,需要封装一下,幸运的是已经有第三方库已经做了所有工作,先引入库:

import $fetch from '@system.fetch'$fetch.fetch({ url: 'https://api.apiopen.top/singlePoetry', method: 'GET'}).then(response => { const result = response.data console.log(`success response, code: ${result.code}, data: ${result.data}`)}).catch(error => { console.log(`Something Error: ${error}`)})

@implementation NSDictionary (SetNilStr)

 pod 'AFSessionOperation', :git => 'https://github.com/robertmryan/AFHTTPSessionOperation.git'

其实,快应用对部分接口方法调用,返回了 Promise
优化,其中就包括数据请求接口;所以,使用如上这种方式,至少代码显得不那么冗长而古老。但,数据请求相关代码,却依然没有得到复用,这就需要对其进行封装处理。

金沙国际官网,//将NSDictionary中的Null类型的项目转化成@””

AFHTTPSessionOperationNSOperation
的子类,同时调用AFNetworking实现了HTTP 请求,下面是请求操作的实现:

如果每次调用接口,都需要 import,外加指定 url,method
等参数,这无异是变相的浪费生命;大道至简,优秀的开发流程,一定是便于编写和维护!所以有必要将其统一封装,如下面所封装的代码(可放置在
helpera/ajax.js 路径下):

+(NSDictionary *)nullDic:(NSDictionary *)myDic

NSString *imangeUrl1 = @"https://upload-images.jianshu.io/upload_images/2025746-368aa3a508d2fbac.jpg?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240";NSString *imangeUrl2 = @"https://upload-images.jianshu.io/upload_images/2025746-27bbf45bea40162c.JPEG?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240";NSOperation *op1 = [AFHTTPSessionOperation operationWithManager:manager HTTPMethod:@"GET" URLString:imangeUrl1 parameters:nil uploadProgress:nil downloadProgress:nil success:^(NSURLSessionDataTask *task, id responseObject) { NSLog(@"finished 1"); UIImage *image = [UIImage imageWithData:responseObject]; _imageView1.image = image;} failure:^(NSURLSessionDataTask *task, NSError *error) { NSLog(@"failed 1 - error = %@", error.localizedDescription);}];NSOperation *op2 = [AFHTTPSessionOperation operationWithManager:manager HTTPMethod:@"GET" URLString:imangeUrl2 parameters:nil uploadProgress:nil downloadProgress:nil success:^(NSURLSessionDataTask *task, id responseObject) { NSLog(@"finished 2"); UIImage *image = [UIImage imageWithData:responseObject]; _imageView2.image = image;} failure:^(NSURLSessionDataTask *task, NSError *error) { NSLog(@"failed 2 - error = %@", error.localizedDescription);}];
function requestHandle { return new Promise((resolve, reject) => { $fetch.fetch({ url: params.url, method: params.method, data: params.data }).then(response => { const result = response.data const content = JSON.parse(result.data) /* @desc: 可跟具体不同业务接口数据,返回你所需要的部分,使得使用尽可能便捷 */ content.code === 200 ? resolve(content.result) : resolve(content.message) }).catch((error, code) => { console.log(`🐛 request fail, code = ${code}`) reject })}/* 此处只是处于示例代码的可运行性,实际项目中,此方法最好予以封装 & 提取 */function queryString(url, query) { let str = [] for (let key in query) { if (typeof query[key] === 'object') { query[key] = JSON.stringify(query[key]) } str.push(key + '=' + query[key]) } let paramStr = str.join return paramStr ? `${url}?${paramStr}` : url}export default { post: function(url, params) { return requestHandle({ method: 'post', url: url, data: params }) }, get: function(url, params) { return requestHandle({ method: 'get', url: queryString(url, params) }) }}

{

op2依赖op1:

这 $fetch.fetch 返回本就是一个
Promise,这里为何额外要包裹的一层处理呢?这样做的好处不仅在于使其依旧可以链式调用,同时对返回数据统一处理,精简返回内容,使得在获取到最终结果处,可以尽可能简单,更加有利于后期做维护,调用方式就可以成为如下这样(涉及某类模块,可统一在相应模块文件下,如:helper/apis/poetry.js
):

NSArray *keyArr = [myDic allKeys];

[op2 addDependency:op1];
import $ajax from './../ajax'const baseUrl = 'https://api.apiopen.top/'export default { getSinglePoetry { return $ajax.get(`${baseUrl}singlePoetry`, data) }, getOtherApi { // other api .... }}

NSMutableDictionary *resDic = [[NSMutableDictionary alloc]init];

最后将op1、op2添加到操作队列中,这会开始执行网络请求:

另外,开发者将接口,按照功能模块规划,分门别类以存放至统一文件夹下,如
helper/apis;如此清晰明了,方便调用,且对于多人协作开发,又不相互响应,减少不必要的冲突。类似善用配置,以表驱动法的编程手法,应该活学活用,贯穿始终。这些理念,早在更优雅的处理-Http-请求
| 开箱即用的 Vue Webpack 脚手架模版中就有阐述。

for (int i = 0; i < keyArr.count; i ++)

[queue addOperations:@[op1, op2] waitUntilFinished:false];

至此,就对接口进行了完美封装处理;在业务层便捷调用,也是需要优化;在快应用,最为高效的办法,就是将上面封装暴露给
global,如此就可以:

{

执行结果:

import { $apis } from './helper'const hook2global = global.__proto__ || globalhook2global.$apis = $apis// 在任何其他页面、组件、js 文件,接可以像如下调用const params = {}$apis.poetry.getSinglePoetry.then(result => { // 处理正常逻辑}).catch(error => { // 处理请求异常逻辑})

id obj = [myDic objectForKey:keyArr[i]];

金沙国际官网 3模拟器截图金沙国际官网 4控制台输出

在业务逻辑中,发起数据请求时候,都需要添加
Loading,提示用户请求正在进行中,以免响应缓慢给用户带来不必要的疑惑;而,在请求成功或失败情形下,都需要对
Loading 进行消除,如此一来就会有下面的逻辑:

obj = [self changeType:obj];

按照我们的设计,op2在op1完成后才执行。

$apis.poetry.getSinglePoetry.then(result => { this.isLoading = false // 处理正常逻辑}).catch(error => { // 处理请求异常逻辑 this.isLoading = false})

[resDic setObject:obj forKey:keyArr[i]];

使用时注意不要造成依赖环,像这样:

很明显,像类似不管请求成功或失败,都需要执行的业务逻辑是存在的,如果分别在对应链式后做处理,谈何优雅呢?在
ES2018 有引入 finally 标准,跟快应用中请求后 complete
回调是一样的作用:不管最后状态如何,都会执行的操作。所以上面的调用,就可以优化成如下代码:

}

[op1 addDependency:op2];[op2 addDependency:op1];
$apis.poetry.getSinglePoetry.then(result => { // 处理正常逻辑}).catch(error => { // 处理请求异常逻辑}).finally => { this.isLoading = false})

return resDic;

这样两个操作都不会执行。

当按照预期这样写的时候,你会发现 finally
链式并未得到调用。查阅一番,兴许你也会得到一个答案,快应用规范没有
finally,如此一来,就不得不打补丁
来予以解决了,可以有的途径不少,下面介绍一种简单无依赖的法子,注入以下代码即可;那么上面关于
ajax.js 的封装则可以优化成如下代码:

}

Promise.prototype.finally = function  { const P = this.constructor return this.then( value => P.resolve(callback.then => value), reason => P.resolve(callback.then => { throw reason }) )}function requestHandle { return new Promise((resolve, reject) => { $fetch.fetch({ url: params.url, method: params.method, data: params.data }).then(response => { const result = response.data const content = JSON.parse(result.data) /* @desc: 可跟具体不同业务接口数据,返回你所需要的部分,使得使用尽可能便捷 */ content.code === 200 ? resolve(content.result) : resolve(content.message) }).catch((error, code) => { console.log(`🐛 request fail, code = ${code}`) reject.finally => { console.log(`✔️ request @${params.url} has been completed.`) resolve}

//将NSArray中的Null类型的项目转化成@””

需要补充说明的是,finally 方法指定的回调函数,用于指定不管
Promise
对象最后状态如何,都会执行的操作;它是与状态无关的,不依赖于 Promise
的执行结果,所以此处 polyfill 返回跟标准一致,回调函数不接受任何参数。

+(NSArray *)nullArr:(NSArray *)myArr

关于快应用数据请求的整体代码优化设计,具体可参见 Github
项目:quickapp-boilerplate-template:
🔨致力于构建更为优雅的「快应用」开发脚手架模板。

{

至此,对处理「快应用」数据请求,相比开发文档中所写到的示例,是不是优雅很多呢?如果你的项目中,涉及数据请求有超过
3
个的可能,那么你就应该做像着样。当然,这不仅仅限于此接口,其他如数据存储,上传下载,你都应该予以封装。这也不仅仅限于快应用,其他如开发
VueReact 等项目,亦是同理。

NSMutableArray *resArr = [[NSMutableArray alloc] init];

@2019-01-17 于深圳.福田 Last Modify:2018-01-19

for (int i = 0; i < myArr.count; i ++)

您可能会感兴趣的文章:

  • 云集优站,尽在「倾城之链」其二
  • 快应用之开发体验纪要
  • 快应用开发资源、教程汇聚
  • TOP 100 大前端超棒精选列表

{

id obj = myArr[i];

obj = [self changeType:obj];

[resArr addObject:obj];

}

return resArr;

}

//将NSString类型的原路返回

+(NSString *)stringToString:(NSString *)string

{

return string;

}

//将Null类型的项目转化成@””

+(NSString *)nullToString

{

return @””;

}

//主要方法

//类型识别:将所有的NSNull类型转化成@””

+(id)changeType:(id)myObj

{

if ([myObj isKindOfClass:[NSDictionary class]])

{

return [self nullDic:myObj];

}

else if([myObj isKindOfClass:[NSArray class]])

{

return [self nullArr:myObj];

}

else if([myObj isKindOfClass:[NSString class]])

{

return [self stringToString:myObj];

}

else if([myObj isKindOfClass:[NSNull class]])

{

return [self nullToString];

}

else

{

return myObj;

}

}

@end

需要转换的时候调用以下方法就OK了,不管有多少个层级,全部转换

//json表示获取到的带有NULL对象的json数据

NSDictionary *newDict = [NSDictionary changeType:json];

发表评论

电子邮件地址不会被公开。 必填项已用*标注

网站地图xml地图