您的位置:首页 > Web前端

Alamofire源码解读系列之错误处理(AFError)

2018-01-31 00:00 615 查看


1.正常用法

enumMovement{
caseLeft
caseRight
caseTop
caseBottom
}

letaMovement=Movement.Left

switchaMovement{
case.Left:
print("left")
default:
print("Unknow")
}

ifcase.Left=aMovement{
print("Left")
}

if.Left==aMovement{
print("Left")
}


2.声明为整型

enumSeason:Int{
caseSpring=0
caseSummer=1
caseAutumn=2
caseWinter=3
}


3.声明为字符串类型

enumHouse:String{
caseZhangSan="Iamzhangsan"
caseLiSi="Iamlisi"
}

letzs=House.ZhangSan
print(zs.rawValue)

enumCompassPoint:String{
caseNorth,South,East,West
}

letn=CompassPoint.North
print(n.rawValue)

lets=CompassPoint(rawValue:"South");


4.声明为浮点类型

enumConstants:Double{
caseπ=3.14159
casee=2.71828
caseφ=1.61803398874
caseλ=1.30357
}

letpai=Constants.π
print(pai.rawValue)


5.其他类型

enumVNodeFlags:UInt32{
caseDelete=0x00000001
caseWrite=0x00000002
caseExtended=0x00000004
caseAttrib=0x00000008
caseLink=0x00000010
caseRename=0x00000020
caseRevoke=0x00000040
caseNone=0x00000080
}


6.enum包含enum

enumCharacter{

enumWeapon{
caseBow
caseSword
caseLance
caseDagger
}

enumHelmet{
caseWooden
caseIron
caseDiamond
}

caseThief
caseWarrior
caseKnight
}

letcharacter=Character.Thief
letweapon=Character.Weapon.Bow
lethelmet=Character.Helmet.Iron


7.结构体和枚举

structScharacter{
enumCharacterType{
caseThief
caseWarrior
caseKnight
}

enumWeapon{
caseBow
caseSword
caseLance
caseDagger
}

lettype:CharacterType
letweapon:Weapon
}

letsc=Scharacter(type:.Thief,weapon:.Bow)
print(sc.type)


8.值关联

enumTrade{
caseBuy(stock:String,amount:Int)
caseSell(stock:String,amount:Int)
}

lettrade=Trade.Buy(stock:"Car",amount:100)
ifcaseletTrade.Buy(stock,amount)=trade{
print("buy\(amount)of\(stock)")
}

enumTrade0{
caseBuy(String,Int)
caseSell(String,Int)
}

lettrade0=Trade0.Buy("Car0",100)
ifcaseletTrade0.Buy(stock,amount)=trade0{
print("buy\(amount)of\(stock)")
}


9.枚举中的函数

enumWearable{
enumWeight:Int{
caseLight=2
}

enumArmor:Int{
caseLight=2
}

caseHelmet(weight:Weight,armor:Armor)

funcattributes()->(weight:Int,armor:Int){
switchself{
case.Helmet(letw,leta):
return(weight:w.rawValue*2,armor:a.rawValue*4)

}
}
}

lettest=Wearable.Helmet(weight:.Light,armor:.Light).attributes()
print(test)

enumDevice{
caseiPad,iPhone,AppleTV,AppleWatch
funcintroduced()->String{
switchself{
case.AppleTV:return"\(self)wasintroduced2006"
case.iPhone:return"\(self)wasintroduced2007"
case.iPad:return"\(self)wasintroduced2010"
case.AppleWatch:return"\(self)wasintroduced2014"
}
}
}
print(Device.iPhone.introduced())


10.枚举中的属性

enumDevice1{
caseiPad,iPhone
varyear:Int{
switchself{
case.iPad:
return2010
case.iPhone:
return2007
}
}
}

letiPhone=Device1.iPhone
print(iPhone.year)


ParameterEncodingFailureReason

通过ParameterEncodingFailureReason我们能够很清楚的看出来这是一个参数编码的错误原因。大家注意reason这个词,在命名中,有或者没有这个词,表达的意境完全不同,因此,Alamofire牛逼就体现在这些细节之中。

publicenumAFError:Error{
///Theunderlyingreasontheparameterencodingerroroccurred.
///
///-missingURL:TheURLrequestdidnothaveaURLtoencode.
///-jsonEncodingFailed:JSONserializationfailedwithanunderlyingsystemerrorduringthe
///encodingprocess.
///-propertyListEncodingFailed:Propertylistserializationfailedwithanunderlyingsystemerrorduring
///encodingprocess.
publicenumParameterEncodingFailureReason{
casemissingURL
casejsonEncodingFailed(error:Error)
casepropertyListEncodingFailed(error:Error)
}
}

ParameterEncodingFailureReason本身是一个enum,同时,它又被包含在AFError之中,这说明枚举之中可以有另一个枚举。那么像这种情况我们怎么使用呢?看下边的代码:

letparameterErrorReason=AFError.ParameterEncodingFailureReason.missingURL

枚举的访问是一级一级进行的。我们再看这行代码:casejsonEncodingFailed(error:Error)。jsonEncodingFailed(error:Error)并不是函数,就是枚举的一个普通的子选项(error:Error)是它的一个关联值,相对于任何一个子选项,我们都可以关联任何值,它的意义就在于,把这些值与子选项进行绑定,方便在需要的时候调用。我们会在下边讲解如何获取关联值。

参数编码有一下几种方式:

把参数编码到URL中

把参数编码到httpBody中

Alamofire中是如何进行参数编码的,这方面的内容会在后续的ParameterEncoding.swift这一篇文章中给出详细的解释。那么编码失败的原因可能为:

missingURL给定的urlRequest.url为nil的情况抛出错误

jsonEncodingFailed(error:Error)当选择把参数编码成JSON格式的情况下,参数JSON化抛出的错误

propertyListEncodingFailed(error:Error)这个同上

综上所述,ParameterEncodingFailureReason封装了参数编码的错误,可能出现的错误类型为Error,说明这些所谓一般是调用系统Api产生的错误。

MultipartEncodingFailureReason

publicenumMultipartEncodingFailureReason{
casebodyPartURLInvalid(url:URL)
casebodyPartFilenameInvalid(in:URL)
casebodyPartFileNotReachable(at:URL)
casebodyPartFileNotReachableWithError(atURL:URL,error:Error)
casebodyPartFileIsDirectory(at:URL)
casebodyPartFileSizeNotAvailable(at:URL)
casebodyPartFileSizeQueryFailedWithError(forURL:URL,error:Error)
casebodyPartInputStreamCreationFailed(for:URL)

caseoutputStreamCreationFailed(for:URL)
caseoutputStreamFileAlreadyExists(at:URL)
caseoutputStreamURLInvalid(url:URL)
caseoutputStreamWriteFailed(error:Error)

caseinputStreamReadFailed(error:Error)
}

多部分编码错误一般发生在上传或下载请求中对数据的处理过程中,这里边最重要的是对上传数据的处理过程,会在后续的MultipartFormData.swift这一篇文章中给出详细的解释,我们就简单的分析下MultipartEncodingFailureReason子选项错误出现的原因:

bodyPartURLInvalid(url:URL)上传数据时,可以通过fileURL的方式,读取本地文件数据,如果fileURL不可用,就会抛出这个错误

bodyPartFilenameInvalid(in:URL)如果使用fileURL的lastPathComponent或者pathExtension获取filename为空抛出的错误

bodyPartFileNotReachable(at:URL)通过fileURL不能访问数据,也就是不可达的

bodyPartFileNotReachableWithError(atURL:URL,error:Error)这个不同于bodyPartFileNotReachable(at:URL),当尝试检测fileURL是不是可达的情况下抛出的错误

bodyPartFileIsDirectory(at:URL)当fileURL是一个文件夹时抛出错误

bodyPartFileSizeNotAvailable(at:URL)当使用系统Api获取fileURL指定文件的size出现错误

bodyPartFileSizeQueryFailedWithError(forURL:URL,error:Error)查询fileURL指定文件size出现错误

bodyPartInputStreamCreationFailed(for:URL)通过fileURL创建inputStream出现错误

outputStreamCreationFailed(for:URL)当尝试把编码后的数据写入到硬盘时,创建outputStream出现错误

outputStreamFileAlreadyExists(at:URL)数据不能被写入,因为指定的fileURL已经存在

outputStreamURLInvalid(url:URL)fileURL不是一个fileURL

outputStreamWriteFailed(error:Error)数据流写入错误

inputStreamReadFailed(error:Error)数据流读入错误

综上所述,这些错误基本上都跟数据的操作相关,这个在后续会做出很详细的说明。

ResponseValidationFailureReason

publicenumResponseValidationFailureReason{
casedataFileNil
casedataFileReadFailed(at:URL)
casemissingContentType(acceptableContentTypes:[String])
caseunacceptableContentType(acceptableContentTypes:[String],responseContentType:String)
caseunacceptableStatusCode(code:Int)
}

Alamofire不管请求是否成功,都会返回response。它提供了验证ContentType和StatusCode的功能,关于验证,再后续的文章中会有详细的解答,我们先看看这些原因:

dataFileNil保存数据的URL不存在,这种情况一般出现在下载任务中,指的是下载代理中的fileURL缺失

dataFileReadFailed(at:URL)保存数据的URL无法读取数据,同上

missingContentType(acceptableContentTypes:[String])服务器返回的response不包含ContentType且提供的acceptableContentTypes不包含通配符(通配符表示可以接受任何类型)

unacceptableContentType(acceptableContentTypes:[String],responseContentType:String)ContentTypes不匹配

unacceptableStatusCode(code:Int)StatusCode不匹配

ResponseSerializationFailureReason

publicenumResponseSerializationFailureReason{
caseinputDataNil
caseinputDataNilOrZeroLength
caseinputFileNil
caseinputFileReadFailed(at:URL)
casestringSerializationFailed(encoding:String.Encoding)
casejsonSerializationFailed(error:Error)
casepropertyListSerializationFailed(error:Error)
}

我们在Alamofire源码解读系列(一)之概述和使用中已经提到,Alamofire支持把服务器的response序列成几种数据格式。

response直接返回HTTPResponse,未序列化

responseData序列化为Data

responseJSON序列化为Json

responseString序列化为字符串

responsePropertyList序列化为Any

那么在序列化的过程中,很可能会发生下边的错误:

inputDataNil服务器返回的response没有数据

inputDataNilOrZeroLength服务器返回的response没有数据或者数据的长度是0

inputFileNil指向数据的URL不存在

inputFileReadFailed(at:URL)指向数据的URL无法读取数据

stringSerializationFailed(encoding:String.Encoding)当使用指定的String.Encoding序列化数据为字符串时,抛出的错误

jsonSerializationFailed(error:Error)JSON序列化错误

propertyListSerializationFailed(error:Error)plist序列化错误

AFError

上边内容中介绍的ParameterEncodingFailureReasonMultipartEncodingFailureReasonResponseValidationFailureReason和ResponseSerializationFailureReason,他们是定义在AFError中独立的枚举,他们之间是包含和被包含的关系,理解这一点很重要,因为有了这种包含的管理,在使用中就需要通过AFError.ParameterEncodingFailureReason这种方式进行操作。

那么最重要的问题就是,如何把上边4个独立的枚举进行串联呢?Alamofire巧妙的地方就在这里,有4个独立的枚举,分别代表4大错误。也就是说这个网络框架肯定有这4大错误模块,我们只需要给AFError设计4个子选项,每个子选项关联上上边4个独立枚举的值就ok了。

这个设计真的很巧妙,试想,如果把所有的错误都放到AFError中,就显得非常冗余。那么下边的代码就呼之欲出了,大家好好体会体会在swift下这么设计的妙用:

caseinvalidURL(url:URLConvertible)
caseparameterEncodingFailed(reason:ParameterEncodingFailureReason)
casemultipartEncodingFailed(reason:MultipartEncodingFailureReason)
caseresponseValidationFailed(reason:ResponseValidationFailureReason)
caseresponseSerializationFailed(reason:ResponseSerializationFailureReason)


AFError的扩展

也许在开发中,我们完成了上边的代码就认为够用了,但对于一个开源框架而言,远远是不够的。我们一点点进行剖析:

现在给定一条数据:

funcfindErrorType(error:AFError){

}

我只需要知道这个error是不是参数编码错误,应该怎么办?因此为AFError提供5个布尔类型的属性,专门用来获取当前的错误是不是某个指定的类型。这个功能的实现比较简单,代码如下:

extensionAFError{
///ReturnswhethertheAFErrorisaninvalidURLerror.
publicvarisInvalidURLError:Bool{
ifcase.invalidURL=self{returntrue}
returnfalse
}

///ReturnswhethertheAFErrorisaparameterencodingerror.When`true`,the`underlyingError`propertywill
///containtheassociatedvalue.
publicvarisParameterEncodingError:Bool{
ifcase.parameterEncodingFailed=self{returntrue}
returnfalse
}

///ReturnswhethertheAFErrorisamultipartencodingerror.When`true`,the`url`and`underlyingError`properties
///willcontaintheassociatedvalues.
publicvarisMultipartEncodingError:Bool{
ifcase.multipartEncodingFailed=self{returntrue}
returnfalse
}

///Returnswhetherthe`AFError`isaresponsevalidationerror.When`true`,the`acceptableContentTypes`,
///`responseContentType`,and`responseCode`propertieswillcontaintheassociatedvalues.
publicvarisResponseValidationError:Bool{
ifcase.responseValidationFailed=self{returntrue}
returnfalse
}

///Returnswhetherthe`AFError`isaresponseserializationerror.When`true`,the`failedStringEncoding`and
///`underlyingError`propertieswillcontaintheassociatedvalues.
publicvarisResponseSerializationError:Bool{
ifcase.responseSerializationFailed=self{returntrue}
returnfalse
}
}

总而言之,这些都是给AFError这个枚举扩展的属性,还包含下边这些属性:

urlConvertible:URLConvertible?获取某个属性,这个属性实现了URLConvertible协议,在AFError中只有caseinvalidURL(url:URLConvertible)这个选项符合要求
///The`URLConvertible`associatedwiththeerror.
publicvarurlConvertible:URLConvertible?{
switchself{
case.invalidURL(leturl):
returnurl
default:
returnnil
}
}


url:URL?获取AFError中的URL,当然这个URL只跟MultipartEncodingFailureReason这个子选项有关
///The`URL`associatedwiththeerror.
publicvarurl:URL?{
switchself{
case.multipartEncodingFailed(letreason):
returnreason.url
default:
returnnil
}
}


underlyingError:Error?AFError中封装的所有的可能出现的错误中,并不是每种可能都会返回Error这个错误信息,因此这个属性是可选的
///The`Error`returnedbyasystemframeworkassociatedwitha`.parameterEncodingFailed`,
///`.multipartEncodingFailed`or`.responseSerializationFailed`error.
publicvarunderlyingError:Error?{
switchself{
case.parameterEncodingFailed(letreason):
returnreason.underlyingError
case.multipartEncodingFailed(letreason):
returnreason.underlyingError
case.responseSerializationFailed(letreason):
returnreason.underlyingError
default:
returnnil
}
}


acceptableContentTypes:[String]?可接受的ContentType
///Theresponse`Content-Type`ofa`.responseValidationFailed`error.
publicvarresponseContentType:String?{
switchself{
case.responseValidationFailed(letreason):
returnreason.responseContentType
default:
returnnil
}
}


responseCode:Int?响应码
///Theresponsecodeofa`.responseValidationFailed`error.
publicvarresponseCode:Int?{
switchself{
case.responseValidationFailed(letreason):
returnreason.responseCode
default:
returnnil
}
}


failedStringEncoding:String.Encoding?错误的字符串编码
///The`String.Encoding`associatedwithafailed`.stringResponse()`call.
publicvarfailedStringEncoding:String.Encoding?{
switchself{
case.responseSerializationFailed(letreason):
returnreason.failedStringEncoding
default:
returnnil
}
}


这里是一个小的分割线,在上边属性的获取中,也是用到了下边代码中的扩展功能:

extensionAFError.ParameterEncodingFailureReason{
varunderlyingError:Error?{
switchself{
case.jsonEncodingFailed(leterror),.propertyListEncodingFailed(leterror):
returnerror
default:
returnnil
}
}
}

extensionAFError.MultipartEncodingFailureReason{
varurl:URL?{
switchself{
case.bodyPartURLInvalid(leturl),.bodyPartFilenameInvalid(leturl),.bodyPartFileNotReachable(leturl),
.bodyPartFileIsDirectory(leturl),.bodyPartFileSizeNotAvailable(leturl),
.bodyPartInputStreamCreationFailed(leturl),.outputStreamCreationFailed(leturl),
.outputStreamFileAlreadyExists(leturl),.outputStreamURLInvalid(leturl),
.bodyPartFileNotReachableWithError(leturl,_),.bodyPartFileSizeQueryFailedWithError(leturl,_):
returnurl
default:
returnnil
}
}

varunderlyingError:Error?{
switchself{
case.bodyPartFileNotReachableWithError(_,leterror),.bodyPartFileSizeQueryFailedWithError(_,leterror),
.outputStreamWriteFailed(leterror),.inputStreamReadFailed(leterror):
returnerror
default:
returnnil
}
}
}

extensionAFError.ResponseValidationFailureReason{
varacceptableContentTypes:[String]?{
switchself{
case.missingContentType(lettypes),.unacceptableContentType(lettypes,_):
returntypes
default:
returnnil
}
}

varresponseContentType:String?{
switchself{
case.unacceptableContentType(_,letresponseType):
returnresponseType
default:
returnnil
}
}

varresponseCode:Int?{
switchself{
case.unacceptableStatusCode(letcode):
returncode
default:
returnnil
}
}
}

extensionAFError.ResponseSerializationFailureReason{
varfailedStringEncoding:String.Encoding?{
switchself{
case.stringSerializationFailed(letencoding):
returnencoding
default:
returnnil
}
}

varunderlyingError:Error?{
switchself{
case.jsonSerializationFailed(leterror),.propertyListSerializationFailed(leterror):
returnerror
default:
returnnil
}
}
}


错误描述

在开发中,如果程序遇到错误,我们往往会给用户展示更加直观的信息,这就要求我们把错误信息转换成易于理解的内容。因此我们只要实现LocalizedError协议就好了。这里边的内容很简单,在这里就直接把代码写上了,不做分析:

extensionAFError:LocalizedError{
publicvarerrorDescription:String?{
switchself{
case.invalidURL(leturl):
return"URLisnotvalid:\(url)"
case.parameterEncodingFailed(letreason):
returnreason.localizedDescription
case.multipartEncodingFailed(letreason):
returnreason.localizedDescription
case.responseValidationFailed(letreason):
returnreason.localizedDescription
case.responseSerializationFailed(letreason):
returnreason.localizedDescription
}
}
}

extensionAFError.ParameterEncodingFailureReason{
varlocalizedDescription:String{
switchself{
case.missingURL:
return"URLrequesttoencodewasmissingaURL"
case.jsonEncodingFailed(leterror):
return"JSONcouldnotbeencodedbecauseoferror:\n\(error.localizedDescription)"
case.propertyListEncodingFailed(leterror):
return"PropertyListcouldnotbeencodedbecauseoferror:\n\(error.localizedDescription)"
}
}
}

extensionAFError.MultipartEncodingFailureReason{
varlocalizedDescription:String{
switchself{
case.bodyPartURLInvalid(leturl):
return"TheURLprovidedisnotafileURL:\(url)"
case.bodyPartFilenameInvalid(leturl):
return"TheURLprovideddoesnothaveavalidfilename:\(url)"
case.bodyPartFileNotReachable(leturl):
return"TheURLprovidedisnotreachable:\(url)"
case.bodyPartFileNotReachableWithError(leturl,leterror):
return(
"ThesystemreturnedanerrorwhilecheckingtheprovidedURLfor"+
"reachability.\nURL:\(url)\nError:\(error)"
)
case.bodyPartFileIsDirectory(leturl):
return"TheURLprovidedisadirectory:\(url)"
case.bodyPartFileSizeNotAvailable(leturl):
return"CouldnotfetchthefilesizefromtheprovidedURL:\(url)"
case.bodyPartFileSizeQueryFailedWithError(leturl,leterror):
return(
"Thesystemreturnedanerrorwhileattemptingtofetchthefilesizefromthe"+
"providedURL.\nURL:\(url)\nError:\(error)"
)
case.bodyPartInputStreamCreationFailed(leturl):
return"FailedtocreateanInputStreamfortheprovidedURL:\(url)"
case.outputStreamCreationFailed(leturl):
return"FailedtocreateanOutputStreamforURL:\(url)"
case.outputStreamFileAlreadyExists(leturl):
return"AfilealreadyexistsattheprovidedURL:\(url)"
case.outputStreamURLInvalid(leturl):
return"TheprovidedOutputStreamURLisinvalid:\(url)"
case.outputStreamWriteFailed(leterror):
return"OutputStreamwritefailedwitherror:\(error)"
case.inputStreamReadFailed(leterror):
return"InputStreamreadfailedwitherror:\(error)"
}
}
}

extensionAFError.ResponseSerializationFailureReason{
varlocalizedDescription:String{
switchself{
case.inputDataNil:
return"Responsecouldnotbeserialized,inputdatawasnil."
case.inputDataNilOrZeroLength:
return"Responsecouldnotbeserialized,inputdatawasnilorzerolength."
case.inputFileNil:
return"Responsecouldnotbeserialized,inputfilewasnil."
case.inputFileReadFailed(leturl):
return"Responsecouldnotbeserialized,inputfilecouldnotberead:\(url)."
case.stringSerializationFailed(letencoding):
return"Stringcouldnotbeserializedwithencoding:\(encoding)."
case.jsonSerializationFailed(leterror):
return"JSONcouldnotbeserializedbecauseoferror:\n\(error.localizedDescription)"
case.propertyListSerializationFailed(leterror):
return"PropertyListcouldnotbeserializedbecauseoferror:\n\(error.localizedDescription)"
}
}
}

extensionAFError.ResponseValidationFailureReason{
varlocalizedDescription:String{
switchself{
case.dataFileNil:
return"Responsecouldnotbevalidated,datafilewasnil."
case.dataFileReadFailed(leturl):
return"Responsecouldnotbevalidated,datafilecouldnotberead:\(url)."
case.missingContentType(lettypes):
return(
"ResponseContent-Typewasmissingandacceptablecontenttypes"+
"(\(types.joined(separator:",")))donotmatch\"*/*\"."
)
case.unacceptableContentType(letacceptableTypes,letresponseType):
return(
"ResponseContent-Type\"\(responseType)\"doesnotmatchanyacceptabletypes:"+
"\(acceptableTypes.joined(separator:","))."
)
case.unacceptableStatusCode(letcode):
return"Responsestatuscodewasunacceptable:\(code)."
}
}
}


作者:老马的春天
链接:https://www.jianshu.com/p/99e6ba32f244
來源:简书
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: