Swift开发黑科技:还在争论MVC和MVVM?现在你应该试试MVP!

本人原创,长文慎入,但此文绝对不会让你失望。
WWDC2015已经过去一段时间了,我发现自从更新了Swift2.0到现在的Swift2.2,我只是跟着版本更新了所有需要更新的语法,依旧自以为是很熟练的Swift程序员。刚入职比较闲碰巧看到了1月份的中国首届Swift大会上大牛们的分享,突然陷入了思考,有了很多新想法又重温了几遍WWDC2015大会的视频,尤其是408和414号视频!!!我下定决心重构自己的代码,下面步入正题,结合Swift开发大会的一些分享,让我们谈谈架构。
通过一个简单的Demo:一个事件提醒的小应用。
这个应用会使用一个TableView混合展示一个时间段的所有待办事宜和这个时间段的节日提醒,由于待办事件和节日的数据构成是不同的,所以需要创建两个模型,它们在TableView上展示的样式也应该有所不同,很常规的我们还需要一个TableViewCell的子类。
现在数据工程里面的目录是这样的:

模型代码:

struct Event {
    var date = ""
    var eventTitle = ""
    init(date:String,title:String){
        self.date = date
        self.eventTitle = title
    }
}

struct Festival {
    var date = ""
    var festivalName = ""
    init(date:String,name:String){
        self.date = date
        self.festivalName = name
    }
}

为了简单我都使用了String类型的数据,至于为什么要使用struct而不使用class,大家可以参考WWDC2015的414号视频,讲的非常清楚,我自己的项目中的数据模型已经全部转成struct了,我会在后面专门写博文讲解struct,这里就不赘述了。这里需要啰嗦一下,注意创建的时候使用的是字面量的方法,而不是可选型,我一直认为使用字面量的方法是更好的选择,可选型很容易被当做语法糖滥用。尤其是数据的初始化中,你确定你真的需要一个空值?拿一个空值能做什么?做某种标志位么?请和你的后台开发人员商议,让他给你一个Bool类型的标志位,而不是一个空值。在可能的情况下,给你的数据模型的属性赋一个语义明确的字面量初始值,比如这里我们使用空字符串作为初始值。如果你的数据只是做展示的不会存在修改情况,你也可以使用如下的方法做初始化,以达到效率的最大化:

struct Event {
    let date:String
    let eventTitle:String
    init(date:String = "",eventTitle:String = ""){
        self.date = date
        self.eventTitle = eventTitle
    }
}

在Swift1.2版本之后,let定义的数据也支持延迟加载了,这里使用了默认参数值做非空的保障。
模型否则在创建一个实例的时候各种可选型的解包或可选绑定会让你吃尽苦头,空值的访问是程序carsh的元凶!
如果如果你更新了Xcode7.3,你会发现在创建一个属性的时候Xcode的提示是“ =“,没错,Xcode推荐你用字面量去做初始化。
有了数据模型后,在Cell上创建两个Label

class ShowedTableViewCell: UITableViewCell {
    //用来展示事件主题或节日名称的Label
    @IBOutlet weak var MixLabel: UILabel!
    //用来展示日期的Label
    @IBOutlet weak var dateLabel: UILabel!


}

MVC架构:
从这里我们将展示传统的MVC的写法,但是包含了一些关键的知识点,所以还是建议您不要跳过。我们通过控制器中的代码去控制数据的展示,由于数据源包含两种数据类型,可以构造两个数组避免数组的异构:

var eventList = [Event]()
    var festivalList = [Festival]()
    let loadedEventList = [Event(date: "2月14",eventTitle: "送礼物")]
    let loadedFestivalList = [Festival(date: "1月1日",festivalName: "元旦"),Festival(date: "2月14",festivalName: "情人节")]

这里使用了struct的默认构造器构造对象,有两个节日提醒:元旦节和情人节,元旦节没什么事情做,情人节那天有个事件提醒”送礼物“,我们使用GCD去模拟数据刷新,整个控制器的代码如下:

import UIKit

let cellReusedID = "ShowedTableViewCell"
class ShowedTableViewController: UITableViewController {

    var eventList = [Event]()
    var festivalList = [Festival]()
    let loadedEventList = [Event(date: "2月14",eventTitle: "送礼物")]
    let loadedFestivalList = [Festival(date: "1月1日",Festival(date: "2月14",festivalName: "情人节")]
    override func viewDidLoad() {
        super.viewDidLoad()
        let delayInSeconds = 2.0
        let popTime = dispatch_time(DISPATCH_TIME_NOW,Int64(delayInSeconds * Double(NSEC_PER_SEC)))
        dispatch_after(popTime,dispatch_get_main_queue()) { () -> Void in
            self.eventList = self.loadedEventList
            self.festivalList = self.loadedFestivalList
            self.tableView.reloadData()
        }
    }



    // MARK: - Table view data source

    override func numberOfSectionsInTableView(tableView: UITableView) -> Int {
        // #warning Incomplete implementation,return the number of sections
        return 1
    }

    override func tableView(tableView: UITableView,numberOfRowsInSection section: Int) -> Int {
        // #warning Incomplete implementation,return the number of rows
        return eventList.count + festivalList.count
    }

    override func tableView(tableView: UITableView,cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
        let cell = tableView.dequeueReusableCellWithIdentifier(cellReusedID,forIndexPath: indexPath) as! ShowedTableViewCell
        //传统的MVC,你需要在这里处理数据本身的同构与异构情况,还得处理数据与视图的逻辑关系
        //这里我们把事件提醒放在节日的前面展示
        if indexPath.row > eventList.count - 1{
            cell.MixLabel.text = festivalList[indexPath.row - eventList.count].festivalName
            cell.dateLabel.text = festivalList[indexPath.row - eventList.count].date
            cell.backgroundColor = UIColor.whiteColor()
            return cell
        } else {
            cell.MixLabel.text = eventList[indexPath.row].eventTitle
            cell.dateLabel.text = eventList[indexPath.row].date
            cell.backgroundColor = UIColor.redColor()
            return cell
        }
    }


}

运行一下看看:

似乎还不错,我们把两个不同的数据结构展现在一张页面上了,并且复用了cell,但是设置cell的代理方法中的代码似乎有点多,而且如果我需要按照时间去排序,那么两个同构的数组作为数据源不好排序,那么重构首先从把同构变成异构开始。由于struct没有继承,按照Swift2.0的精神,此时我们需要提炼两个数据模型的共性,方法是利用protocol,观察到Event和Festival都有date属性,所以写一个协议:

protocol hasDate{
    var date:String {get}
}

这里这个协议只有一个属性date,Swift协议中定义的属性只有声明,遵守协议的对象必须实现这个属性,但是不限于存储属性还是计算属性。协议中定义的属性必须指定最低的访问级别,这里的date必须是可读的,至于可写的权限取决于实现该协议的数据类型中的定义。由于我们的Event和Festival都具有了date属性,直接让二者遵守hasDate协议,不要用扩展的方式让二者遵守协议,编译器报错的,很怪0 0.
修改并化简控制器中的数据源,使用异构数据源,现在控制器的代码如下:

import UIKit

let cellReusedID = "ShowedTableViewCell"
class ShowedTableViewController: UITableViewController {

    var dataList = [hasDate]()
    var loadeddataList:[hasDate] = [Event(date: "2月14",eventTitle: "送礼物"),Festival(date: "1月1日",festivalName: "情人节")]
    override func viewDidLoad() {
        super.viewDidLoad()
        let delayInSeconds = 2.0
        let popTime = dispatch_time(DISPATCH_TIME_NOW,dispatch_get_main_queue()) { () -> Void in
            //注意这里,我故意把loadeddataList中的数据打乱了,为了实现异构数据的按照某个公共类型的属性的排序,使用了Swift内置的sort函数,String遵守了Compareable协议,这里为了简单吧date指定为String类型,如果是NSDate,你可以在sort的闭包中指定合适的排序规则。
            self.dataList = self.loadeddataList.sort{$0.date < $1.date}
            self.tableView.reloadData()
        }
    }



    // MARK: - Table view data source

    override func numberOfSectionsInTableView(tableView: UITableView) -> Int {
        return 1
    }

    override func tableView(tableView: UITableView,numberOfRowsInSection section: Int) -> Int {
        return dataList.count
    }

    override func tableView(tableView: UITableView,cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
        let cell = tableView.dequeueReusableCellWithIdentifier(cellReusedID,forIndexPath: indexPath) as! ShowedTableViewCell
        //注意这里,通过可选绑定进行异构数据的类型控制
        if let event = dataList[indexPath.row] as? Event{
            cell.MixLabel.text = event.eventTitle
            cell.dateLabel.text = event.date
            cell.backgroundColor = UIColor.redColor()
            return cell
        } else if let festival = dataList[indexPath.row] as? Festival{
            cell.MixLabel.text = festival.festivalName
            cell.dateLabel.text = festival.date
            cell.backgroundColor = UIColor.whiteColor()
            return cell
        } else {
            return cell
        }
    }
}

运行一下:

没有任何问题。对异构数组的类型判断的写法来自于WWDC2015上的408号视频,现在控制器里的代码已经精简了很多了,我们解决了异构的问题,对于MVC来说,这似乎已经精简到极限了。这是一个简单的Demo,在真正的工程中一个控制器当中的代码可能有几百上千行,或者有多个TableView,这个时候MVC的弊端就显现了,在几百行代码中可能有一百行都用来做数据与视图的绑定,而数据模型和视图本身的代码定义中却只有寥寥数十行,控制器的负担太重了!因此有人提出了将控制器中有关模型与视图的逻辑的代码提出到一个单独的区域进行处理,这就是MVVM架构的由来。
MVVM架构
对MVVM架构的解读我想引用Swift开发者大会上李信洁前辈的示例写法,通过POP来实现一个MVVM,并且对其写法进行了一些精简。我们先不修改View和Modal的代码,因为需要更新的是一个cell,所以首先需要写一个传递Modal中数据的协议:

protocol CellPresentable{
    var mixLabelData:String {get set}
    var dateLabelData:String {get set}
    var color: UIColor {get set}
    func updateCell(cell:ShowedTableViewCell)
}

这个协议的思想是显示地声明一个更新cell的方法,并根据cell需要的数据声明两个属性,我们并不关心mixLabel和dateLabel的数据从哪里来,叫什么名字,但他们的功能是确定的,Swift2.0之后可以扩展协议,下面通过协议扩展给这个协议增加默认的实现,这样在绑定数据时可以减少代码量:

extension CellPresentable{
    func updateCell(cell:ShowedTableViewCell){
        cell.MixLabel.text = mixLabelData
        cell.dateLabel.text = dateLabelData
        cell.backgroundColor = color
    }
}

好了,我们写好了,下一步我们要修改cell的代码,增加一个方法接受一个CellPresentable:

class ShowedTableViewCell: UITableViewCell {
    //用来展示事件主题或节日名称的Label
    @IBOutlet weak var MixLabel: UILabel!
    //用来展示日期的Label
    @IBOutlet weak var dateLabel: UILabel!

    func updateWithPresenter(presenter: CellPresentable) {
        presenter.updateCell(self)
    }
}

这里也做了一些改进,李信洁前辈的示例中是针对每一个控件去定义方法的,其实对一个View的所有IBOutlet做更新不就是更新它自己么,所以这里我的写法是直接传入self。然后(我也不想多说然后,但是步骤就是这么多)为了绑定异构的Model和View你还需要定义一个ViewModel,并且通过定义不同的init实现数据绑定:

struct ViewModel:CellPresentable{
    var dateLabelData = ""
    var mixLabelData = ""
    var color = UIColor.whiteColor()
    init(modal:Event){
        self.dateLabelData = modal.date
        self.mixLabelData = modal.eventTitle
        self.color = UIColor.redColor()
    }
    init(modal:Festival){
        self.dateLabelData = modal.date
        self.mixLabelData = modal.festivalName
        self.color = UIColor.whiteColor()
    }
}

最后我们终于可以去修改我们的控制器了,控制器中需要更改的是与cell有关的datasource方法:

override func tableView(tableView: UITableView,forIndexPath: indexPath) as! ShowedTableViewCell
        if let event = dataList[indexPath.row] as? Event{
            let viewModel = ViewModel(modal: event)
            cell.updateWithPresenter(viewModel)
            return cell
        } else if let festival = dataList[indexPath.row] as? Festival{
            let viewModel = ViewModel(modal: festival)
            cell.updateWithPresenter(viewModel)
            return cell
        } else {
            return cell

        }
    }
}

这段代码写的我满头大汗,编译运行,幸运的是运行的结果是正确的:

我在想MVVM模式的意义是什么?我在使用MVVM之前甚至需要考虑一下值不值得花时间去写成MVVM的模样,因为MVVM需要给所有的view提供协议,并且将所有的数据模型的绑定过程写进一个新的数据结构ViewModal中,但其实这个ViewModel的价值非常之小,除了数据绑定,没有其他作用了,里面甚至只有空洞的init构造器,我想我已经决定放弃这个思路了。
MVP的萌芽阶段
我继续着自己的思考,大会上傅若愚前辈分享的示例给了我很大的启发,因为他提供了一个没有中间层的模型!我一直在思考这个模型,并且在入职的第一个项目中一直在按照他的模型来组织自己的代码,直到我顿悟了自己的MVP模型。下面简单介绍一下傅若愚前辈的思路,这个思路的优势在于所有的数据和模型绑定都只需要两个通用的协议:

//视图使用的协议
protocol RenderContext{
    func renderText(texts:String...)
    func renderImage(images:UIImage...)
}
//数据使用的协议
protocol ViewModelType{
    func renderInContext(context:RenderContext)
}

上面是大会上傅若愚前辈的原版,在介绍这个协议的用法之前,我觉得应该先做一点点改进,ViewModalType应该改成:

protocol ViewModelType{
    func renderInContext<R:RenderContext>(context:R)
}

这两个版本都可以通过编译,差别在运行的效率上,下面我在playground中展示一个示例,这个示例来源于《Advanced Swift》这本书,其实苹果的WWDC2015 408号视频中也明确表述了不要把协议当做参数类型,而写成泛型的约束,但是没有详细讲解为什么,下面是示例:

func takesProtocol(x: CustomStringConvertible) { //
    print ( sizeofValue(x))
}
func takesPlaceholder<T: CustomStringConvertible>(x: T) {
    print ( sizeofValue(x))
}

两个方法,前者使用协议作为参数的类型,后者使用协议作为泛型的约束条件,两个方法都会打印参数的长度,调用一下试试:

takesProtocol(1 as Int16) takesPlaceholder(1 as Int16)

打印结果:

换成类再打印一次:

没错,由于协议本身既可以被类遵守、也可以被结构体、枚举遵守,也就是说既可以被引用类型遵守也可以被值类型遵守,把协议当做参数类型,实际上会创造一个Box类型,里面会为引用类型遵守者预留地址也会为值类型遵守者预留地址,甚至需要存储一个指针长度找到协议的真正继承类型。而Swift2.0之后编译器得到了加强,具有了泛型特化的功能,对代码中的泛型在编译时就会确定其真正的类型,不耗费任何性能。
下面我们用改造后的傅若愚前辈的协议来改造Demo,你需要让你的数据模型去遵守RenderContext,然后根据模型的参数类型将每一个参数存入对应类型方法的参数列表中,这些方法都是可变参数,不限制数量,但是参数的类型是确定的。这种使用参数类型做通用类型的写法消灭了中间的ViewModel层,把Model和View直接对接了。由于Swift要求每一个协议的遵守者都必须实现协议的全部方法,而有些方法的数据模型并没有,所以你在使用之前需要使用协议扩展为这些方法实现一个空的实现:

protocol RenderContext{
    func renderText(texts:String...)
    func renderImage(images:UIImage...)
}

extension RenderContext{
    func renderText(texts:String...){

    }
    func renderImage(images:UIImage...){

    }
}

现在你的模型应该是下面这样:

struct Event:hasDate,ViewModelType{
    var date = ""
    var eventTitle = ""
    func renderInContext<R : RenderContext>(context: R) {
        context.renderText(date,eventTitle)
    }
}

struct Festival:hasDate,ViewModelType{
    var date = ""
    var festivalName = ""
    func renderInContext<R : RenderContext>(context: R) {
        context.renderText(date,festivalName)
    }
}

视图的代码应该是这样的:

class ShowedTableViewCell: UITableViewCell,RenderContext {
    //用来展示事件主题或节日名称的Label
    @IBOutlet weak var MixLabel: UILabel!
    //用来展示日期的Label
    @IBOutlet weak var dateLabel: UILabel!

    func renderText(texts: String...) {
        dateLabel.text = texts[0]
        MixLabel.text = texts[1]
    }
}

由于遵守了多个协议,所以控制器中原本的异构类型不合适了,此时可以给多个协议类型写一个别名方便使用,记得顺便更新一下你的Model,提高可读性:

typealias DateViewModel = protocol<hasDate,ViewModelType>

现在控制器中的数据源可以使用新的异构类型了:

var dataList = [DateViewModel]()
    var loadeddataList:[DateViewModel] = [Event(date: "2月14",Festival(date: "1月1日",festivalName: "情人节")]

然后更新cell的代理方法:

override func tableView(tableView: UITableView,cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
        let cell = tableView.dequeueReusableCellWithIdentifier(cellReusedID,forIndexPath: indexPath) as! ShowedTableViewCell
        dataList[indexPath.row].renderInContext(cell)
        return cell
    }

不错,代码简洁了很多,运行一下:

等等,我们似乎遗漏了一些东西,cell的背景颜色呢?好吧让我们加上,可是我该去哪里加呢?去控制器中吗?不不坚决不能碰控制器,那么只能去cell中了,现在问题出现了,当两个模型共享一个视图的时候,我该如何判断数据源从哪里来?renderText(texts: String…)这样的写法已经完全失去了异构的特性,那么试着这样写,在数据传递参数的时候多传一个String好了,反正参数是我们的自由:

struct Event:DateViewModel{
    var date = ""
    var eventTitle = ""
    func renderInContext<R : RenderContext>(context: R) {
        context.renderText(date,eventTitle,"red")
    }
}

这样在检验的时候就看最后一个参数就好了:

class ShowedTableViewCell: UITableViewCell,RenderContext {
    //用来展示事件主题或节日名称的Label
    @IBOutlet weak var MixLabel: UILabel!
    //用来展示日期的Label
    @IBOutlet weak var dateLabel: UILabel!

    func renderText(texts: String...) {
        dateLabel.text = texts[0]
        MixLabel.text = texts[1]
        if texts[2] == "red"{
            backgroundColor = UIColor.redColor()
        }
    }
}

这里有个语法糖,可变参数的方法,在取参时不会发生越界,因为Festival的renderText方法只传了两个值,运行结果又正常了。那么如果我粗心把参数写错顺序了呢?结果成了这样:

如果我的Festival中多了一个Int类型,而Event中恰巧没有呢?按照值去区分参数不是一个好主意,因为你用下标从一个数组中取值的时候除了它的类型不能得到任何信息,甚至都不知道这个值存不存在!我再次陷入了思考,既然View需要的是Model中的属性,这不就等于需要Model自己么,那么为什么我们不能直接传递Modal自己呢?
MVP!
所以我再次改造了傅若愚前辈的协议,顺便把名字改的好辨认一点,原来的名字太容易出错了- -现在它是这样子的:

//视图使用的协议
protocol ViewType{
    func getData<M:ModelType>(data:M)
}
//数据使用的协议
protocol ModelType{
    func giveData<V:ViewType>(context:V)
}

不需要在扩展中写默认实现,因为传值是相互且确定的,所以方法一定会被实现。
模型是这样子的:

typealias DateViewModel = protocol<hasDate,ModelType>
struct Festival:DateViewModel{
    var date = ""
    var festivalName = ""
    func giveData<V : ViewType>(context: V) {
        context.getData(self)
    }
}

struct Event:DateViewModel{
    var date = ""
    var eventTitle = ""
    func giveData<V : ViewType>(context: V) {
        context.getData(self)
    }
}

视图:

class ShowedTableViewCell: UITableViewCell,ViewType {
    //用来展示事件主题或节日名称的Label
    @IBOutlet weak var MixLabel: UILabel!
    //用来展示日期的Label
    @IBOutlet weak var dateLabel: UILabel!

    func getData<M : ModelType>(data: M) {
        if let event = data as? Event{
            MixLabel.text = event.eventTitle
            dateLabel.text = event.date
            backgroundColor = UIColor.redColor()
        } else if let festival = data as? Festival{
            MixLabel.text = festival.festivalName
            dateLabel.text = festival.date
        }
    }
}

再次用苹果官方给出的异构判断方法解决异构,协议不同于类,没有那么多继承上的检查,所以使用as?是很高效的,最后只要给控制器中的代码换个名字就够了:

override func tableView(tableView: UITableView,forIndexPath: indexPath) as! ShowedTableViewCell
        dataList[indexPath.row].giveData(cell)
        return cell
    }

完成,运行效果:

如果还没有人发现这个架构,我想起个名字:MVP(Model-View-Protocol),有趣又贴切。

写在后面:
博主欠了欠身子,从吃完晚饭写到了半夜,一口气完成了本文,如果你喜欢我的文章并且得到了启发,欢迎转载传阅,注明出处即可。在Swift1.X时代我觉得Swift脆弱的像只小猫,Swift2.0之后我才突然发现苹果缔造的是一只野兽。通过不断锻炼自己面向协议编程的能力,我有了很多新的体会,想起了迪杰斯特拉老爷子著名的goto有害论,请准许我大胆预言一下,在面向协议的世界中AnyObject也是有害的,点到为止。

关于博主本人: 《Swift开发手册:技巧与实战》作者。国内计算机领域的某名校毕业,学习不差,初入社会,曾只身离校北漂妄图以退学抗议畸形的研究生教育,后心疼父母返校完成学业。从2014年底开始接触Swift后一发不可收拾,至今保持狂热,小人物大梦想,孜孜不倦致力于改善iOS编程体验。欢迎大家留言交流,力所能及之处,必倾囊相授。

版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 dio@foxmail.com 举报,一经查实,本站将立刻删除。

相关推荐


软件简介:蓝湖辅助工具,减少移动端开发中控件属性的复制和粘贴.待开发的功能:1.支持自动生成约束2.开发设置页面3.做一个浏览器插件,支持不需要下载整个工程,可即时操作当前蓝湖浏览页面4.支持Flutter语言模板生成5.支持更多平台,如Sketch等6.支持用户自定义语言模板
现实生活中,我们听到的声音都是时间连续的,我们称为这种信号叫模拟信号。模拟信号需要进行数字化以后才能在计算机中使用。目前我们在计算机上进行音频播放都需要依赖于音频文件。那么音频文件如何生成的呢?音频文件的生成过程是将声音信息采样、量化和编码产生的数字信号的过程,我们人耳所能听到的声音频率范围为(20Hz~20KHz),因此音频文件格式的最大带宽是20KHZ。根据奈奎斯特的理论,音频文件的采样率一般在40~50KHZ之间。奈奎斯特采样定律,又称香农采样定律。...............
前言最近在B站上看到一个漂亮的仙女姐姐跳舞视频,循环看了亿遍又亿遍,久久不能离开!看着小仙紫姐姐的蹦迪视频,除了一键三连还能做什么?突发奇想,能不能把舞蹈视频转成代码舞呢?说干就干,今天就手把手教大家如何把跳舞视频转成代码舞,跟着仙女姐姐一起蹦起来~视频来源:【紫颜】见过仙女蹦迪吗 【千盏】一、核心功能设计总体来说,我们需要分为以下几步完成:从B站上把小姐姐的视频下载下来对视频进行截取GIF,把截取的GIF通过ASCII Animator进行ASCII字符转换把转换的字符gif根据每
【Android App】实战项目之仿抖音的短视频分享App(附源码和演示视频 超详细必看)
前言这一篇博客应该是我花时间最多的一次了,从2022年1月底至2022年4月底。我已经将这篇博客的内容写为论文,上传至arxiv:https://arxiv.org/pdf/2204.10160.pdf欢迎大家指出我论文中的问题,特别是语法与用词问题在github上,我也上传了完整的项目:https://github.com/Whiffe/Custom-ava-dataset_Custom-Spatio-Temporally-Action-Video-Dataset关于自定义ava数据集,也是后台
因为我既对接过session、cookie,也对接过JWT,今年因为工作需要也对接了gtoken的2个版本,对这方面的理解还算深入。尤其是看到官方文档评论区又小伙伴表示看不懂,所以做了这期视频内容出来:视频在这里:本期内容对应B站的开源视频因为涉及的知识点比较多,视频内容比较长。如果你觉得看视频浪费时间,可以直接阅读源码:goframe v2版本集成gtokengoframe v1版本集成gtokengoframe v2版本集成jwtgoframe v2版本session登录官方调用示例文档jwt和sess
【Android App】实战项目之仿微信的私信和群聊App(附源码和演示视频 超详细必看)
用Android Studio的VideoView组件实现简单的本地视频播放器。本文将讲解如何使用Android视频播放器VideoView组件来播放本地视频和网络视频,实现起来还是比较简单的。VideoView组件的作用与ImageView类似,只是ImageView用于显示图片,VideoView用于播放视频。...
采用MATLAB对正弦信号,语音信号进行生成、采样和内插恢复,利用MATLAB工具箱对混杂噪声的音频信号进行滤波
随着移动互联网、云端存储等技术的快速发展,包含丰富信息的音频数据呈现几何级速率增长。这些海量数据在为人工分析带来困难的同时,也为音频认知、创新学习研究提供了数据基础。在本节中,我们通过构建生成模型来生成音频序列文件,从而进一步加深对序列数据处理问题的了解。
基于yolov5+deepsort+slowfast算法的视频实时行为检测。1. yolov5实现目标检测,确定目标坐标 2. deepsort实现目标跟踪,持续标注目标坐标 3. slowfast实现动作识别,并给出置信率 4. 用框持续框住目标,并将动作类别以及置信度显示在框上
数字电子钟设计本文主要完成数字电子钟的以下功能1、计时功能(24小时)2、秒表功能(一个按键实现开始暂停,另一个按键实现清零功能)3、闹钟功能(设置闹钟以及到时响10秒)4、校时功能5、其他功能(清零、加速、星期、八位数码管显示等)前排提示:前面几篇文章介绍过的内容就不详细介绍了,可以看我专栏的前几篇文章。PS.工程文件放在最后面总体设计本次设计主要是在前一篇文章 数字电子钟基本功能的实现 的基础上改编而成的,主要结构不变,分频器将50MHz分为较低的频率备用;dig_select
1.进入官网下载OBS stdioOpen Broadcaster Software | OBS (obsproject.com)2.下载一个插件,拓展OBS的虚拟摄像头功能链接:OBS 虚拟摄像头插件.zip_免费高速下载|百度网盘-分享无限制 (baidu.com)提取码:6656--来自百度网盘超级会员V1的分享**注意**该插件必须下载但OBS的根目录(应该是自动匹配了的)3.打开OBS,选中虚拟摄像头选择启用在底部添加一段视频录制选择下面,进行录制.
Meta公司在9月29日首次推出一款人工智能系统模型:Make-A-Video,可以从给定的文字提示生成短视频。基于**文本到图像生成技术的最新进展**,该技术旨在实现文本到视频的生成,可以仅用几个单词或几行文本生成异想天开、独一无二的视频,将无限的想象力带入生活
音频信号叠加噪声及滤波一、前言二、信号分析及加噪三、滤波去噪四、总结一、前言之前一直对硬件上的内容比较关注,但是可能是因为硬件方面的东西可能真的是比较杂,而且需要渗透的东西太多了,所以学习进展比较缓慢。因为也很少有单纯的硬件学习研究,总是会伴随着各种理论需要硬件做支撑,所以还是想要慢慢接触理论学习。但是之前总找不到切入点,不知道从哪里开始,就一直拖着。最近稍微接触了一点信号处理,就用这个当作切入点,开始接触理论学习。二、信号分析及加噪信号处理选用了matlab做工具,选了一个最简单的语音信号处理方
腾讯云 TRTC 实时音视频服务体验,从认识 TRTC 到 TRTC 的开发实践,Demo 演示& IM 服务搭建。
音乐音频分类技术能够基于音乐内容为音乐添加类别标签,在音乐资源的高效组织、检索和推荐等相关方面的研究和应用具有重要意义。传统的音乐分类方法大量使用了人工设计的声学特征,特征的设计需要音乐领域的知识,不同分类任务的特征往往并不通用。深度学习的出现给更好地解决音乐分类问题提供了新的思路,本文对基于深度学习的音乐音频分类方法进行了研究。首先将音乐的音频信号转换成声谱作为统一表示,避免了手工选取特征存在的问题,然后基于一维卷积构建了一种音乐分类模型。
C++知识精讲16 | 井字棋游戏(配资源+视频)【赋源码,双人对战】
本文主要讲解如何在Java中,使用FFmpeg进行视频的帧读取,并最终合并成Gif动态图。
在本篇博文中,我们谈及了 Swift 中 some、any 关键字以及主关联类型(primary associated types)的前世今生,并由浅及深用简明的示例向大家讲解了它们之间的奥秘玄机。