在Swift中应用Grand Central Dispatch 下

from:http://www.cocoachina.com/swift/20150130/11054.html


4 3430

本文由loveltyoic(博客)翻译自raywenderlich,原文:Grand Central Dispatch Tutorial for Swift: Part 1/2

欢迎来到本GCD教程的第二同时也是最终部分!

在第一部分中,你学到了并发,线程以及GCD的工作原理。通过使用dispatch_barrrier和dispatch_sync,你做到了让PhotoManager单例在读写照片时是线程安全的。除此之外,你用到dispatch_after来提示用户,优化了用户体验。还有,使用dispatch_async异步执行CPU密集型任务,从而为视图控制器初始化过程减负。

如果你跟着教程做,现在可以从第一部分的示例工程继续。如果你没有完成第一部分或不想再用你的工程,可以下载第一部分的完成文件

是时候进一步探索GCD了!

纠正过早出现的弹窗

你可能注意到,当你通过 Le Internet 选项添加照片时,会有提示框在图片下载完成之前就弹出,如下图:

错误在于 PhotoManager 里的 downloadPhotosWithCompletion:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
funcdownloadPhotosWithCompletion(completion:BatchPhotoDownloadingCompletionClosure?){
var storedError:NSError!
for address in [OverlyAttachedGirlfriendURLString,
SuccessKidURLString,
LotsOfFacesURLString]{
leturl=NSURL(string:address)
letphoto=DownloadPhoto(url:url!){
image,error in
if error!=nil{
storedError=error
}
}
PhotoManager.sharedManager.addPhoto(photo)
}
letcompletion=completion{
completion(error:storedError)
}
}

这里在方法的最后调用completion闭包——你会想当然的认为所有图片都下载完了。但不幸的是,在此时无法保证。

DownloadPhoto类的实例方法从一个URL下载图片并且不等下载完成就立即退出。换言之,downloadPhotosWithCompletion在最后调用completion闭包,就好像其中的所有方法都在顺序执行,并且在每个方法完成后才执行下一个。

然而,DownloadPhoto(url:)是异步并且立即返回的——所以目前的方式不能正常工作。

downloadPhotosWithCompletion应该在所有图片下载任务都完成后再调用自己的completion闭包。问题是:你怎么监视并发的异步事件呢?你不知道它们何时完成,以何种顺序。

也许你可以用多个Bool值来追踪下载情况,但那不容易扩展。而且坦白讲,那是很丑陋的代码。

幸运的是,dispatch groups就是专为监视多个异步任务的完成情况而设计的。

调度组(Dispatch Groups)

调度组在一组任务都完成后会发出通知。这些任务可以是异步或同步的,甚至可以分布在不同的队列。调度组还可以通过同步或异步的方式来通知。因为任务在不同的队列中,disptch_group_t实例用来追踪队列中的不同任务。

在组内所有事件都完成时,GCD API提供了两种方式发送通知。

第一种是dispatch_group_wait,它会阻塞当前进程,直到所有任务都完成或是等待超时。这正是我们的例子中需要的方式。

打开 PhotoManager.swift ,替换downloadPhotosWithCompletion:

19
20
21
22
23
24
25
26
27
28
29
dispatch_async(GlobalUserInitiatedQueue){ //1
storedError:NSError!
downloadGroup=dispatch_group_create() //2
LotsOfFacesURLString]
{
leturl=NSURL(string:address)
dispatch_group_enter(downloadGroup) //3
letphoto=DownloadPhoto(url:url!){
in
leterror=error{
storedError=error
}
dispatch_group_leave(downloadGroup) //4
}
PhotoManager.sharedManager.addPhoto(photo)
}
dispatch_group_wait(downloadGroup,DISPATCH_TIME_FOREVER) //5
dispatch_async(GlobalMainQueue){ //6
letcompletion=completion{ //7
completion(error:storedError)
}
}
}
}

逐一来看注释:

  • 因为使用dispatch_group_wait阻塞了当前进程,要用dispatch_async将整个方法放到后台队列,才能保证主线程不被阻塞。

  • 创建一个调度组,作用好比未完成任务的计数器。

  • dispatch_group_enter通知调度组一个任务已经开始。你必须保证dispatch_group_enter和dispatch_group_leave是成对调用的,否则程序会崩溃。

  • 通知任务已经完成。再一次,这里保持进和出相匹配。

  • dispatch_group_wait等待所有任务都完成直到超时。如果在任务完成前就超时了,函数会返回一个非零值。可以通过返回值来判断是否等待超时;不过,这里你用DISPATCH_TIME_FOREVER来表示一直等待。这意味着,它会永远等待!没关系,因为图片总是会下载完的。

  • 此时,你可以保证所有图片任务都完成或是超时了。接下来在主队列中加入完成闭包。闭包晚些时候会在主线程中执行。

  • 执行闭包。

运行app,下载几张图片,留意你的app是如何表现的。

Note:如果网速太快以至于分辨不出何时执行的闭包,你可以修改设备的设置。在 Setting 中的Developer Section 。打开 Network Link Conditioner,选择“Very Bad Network”。

如果在模拟器上,用工具变更网速。这是你武器库中一个很好的工具,它让你清楚在不佳的网络下你的app会发生什么。

这个方案目前不错,但最好能避免阻塞进程。你下一步的工作是重写这个方法来异步通知下载完成。

在学习下一个调度组的用法前,先看看怎样在不同的队列类型下使用调度组。

  • 自定义顺序队列:好选择。当一组任务完成时用它发送通知。

  • 主队列(顺序):在当前情景下是不错的选择。但你要谨慎地在主队列中使用,因为同步等待所有任务会阻塞主线程。然而,当一个需要较长时间的任务(比如网络请求)完成时,异步更新UI是很好的选择。

  • 并发队列:好选择。用于调度组和通知。

调度组,再来一次

做的不错,但是异步调度到另一个队列然后用 dispatch_group_wait 阻塞还是有一些笨拙。还有另一种方式…

在 PhotoManager.swift 中找到downloadPhotosWithCompletion并替换之:

27
downloadGroup=dispatch_group_create()
dispatch_group_enter(downloadGroup)
dispatch_group_leave(downloadGroup)
dispatch_group_notify(downloadGroup,GlobalMainQueue){ //2
letcompletion=completion{
completion(error:storedError)
}
}
}

异步方法是如何工作的:

  • 新的实现不需要把方法放进dispatch_async中,因为你并没有阻塞主线程。

  • dispatch_group_notify异步执行闭包。当调度组内没有剩余任务的时候闭包才执行。同样要指明在哪个队列中执行闭包。当下,你需要在主队列中执行闭包。

这是更优雅的方法,并且不会阻塞任何进程。

并发过多带来的危险

通过支配这些新工具,你应该将每件事都线程化,对吗?

看看PhotoManager中的downloadPhotosWithCompletion。你会发现通过for循环下载了三张图片。现在来看看能否通过并发执行for循环来提速。

是时候请出dispatch_apply了。

dispatch_apply像for循环一样,只不过它会并发地执行循环过程。这个函数是同步的,所以像普通的for循环一样,dispatch_apply在所有工作都完成后才返回。

要注意循环的最佳次数,如果有太多循环但每个循环内只有很小的工作量,那么额外的开销会抹杀掉并发带来的好处。 步进 (striding)可以帮助到你。它让你在每次循环中做多件工作。

什么时候用dispatch_apply合适?

  • 自定义顺序队列:在顺序队列中使用dispatch_apply完全无意义;它的效果和for循环一样。

  • 主队列(顺序):理由同上,用for循环就可以了。

  • 并发队列:明智之选,尤其是你需要追踪任务进度时。

替换downloadPhotosWithCompletion如下:

downloadGroup=dispatch_group_create()
letaddresses=[OverlyAttachedGirlfriendURLString,
LotsOfFacesURLString]
dispatch_apply(UInt(addresses.count),GlobalUserInitiatedQueue){
i in
letindex=Int(i)
letaddress=addresses[index]
leturl=NSURL(string:address)
dispatch_group_enter(downloadGroup)
letphoto=DownloadPhoto(url:url!){
in
leterror=error{
storedError=error
dispatch_group_leave(downloadGroup)
PhotoManager.sharedManager.addPhoto(photo)
}
letcompletion=completion{
completion(error:storedError)
}

现在你的循环可以并发执行了;调用 dispatch_apply 时,第一个参数是循环的次数,第二个参数是执行任务的队列,第三个参数是闭包。

尽管你的代码在添加图片时是线程安全的,但是图片的顺序取决于线程完成的顺序。

运行app,用 Le Internet 添加一些图片,发现不同了吗?

在真机上运行新的代码会发现 些许 的速度提升。但是这值得吗?

实际上,在这里并不值得这么做。原因如下:

  • 你很可能因为并行而花费了比for循环更多的开销。你应该结合合适的步长对 非常大 的集合使用dispatch_apply。

  • 开发app的时间有限——不要花时间过早优化。如果你想优化,那么就优化那些值得优化的东西。用Instruments测试app以找到最耗时间的方法。如何使用Instruments。

  • 一般说来,代码优化会让你的代码变得更复杂。你要确定带来的好处值得你增加复杂性。

记住,不要痴迷于优化。否则只会让你自己为难,也让看你代码的人抓狂。

取消调度块

iOS 8 和 OS X Yosemite引入了 调度对象块 (dispatch block object)。它们实现起来就像对闭包再包装一层。调度对象块可以做到很多事情,比如为队列中的对象设置QoS等级来决定优先级,但最显著的能力是可以取消块的执行。要明白对象块只有在轮到它执行之前才可以取消(一旦开始执行就不能取消了)。

为了说明这个问题,首先用 Le Internet 下载一些图片,然后取消它们。替换 PhotoManager.swift 中的downloadPhotosWithCompletion:

29
30
31
32
33
34
35
36
37
38
39
40
41
42
letdownloadGroup=dispatch_group_create()
addresses=[OverlyAttachedGirlfriendURLString,monospace!important; font-size:1em!important; min-height:auto!important">addresses+=addresses+addresses //1
blocks:[dispatch_block_t]=[] //2
0..<addresses.count{
letblock=dispatch_block_create(DISPATCH_BLOCK_INHERIT_QOS_CLASS){ //3
letindex=Int(i)
letaddress=addresses[index]
leturl=NSURL(string:address)
letphoto=DownloadPhoto(url:url!){
in
leterror=error{
storedError=error
dispatch_group_leave(downloadGroup)
PhotoManager.sharedManager.addPhoto(photo)
}
blocks.append(block)
dispatch_async(GlobalMainQueue,block) //4
block blocks[3..<blocks.count]{ //5
letcancel=arc4random_uniform(2) //6
cancel==1{
dispatch_block_cancel(block) //7
//8
}
}
letcompletion=completion{
completion(error:storedError)
}
}
}
  • 扩展addresses数组,将每个地址复制3份。

  • 这个数组用来保存接下来创建的对象块。

  • dispatch_block_create创建一个对象块。第一个参数是一个表明了块特征的标志。此处的标志让块从它进入的队列那里继承QoS等级。第二个参数是闭包形式的块定义。

  • 块被异步的调度到全局主队列。这里用全局主队列是因为它是一个顺序队列,可以方便我们取消对象块。当前代码已经在主线程中执行着,所以你可以保证下载任务将在此之后才执行(也就是这个downloadPhotosWithCompletion返回后才轮到下载任务执行)。

  • 取数组中第三个到结尾的部分。

  • arc4random_uniform会随机返回一个0到上界之间(不含上界)的整数。以2为上界会得到0或1,像投硬币一样。

  • 如果随机数是1,则取消块。前提是,块还在队列中并且没开始。块在执行的过程中是不可以取消的。

  • 因为所有块都加入调度组了,不要忘记移除被取消的那些块。

运行,从 Le Internet 添加图片。你会看到app下载3张图片,以及随机数量的额外图片。那些没下载的图片是因为在加入队列 后 被取消了。这是一个刻意设计的例子,但是很好的演示了怎样使用调度对象块以及如何取消它。

调度对象块能做更多事情,别忘了查看文档。

五花八门的GCD趣用

等等!还有更多!下面展示一些常规用途之外的功能。尽管你不会经常使用这些工具,但他们可能在特定情况下非常有用。

测试异步代码

这听起来很疯狂,但是你知道Xcode拥有测试功能吗?:]我知道,有时我喜欢假装它不存在,但是编写和运行测试对构建复杂的代码很重要。

Xcode中的测试运行在XCTestCase的子类之下,它会运行所有以test开头的方法。测试跑在主线程下,所以你可以认为测试是顺序执行的。

一旦给定的测试方法返回了,XCTest 会认为这个测试完成了而去做下一个测试。这就是说,在下一个测试执行过程中,前一个测试中的异步代码也在继续执行。

网路请求通常是异步的,因为你不想阻塞主线程。一旦测试方法返回,测试也就结束了,因此很难对网络请求做测试。

我们简单看一下两种普遍的测试异步代码的方法:信号量(semaphores)和 期望(expectations)。

信号量

信号量是一个古老学院派的线程概念,它是由谦逊的Edsger W. Dijkstra提出的。信号量是很复杂的话题,因为它建立在错综复杂的操作系统函数之上。

如果你想了解更多信号量的知识,查阅细说信号量原理。如果你是学院派,有一个用到了信号量的经典软件开发问题叫做哲学家进餐问题

信号量让你控制多个消费者对有限资源的获取。例如,如果你创建一个信号量来控制拥有2个资源的资源池,那么同一时刻最多有两个线程可以进入临界区。其它也想使用资源的线程必须在FIFO队列中等待。

打开 GooglyPuffTests.swift 并替换掉 downloadImageURLWithString:

16
funcdownloadImageURLWithString(urlString:String){
leturl=NSURL(string:urlString)
letsemaphore=dispatch_semaphore_create(0) //1
letphoto=DownloadPhoto(url:url!){
in
leterror=error{
XCTFail( "\(urlString)failed.\(error.localizedDescription)" )
}
dispatch_semaphore_signal(semaphore) //2
}
lettimeout=dispatch_time(DISPATCH_TIME_NOW,DefaultTimeoutLengthInNanoSeconds)
dispatch_semaphore_wait(semaphore,timeout)!=0{ //3
"\(urlString)timedout" )
}
}

以上代码中信号量的工作原理:
1. 创建信号量。参数表明信号量起始值。这个值代表了起始阶段可以获取信号量的线程数目(增加信号量就是发信号,用0做初始值代表当前没有线程可以获取信号量)。 2. 在完成闭包中,你告诉信号量不再需要资源。这会使信号量增加,同时给其他等待资源的任务发信号,通知当前信号量可用。
3. 等待信号量并设置超时时间。这个调用会阻塞当前进程直到收到信号。非0返回表示等待已超时。在这种情况下,测试失败,因为网络请求不应该超过10秒——相当合理的假设!
译者注:说下我的理解:首先创建了信号量,但此时因为信号量是0,没有线程可以获取它,注释3中对信号量的等待会阻塞。只有在图片下载好了以后,才会发送一个信号量,那么注释3对信号量的获取就成功了,并退出等待。但如果图片下载失败呢?就不会调用注释2这句触发信号的语句,那么注释3就会等待超时,从而测试失败。)

Product/Test 或 cmd+U 运行测试。测试应该成功。

断掉网络连接并再次测试;如果在真机测试,请开启飞行模式。如果在模拟器上,直接断网就好了。测试在10秒后会返回失败的结果。很好,起作用了!

这是相当微不足道的测试,但是如果你和服务端团队一起工作,这些基础测试可以避免一些涉及网络问题的无端指责。

期望(expectations)

XCTest框架提供了另一种使用 期望 来测试异步代码的方法。这种特性让你首先设置你的期望——你希望发生的事——然后再开始异步任务。接下来测试会一直等待,直到异步任务将期望标记为 已完成 。

替换 GooglyPuffTests.swift 中的downloadImageURLWithString:

18
letdownloadExpectation=expectationWithDescription( "Imagedownloadedfrom\(urlString)" ) downloadExpectation.fulfill() waitForExpectationsWithTimeout(10){ error XCTFail(error.localizedDescription)
}
}

工作原理:
1. 用expectationWithDescription生成期望。测试会在日志上显示其中的字符串参数,所以请描述你期望发生的事。
2. 在异步执行的闭包中调用fulfill来标记期望已达成。
3. 调用线程用waitForExpectationsWithTimeout等待期望达成。如果等待超时会视为出错。

运行测试。结果和使用信号量没什么不同,但使用XCTest框架是更清晰易读的方案。

调度源(Dispatch Sources)

GCD中存在一个特别有趣的特性叫调度源,它是一个包含底层功能的百宝囊,帮助你响应或监控Unix信号,文件描述符(file descriptors),Mach端口,VFS Nodes,以及其他复杂的东西。所有这些都超出了本教程的范围,但是你可以尝试着使用一下调度源对象。

第一次使用调度源的用户可能会迷失其中,所以你首先要理解dispatch_source_create的工作原理。下面是创建它的函数原型:

5
funcdispatch_source_create(
type:dispatch_source_type_t,
handle:UInt,monospace!important; font-size:1em!important; min-height:auto!important">mask:UInt,monospace!important; font-size:1em!important; min-height:auto!important">queue:dispatch_queue_t!)->dispatch_source_t!

第一个参数type: dispatch_source_type_t是最重要的参数,因为它描述了句柄(handle)和掩码(mask)参数。你需要查看Xcode文档来弄清楚dispatch_source_type_t的参数有哪些可选项。

这里你会监视DISPATCH_SOURCE_TYPE_SIGNAL。如文档所述:

调度源监控当前进程的信号。句柄(handle)是信号数字(int)。掩码(mask)没用到(传0)。

Unix信号列表可以从signal.h找到。在顶部有一串#define。在这些信号列表中,你将要监控SIGSTOP信号。这个信号会在进程接收到不可抗拒的挂起指令时被发送。这个信号与你用LLDB debugger调试程序时发送的信号相同。

进入 PhotoCollectionViewController.swift ,在viewDidLoad附近添加下面的代码。你需要为类添加两个私有属性,并在viewDidLoad的开始处添加段代码,在调用superclass和ALAssetLibrary之间:

24
#ifDEBUG
private signalSource:dispatch_source_t!
signalOnceToken=dispatch_once_t()
#endif
overridefuncviewDidLoad(){
super .viewDidLoad()
#ifDEBUG//1
dispatch_once(&signalOnceToken){ //2
letqueue=dispatch_get_main_queue()
self.signalSource=dispatch_source_create(DISPATCH_SOURCE_TYPE_SIGNAL,
UInt(SIGSTOP),queue) letsource=self.signalSource{ //4
dispatch_source_set_event_handler(source){ //5
NSLog( "Hi,Iam:\(self.description)" )
}
dispatch_resume(source) //6
}
#endif
//Theotherstuff
}

这段代码有点难懂,因此逐个注释来讲解:
1. 最好只在DEBUG模式下编译这段代码,因为这可能让不怀好意者洞见很多信息。:] 在 Project Settings –> Build Settings –> Swift Compiler – Custom Flags –> Other Swift Flags –> Debug 下添加 -D DEBUG 。
2. 用dispatch_once一次性初始化调度源。
3. 初始化signalSource变量。你指明对信号感兴趣并且提供SIGSTOP做第二个参数。除此之外,你用主队列处理接收到的事件——稍后你会发现为什么。
4. 如果参数错误,调度源对象不会被创建。因此,你应该在使用它之前确保调度源是有效的。
5. dispatch_source_set_event_handler注册了一个事件处理闭包,当你接收到监控的信号时会调用这个闭包。
6. 默认情况下,所有调度源在开始都处于挂起状态。当你想监视事件时,必须让源对象继续执行。

运行app;暂停调试器然后立即恢复。检查控制台(console),你会看到类似下面的信息:

1
2014-08-1212:24:00.514GooglyPuff[24985:5481978]Hi,Iam:

你的app现在可以感知到调试(debugging-aware)了!这真棒,但在现实中怎样用它呢?

你可以用它调试一个对象并在恢复app时展示数据;你也可以自定义一些安全逻辑来保护app,当恶意攻击者在你的程序上附着调试器的时候。

有趣的想法是把这个方法当做堆栈追踪工具,来找到你想要在调试器中修改的对象。

设想一下这样的场景。当你意外地停掉调试器时,你很难处在期望的栈帧上。而现在你可以在任意时刻停止调试器并让代码执行到你期望的位置。这很有用,当你想执行一段从调试器很难达到的代码。试一试!

在viewDidLoad中的NSLog语句处设置断点。暂停调试器,然后再开始;app会命中你刚刚设置的断点。现在你已经深入到PhotoCollectionViewController方法中了。现在你可以随心所欲地使用PhotoCollectionViewController实例了。多么便捷!

注意:如果在调试器中你不知道哪个线程是哪个,来看一下。主线程总是第一个,libdispatch,GCD的协调器是第二个。剩下的线程要看硬件当时在做什么样的工作。

在调试器中,输入:

poself.navigationItem.prompt="WOOT!"

然后继续执行app。你会看到如下所示:

通过这个方法,你可以更新UI,探查类的属性,甚至执行方法——无需重启app来进入特定的工作流状态。很巧妙。

下一步?

下载最终的工程

我不想重提,但是你真的应该看一下怎样使用Instruments。如果你想优化app,绝对需要这个。Instruments可以概述程序中哪些代码相对其它代码执行更久。如果你想知道代码实际的执行时间,很可能需要一些自制的解决方案。

同时学习如何在Swift中使用NSOperations和NSOperationQueue,一种基于GCD的并发技术。实际上,这是使用GCD的最佳实践。NSOperations提供更好的控制,处理最多的并发操作,在牺牲一定速度的情况下更加面向对象。

记住,除非你有特别的理由深入底层,你应该始终尝试并坚持使用更高层的API。只在你想学习更多或做一些非常非常“有趣”的事时才进入到Apple的“暗黑艺术”(dark art)中探险。:]

祝你好运,尽情欢乐!

版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 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)的前世今生,并由浅及深用简明的示例向大家讲解了它们之间的奥秘玄机。