教你制作 --- MAC OS 启动盘制作工具

2017-08-02 10:37:40来源:http://www.cocoachina.com/apple/20170801/20062.html作者:CocoaChina人点击

分享

本文为CocoaChina网友 品位生活 投稿



说起启动盘制作工具,目前国外的开发人员已经开发有《DiskMaker》 和 《Install Disk Creator》这两个软件。


DiskMaker下载地址:


http://diskmakerx.com/


Install Disk Creator 下载地址:


https://macdaddy.io/install-disk-creator/


我在写代码的时候还不知道有这两个工具,后来知道了有这两个工具了,所以本篇文章更多的是讲解下我当初的基本制作过程,顺便把源码分享给大家。


一、起因


由于本人经常折腾系统,所以很容易就搞坏了系统,然后每次都需要做系统盘,然后重做系统,很是麻烦。大概是要做一下步骤:


进入MAC 终端程序


输入"sudo"


输入"空格"


拖文件“createinstallmedia”到终端(文件位置在安装程序》右键显示包文件》Contents》Resources里)(程序自动空格,若无空格请自行空格)


然后输入 "--volume"


输入"空格"


拖你准备的盘符为 "disk"盘或分区到终端(程序自动空格,若无空格请自行空格)


输入"--applicationpath"


输入"空格"


拖OS X安装程序到终端(程序自动空格,若无空格请自行空格)


然后输入 "--nointeraction"


按“return”(即回车)


输入系统密码(密码不会显示,直接回车)


等待启动盘制作完成。


然后我思考是否能做一个程序能够更方便的制作启动盘的一个工具,然后就开始行动了。


二、方案 与 思考


制作之前,我考虑到基本上需要做到的基本功能要有:


确定主要核心命令执行的方案。


做到能够调用系统终端命令。


做到能够使用Root权限调用系统终端命令。


做到能够自动识别获取到当前的磁盘盘符。


做到能够区分系统盘符和移动磁盘盘符。


做到能够监听磁盘重命名,插入,和卸载的变化。


做到能够监听制作的进度。


做到能够监听基本的制作过程的错误处理。


设计一个磁盘处理相关的类,用于外部调用处理。


主要核心命令执行的三个方案:


(1)使用NSTask 执行核心命令

NSTask是MAC OS X用来执行系统终端命令的一个类,所以使用NSTask可以执行系统终端命令,但是NSTask有一个缺点,在我所知的是,无法以Root权限进行执行命令,但是正好启动盘制作工具 createinstallmedia 是必须需要Root权限执行的,所以说这个方案不考虑了。


(2)使用 AppleScript 制作脚本,OS 开发程序调用

NSAppleScript 是可以执行AppleScript脚本的 一个类,用它可以执行AppleScript脚本,在AppleScript中,可以获得Root权限进行执行命令。


DiskMaker 就是用AppleScript脚本实现的核心功能,可以下载此软件,然后右键--显示包内容,然后找到源码.



(3)使用STPrivilegedTask执行终端命令,支持Root权限

可以通过 AuthorizationRef 来获取权限,执行命令,STPrivilegedTask就是对AuthorizationRef的封装,类似于NStask,只不过可以以Root权限执行命令。


最终我选择了使用 STPrivilegedTask 来实现核心命令执行。


三、行动


UI界面:

首先,我使用Xcode 创建了Mac OS 工程,然后


在Main.storyboard调整出了基本界面。


添加了 Combox 控件用于展示磁盘列表。


添加了按钮,用于触发进行开始制作。


添加了NSView实现NSDraggingDestination代理方法,用于接收用户拖拽的系统文件。


核心功能讲解:

一些比较简单的基本控件的使用我就不详细说了,例如拖拽获取文件,combox控件的使用等等。


(1)获取监听磁盘装载与卸载

想要获取和监听磁盘装载与卸载,需要用过苹果的一个 DiskArbitration 框架,这个框架提供了可支持注册监听磁盘的装载与卸载,和信息修改等回调事件。


下面直接贴上来有关代码


+ (void)registerDiskNotice{//创建一个新的会话
DASessionRef session = DASessionCreate(kCFAllocatorDefault);//注册一个回调函数被称为磁盘时已经探测。
DARegisterDiskMountApprovalCallback(session,NULL,hello_diskmount,NULL);//注册一个回调函数的调用,每当一个卷卸载。
DARegisterDiskUnmountApprovalCallback(session, NULL, goodbye_diskmount, NULL);//注册一个回调函数称为每当一个磁盘已经出现了。
DARegisterDiskAppearedCallback(session, NULL, hello_disk, NULL);//注册一个回调函数称为每当一个磁盘已经消失了。
DARegisterDiskDisappearedCallback(session, NULL, goodbye_disk, NULL);//注册磁盘信息变化回调
DARegisterDiskDescriptionChangedCallback(session, NULL, NULL, DiskDescription, NULL);//运行循环的调度会话。
DASessionScheduleWithRunLoop(session,CFRunLoopGetCurrent(), kCFRunLoopDefaultMode);//注销一个核心基础对象。
CFRelease(session);
}void DiskDescription( DADiskRef disk,CFArrayRef keys,void *context){// 磁盘休息修改后,通过代理,告诉comBox要刷新视图
[selfObjc.comBoxdelegate diskDidChangeState:selfObjc.diskDict];
}void hello_disk(DADiskRef disk, void *context){
[selfObjc diskChange:DiskChangeTypeAppear disk:disk];
}void goodbye_disk(DADiskRef disk, void *context){
[selfObjc diskChange:DiskChangeTypeDismiss disk:disk];/// 磁盘拔出或异常中断后,发出通知,用于处理中断的通知。
[[NSNotificationCenter defaultCenter]postNotificationName:DiskDisappeared object:(__bridge id _Nullable)(disk)];
}DADissenterRef hello_diskmount(DADiskRef disk, void *context){
[selfObjc diskChange:DiskChangeTypeAppear disk:disk];return NULL;
}DADissenterRef goodbye_diskmount(DADiskRef disk, void *context){
[selfObjc diskChange:DiskChangeTypeDismiss disk:disk];return NULL;
}
(2)区分系统磁盘和移动磁盘

为了能够区分系统磁盘和移动磁盘,肯定是先要获取磁盘的相关信息,磁盘出现后的,监听回调里面有一个DADiskRef 类型的参数 ,这个DADiskRef变量里面存储了磁盘相关的信息,通过 DADiskCopyDescription 函数,把disk对象当做参数,调用后可以获取到磁盘描述信息,类型可转换为字典,如下图:



通过打印出来的信息可看到不少相关有用的内容,例如:


DAMediaBSDName:


用于唯一标识的磁盘,如果磁盘有重名那么BSDName也是唯一的。


DAVolumeName:


是卷名,也是有用的。因为检测可以检测到磁盘和卷,一个磁盘可以有多个卷,磁盘的话,是没有DAVolumeName的,可以用于更准确的筛选。


DAMediaSize:


磁盘的大小


DADeviceProtocol:


这个是最主要的,如果是移动磁盘,DADeviceProtocol 就会是USB,所以可通过这个计算。


过滤系统磁盘 和 移动磁盘的代码:


相关宏定义:



/// 统一处理磁盘的装载与卸载- (void)diskChange:(DiskChangeType)state disk:(DADiskRef)disk{
NSLog(@"%@",Disk_Des);// 过滤掉系统盘符,和小于8G的盘符
if (![VolumeName length] || ![BSDName length] || ![self checkDiskType:disk]) {return;
}// 用于记录磁盘,为了给combox传递数据
switch (state) {case DiskChangeTypeAppear:
[self.diskDict setValue:(__bridge id _Nullable)(disk) forKey:BSDName];break;case DiskChangeTypeDismiss:
[self.diskDict removeObjectForKey:BSDName];default:
break;
}
[self.comBoxdelegate diskDidChangeState:self.diskDict];
}/// 检查是不是移动盘符,和是否小于8G- (BOOL)checkDiskType:(DADiskRef)disk{if (![DiskProtocol isEqualToString:@"USB"] || !VolumeName || DiskSize < 8.0) {return NO;
}return YES;
}
(3)监听磁盘信息的变化,例如重命名

注册这个监听即可


//注册磁盘信息变化回调
DARegisterDiskDescriptionChangedCallback(session, NULL, NULL, DiskDescription, NULL);
(4)监听制作进度

这里说一个思路,制作启动盘的时候,createinstallmedia会自动格式化磁盘,然后把指定的系统盘文件,慢慢拷贝进移动盘中,所以监听制作进度,我这里使用的是时钟,不断获取磁盘中系统文件的大小,如果达到和原文件一样的大小,说明就是制作进度完成了。


(5)调用系统终端命令

主要是看文档,然后看下STPrivilegedTask 怎么使用即可,剩下就是设计逻辑,和实现方式。


我大概说下我这边的:


首先是根据各个外部的变量,进行拼接成字符串,然后传递进DSTask自定义处理的一个类中,再进行分割参数,然后设置参数,和启动工具,然后执行launch命令即可。


- (void)runShell{if (!self.codeArray.count) { return; }
[self resetTask];// 配置运行参数
NSString *code = self.codeArray[0];NSMutableArray *parameter = [code componentsSeparatedByString:@" "].mutableCopy;for (int i =0; i
其它说明:

还有很多细节没有说到,例如判断磁盘是否装载,制作过程中断处理,combox和工具类的建立和处理,逻辑之间的数据交换,空格文件名和磁盘空格的处理,等等这些,详细的可去看源码。


另外STPrivilegedTask 默认有个BUG,中文不支持,会计算错误长度。


需要修改这一块:
for (int i = 0; i < numberOfArguments; i++) {NSString *argString = arguments[i];NSUInteger stringLength = [argString length];
args[i] = malloc((stringLength + 1) * sizeof(char));
snprintf(args[i], stringLength + 1, "%s", [argString fileSystemRepresentation]);
}
为:for (int i = 0; i < numberOfArguments; i++) {NSString *argString = arguments[i];
args[i] = [argString UTF8String];
}
使用注意:

在使用STPrivilegedTask,我发现无法监听到执行过程中内部的一些错误回调,所以说只有尽量避免了这种情况的出现,如果使用中发现制作不成功,或者点击制作后很久没有变化,可能内部命令提示出现错误,可手动格式化磁盘一次再进行制作,或者拔插磁盘后重新进行制作。


源码地址: https://github.com/DaSens/StartupDisk


最新文章

123

最新摄影

微信扫一扫

第七城市微信公众平台