肇鑫的技术博客

业精于勤,荒于嬉

AVMovie始终显示视频时长为0问题的分析、解决

推特仅支持0.5秒到140秒之内长度的视频,因此上传前要先检验视频的长度。测试时,我发现,Finder分享的视频时长始终为0。

我查询了一下,最初看到的说法是,AVAssetAVMovie的父类)的加载是异步的,不能够立即获得时长,而需要用键值观察的方法,注册通知来观察duration这个属性何时变化。昨晚看到这里,我就睡了。

今天再看,发现有协议AVAsynchronousKeyValueLoading,可以用来异步处理键值。方法loadValuesAsynchronously(forKeys:completionHandler:)会在加载完成后执行completionHandler。这样就不用我们自己手动注册键值观察的通知了。

但是,我测试这个方法不好用。Finder传来的视频,无论是否使用这个异步方法,时长都始终为0。

我不用Finder,采用照片应用进行调试,发现无论是不是用这个异步方法,都能获得正确的时长。

结合上一篇Finder分享视频到共享扩展的一些限制的小结。我产生了新的想法,经过测试,我的猜想是正确的。

那就是,AVMovie在读取URL的时候,会正确获得时长,如果是Data的话,则不可以。想测试也很简单,把URL变成Data,再创建AVMovie;或者把Data写入到URL,再创建AVMovie。就可以证实了。

我不清楚这个是苹果API的错误,还是特性。但是这个至少是没有注明的。

Finder分享视频到共享扩展的一些限制的小结

通常,我们使用NSItemProviderloadItem(forTypeIdentifier:options:completionHandler:)方法来加载对应类型的对象。但是Finder提供的信息有问题。不能使用推荐的类型来获得想要的对象。

比如分享mp4文件时,Finder提供的NSItemProvider是这样的。

<NSItemProvider: 0x6000000bfa40> {types = (
    "public.file-url",
    "public.url",
    "public.mpeg-4"
)}

你用"public.mpeg-4"作为类型来获取数据,不能获得视频文件对应的URL。这是很罕见的。因为大部分情况下,比如通过照片应用来获得视频时,都可以通过这种方式获得URL。

Finder提供给"public.mpeg-4"实际上是一个Data。但是这个Data用在这里实际上是有问题的。苹果不应该这么做。为什么呢?因为相比于其它类型的文件,比如图片或者声音之类的,视频文件可以是很大的。如果你要使用这个视频文件,那么通过Data来加载,就会占据大量的内存。实际上,苹果自己就在加载视频的文档中写到,不要全部加载到内存。

apple data warning

因为存在这个限制,Finder实际上传来的Data可能是不完整的。比如我尝试分享一个4GB的视频时,实际获得的Data的大小是415MB,并不完整。

结论,既然Data既占内存,又可能不完整,为什么Finder还要提供呢?只能说,苹果做错了。

临时的处理办法是这样的,先用"public.mpeg-4"尝试获取URL,如果获取失败。那么就看NSItemProvider是否支持URL。如果支持就再用"public.url"尝试获取URL。如果还不行,那就认为是没法处理。为什么不反过来呢?因为大部分的情况,都是第一种方式就能直接处理。Finder这么做实际上是错误的。但是为了适应它,才需要调整我们的代码。

为什么说只有远程通知才是最吼的!(续)

这篇文章会根据我的体验和理解进行增补和删减

如果是新增,那么内容会直接添加。
如果是修改或删减。我会将原有的段落标记加上删除线。然后再写上新段落。
如果出现多次修改和删减。我会添加版本的提示。如果没有提示,那么相邻的删除线的段落,就是同一个版本的。

简析远程推送服务的解决方案

苹果不承诺远程通知的可靠性。所以远程通知在实际使用中,经常有延迟,甚至丢失的情况。这时,你有两个选择。PushKit或调教苹果的apns服务器。

苹果不承诺远程通知的可靠性。并且由于我们在使用远程通知时,往往会使用第三方的推送服务。因此,一旦通知出现延迟甚至丢失,那么既有可能是苹果的问题,也可能是第三方服务的问题。就目前我自己的感受来说,苹果的服务要比第三方服务更可靠一些。

如果你的程序对于实时性要求较高。那么可以使用收费的第三方服务,这样可以保证你的推送的质量。或者使用自己的推送服务器。更进一步,你还可以使用PushKit

PushKit

PushKit是苹果伴随iOS 8推出的特殊通知,它针对的是一般远程通知的缺点。最初的设计目标是提供给VoIP的app以实时的通知。iOS 9的时候,苹果又增加了对于手表表盘上小部件的推送。

一般远程通知的缺陷

  1. 苹果不保证远程通知一定能到达。苹果会根据它的apns服务器的情况,动态来处理你的推送请求。也就是说,你的请求可能被执行,也可能会延迟,甚至被忽略(忘记)。
  2. 苹果的apns服务器最多只保留最后一条的推送请求。比如用户设备离线的情况下,你发出了多条通知,那么最终用户至多只能收到最后一条。
  3. 如果用户通过呼出任务管理,并且上滑移除app。那么该app的远程推送,将不能唤醒该app到后台,直到用户手动打开该app,或重启手机并解锁进入。
  4. 远程通知注册的token,隔一段时间会自动变化。这时如果还按照旧的申请,就会收不到消息。

苹果的处理方式

  1. 针对问题1。苹果保证PushKit通知会即时到达。看其他人写的评测,基本上延迟只有1秒。因为VoIP是类似打电话的功能,这个延迟高了,服务就没法用了。
  2. 用户设备可以在收到远程通知后,向自己的服务器,苹果称之为Provider,进行反馈。由于通知是即时的,服务器在“向要求苹果apns的服务器发出通知”的命令发出后,如果在特定时间没有收到用户设备的反馈,就可以认定用户的设备是离线的。这之后如果还需要给用户设备发通知,就可以先缓存起来,而不要发送到苹果的apns服务器。服务器可以在确认用户设备启动之后,再一次性将缓存的通知发出,这样,用户就可以得到在设备离线期间缓存的所有信息。
  3. PushKit的通知仍可以唤醒程序。
  4. PushKit的token是不变的。

PushKit的缺点

  • 实现比远程通知更为复杂。除了远程通知的要实现之外,还要向Provider做出反馈。
  • 国内没有现成的第三方服务,需要使用国外的第三方服务。或者自己编写Provider

调教苹果的apns服务器

如果你不想实现PushKit,那么你可以采用下面的比较脏的方式来调教苹果的apns服务器。

我的程序每小时通知一次设备。但是我发现,这个通知有时候会丢失,每天大概会丢个两回左右。

更进一步研究,我发现,如果通知没有及时到达,那么我可以在一段时间之后,补发一个通知。由于我没有实现PushKitProvider服务器不知道上一个通知到达了没有,因此我必须每小时发送两次通知。iOS端如果收到了第一个通知,那么可以做下标记,第二个通知到来时,就直接忽略,不要向Apple Watch传送就可以了。

这个调教的方式的原理是这样的。当apns服务器繁忙时,我猜测苹果采用了一个典型的美国式公平的策略。即大家都丢一些,而不是规定一个资源上限,占用资源多的丢,占用少的不丢。这导致一个现象,就是爱哭的孩子有糖吃。比如我的程序,原本每小时只需要一次推送。但是由于苹果的apns服务器会偶发的抛弃其中的1-2个通知。那么我为了保证通知的必须到达,就需要每小时发两次。实际上这导致苹果apns服务器的负载增加了一倍。但是我没有办法,因为只有这样,才能保证我的程序每小时至少刷新一次。

这个方法相对于PushKit,优点是实现简单。缺点也很明显,不考虑苹果apns的服务器负载增加的损失。只考虑开发者自己这边,最主要的缺点是通知到来的时间不稳定。既可能在第一次的时间到来,也可能是第二次。如果是对于通知到来时间,有特定要求的程序,那就不能用这种方式,而必须使用PushKit

系列文章

为什么说只有远程通知才是最吼的!
为什么说只有远程通知才是最吼的!(续)
为什么说只有远程通知才是最吼的!(续+1)