时光地下铁

走走停停,看看风景

0%

Swift网络库最著名的就是Alamofire,但是在实际项目中,想要尽可能简单的发起网络请求,必须对Alamofire进行一层封装。依赖Alamofire的封装库也有很多优秀的,比如Moya。但是经过项目的使用,我发现Moya用起来并不简单,它有以下问题:

  • 硬编码较多
    Moya使用时要在指定的方法中添加请求方法,请求参数,路径。
  • 封装复杂
    Alamofire已经是一个功能繁多的网络库了,Moya的二次封装,既要保证功能完整,又要使用面向协议,导致了过多的抽象,用户在使用时,大都需要三次业务的封装,虽然也面向协议了,但却复杂了。

因此对于众多中小项目而言,笔者觉得Moya并不是一个合适的选项。合适的网络库,应该是对基础库(Alamofire)的精简,保证既简单易用,又有灵活的扩展性。

那发起的请求,要简化到什么程度?
只需指定参数路径,就可以发起请求,最好使用类方法,来直接发起请求。
而简化的越彻底,其他地方要做的就越多,因此,通常情况下,也要设计出以下功能:

  • 统一设置公共参数
  • 统一设置请求头
  • 统一的设置加载指示器
  • 统一解析数据
  • 等等

我根据以上要求,花了两天的空闲时间,写了一个轻量级的网络库LightNetwork,抛砖引玉。
该库使用面向对象的封装逻辑,下面是它的介绍。

发起请求

比如发起一个修改用户名的请求,外部暴露参数userName即可,方法内部指定path和parameter。调用时直接使用类方法调用。

1
2
3
4
5
6
7
8
9
10
11
class ExampleRequest : ExampleBaseRequest {
class func modify(userName:String,success: @escaping LNRequestSuccess, failure:@escaping LNRequestFailure) {
self.post(path: "/modify", parameters: ["userName":userName], success: success, failure: failure)
}
}
//use
ExampleRequest.modify(userName: "Light") { request, responseData in
print("Success:\(responseData)")
} failure: { request, errorDescription in
print("Failure:\(errorDescription)")
}

如果觉得统一管理URL更好,也可以使用静态字符串。

1
2
3
4
5
6
7
8
9
struct URLPath {
static let query : String = "/query"
static let goods : String = "/goods"
static let modify : String = "/modify"
}

class func modify(userName:String,success: @escaping LNRequestSuccess, failure:@escaping LNRequestFailure) {
self.post(path: URLPath.modify, parameters: ["userName":userName], success: success, failure: failure)
}

全局配置

设置BaseURL:

1
2
3
var config = LNNetworkConfiguration(baseURL: URL(string: "https://example.com/"))
config.networkInterceptor = ExampleLightInterceptor.init()
LNNetworkManager.default.configuration = config

其中config.networkInterceptor是对所有网络请求的拦截器,可在拦截器中统一的添加参数,请求头。
配置全局拦截器:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
class ExampleLightInterceptor: LNNetworkInterceptor {
/// 添加公共参数
func interceptor(_ request: LNBaseRequest, parameter: Parameters?) -> Parameters? {
var para : Dictionary<String, Any> = parameter ?? Dictionary<String, Any>.init()
para["public"] = "test"
return para
}
/// 添加公共请求头
func interceptor(_ request: LNBaseRequest, headerFields: Alamofire.HTTPHeaders?) -> Alamofire.HTTPHeaders? {
var header = HTTPHeaders();
headerFields?.dictionary.forEach { header.add(name: $0.key, value: $0.value) }
header.add(name: "publicHeader", value: "test")
return header
}
//请求开始。可配置显示加载指示器
func interceptor(start request: LNBaseRequest) {
print("Request start: \(request.url)")
}
//请求结束。可配置隐藏加载指示器
func interceptor(end request: LNBaseRequest) {
print("Request end: \(request.url)")
}
}

配置统一的响应处理

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
class ExampleBaseRequest: LNRequest {
//自定义error code,并使用LNRequestFailure返回
var errorCode : Int = 0
var responseData : Any?
// 自定义统一处理响应结果
override func process(response: AFDataResponse<Data?>, success: LNRequestSuccess?, failure: LNRequestFailure? = nil) {
switch response.result {
case let .success(data):
guard let sourceData = data else { return }
do {
let jsonData = try JSONSerialization.jsonObject(with: sourceData, options: .allowFragments)

let code : Int = (jsonData as! [String : Any])["code"] as! Int
if code == 200 {
success?(self, jsonData)
}else {
failure?(self, ExampleServerError.invalidParameter)
}

} catch {
let error = AFError.responseSerializationFailed(reason: .jsonSerializationFailed(error: error))
failure?(self, error)
}
case let .failure(error):
failure?(self, error)
}
}
}

经过上述的配置,基本上满足的日常网络请求的需要,同时支持很方便的下载,上传,进度监控,设置缓存逻辑,超时时长等。详细的配置可以参考示例代码.

兼容性

LightNetwork中的请求方法,本身就是调用Alamofire的请求方法,LightNetwork所做的只是Alamofire多参数的方法进行类化。即将Alamofire请求方法的参数,写成LightNetwork LNBaseRequest类的属性,这样即方便了统一配置,也便于Alamofire方法的简化。同时,返回参数保持和Alamofire一致,保留了Alamofire方法的链式调用。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
//Alamofire
open func request(_ convertible: URLConvertible,
method: HTTPMethod = .get,
parameters: Parameters? = nil,
encoding: ParameterEncoding = URLEncoding.default,
headers: HTTPHeaders? = nil,
interceptor: RequestInterceptor? = nil,
requestModifier: RequestModifier? = nil)
-> DataRequest
//LightNetwork
open class LNBaseRequest: NSObject {
open var path: String = ""
open var method: HTTPMethod = .get
open var parameters: Parameters?
open var encoding: ParameterEncoding = URLEncoding.default
open var headers: HTTPHeaders?
open var interceptor: RequestInterceptor?
open var requestModifier: Alamofire.Session.RequestModifier?
open var cachePolicy: URLRequest.CachePolicy = .useProtocolCachePolicy
open var timeoutInterval: TimeInterval = 60

open class func request(path: String,
method: HTTPMethod = .get,
parameters: Parameters? = nil,
success: LNRequestSuccess?,
failure: LNRequestFailure?)
-> DataRequest?
}

以上~

OC类型的代码,底层实现都是C/C++语言,可以说,OC语言就是对C/C++语言的封装,比如,任何OC对象,添加__bridge const void *修饰,都可以转变为void指针类型。

阅读全文 »

方法名后缀为”!”的方法

在Ruby中,方法名后缀为”!”的方法通常表示该方法会修改调用它的对象。这种约定起源于Smalltalk语言,在Ruby中广泛使用。

当一个方法后面带有”!”符号时,这个方法通常会修改调用它的对象的状态。例如,如果我们对一个数组调用sort!方法,这个方法会对数组进行排序,并返回排序后的数组,而原始数组将被修改。

阅读全文 »

最近觉得之前的网站图标设计的太简陋,于是我设计了一个新图标,新图标是一个背着的大熊猫图案,灵感来自于背着人们吃竹子的熊猫。通过新图标,我想表达的是一只大熊猫,坐在那里,独自思考的场景,对于这个博客,这个图标比较契合。

前几年,简单图床发布之后,反映了了,于是我基本上放弃了维护,因为没有来自独立软件的收入,我的开发者账号也没有在续费了。因此软件也自动下架了。

通过此事我也学到了一些小小经验:

  • 要明白软件的目的是为了服务用户

  • 运营很重要

  • 软件收入很重要,即使开个赠个咖啡的入口也行

Dart支持展开操作符(spread operator)...和空感展开操作符(null-aware spread operator)...?

展开操作符支持所有的集合(Collections)类型,包括Set,List,Map。

展开操作符(spread operator)

展开操作符...可以将一个列表的所有值,插入到另一个列表中。

1
2
3
4
var list = [1, 2, 3];
var list2 = [0, ...list];
assert(list2.length == 4);
// list2 = [0, 1, 2, 3]
空感展开操作符(null-aware spread operator)

如果展开操作符右边的表达式可能为空,为了避免程序出问题可以使用空感展开操作符(null-aware spread operator)...?

1
2
3
List<int>? list;
var list2 = [0, ...?list];
assert(list2.length == 1);

Objective-C的Category特性,使的开发者在处理某些问题时,简单而又方便。理解Category的源码,对OC编程的理解会有很大的帮助。

Category的处理源码在libobjc.A.dylib中,苹果已经开源,但是下载后直接使用Xcode编译,会报很多错误。网上有很多帖子,介绍如何解决libobjc编译错误,这里就不介绍了。在github上有个开源项目objc4,提供了可编译版本的objc工程,可按需索取。目前苹果刚发布macOS Big Sur 11.0.1,适配该系统的Objc还没有开源,objc4在这个系统上会有一点错误,不过不影响阅读,各个函数,变量定义之间的跳转也没有问题。

阅读全文 »

2020-10-05

双十一准备攒一台主机。

CPU准备选十代i7k。以下配置都是按照该CPU来配置的。选配的理念是高性能,静音,无灯光。这个配置未来5年够躁的了。

阅读全文 »

iOS为一名iOS开发者,在日常工作中常常会遇到设计师,产品经理犯一些常识性的错误,令人啼笑皆非。有些扯皮,因为没有共识,浪费了很多时间。部分设计师和产品经理对iOS系统的理解停留在表面。我这篇分享,将从程序员的角度带你去认识一下iOS系统的UI。

读完之后的目标:

  • 认识iOS操作系统中大部分系统控件的UI

  • 理解iOS开发中视图组织的内部逻辑

  • 一些常识

  • 一些设计资源的分享

阅读全文 »

用了两天的空闲时间,看了是枝裕和导演的《小偷家族》。

剧中讲述的是一个生活在底层的一个“小偷家庭”的故事。家庭之中有弟弟祥太,妹妹百合,姐姐亚纪,父亲母亲,还有奶奶组成。家庭的生活的开支靠打零工和偷东西维持。这个看似完整的家庭,各个成员之间却没有什么血缘关系。

妹妹百合饱受原生家庭的虐待,在一个冬夜被“父亲”柴田治发现后,带到家中。百合在这个家庭中,慢慢的找回幸福,慢慢的感受到爱,然后融入到这个充满快乐的家庭。

刚才不小心看到一个影评,和我想写的一样。嗯,就写到这吧。

最近学了一个网上录播的课程,又想到了大学时代上课的日子,有了一些新的想法。

教育公平的理念强调起点公平、过程公平和结果公平,我们国家一直倡导教育公平,想要尽量均衡的分配教育资源,但是教育公平提出了这么多年,教育公平在社会上的实现依旧不尽人意。就连高考也只是相对的省内公平,各个省市的录取情况一对比,不公平现象立现。高考人数多的省份和高校数量少的省份产生内卷,极度的消耗人才。

就算你走过了高考,上了大学,连大学也分三六九等,985,211,一本二本三本,各个大学之间,教育不公平更是触目惊心。包括教师质量,教学水平,教学设施水平,科研经费划比等等,各个学校千差万别。因此教育公平在大学之间更是无从谈起。

类似教育公平这种社会现象,就是这么奇怪,明明每个人都知道这个事不好,但是因为能凑合,所以大都睁一只眼闭一只眼,然后说一句随大流的话,习惯了就好。我最近思考了这个问题,有一个新想法,希望能为教育公平的实现添一点愚见。

希望国家可以建立统一的课程录播制度。教师可以利用录播的课程,查找自己不足,提升教学水平;学生可以利用录播的课程进行复习;经教育部门或者学校的整理,将优秀的录播课程上传至教育网,可供全国在校的学生观看,学习;教育部门或学校再经过筛选,可以将录播课程放到公网上,供全国的人民观看,学习。这样,每个人都可以学到优秀的课程。目前中国的大学是有互联互通的教育网的,国家互联网也几乎已经通到各个地区,因此实现此想法的基础设施已经具备。我一直相信科技能够改变生活,科技应该为人类过上更加美好的生活服务,合适的利用科技的发展可以促进教育更加公平。

目前很多学校已经对优秀的课程进行录播了,像北大的公开课,还有各个大学和网易合作的公开课等,但是互联互通,公益性,课程的全面性,都做的不好。

我想象有这样的未来,不管你身处何处,只要是中华人民共和国国民,就可以像她的宪法和教育法中说的那样,有公平的受教育的机会,有终身学习的良好条件,可以得到全面发展,摆脱愚昧和无知,做一个幸福的人。