DaFileShare 开发笔记

一直以来都觉得电脑分享文件到手机上比较麻烦,试过不少软件,不是收费很贵就是不太方便,至于通过 USB 线——感觉这个更不方便。每次通过微信,QQ 之类的 IM 传了文件之后,就想要开发一个简单的文件分享工具。

DaFileShare 的代码很简单,分成两个部分:

  • Command line 的 http server 提供文件分享功能
  • Mac App 对其进行封装,提供拖放支持

开发 DaFileShare 之前没有做过 Mac App 的开发,也没有接触过 Swift,一切都是新学,所以记一下Mac App的开发过程,以及遇到的问题,至于 Command line 部分,没有什么好讲的。

第一个遇到的问题就是拖放

拖放

Macos 的拖放是通过剪切板实现的,首先App需要注册拖放的类型。第一个纠结的问题是:在哪里注册?

参考了网上的文章和 Apple 的开发文档,NSView 提供了拖放的支持,但是还需要进行一些处理。经过测试,awakFromNib 是一个很好的入口,哪怕用的是StoryBoard,一样会进入这个函数。

override func awakeFromNib() {
super.awakeFromNib()
self.registerForDraggedTypes([NSPasteboard.PasteboardType.fileURL])
}

因为新手的原因,不知道如何把 NSView 类和 StoryBoard,Google 了很久也没有任何发现,直到后来看到 identity inspector 下的 Custom Class,尝试着把 StoryBoard 中的 view 的类改为自己的类才算成功。

获取拖放的数据

可能是因为 Swift 的快速发展,网上一些文章已经过时,获取剪切板数据的方法在 Xcode 11.4.1 下已经失效了。下面是可以工作的代码:

override func performDragOperation(_ sender: NSDraggingInfo) -> Bool {
let pboard = sender.draggingPasteboard
let dragTypes = pboard.types! as NSArray
if dragTypes.contains(NSPasteboard.PasteboardType.fileURL) {
let files = pboard.propertyList(forType: NSPasteboard.PasteboardType(rawValue: "NSFilenamesPboardType")) as! [String]
let numberOfFiles = files.count
if numberOfFiles > 0 {
let filePath = files[0] as String
if let delegate = self.delegate {
NSLog("file path \(filePath)")
}
}
}

return true
}

关闭

第二个遇到的大问题是关闭:view 的关闭,窗口的关闭,App 的关闭。
由于想在退出 App 的时候进行一些处理,特别是 Command line 的 http server 进程的关闭,结果就遇到问题了。

同步关闭

首先,点击窗口上的关闭按钮后,只是关闭了窗口,但是 App 没有退出。再次点击 Dock 上的 App 图标,也没有像其他应用一样再次打开窗口,而是没有任何反应。
因为我不需要再打开窗口,只需要同步关闭,这个比较好处理,在 AppDelegate 中加一个函数即可:

func applicationShouldTerminateAfterLastWindowClosed(_ sender: NSApplication) -> Bool {
return true
}

不同的关闭方式

然后,只需要找个地方同一处理关闭事件就好了吧?——然而并不是!
正常的关闭方式有三种:关闭按钮,菜单Quit,Cmd+Q。三种方式,三个表现形式!!


关闭按钮

窗口上的关闭按钮可能是最正常的方式了。点击后,ViewControllerviewDidDisappear 被调用,但是如果你在 AppDelegate 有写
applicationShouldTerminate 函数,你会发现这个函数关闭没有调用到。

菜单 Quit

通过菜单——不管是 Dock 上的菜单还是菜单栏上——的Quit,applicationShouldTerminate 被调用到了,但是,viewDidDisappear 没有调用。

Cmd+Q

一句话,什么都不调用!!


通过查阅资料,是因为在”macOS 10.6”中引入了一个叫做Sudden Termination的机制来快速关闭 App。由于刚开始接触 macos 开发,不想一开始就钻研太深,还是尽早把 App 实现才是正事,所以只看了如何关闭这个机制。没错,这个Sudden Termination可以关闭,只需要一行代码:

func applicationDidFinishLaunching(_ aNotification: Notification) {
// Insert code here to initialize your application
ProcessInfo.processInfo.disableSuddenTermination()
}

至此,这三种关闭方式都会调用到applicationShouldTerminateviewDidDisappear,选一个地方处理就可以。我是在viewDidDisappear中进行处理,因为主要的逻辑代码在ViewController中。

异常关闭

异常关闭有两种:一种是程序出现 Exception 导致退出的,一种是被 kill 掉。

根据 Windows 上的经验,第一种应该有一个全局处理比如“unCaughtException” 这样的函数来处理,但是我没有尝试;第二种应该是需要捕获 signal 来处理,我尝试了但是没有成功,于是为了省事,把 kill signal屏蔽掉了:

func applicationDidFinishLaunching(_ aNotification: Notification) {
// Insert code here to initialize your application
ProcessInfo.processInfo.disableSuddenTermination()

signal(SIGTERM, SIG_IGN) // ignore the termination signal
}

开发环境

Xcode 11.4.1,Swift 5,macos 10.15.4

Download

你可以下载 DaFileShare