一、 SnapKit

swift
// 1 view.safeAreaLayoutGuide = 安全区域
// 它是自动帮你避开导航栏、TabBar、刘海、底部小黑条 的布局工具
backView.snp.makeConstraints { make in // lightGray
make.edges.equalTo(view.safeAreaLayoutGuide).inset(10) // 内嵌 距离父视图四周 10pt
}
swift
// 2 offset 偏移 向外偏移 往正方向移动(边向外扩)
// inset 内嵌 向内收缩 往反方向缩进(边向内收)
backView1.snp.makeConstraints { make in
make.left.right.equalToSuperview().inset(20) //内嵌
make.top.equalToSuperview()
make.height.equalTo(40)
}
backView2.snp.makeConstraints { make in
make.top.equalTo(backView1.snp.bottom).offset(10)
make.left.width.height.equalTo(backView1)
}
swift
// 3 设置size
backView3.snp.makeConstraints { make in
make.bottom.equalToSuperview().inset(10)
make.left.equalToSuperview().offset(10)
make.size.equalTo(CGSize(width: 100, height: 40))
}
swift
// 4 设置宽高比 设置 center centerX
backView4.snp.makeConstraints { make in
make.centerY.equalToSuperview().offset(100)
make.left.equalToSuperview().offset(10)
make.width.equalTo(100)
make.height.equalTo(backView4.snp.width).multipliedBy(0.5) // 高是宽的 0.5 倍
}
swift
// 5 高度自适应
backView5.text = getRandomString(length: 60) // 随机60个汉字
backView5.snp.makeConstraints { make in // blue
make.left.equalTo(backView4.snp.right).offset(10)
make.top.equalTo(backView4)
make.right.equalToSuperview().inset(10)
}
swift
// 6 lessThanOrEqualTo 不超过
backView6.text = getRandomString(length: 200)
backView6.snp.makeConstraints { make in // purple
make.top.equalTo(backView5.snp.bottom).offset(5)
make.left.equalTo(backView).offset(30)
make.right.equalTo(backView).inset(10)
make.height.lessThanOrEqualTo(100)
}
swift
// 7 优先级
// 什么是 约束优先级(priority)? iOS 自动布局里,如果两个约束冲突了
// 比如:一边要求宽度 = 100,一边又要求宽度 = 200)系统就会看优先级:谁数字大,听谁的!
// .low = 250
// .medium = 500
// .high = 750
// .required = 1000(默认)
backView7.text = getRandomString(length: 200)
backView7.snp.makeConstraints { make in // yellow
make.top.equalTo(backView2.snp.bottom).offset(10)
// 希望尽量左右撑满,但优先级低 (750)
make.left.greaterThanOrEqualToSuperview().offset(20).priority(750)
make.right.lessThanOrEqualToSuperview().offset(-20).priority(750)
// 核心:宽度最大为 300,优先级高 (1000 - 必须满足)
make.width.lessThanOrEqualTo(300).priority(.required) // 默认就是 1000
}
二、 Alamofire
1.1 GET / POST
swift
// 配置全局session
var session: Session = {
// 1. 创建配置
let configuration = URLSessionConfiguration.default
// 2. 配置 Session 参数
configuration.timeoutIntervalForRequest = 30
configuration.timeoutIntervalForResource = 60
configuration.allowsCellularAccess = true
// 3. 配置全局 Header
configuration.httpAdditionalHeaders = [
"Accept": "application/json",
"User-Agent": "MyApp/1.0",
"Content-Type": "application/x-www-form-urlencoded"
]
// 4. 创建 Session
let sess = Session(configuration: configuration)
return sess
}()
swift
// 发送请求 get/post
func loadData() {
let url = "https://v.juhe.cn/toutiao/index"
var head = HTTPHeaders()
head.add(HTTPHeader(name: "Content-Type", value: "application/x-www-form-urlencoded"))
let parames = ["key":"ly8bc00637b81dead3162dcd12f06e5a1e"]
session.request(url, method: .post, parameters: parames, headers: head)
.responseDecodable(of: AFListModel.self) { (response: AFDataResponse<AFListModel>) in
print("---respose",response)
switch response.result {
case .success(let value):
self.model = value
self.tableView.reloadData()
print(self.model ?? "")
case .failure(let error):
print("---error---",error)
}
}
}
// public let AF = Session.default
1.2 表单上传
swift
func uploadToPicGo() {
// 1. 准备图片
guard let image = UIImage(named: "nonedata"),
let imageData = image.jpegData(compressionQuality: 0.8) else {
print("❌ 图片加载失败")
return
}
// 2. 配置请求
let url = "https://www.picgo.net/api/1/upload"
var headers = HTTPHeaders()
headers.add(name: "Content-Type", value: "multipart/form-data")
// 3. 发起上传
AF.upload(multipartFormData: { formData in
formData.append(imageData, // 文件的value
withName: "image", // 文件的key
fileName: "test_photo.jpg", // 服务器保存时用的名字
mimeType: "image/jpeg") // 文件类型
}, to: url, method: .post, headers: headers)
.responseString { response in
switch response.result {
case .success(let value):
print("上传成功!")
print("服务器返回: \(value)")
// URL 在 data.url 字段
if let json = value as? [String: Any],
let data = json["data"] as? [String: Any],
let imageUrl = data["url"] as? String {
print("📸 图片地址: \(imageUrl)")
}
case .failure(let error):
print("上传失败: \(error)")
}
}
}
// 传文件路径
// public func append(_ fileURL: URL, withName name: String, fileName: String, mimeType: String) { ...... }
// responseString 为临时打印数据, 开发推荐用这个模型去接收返回值
// .responseDecodable(of: AFListModelself { (response: AFDataResponse<AFListModel>) in ...... }
1.3 下载
swift
func downloadVideo() {
guard let url = URL(string: testURL) else {
statusLabel.text = "无效的 URL"
return
}
// 1. 定义下载文件的保存位置
let destination: DownloadRequest.Destination = { _, _ in
let documentsURL = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask)[0]
let fileURL = documentsURL.appendingPathComponent("downloaded_test_video.mp4")
// 如果之前有相同文件,先删除,确保测试每次都重新下载
if FileManager.default.fileExists(atPath: fileURL.path) {
try? FileManager.default.removeItem(at: fileURL)
}
return (fileURL, [.removePreviousFile, .createIntermediateDirectories])
}
statusLabel.text = "开始下载..."
// 2. 发起下载请求并监听进度
AF.download(url, to: destination)
.downloadProgress { progress in
DispatchQueue.main.async { // 更新
self.progressView.progress = Float(progress.fractionCompleted)
let percent = Int(progress.fractionCompleted * 100)
self.statusLabel.text = "下载进度: \(percent)%"
print("进度: \(percent)%")
}
}
.response { response in
DispatchQueue.main.async { // 更新
switch response.result {
case .success(let fileURL):
self.statusLabel.text = "下载完成!"
print("文件保存至: \(fileURL?.path ?? "未知路径")")
case .failure(let error):
self.statusLabel.text = "下载失败: \(error.localizedDescription)"
print("错误: \(error)")
}
}
}
}
三、 moya
Moya 核心
- 你需要创建一个遵循
TargetType协议的枚举(Enum) - 用它来列出应用中的所有 API 端点。
3.1 配置moya
swift
/// 1- 定义 loding样式, session, 超时, MoyaProvider
import HandyJSON
import Moya
import Alamofire
import Result
import MBProgressHUD
import SwiftyJSON
/// loding样式
let LoadingPlugin = NetworkActivityPlugin { (type, target) in
DispatchQueue.main.async {
guard let vc = topVC else { return }
switch type {
case .began:
MBProgressHUD.hide(for: vc.view, animated: false)
MBProgressHUD.showAdded(to: vc.view, animated: true)
case .ended:
MBProgressHUD.hide(for: vc.view, animated: true)
}
}
}
// 配置session
let session: Session = {
// let evaluators: [String: ServerTrustEvaluating] = [
// "abcd.citibank.com": DisabledTrustEvaluator(),
// "eat.beef.com.cn": DisabledTrustEvaluator()
// ]
//
// let serverTrustManager = ServerTrustManager(evaluators: evaluators)
// let configuration = URLSessionConfiguration.default
// return Session(configuration: configuration, serverTrustManager: serverTrustManager)
return Session(configuration: URLSessionConfiguration.default)
}()
// 配置超时
let timeoutClosure = {(endpoint: Endpoint, closure: MoyaProvider<HttpRueqest>.RequestResultClosure) -> Void in
if var urlRequest = try? endpoint.urlRequest() {
//设置请求时长
urlRequest.timeoutInterval = 15
closure(.success(urlRequest))
} else {
closure(.failure(MoyaError.requestMapping(endpoint.url)))
}
}
// 无加载动画
let ApiProvider = MoyaProvider<HttpRueqest>(requestClosure: timeoutClosure, session: session)
// 有加载动画
let ApiLoadingProvider = MoyaProvider<HttpRueqest>(requestClosure: timeoutClosure, session: session, plugins: [LoadingPlugin])
/// 2- 定义 枚举
// 每一个case 代表一个接口
enum HttpRueqest {
// 获取验证码
case getCode
//任务详情
case taskDetail(tkid:String, excutionStatus: String, idList: [String], testType: String ,orderStatus:String)
// 测试
case test
// 笑话列表
case joke
}
/// 3- 枚举遵守 TargetType 协议
// 配置每个接口的 url 参数 请求方式 header
extension HttpRueqest: TargetType { // TargetType协议需要 实现如下变量
// 3.1 配置基础base url
var baseURL: URL {
switch self {
case .test :
//生产
// return URL(string: "https://ubpapi.beetest.com.cn/")!
//测试
return URL(string: "https://v.juhe.cn/")!
default:
return URL(string: "https://v.juhe.cn/" )!
}
}
// 3.2 配置具体路径
var path: String {
switch self {
case .getCode:
return "ucenter/sms/" + (UserDefaults.standard.string(forKey: AccountInfo().phoneNum) ?? "" ) + "/13" + "/" + (UserDefaults.standard.string(forKey: AccountInfo().pictureNum) ?? "aaaaaa" )
case .taskDetail:
return "ucenter/login/cbsp/"
case .test:
return "test"
case .joke:
return "joke/content/list"
}
}
// 3.3 http方法
var method: Moya.Method {
switch self {
case .getCode,.joke:
return .get
case .taskDetail, .test:
return .post
}
}
// 3.4 请求参数
var task: Moya.Task {
var param: [String:Any] = [:]
switch self {
case .getCode:
param = [:]
case .taskDetail:
param = [:]
case .test:
param = [:]
case .joke:
let timeStamp10 = Int(Date().timeIntervalSince1970)
param = ["sort":"desc",
"time":timeStamp10,
"key":"ly7f7b6e816760ec15f878b0e0ec7aa5e9"
]
return .requestParameters(parameters: param, encoding: URLEncoding.default)
}
return .requestParameters(parameters: param, encoding: JSONEncoding.default)
}
// 3.5 请求头可选
var headers: [String : String]? {
switch self {
case .test, .taskDetail:
return ["Content-Type":"application/json" , "Accept":"application/json",
"authorization": String(UserDefaults.standard.string(forKey: AccountInfo().token) ?? "")]
case .joke:
return ["Content-Type":"application/x-www-form-urlencoded"]
default:
return ["Content-Type":"application/json" , "Accept":"application/json"]
}
}
}
/// 4 封装网络请求
extension MoyaProvider {
@discardableResult
// 返回 returnData
public func request<T: HandyJSON>(_ target: Target,
model: T.Type,
completion: ((_ returnData: T?) -> Void)?) -> Cancellable? {
// 判断网络
CCTools.shared.netWorkReachability { (status) in
if status == .notReachable {
CProgressHUD.hideHud()
CProgressHUD.showErrorMessage(message: "网络异常")
return ;
}
}
return request(target, completion: { (result) in // (_ result: Result<Moya.Response, MoyaError>)
// 1 没有回调函数
guard let completion = completion else {
return
}
print("-------->target:",target)
print("-------->url:",String(describing: result.value?.request))
// 2 没有返回结果
guard let returnData = try? result.value?.mapModel(T.self) else {
completion(nil)
return
}
// 3 返回 returnData
completion(returnData)
})
}
}
/// 5 数据转模型
extension Response {
func mapModel<T: HandyJSON>(_ type: T.Type) throws -> T {
// 1 转json
let jsonString = String(data: data, encoding: .utf8)
print(jsonString ?? "")
// 2 转字典
let dict = try? JSONSerialization.jsonObject(with: (jsonString?.data(using: .utf8))!, options: .mutableContainers)
if dict != nil {
_ = dict as! NSDictionary
}
// 3 转模型
guard let model = JSONDeserializer<T>.deserializeFrom(json: jsonString) else {
throw MoyaError.jsonMapping(self)
}
// 4 返回模型
return model
}
}
3.2 使用moya
swift
// 1 moya原生方法 发送请求
// -> 获取json数据
func load1 () {
ApiLoadingProvider.request(.joke) { result in
print(result)
switch result {
case .success(let resp):
print("->状态码:\(resp.statusCode)") // 打印状态码
let jsonStr = String(data: resp.data, encoding: .utf8) ?? "空数据"
print("->接口返回原始JSON:\(jsonStr)")
case .failure(let err):
print("->请求失败:\(err.localizedDescription)")
}
}
}
// resp 为 Moya.Response类型, 通过获取属性data从而获取数据json
// result: Result<Moya.Response, MoyaError>)枚举
// 所以let resp 为 Moya.Response类型, case success(Success)
// 注意! 直接打印 result 不能打印出错误信息,需匹配success, failure 才行
swift
// 2 moya原生方法 + 封装 发送请求
// -> 获取模型
func load2() {
ApiLoadingProvider.request(.joke, model: jockModel.self) { [unowned self] result in
if result?.error_code == 0, let data = result?.result?.data {
self.dataSource.append(contentsOf: data)
self.tableView.reloadData()
} else {
CProgressHUD.showErrorMessage(message: result?.reason)
}
}
}
// result为 jockModel模型
swift
// 3 Alamofire 原生请求
// -> 获取模型
func load3() {
let url = "https://v.juhe.cn/joke/content/list"
var head = HTTPHeaders()
head.add(HTTPHeader(name: "Content-Type",
value: "application/x-www-form-urlencoded"))
let parames = ["sort":"desc",
"time":Int(Date().timeIntervalSince1970),
"key":"ly7f7b6e816760ec15f878b0e0ec7aa5e9",
"page": "1",
"pagesize": 20
] as [String : Any]
AF.request(url, method: .post, parameters: parames, headers: head)
.responseDecodable(of: jockModel.self) { (response: AFDataResponse<jockModel>) in
print("---respose",response)
switch response.result {
case .success(let value):
self.dataSource.append(contentsOf: value.result?.data ?? [])
let model: jockLastModel = self.dataSource[0]
print("---->",model.content ?? "")
case .failure(let error):
print("---error---",error)
}
}
}
// success value 为 jockModel模型
// response为结构体类型接收泛型, DataResponse<Success, AFError>
// DataResponse 的result属性为 枚举类型 result: Result<Success, Failure>