iOS新手用swift写一个macos打包工具 一键打包到指定位置

栏目: Swift · 发布时间: 6年前

内容简介:打包出的app运行如下图,使用磁盘压缩成dmg,直接打开package.dmg即可概述整个流程就是,通过recoverAndSet()函数恢复之前保存数据,start()检查路径后会替换内部package.sh的动态路径,然后起一个线程创建Process(),通过Pipe()监控脚本执行输出,捕获异常

使用dmg安装macos app

打包出的app运行如下图,使用磁盘压缩成dmg,直接打开package.dmg即可

iOS新手用swift写一个macos打包 <a href='https://www.codercto.com/tool.html'>工具</a>  一键打包到指定位置

配置完毕后点击start运行打包脚本,生成ipa到指定目录

该项目用swift开发,项目和dmg保存在

https://github.com/gwh111/tes...

流程解析

概述整个流程就是,通过recoverAndSet()函数恢复之前保存数据,start()检查路径后会替换内部package.sh的动态路径,然后起一个线程创建Process(),通过Pipe()监控脚本执行输出,捕获异常

1.recoverAndSet()

通过UserDefaults简单地记住上次打包的路径,下次写了新代码后即可点击start立即打包

恢复时把值传给控件

func recoverAndSet() {
        
        let objs:[Any]=[projectPath,projectName,exportOptionsPath,ipaPath]
        let names:[NSString]=["projectPath","projectName","exportOptionsPath","ipaPath"]
        
        for i in 0...3{
            print(i)
            let key=names[i]
            let obj=objs[i] as! NSTextField
            let v=UserDefaults.standard.value(forKey: key as String)
            if (v == nil){
                continue
            }
            obj.stringValue=(v as? String)!
        }
        let ps=UserDefaults.standard.value(forKey: "projectName" as String)
        if (ps==nil){
        }else{
            projectName.stringValue=(ps as? String)!;
        }
        let dr=UserDefaults.standard.value(forKey: "debugRelease")
        if (dr==nil){
        }else{
            debugRelease.selectedSegment=dr as! Int;
        }
        
        debugRelease.action = #selector(segmentControlChanged(segmentControl:))
    }

2.selectPath()

通过NSOpenPanel()创建打开文档面板对象,选择文件目录,而不是手动输入

通常项目路径名和项目名称是一致的,这里使用了path.components(separatedBy:"/")将路径分割自动取工程名

@IBAction func selectPath(_ sender: NSButton) {
        let tag=sender.tag
        print(tag)
        
        // 1. 创建打开文档面板对象
        let openPanel = NSOpenPanel()
        // 2. 设置确认按钮文字
        openPanel.prompt = "Select"
        // 3. 设置禁止选择文件
        openPanel.canChooseFiles = true
        if tag==0||tag==2 {
            openPanel.canChooseFiles = false
        }
        // 4. 设置可以选择目录
        openPanel.canChooseDirectories = true
        if tag==1 {
            openPanel.canChooseDirectories = false
            openPanel.allowedFileTypes=["plist"]
        }
        // 5. 弹出面板框
        openPanel.beginSheetModal(for: self.view.window!) { (result) in
            // 6. 选择确认按钮
            if result == NSApplication.ModalResponse.OK {
                // 7. 获取选择的路径
                let path=openPanel.urls[0].absoluteString.removingPercentEncoding!
                if tag==0 {
                    self.projectPath.stringValue=path
                    let array=path.components(separatedBy:"/")
                    if array.count>1{
                        let name=array[array.count-2]
                        print(array)
                        print(name as Any)
                        self.projectName.stringValue=name
                    }
                }else if tag==1 {
                    self.exportOptionsPath.stringValue=path
                }else{
                    self.ipaPath.stringValue=path
                }
            
                let names:[NSString]=["projectPath","exportOptionsPath","ipaPath"]
                UserDefaults.standard.setValue(openPanel.url?.path, forKey: names[tag] as String)
                UserDefaults.standard.setValue(self.projectName.stringValue, forKey: "projectName")
                UserDefaults.standard.synchronize()
                
//                self.savePath.stringValue = (openPanel.directoryURL?.path)!
//                // 8. 保存用户选择路径(为了可以在其他地方有权限访问这个路径,需要对用户选择的路径进行保存)
//                UserDefaults.standard.setValue(openPanel.url?.path, forKey: kSelectedFilePath)
//                UserDefaults.standard.synchronize()
            }
            // 9. 恢复按钮状态
//            sender.state = NSOffState
        }
    }

3.start()

通过str.replacingOccurrences(of: "file://", with: "")将路径和sh里的路径替换

通过DispatchQueue.global(qos: .default).async获取Concurrent Dispatch Queue并开启Process()

在处理完的terminationHandler里回到主线程更新UI

@IBAction func start(_ sender: Any) {
        
        guard projectPath.stringValue != "" else {
            self.logTextField.stringValue="工程目录不能为空";
            return
        }
        guard projectName.stringValue != "" else {
            self.logTextField.stringValue="工程名不能为空";
            return
        }
        guard exportOptionsPath.stringValue != "" else {
            self.logTextField.stringValue="exportOptions不能为空 xcode生成ipa文件夹中包含";
            return
        }
        guard ipaPath.stringValue != "" else {
            self.logTextField.stringValue="输出ipa目录不能为空";
            return
        }
        
        
        var str1="abc"
        let str2="abc"
        if str1==str2{
            print("same")
        }
        
        //save
        let objs:[Any]=[projectPath,exportOptionsPath,ipaPath]
        let names:[NSString]=["projectPath","exportOptionsPath","ipaPath"]
        for i in 0...2{
            let obj=objs[i] as! NSTextField
            UserDefaults.standard.setValue(obj.stringValue, forKey: names[i] as String)
        }
        UserDefaults.standard.setValue(self.projectName.stringValue, forKey: "projectName")
        UserDefaults.standard.setValue(self.debugRelease.selectedSegment, forKey: "debugRelease")
        UserDefaults.standard.synchronize()
        
//        self.showInfoTextView.string="abc";
        
        if isLoadingRepo {
            self.logTextField.stringValue="正在执行上一个任务";
            return
        }// 如果正在执行,则返回
        isLoadingRepo = true   // 设置正在执行标记
        
        let projectStr=self.projectPath.stringValue
        let nameStr=self.projectName.stringValue
        let plistStr=self.exportOptionsPath.stringValue
        let ipaStr=self.ipaPath.stringValue
        
        let returnData = Bundle.main.path(forResource: "package", ofType: "sh")
        let data = NSData.init(contentsOfFile: returnData!)
        var str =  NSString(data:data! as Data, encoding: String.Encoding.utf8.rawValue)! as String
        if debugRelease.selectedSegment==0 {
            str = str.replacingOccurrences(of: "DEBUG_RELEASE", with: "debug")
        }else{
            str = str.replacingOccurrences(of: "DEBUG_RELEASE", with: "release")
        }
        str = str.replacingOccurrences(of: "NAME_PROJECT", with: nameStr)
        str = str.replacingOccurrences(of: "PATH_PROJECT", with: projectStr)
        str = str.replacingOccurrences(of: "PATH_PLIST", with: plistStr)
        str = str.replacingOccurrences(of: "PATH_IPA", with: ipaStr)
        str = str.replacingOccurrences(of: "file://", with: "")
        print("返回的数据:\(str)");
        
        self.logTextField.stringValue="执行中。。。";
        
        DispatchQueue.global(qos: .default).async {
            
//            str="aaaabc"
//            str = str.replacingOccurrences(of: "ab", with: "dd")
            
//            print(self.projectPath.stringValue)
//            print(self.exportOptionsPath.stringValue)
//            print(self.ipaPath.stringValue)
            
            
            let task = Process()     // 创建NSTask对象
            // 设置task
            task.launchPath = "/bin/bash"    // 执行路径(这里是需要执行命令的绝对路径)
            // 设置执行的具体命令
            task.arguments = ["-c",str]
            
            task.terminationHandler = { proce in              // 执行结束的闭包(回调)
                self.isLoadingRepo = false    // 恢复执行标记
                
                //5. 在主线程处理UI
                DispatchQueue.main.async(execute: {
                    self.logTextField.stringValue="执行完毕";
                })
            }
            
            self.captureStandardOutputAndRouteToTextView(task)
            task.launch()                // 开启执行
            task.waitUntilExit()       // 阻塞直到执行完毕
            
        }
        
    }

4.captureStandardOutputAndRouteToTextView()

对执行脚本的日志监控

为了看到脚本报错或执行成功提示,使用Pipe()监控 NSPipe一般是两个线程之间进行通信使用的

在osx 系统中 ,沙盒有个规则:在App运行期间通过NSOpenPanel用户手动打开的任意位置的文件,把这个这个路径保存下来,后面都是可以直接用这个路径继续访问文件,但当App退出后再次运行,这个路径默认是不可以访问的

fileprivate func captureStandardOutputAndRouteToTextView(_ task:Process) {
        //1. 设置标准输出管道
        outputPipe = Pipe()
        task.standardOutput = outputPipe
        
        //2. 在后台线程等待数据和通知
        outputPipe.fileHandleForReading.waitForDataInBackgroundAndNotify()
        
        //3. 接受到通知消息
        
        observe=NotificationCenter.default.addObserver(forName: NSNotification.Name.NSFileHandleDataAvailable, object: outputPipe.fileHandleForReading , queue: nil) { notification in
            
            //4. 获取管道数据 转为字符串
            let output = self.outputPipe.fileHandleForReading.availableData
            let outputString = String(data: output, encoding: String.Encoding.utf8) ?? ""
            if outputString != ""{
                //5. 在主线程处理UI
                DispatchQueue.main.async {
                    
                    if self.isLoadingRepo == false {
                        let previousOutput = self.showInfoTextView.string
                        let nextOutput = previousOutput + "\n" + outputString
                        self.showInfoTextView.string = nextOutput
                        // 滚动到可视位置
                        let range = NSRange(location:nextOutput.utf8CString.count,length:0)
                        self.showInfoTextView.scrollRangeToVisible(range)
                        
                        if self.observe==nil {
                            return
                        }
                        NotificationCenter.default.removeObserver(self.observe!)
                        
                        return
                    }else{
                        let previousOutput = self.showInfoTextView.string
                        var nextOutput = previousOutput + "\n" + outputString as String
                        if nextOutput.count>5000 {
                            nextOutput=String(nextOutput.suffix(1000));
                        }
                        // 滚动到可视位置
                        let range = NSRange(location:nextOutput.utf8CString.count,length:0)
                        self.showInfoTextView.scrollRangeToVisible(range)
                        self.showInfoTextView.string = nextOutput
                    }
                }
            }
            
            if self.isLoadingRepo == false {
                return
            }
            //6. 继续等待新数据和通知
            self.outputPipe.fileHandleForReading.waitForDataInBackgroundAndNotify()
        }
    }

-exportOptions.Plist 常用文件内容格式

compileBitcode

  • For non-App Store exports, should Xcode re-compile the app from bitcode? Defaults to YES

embedOnDemandResourcesAssetPacksInBundle

  • For non-App Store exports, if the app uses On Demand Resources and this is YES, asset packs are embedded in the app bundle so that the app can be tested without a server to host asset packs. Defaults to YES unless onDemandResourcesAssetPacksBaseURL is specified

method

  • Describes how Xcode should export the archive. Available options: app-store, ad-hoc, package, enterprise, development, and developer-id. The list of options varies based on the type of archive. Defaults to development

teamID

  • The Developer Portal team to use for this export. Defaults to the team used to build the archive

thinning

  • For non-App Store exports, should Xcode thin the package for one or more device variants? Available options: <none> (Xcode produces a non-thinned universal app), <thin-for-all-variants> (Xcode produces a universal app and all available thinned variants), or a model identifier for a specific device (e.g. "iPhone7,1"). Defaults to <none>

uploadBitcode

  • For App Store exports, should the package include bitcode? Defaults to YES

uploadSymbols

  • For App Store exports, should the package include symbols? Defaults to YES

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持 码农网

查看所有标签

猜你喜欢:

本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们

Big Java Late Objects

Big Java Late Objects

Horstmann, Cay S. / 2012-2 / 896.00元

The introductory programming course is difficult. Many students fail to succeed or have trouble in the course because they don't understand the material and do not practice programming sufficiently. ......一起来看看 《Big Java Late Objects》 这本书的介绍吧!

URL 编码/解码
URL 编码/解码

URL 编码/解码

UNIX 时间戳转换
UNIX 时间戳转换

UNIX 时间戳转换

RGB HSV 转换
RGB HSV 转换

RGB HSV 互转工具