iOS 客户端(Swift 4)和服务器(PHP)的交互

应用场景:C/S 架构的应用程序中,常常以手机客户端作为前端界面,以远程服务器作为后端。以登录操作为例,用户在手机上输入了自己的用户名和密码,手机 APP 将用户输入的信息发送给服务器,服务器在数据库中进行查询匹配后返回该登录操作是否合法。

本文使用的环境如下:

  • 客户端:iOS 12.0(Swift 4.2)
  • 服务端:Ubuntu 16.04(PHP 7.0)

大致思路如下:客户端通过 GET 请求的方式向服务器提交数据,服务器进行核实后将结构以 JSON 的格式返回。

PHP 接收 GET 请求

继续上文中所举的登录例子。假设一组用户名和密码通过 GET 请求送到了服务器,我们需要判断其是否合法,并将结果以 JSON 的格式返回。

<?php

if (validate_user($_GET["username"], $_GET["password"])) {
$result["code"] = 0;
$result["info"] = "Success!";
echo json_encode($result);
} else {
$result["code"] = 1;
$result["info"] = "Wrong username or password!";
echo json_encode($result);
}

?>

这里用到了一个 PHP 中一个很有用的函数:json_encode()。它可以将一个 PHP Array 编码成 JSON 格式。如果成功,程序将会返回:

{  
"code":0,
"info":"Success!"
}

如果失败,程序将会返回:

{  
"code":1,
"info":"Wrong username or password!"
}

如果 PHP Array 存在嵌套(即 Array 中某个元素的键值是另一个 Array),那么转换得到的 JSON 字符串也会嵌套:

{  
"code":0,
"info":{
"sid":"1",
"username":"s1",
"password":"p1",
"realname":"Alfred",
"gender":"Male"
}
}

Swift 4 实现 GET 请求

假设我们已经写好了服务端的 PHP 代码并把它放在了 http://127.0.0.1/signin.php

GET 请求的变量就在链接中,比如:

http://127.0.0.1/signin.php?username=s1&password=p1

我们在客户端中所要做的就是将这个请求发送给服务器,并接收其返回的 JSON 文本。

func makeSignInCall(_ username : String, _ password : String) {
let url = URL(string: "http://127.0.0.1/signin.php?username=\(username)&password=\(password)")
let urlRequest = URLRequest(url: url!)
let urlConfig = URLSessionConfiguration.default
let urlSession = URLSession(configuration: urlConfig)
let task = urlSession.dataTask(with: urlRequest) { (data, response, error) in
guard let responseData = data else {
self.showAlert("Error", "Response Error.")
return
}
do {
let responseJson = try JSONSerialization.jsonObject(with: responseData, options: [])
let responseDic = responseJson as! [String : Any]
if (responseDic["code"] as! Int == 1) {
self.showAlert("Error", responseDic["info"] as! String)
} else {
self.showAlert("Success", responseDic["info"] as! String)
// Do other things
}
} catch let parsingError {
self.showAlert("Error", parsingError.localizedDescription)
return
}
}
task.resume()
}

上一段代码调用了 self.showAlert 函数,它的作用就是弹窗报告信息。实现如下:

func showAlert(_ title : String, _ msg : String) {
let alert = UIAlertController(title: title, message: msg, preferredStyle: .alert)
alert.addAction(UIAlertAction(title: NSLocalizedString("OK", comment: "Default action"), style: .default, handler: { _ in }))
DispatchQueue.main.async { [unowned self] in
self.present(alert, animated: true, completion: nil)
}
}

将这两段代码均放在 ViewController 的类定义中即可。

解析常规 JSON

值得注意的是第一个函数中解析 JSON 文本的代码:

let responseJson = try JSONSerialization.jsonObject(with: responseData, options: [])
let responseDic = responseJson as! [String : Any]
if (responseDic["code"] as! Int == 1) {
self.showAlert("Error", responseDic["info"] as! String)
} else {
self.showAlert("Success", responseDic["info"] as! String)
// Do other things
}

我们来逐行解释一下这段代码。首先我们需要将服务器返回的 responseData 转换为字符串格式的 JSON:

let responseJson = try JSONSerialization.jsonObject(with: responseData, options: [])

responseJson 即为 JSON 字符串。接下来我们需要将其转换为 Swift 中的 Dictionary 类型:

let responseDic = responseJson as! [String : Any]

这时我们就能一一访问字典中的元素了:

  • responseDic["code"] 是一个整数类型的值,因此我们需要将其解释为 IntresponseDic["code"] as! Int
  • responseDic["info"] 是一个字符串类型的值,因此我们需要将其解释为 StringresponseDic["info"] as! String

解析嵌套 JSON

如果服务器返回的 JSON 存在嵌套:

{  
"code":0,
"info":{
"sid":"1",
"username":"s1",
"password":"p1",
"realname":"Alfred",
"gender":"Male"
}
}

也就是说 responseDic["info"] 不是常规的 IntString,而是另一个子 JSON 文本串。解决办法很简单,将其解释为 [String : Any] 类型,并另存为一个子字典:

let userInfoDic = responseDic["info"] as! [String : Any]

此时 userInfoDic 即为子 JSON 转换得到的字典。使用 userInfoDic["sid"] 即可访问 sid 元素。

解析二维数组 JSON

如果服务器返回的 JSON 是个二维数组的表达:

[  
{
"username":"t1",
"gender":"Male"
},
{
"username":"t2",
"gender":"Female"
}
]

将其解释为 [[String : Any]] 类型即可:

let responseDic2D = responseJson as! [[String : Any]]
for responseDic in responseDic2D {
print(responseDic["username"] as! String)
print(responseDic["gender"] as! String)
}