Zhihao's Studio.

上架沙盒化应用,重启应用后保持文件持久访问性指南

Word count: 1,327 / Reading time: 5 min
2018/08/24 Share

前言

Invisibility Cloak上架Mac App Store了,在此过程中,遇到了一些困难,在这里记录一下又一次与苹果审核团队撕逼的过程。

开发这款软件的初衷将xxx.mp4/xxx.avi/xxx.mkv在白天藏起来,免得被别人发现。 在Apple store上发现了一款类似的文件隐藏软件Secret Folder,售价128元,而且卖的不错。我觉得这个应用蛮有用(坏笑),而且实现起来难度不是特别大,于是就做了一个相同功能的软件,并将它上架了。

功能上,我额外支持了drag&drop添加文件的操作,比Secret Folder的体验更丝滑一些。定价上,仅为Secret Folder的一折,12RMB,现在特价一周,仅售6元,有将某些文件在白天或演讲时藏起来的朋友们别错过。 苹果商店下载地址




Download on the app store

上架过程中遇到的坑

不得不说,mac应用的审核比iOS应用的审核快多了,短短两天内审核了四次,这或许也从一定程度上反映了两者提交应用数量的差别。

第一次拒绝我的原因很奇葩,说我的名字起的不好,AVHider难道有些露骨?

第二次拒绝我是因为我勾选了Downloads/Picture/Music/Movie folder的读写权限,隐藏文件嘛,能Access的路径当然是越多越好了,但苹果不这么认为,他认为不该勾选的权限绝不能勾。

沙盒中的File Access

第三次被拒其实有点懵,因为我在《macOS开发基础教程》这本书里看到说如果要上架,一定要勾上sandbox和printing,但苹果审核团队以printing对我没用为由,拒绝了我。或许是我理解有误,也有可能是作者的笔误。

《macOS开发基础教程》一书中关于上架前注意事项的描述

第四次拒绝的原因是为了解决所有路径文件可用性时,我采取了一种粗暴的手法,直接在.entitlements文件里增加了根目录,即所有文件

1
2
3
4
<key>com.apple.security.temporary-exception.files.absolute-path.read-write</key>
<array>
<string>/</string>
</array>

苹果让我重新找其他方法上架,于是我找到了本文需要重点介绍的:com.apple.security.files.bookmarks.app-scope.

沙盒访问机制中的File Access

在iOS和macOS中,每个应用都有一个专属存储空间,它就是沙盒(sandbox)。就macOS来说,苹果规定从macOS X 10.6开始,所有发布到Mac App Store的应用都必须遵从沙盒约定,主要是考虑到第三方恶意应用对系统进行攻击,从安全性角度出发, 对应用访问的系统资源,硬件外设,文件,网络,XPC等做了严格的限制。

引入沙盒机制前后APP对系统资源、数据开放程度对比

在没有上架之前,我没有考虑过这个问题,同样,如果你的应用不准备上架,可以不开启Sandbox,随意访问mac上的文件和数据。

Sandbox中的File Access

经过第二次被拒之后,我只勾上了用户选择的文件这个Type的读/写权限。但我很快就发现了问题,在应用没重启之前,一切正常运行,但是应用退出再重新打开之后,之前的文件就失去了读写的权限。

我们需要找到一种策略,让app记住我们曾经有过某个文件路径的读写权限,这就是app-scoped bookmark。翻译成中文是书签,也很好理解,就好像我们读一本书,某天读累了,夹一张书签在上次看过的地方,下次我们再读的时候,就可以快速回到这一页,而不必从头开始一页页的翻阅了。

既然要记录下来,就涉及到持久化的问题,由于书签比较轻量级,因此我选用了相对简单的NSUserDefaults. 网上有OC版本的代码,我用Swift进行了改写,代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
func saveBookmarks(_ filePath : String){
let userDefault = UserDefaults.standard
let folderPath = NSURL(fileURLWithPath: filePath)
print(folderPath.absoluteString!)
do {
let bookmark = try folderPath.bookmarkData(options: .securityScopeAllowOnlyReadAccess, includingResourceValuesForKeys: nil, relativeTo: nil)
userDefault.set(bookmark, forKey: folderPath.absoluteString!)
} catch let error as NSError {
print("Set Bookmark Fails: \(error.description)")
}
}
func readBookmarks(_ filePath : String){
let userDefault = UserDefaults.standard
if let bookmarkData = userDefault.object(forKey: filePath) as? NSData {
do {
let url = try NSURL.init(resolvingBookmarkData: bookmarkData as Data, options: .withoutUI, relativeTo: nil, bookmarkDataIsStale: nil)
url.startAccessingSecurityScopedResource()
} catch let error as NSError {
print("Bookmark Access Fails: \(error.description)")
}
}
}

分为保存书签和读取书签两个函数,考虑到key的唯一性,为了避免冲突我以文件路径为key存bookmark。每次应用启动之后,都根据之前的路径索取这些路径的读写权限。当然这里也是有注意点的,主要是当文件路径中包含数字、字母之外的字符(如中文字符、特殊字符等)时,需要注意转码

上架后思考

上架的第一天,在中国区付费应用总排行榜出乎我的意料的排到了第121名。这件事说明了: 1.小而美的应用是有市场的;2.用户对价格还是很敏感的;3.不少用户愿意为他们需要的应用付费。

CATALOG
  1. 1. 前言
  2. 2. 上架过程中遇到的坑
  3. 3. 沙盒访问机制中的File Access
  4. 4. 上架后思考