2015年底苹果开源swift,现在swift也可以在Linux(ubuntu)上运行了,所以提供了swift做为后台开发语言的前提,加之github上也有很多的swift框架,做为一名iOS开发客户端和服务端都是用同一种语言减少了学习成本,而且看起来也是很coooooooooooooooool的一件事。
swift后台框架 | github-start | 官网 |
---|---|---|
vapor | https://vapor.codes/ | |
Perfect | https://www.perfect.org/ | |
Kitura | http://www.kitura.io/ |
从github上clone一个模版工程
git clone https://github.com/PerfectlySoft/PerfectTemplate.git cd PerfectTemplate swift build .build/debug/PerfectTemplate
可以在终端控制台中看到类似下面的内容:
[INFO] Starting HTTP server localhost on 0.0.0.0:8181
服务器现在已经运行并等待连接。从浏览器打开http://localhost:8181/ 可以看到欢迎信息。完整的源代码请参考PerfectTemplate项目模板。
在的终端命令行内输入:SPM能够创建一个Xcode项目
swift package generate-xcodeproj
把对应的静态文件放在Products目录下,浏览器输入地址:http://localhost:8181/文件名(http://localhost:8181/baymax.html)即可访问
// 启动配置
let confData = [
"servers": [
[
"name":"localhost",
"port":8181,
"routes":[
["method":"get", "uri":"/", "handler":handler],
["method":"get", "uri":"/crawler", "handler":handlerCrawler],
["method":"get", "uri":"/home", "handler":handlerHome],
["method":"post", "uri":"/videolist", "handler":handlerVideolist],
["method":"get", "uri":"/**", "handler":PerfectHTTPServer.HTTPHandler.staticFiles,
"documentRoot":"./webroot",
"allowResponseFilters":true]
],
"filters":[
[
"type":"response",
"priority":"high",
"name":PerfectHTTPServer.HTTPFilter.contentCompression,
]
]
]
]
]
// 启动服务
do {
try HTTPServer.launch(configurationData: confData)
} catch {
fatalError("\(error)")
}
["method":"get", "uri":"/home", "handler":handlerHome]
func handlerHome(request: HTTPRequest, response: HTTPResponse) {
let jsonString = MySQLOperation().selectHomeTabelData()
response.setBody(string: jsonString!)
response.completed()
}
clone下来的代码我们可以在Package.swift中添加自己需要的dependencies:
import PackageDescription
let package = Package(
name: "PerfectTemplate",
targets: [],
dependencies: [
.Package(url: "https://github.com/PerfectlySoft/Perfect-HTTPServer.git", majorVersion: 3),
]
)
也可以使用Perfect Assistant做依赖管理,使用事例
使用Perfect实现后台,swift实现客户端,设计了一个简单的dilidili程序。其中后台主要实现了爬虫爬取dilidili视频数据,操作MySQL的增删改查,客户端首页的请求,视频列表请求。客户端主要实现了使用swift实现数据请求,json解析,视频播放。
// 添加路由
["method":"get", "uri":"/crawler", "handler":handlerCrawler],
// handler
func handlerCrawler(request: HTTPRequest, response: HTTPResponse) {
response.setHeader(.contentType, value: "text/html")
let url = "http://www.dilidili.wang/watch3/62066/"
response.appendBody(string: Crawler.requestData(url: url))
response.completed()
}
// 解析HTML,返回vidoeURLArray, titleArray, indexArray
static func extendHTML(html:String) -> (vidoeURLArray:Array<String>, titleArray:Array<String>, indexArray:Array<String>) {
var nextVideoArray : Array<String> = Array()
var titleArray : Array<String> = Array()
var indexArray : Array<String> = Array()
let xDoc = HTMLDocument(fromSource: html)
for item in (xDoc?.getElementsByTagName("div"))! {
let itemAtt = item.getAttribute(name: "class")
if itemAtt == "num con24 clear" {
var nextVideo : String = ""
var title : String = ""
var index : String = ""
let childNodes = item.childNodes
for childItem in childNodes {
if childItem.nodeName == "a" {
nextVideo = (childItem.attributes?.getNamedItem(name: "href")?.nodeValue)!
title = (childItem.attributes?.getNamedItem(name: "title")?.nodeValue)!
index = childItem.nodeValue!
nextVideoArray.append(nextVideo)
titleArray.append(title)
indexArray.append(index)
}
}
}
}
var vidoeURLArray:Array<String> = Array()
for i in 0..<nextVideoArray.count {
do {
let resultHTML:String = try CURLRequest(nextVideoArray[i]).perform().bodyString
let str:NSString = resultHTML as NSString
let videoURL1 = videoURL(videoURL: str)
if videoURL1 != "NotFound" {
vidoeURLArray.append(videoURL1)
}
} catch {
print("ERROR")
}
}
return (vidoeURLArray, titleArray, indexArray)
}
class MySQLManager {
var mysql : MySQL!
static let shareInstance : MySQLManager = {
let instance = MySQLManager()
let testHost = "127.0.0.1"
let testUser = "root"
let testPassword = "dw123456"
let testDB = "dilidili"
instance.mysql = MySQL()
let connected = instance.mysql.connect(host: testHost, user: testUser, password: testPassword, db: testDB)
if connected {
print("connectedSuccess")
} else {
print("connectedError")
}
guard connected else {
// 验证一下连接是否成功
print(instance.mysql.errorMessage())
return instance
}
return instance
}()
private init(){}
}
/// 插入数据到home表
///
/// - Parameters:
/// - animeID: 动画id
/// - animeTitle: 动画标题
/// - animeImage: 动画封面图片地址
/// - vidoeURLArray: 动画每集播放地址
/// - titleArray: 动画每集标题
/// - indexArray: 动画每集集数
/// - Returns: 插入数据是否成功
func insertToDataBase(animeID:Int, animeTitle:String, animeImage:String, vidoeURLArray:Array<String>, titleArray:Array<String>, indexArray:Array<String>) -> Bool {
let homeValues = "(\(animeID), '\(animeTitle)', '\(animeImage)')"
let homeStatement = "insert into home (id, animateTitle, animateImage) values \(homeValues)"
let isHomeSuccess = self.mysql.query(statement: homeStatement)
if isHomeSuccess {
print("insertHomeSuccess")
} else {
print("insertHomeError")
}
let headEmpty = indexArray.count - vidoeURLArray.count
for i in 0..<indexArray.count {
if i < indexArray.count - 1 && i < vidoeURLArray.count && i < titleArray.count && i < indexArray.count {
let videoValues = "(\(animeID), '\(vidoeURLArray[i])', '\(titleArray[i+headEmpty])', '\(indexArray[i+headEmpty])')"
let videoStatement = "insert into video (animateID, animateURL, animateName, animateIndex) values \(videoValues)"
let isVideoSuccess = self.mysql.query(statement: videoStatement)
if isVideoSuccess {
print("insertVideoSuccess")
} else {
print("insertVideoError")
}
}
}
return isHomeSuccess
}
/// 查询home表
///
/// - Returns: 返回查询json结果
func selectHomeTabelData() -> String? {
let statement = "select * from home"
let isHomeSelectSuccess = self.mysql.query(statement: statement)
if isHomeSelectSuccess {
// 在当前会话过程中保存查询结果
let results = mysql.storeResults()!
var array = [[String:String]]() //创建一个字典数组用于存储结果
results.forEachRow { row in
guard let id = row.first! else {//保存选项表的id名称字段,应该是所在行的第一列,所以是row[0].
return
}
var dic = [String:String]() //创建一个字典数于存储结果
dic["id"] = "\(id)"
dic["animateTitle"] = "\(row[1]!)"
dic["animateImage"] = "\(row[2]!)"
array.append(dic)
}
self.responseJson[ResultKey] = RequestResultSuccess
self.responseJson[ResultListKey] = array
} else {
self.responseJson[ResultKey] = RequestResultFaile
self.responseJson[ErrorMessageKey] = "查询失败"
}
guard let josn = try? responseJson.jsonEncodedString() else {
return nil
}
return josn
}
主要有两个API接口,查询首页信息和查询每集信息
["method":"get", "uri":"/home", "handler":handlerHome],
["method":"post", "uri":"/videolist", "handler":handlerVideolist],
// 首页信息
func handlerHome(request: HTTPRequest, response: HTTPResponse) {
let jsonString = MySQLOperation().selectHomeTabelData()
response.setBody(string: jsonString!)
response.completed()
}
// 每集信息
func handlerVideolist(request: HTTPRequest, response: HTTPResponse) {
guard let animateID: String = request.param(name: "animateID") else {
print("animateID为nil")
return
}
let jsonString = MySQLOperation().selectVideoTabelData(animateID: animateID)
response.setBody(string: jsonString!)
response.completed()
}
/// 查询home表
///
/// - Returns: 返回查询json结果
func selectHomeTabelData() -> String? {
let statement = "select * from home"
let isHomeSelectSuccess = self.mysql.query(statement: statement)
if isHomeSelectSuccess {
// 在当前会话过程中保存查询结果
let results = mysql.storeResults()!
var array = [[String:String]]() //创建一个字典数组用于存储结果
results.forEachRow { row in
guard let id = row.first! else {//保存选项表的id名称字段,应该是所在行的第一列,所以是row[0].
return
}
var dic = [String:String]() //创建一个字典数于存储结果
dic["id"] = "\(id)"
dic["animateTitle"] = "\(row[1]!)"
dic["animateImage"] = "\(row[2]!)"
array.append(dic)
}
self.responseJson[ResultKey] = RequestResultSuccess
self.responseJson[ResultListKey] = array
} else {
self.responseJson[ResultKey] = RequestResultFaile
self.responseJson[ErrorMessageKey] = "查询失败"
}
guard let josn = try? responseJson.jsonEncodedString() else {// Dictionary转json
return nil
}
return josn
}
/// 查询video的每集信息
///
/// - Parameter animateID: 动画id
/// - Returns: 返回查询json结果
func selectVideoTabelData(animateID:String) -> String? {
let videoValues = "('\(animateID)')"
let statement = "select * from video where animateID=\(videoValues)"
let isVideoSelect = self.mysql.query(statement: statement)
if isVideoSelect {
print("VideoSelectSuccess")
// 在当前会话过程中保存查询结果
let results = mysql.storeResults()!
var array = [[String:String]]() //创建一个字典数组用于存储结果
results.forEachRow { row in
var dic = [String:String]() //创建一个字典数于存储结果
dic["animateID"] = "\(row[1]!)"
dic["animateURL"] = "\(row[2]!)"
dic["animateName"] = "\(row[3]!)"
dic["animateIndex"] = "\(row[4]!)"
array.append(dic)
}
self.responseJson[ResultKey] = RequestResultSuccess
self.responseJson[ResultListKey] = array
} else {
print("VideoSelectError")
self.responseJson[ResultKey] = RequestResultFaile
self.responseJson[ErrorMessageKey] = "查询失败"
}
guard let josn = try? responseJson.jsonEncodedString() else {// Dictionary转json
return nil
}
return josn
}
Apple Push Notification service。苹果推送通知服务
let center = UNUserNotificationCenter.current()
center.requestAuthorization(options: [.alert, .sound, .badge]) { granted, error in
print("Request permission for notifications: \(granted)")
UIApplication.shared.registerForRemoteNotifications()
center.delegate = self
}
2. APNS返回一个device token,通过didRegisterForRemoteNotificationsWithDeviceToken获取device token(跟随设备,同一设备使用同一个device token)
func application(_ application: UIApplication, didRegisterForRemoteNotificationsWithDeviceToken deviceToken: Data) {
guard let deviceTokenUse:Data = deviceToken else {
return
}
}
let hex = deviceTokenUse.hexString
#if arch(i386) || arch(x86_64)
let urlString = "http://127.0.0.1:8181/notification/add"
#else
// let urlString = "http://172.26.147.180:8181/notification/add"
let urlString = "http://192.168.3.29:8181/notification/add"
// let urlString = "http://172.26.83.6/notification/add"
#endif
let parameters:Dictionary = ["deviceId":hex]
Alamofire.request(urlString, method: .post, parameters: parameters, encoding: URLEncoding.default, headers: nil)
.responseString { (response) in
print("response")
}
NotificationPusher(apnsTopic: notificationsTestId)
.pushAPNS(configurationName: notificationsTestId, deviceTokens: [array[i]], notificationItems: [.alertBody("Hello!"),.sound("default")]) { (responses) in
print("\(responses)")
}
首页使用UICollectionView承载展示数据,使用Alamofire框架去请求数据(AFNetworking的swift版),使用代码+xib进行布局
// Alamofire发送请求首页数据
Alamofire.request(urlRequest, method: .get, parameters: nil, encoding: URLEncoding.default, headers: nil)
.responseJSON { (response) in
if let value = response.result.value {
let json = JSON(value)
if let dic = json.dictionary {
let result = dic["result"]?.string
if result == "SUCCESS" {
self.dataSourceArray = json["list"].arrayObject! as Array
self.collectionView?.reloadData()
}
}
}
}
使用SnapKit(Masonry的swift版)进行视频区域的布局(当视频全屏的时候)
player.snp.remakeConstraints { (make) in
make.top.equalTo(self.view.snp.top)
make.left.equalTo(self.view.snp.left)
make.right.equalTo(self.view.snp.right)
if isFullscreen {
make.bottom.equalTo(self.view.snp.bottom)
} else {
make.height.equalTo(view.snp.width).multipliedBy(9.0/16.0).priority(500)
}
}
Image Literal
可以尝试使用git svn
使用git clone 一个svn的项目
git svn clone svn地址 -s
git svn dcommit提交