OSX 沙盒机制
OSX自从10.6系统开始引入沙盒机制,规定发布到Mac AppStore的应用,必须遵守沙盒约定。沙盒对应用访问的系统资源,硬件外设,文件,网络,XPC,都做了严格的限制,这样能防止恶意的App通过系统漏洞,攻击系统,获取控制权限,保证了OSX系统的安全。
沙盒相当于给每个App一个独立的空间,你只能在自己的小天地里面玩。要获取自己空间之外的资源必须获得授权。
应用沙盒化
应用开发完成提交到App Store时,必须进行沙盒化。切换到工程target设置Tab的Capabilities中,第一项就是App Sandbox开关,点击ON,表示应用使用沙盒。
我们来逐一看看沙盒的这些设置项.
-
Network:网络访问控制
Incoming Connections(Server) :应用做为Server对外提供HTTP,FTP等服务时需要打开
Outgoing Connections(Client):做为客户端,访问服务器时需要打开 -
Hardware:硬件资源控, Printing为必须勾选。App的默认第一个顶级菜单中有打印功能的子菜单。
Camera
Micophone
USB
Printing -
App Data:获取系统的联系人,位置,日历服务时需要打开
Contacts
Location
Calendar -
File Access:文件和用户目录的访问控制,分为禁止none,只读,读写3类
User Selected File:文档类应用或者需要用户选择打开某个文件时,需要选择合适的访问权限.
Downloads Folder
Pictures Folder
Music Folder
Movies Folder
上图的沙盒配置表示应用需要连接服务器获取数据;应用菜单中有打印功能的菜单;允许用户打开文件并进行读写操作。
如果应用中不需要的权限项,一律不要打开。否则App Review团队会拒绝你的应用上架.
沙盒配置信息存储
沙盒中每个需要访问权限的项都对应一个key,对应的value,YES 或 NO表示是否允许访问。
选择配置了沙盒的访问控制信息后,Xcode会自动保存到一个扩展名为.entitlements的plist文件中
应用打包时会对这个文件进行签名
.entitlements文件是一个plist文件,里面存储了sandbox的访问权限配置项信息.
<?xml version=1.0
encoding=UTF-8
?>
<!DOCTYPE plist PUBLIC -//Apple//DTD PLIST 1.0//EN
http://www.apple.com/DTDs/PropertyList-1.0.dtd
>
com.apple.security.app-sandbox
com.apple.security.files.user-selected.read-write
com.apple.security.network.client
com.apple.security.print
应用运行期间要获取某个权限时,系统都会通过.entitlements去检查应用是否有授权,如果没有就拒绝访问。
文件沙盒编程
我们通过一个简单的文件读写访问App来说明一下文件应用如何沙盒化。
App功能包括如下:
- 用户打开一个txt文本文件,将文件路径显示到TextField输入框,内容显示到TextView中。
- 用户可以修改文件,修改后可以保存。
- 应用下一次启动时能自动打开上次修改过的文件。
8.3.1 设计界面
拖放NSTextField,NSTextView, 2个NSButton到window界面上,调整大小位置如下图。
将NSTextField,NSTextView绑定到Outlet变量。
@property (weak) IBOutlet NSTextField *filePathField;
@property (unsafe_unretained) IBOutlet NSTextView *textView;
分别给2个按钮绑定事件响应方法。
- (IBAction)openFileAction:(id)sender
- (IBAction)saveFileAction:(id)sender
代码实现:将首次打开的文件路径保存到NSUserDefaults,每次App启动先检查NSUserDefaults中是否保存有文件路径,有的话,读取文件内容显示。
App启动处理
-
(void)applicationDidFinishLaunching:(NSNotification *)aNotification {
NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
NSString *path = [defaults objectForKey:kFilePath]; ;// @
/Users/zhaojw/Desktop/my.txt
;//如果文件路径不存在,不做任何处理
if(!path){
return ;
}NSError *error;
//文件路径显示
self.filePathField.stringValue = path;
NSURL *url = [NSURL fileURLWithPath:path];
//读取文件内容
NSString *string = [NSString stringWithContentsOfURL:url encoding:NSUTF8StringEncoding error:&error];
if(!error){
self.textView.string = string;
}
}
打开文件处理流程
-
(IBAction)openFileAction:(id)sender {
NSOpenPanel *openDlg = [NSOpenPanel openPanel];
openDlg.canChooseFiles = YES ;
openDlg.canChooseDirectories = YES;
openDlg.allowsMultipleSelection = YES;
openDlg.allowedFileTypes = @[@
txt
];NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
[openDlg beginWithCompletionHandler: NSInteger result{
if(result==NSFileHandlingPanelOKButton){ NSArray *fileURLs = [openDlg URLs]; for(NSURL *url in fileURLs) { NSError *error; //保存文件路径 [defaults setObject:url.path forKey:kFilePath]; [defaults synchronize]; self.filePathField.stringValue = url.path; //读取文件内容 NSString *string = [NSString stringWithContentsOfURL:url encoding:NSUTF8StringEncoding error:&error]; if(!error){ self.textView.string = string; } } }
}];
}
保存文件
-
(IBAction)saveFileAction:(id)sender {
NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
//获取文件路径
NSString *path = [defaults objectForKey:kFilePath]; ;if(!path){
return ;
}NSString *text = self.textView.string;
NSError *error;
NSURL *url = [NSURL fileURLWithPath:path];
//保存文件内容
[text writeToURL:url atomically:YES encoding:NSUTF8StringEncoding error:&error];if(error){
NSLog(@save file error %@
,error);
}
}
运行App,打开txt文件,修改保存。再次运行,能自动显示上次的文件内容,一切OK!
应用沙盒化:打开App Sandbox开关,勾选On;在File Access中User Selected File项中选择Read/Write文件读写权限.
运行App,打开txt文件,文件内容能正常显示。修改内容保存后退出App。再次运行App,发现上次打开的文件内容不能显示了!问题出在哪里呢 ?
Security-scoped bookmark
在File Access中User Selected File项中选择Read/Write文件读写权限的,
沙盒有个默认的规则:在App运行期间通过NSOpenPanel打开的任意位置的文件,把这个这个路径保存下来,后面都是可以直接用这个路径继续访问获取文件内容的。
但是App重新启动后,这个文件路径就不能直接访问了。要想永久的获得应用的Container目录之外的文件,这里必须讲一下Security-Scoped Bookmark。
Security-scoped bookmarks有2种:
An app-scoped bookmark:可以对应用中打开的文件或文件夹在以后永久性访问而不需要再次通过NSOpenPanel打开。这种bookmark方式使用的比较多。
A document-scoped bookmark:提供对特定的文档的永久访问权。可以理解为针对文档嵌套的一种权限模式。比如你开发一个能编辑ppt文档的应用,里面嵌入了视频文件,图片文件连接。那么下次打开这个ppt文档时就能直接访问这些文件而不需要在通过NSOpenPanel打开获得授权。
保存打开的文件URL的bookmark
获取到URL的bookmarkData存储到NSUserDefaults
NSData *bookmarkData = [url bookmarkDataWithOptions:NSURLBookmarkCreationWithSecurityScope includingResourceValuesForKeys:nil relativeToURL:nil error:NULL];
应用启动时通过URL的bookmark获取文件授权
通过bookmark数据解析获取授权的NSURL,并且执行startAccessingSecurityScopedResource方法得到访问权限。
执行block回调完成相关内容读取后,执行stopAccessingSecurityScopedResource停止授权。
NSURL *allowedUrl = [NSURL URLByResolvingBookmarkData:bookmarkData options:NSURLBookmarkResolutionWithSecurityScope|NSURLBookmarkResolutionWithoutUI relativeToURL:nil bookmarkDataIsStale:&bookmarkDataIsStale error:NULL];
@try {
[allowedUrl startAccessingSecurityScopedResource];
block();
} @finally {
[allowedUrl stopAccessingSecurityScopedResource];
}
在打开一次文件,让应用保存文件的bookmark。关闭应用在执行一次,应用起动后你会发现文件的数据被自动读取出来了。