前言
过去的2016年是一个直播年,各大平台都相继接入了直播频道,电商,社交…更是火了一批做视频的,譬如喵播,映客,都斗鱼等直播平台。全民直播,一下子掀起了直播的技术潮,今天要聊聊如何实现一个ios的直播app。
首先来看最终的效果:
最近也是因为入门swift不久,在网上找了一个项目就开始模仿,本项目用到的第三方库:
Alamofire
Kingfisher
Swift3.0的蝶变
swift3.0相对于2.x,渐渐的脱离了oc和c的风格,不管是从命名规范还是新能上都有了较大的提升,笔者认为应该是未来一个相对稳定的版本,而不是1.0和2.0时代的实验版本。相对于2.x,我们来看3.0或以后的3.x主要有哪些特性:
稳定二进制接口(ABI)
API大家都知道是应用程序接口 API只是提供函数签名,而ABI是系统和语言层面的 如果ABI稳定 意味着以后Swift版本更新升级 我们不需要再修改老版本 Swift 语言编译的库了。
弹性/韧性 解决易碎二进制接口问题
Fragile binary interface problem是面向对象编程语言的通病 如果在程序中引入了外部库 我们的的程序中使用并继承了该外部库中的类 如果外部库有改动 我们必须重新编译所有该类的继承树 而这类问题被称为脆弱的基类 (Fragile base class)
可移植性
这个对于高级语言是很重要的特性,这意味着Swift可被移植到其他平台上。
全面支持泛型特性
Swift 2.2已经很好的支持泛型 但是还不够完善,Swift 3.0开始 将全面支持泛型的所有特性。
新的API设计规范
Swift3.0 发布了新的语言设计规范 其中在Swift3.0中标准库和核心库将会遵循这个设计规范。规范地址
从函数参数中删除var关键字
func doSomethingWithVar(var i: Int) {
i = 2 // This will NOT have an effect on the caller's Int that was passed, but i can be modified locally
}
func doSomethingWithInout(inout i: Int) {
i = 2 // This will have an effect on the caller's Int that was passed.
}
doSomethingWithVar(x)
print(x) // 1
doSomethingWithInout(&x)
print(x) // 2
删除var是因为var与inout会产生歧义和混乱。
为autoreleasepool添加错误处理
旧版autoreleasepool处理错误方式:
func doWork() throws -> Result {
var result: Result? = nil
var error: ErrorProtocol? = nil
autoreleasepool {
do {
... actual computation which hopefully assigns to result but might not ...
} catch let e {
error = e
}
}
guard let result = result else {
throw error!
}
return result!
}
Swift3.0 autoreleasepool 处理错误方式:
public func autoreleasepool<Result>(@noescape body: () throws -> Result) rethrows -> Result
func doWork() throws -> Result {
return try autoreleasepool
{
... actual computation which either returns or throws ...
}
}
允许直接引用(Default, Private, Repeat)关键字成员
在Swift3.0之前我们引用default和repeat成员时 需要这样写:
let cell = UITableViewCell(style: .`default`, reuseIdentifier: nil)
particleSystem.imageSequenceAnimationMode = SCNParticleImageSequenceAnimationMode.`repeat`
Swift3.0时 允许我们直接访问default repeat 关键字成员:
let cell = UITableViewCell(style: .default, reuseIdentifier: nil)
particleSystem.imageSequenceAnimationMode = SCNParticleImageSequenceAnimationMode.repeat
将声明式@noescape和@autoclosure 改为类型属性
func f(@noescape fn : () -> ()) {} // declaration attribute
//新的语法
func f(fn : @noescape () -> ()) {} // type attribute.
func f2(a : @autoclosure () -> ()) {} // type attribute.
重命名 Debug 标示符
Debug 标示符重命名后将会与#available #selector 关键字统一风格。
__FILE__ -> #file
__LINE__ -> #line
__COLUMN__ -> #column
__FUNCTION__ -> #function
__DSO_HANDLE__ -> #dsohandle
斗鱼部分代码分析
本app采用的是mvvm的开发架构,做到业务,数据,页面的真正分离,我们来看几个核心的类:
base
import UIKit
private let kItemMargin : CGFloat = 10
private let kHeaderViewH : CGFloat = 50
private let NormalCellID = "NormalCellID"
private let HeaderViewID = "HeaderViewID"
let kNormalItemW = (kScreenW - 33 * kItemMargin) / 2
let kNormalItemH = kNormalItemW * 3 / 4
let kPrettyItemH = kNormalItemW * 5 / 4
let PrettyCellID = "PrettyCellID"
class BaseAnchorVC: BaseVC {
//!表示用到的时候保证有值
var baseVM : BaseVM!
lazy var collectionView : UICollectionView = {[unowned self] in
let layout = UICollectionViewFlowLayout()
layout.itemSize = CGSize(width: kNormalItemW, height: kNormalItemH)
layout.minimumLineSpacing = 0
layout.minimumInteritemSpacing = kItemMargin
layout.headerReferenceSize = CGSize(width: kScreenW, height: kHeaderViewH)
layout.sectionInset = UIEdgeInsets(top: 0, left: kItemMargin, bottom: 0, right: kItemMargin)
let collectionView = UICollectionView(frame: self.view.bounds, collectionViewLayout: layout)
collectionView.backgroundColor = UIColor.white
collectionView.dataSource = self
collectionView.delegate = self
collectionView.autoresizingMask = [.flexibleHeight, .flexibleWidth]
collectionView.register(UINib(nibName: "CollectionNormalCell", bundle: nil), forCellWithReuseIdentifier: NormalCellID)
collectionView.register(UINib(nibName: "CollectionPrettyCell", bundle: nil), forCellWithReuseIdentifier: PrettyCellID)
collectionView.register(UINib(nibName: "CollectionHeaderView", bundle: nil), forSupplementaryViewOfKind: UICollectionElementKindSectionHeader, withReuseIdentifier: HeaderViewID)
return collectionView
}()
override func viewDidLoad() {
super.viewDidLoad()
setupUI()
loadData()
}
}
extension BaseAnchorVC {
override func setupUI() {
contentView = collectionView
view.addSubview(collectionView)
super.setupUI()
}
}
extension BaseAnchorVC {
func loadData() {
}
}
extension BaseAnchorVC : UICollectionViewDataSource {
func numberOfSections(in collectionView: UICollectionView) -> Int {
return baseVM.anchorGroups.count
}
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return baseVM.anchorGroups[section].anchors.count
}
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: NormalCellID, for: indexPath) as! CollectionNormalCell
cell.anchor = baseVM.anchorGroups[indexPath.section].anchors[indexPath.item]
return cell
}
func collectionView(_ collectionView: UICollectionView, viewForSupplementaryElementOfKind kind: String, at indexPath: IndexPath) -> UICollectionReusableView {
let headerView = collectionView.dequeueReusableSupplementaryView(ofKind: kind, withReuseIdentifier: HeaderViewID, for: indexPath) as! CollectionHeaderView
headerView.group = baseVM.anchorGroups[indexPath.section]
return headerView
}
}
extension BaseAnchorVC : UICollectionViewDelegate {
func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
let anchor = baseVM.anchorGroups[indexPath.section].anchors[indexPath.item]
anchor.isVertical == 0 ? pushNormalRoomVc(anchor) : presentShowRoomVc(anchor)
}
private func presentShowRoomVc(_ anchor : AnchorModel) {
let showVc = ShowRoomVC()
showVc.anchor = anchor
present(showVc, animated: true, completion: nil)
}
private func pushNormalRoomVc(_ anchor : AnchorModel) {
let normalVc = NormalRoomVC()
normalVc.anchor = anchor
navigationController?.pushViewController(normalVc, animated: true)
}
}
import UIKit
class GameVC: BaseAnchorVC {
fileprivate lazy var gameVM : GameVM = GameVM()
fileprivate lazy var menuView : MenuView = {
let menuView = MenuView.menuView()
menuView.frame = CGRect(x: 0, y: -kMenuViewH, width: kScreenW, height: kMenuViewH)//设置collectionView的-y,放置menuView
return menuView
}()
}
extension GameVC {
override func setupUI() {
super.setupUI()
collectionView.addSubview(menuView)
collectionView.contentInset = UIEdgeInsets(top: kMenuViewH, left: 0, bottom: 0, right: 0)//设置内边距
}
}
extension GameVC{
override func loadData() {
baseVM = self.gameVM
gameVM.requestData {
self.collectionView.reloadData()
var gameGroups = Array(self.gameVM.anchorGroups[1...15])//0...15 & gameGroups.removeFirst()
let moreGroup = AnchorGroup()
moreGroup.tag_name = "更多分类"
gameGroups.append(moreGroup)
self.menuView.groups = gameGroups
self.loadDataFinished()
}
}
}
请求类:
import UIKit
import Alamofire
enum MethodType {
case get
case post
}
class HttpTools {
class func requestData(_ type : MethodType, URLString : String, parameters : [String : Any]? = nil, finishedCallback : @escaping (_ result : Any) -> ()) {
let method = type == .get ? HTTPMethod.get : HTTPMethod.post
Alamofire.request(URLString, method: method, parameters: parameters).responseJSON { (response) in
guard let result = response.result.value else {
print(response.result.error)
return
}
finishedCallback(result)
}
}
}
附:swift斗鱼app界面
斗鱼完整代码oc
oc代码原文