在线观看不卡亚洲电影_亚洲妓女99综合网_91青青青亚洲娱乐在线观看_日韩无码高清综合久久

鍍金池/ 教程/ iOS/ 在沙盒中編寫腳本
與四軸無人機的通訊
在沙盒中編寫腳本
結構體和值類型
深入理解 CocoaPods
UICollectionView + UIKit 力學
NSString 與 Unicode
代碼簽名探析
測試
架構
第二期-并發(fā)編程
Metal
自定義控件
iOS 中的行為
行為驅動開發(fā)
Collection View 動畫
截圖測試
MVVM 介紹
使 Mac 應用數(shù)據(jù)腳本化
一個完整的 Core Data 應用
插件
字符串
為 iOS 建立 Travis CI
先進的自動布局工具箱
動畫
為 iOS 7 重新設計 App
XPC
從 NSURLConnection 到 NSURLSession
Core Data 網(wǎng)絡應用實例
GPU 加速下的圖像處理
自定義 Core Data 遷移
子類
與調試器共舞 - LLDB 的華爾茲
圖片格式
并發(fā)編程:API 及挑戰(zhàn)
IP,TCP 和 HTTP
動畫解釋
響應式 Android 應用
初識 TextKit
客戶端
View-Layer 協(xié)作
回到 Mac
Android
Core Image 介紹
自定義 Formatters
Scene Kit
調試
項目介紹
Swift 的強大之處
測試并發(fā)程序
Android 通知中心
調試:案例學習
從 UIKit 到 AppKit
iOS 7 : 隱藏技巧和變通之道
安全
底層并發(fā) API
消息傳遞機制
更輕量的 View Controllers
用 SQLite 和 FMDB 替代 Core Data
字符串解析
終身學習的一代人
視頻
Playground 快速原型制作
Omni 內(nèi)部
同步數(shù)據(jù)
設計優(yōu)雅的移動游戲
繪制像素到屏幕上
相機與照片
音頻 API 一覽
交互式動畫
常見的后臺實踐
糟糕的測試
避免濫用單例
數(shù)據(jù)模型和模型對象
Core Data
字符串本地化
View Controller 轉場
照片框架
響應式視圖
Square Register 中的擴張
DTrace
基礎集合類
視頻工具箱和硬件加速
字符串渲染
讓東西變得不那么糟
游戲中的多點互聯(lián)
iCloud 和 Core Data
Views
虛擬音域 - 聲音設計的藝術
導航應用
線程安全類的設計
置換測試: Mock, Stub 和其他
Build 工具
KVC 和 KVO
Core Image 和視頻
Android Intents
在 iOS 上捕獲視頻
四軸無人機項目
Mach-O 可執(zhí)行文件
UI 測試
值對象
活動追蹤
依賴注入
Swift
項目管理
整潔的 Table View 代碼
Swift 方法的多面性
為什么今天安全仍然重要
Core Data 概述
Foundation
Swift 的函數(shù)式 API
iOS 7 的多任務
自定義 Collection View 布局
測試 View Controllers
訪談
收據(jù)驗證
數(shù)據(jù)同步
自定義 ViewController 容器轉場
游戲
調試核對清單
View Controller 容器
學無止境
XCTest 測試實戰(zhàn)
iOS 7
Layer 中自定義屬性的動畫
第一期-更輕量的 View Controllers
精通 iCloud 文檔存儲
代碼審查的藝術:Dropbox 的故事
GPU 加速下的圖像視覺
Artsy
照片擴展
理解 Scroll Views
使用 VIPER 構建 iOS 應用
Android 中的 SQLite 數(shù)據(jù)庫支持
Fetch 請求
導入大數(shù)據(jù)集
iOS 開發(fā)者的 Android 第一課
iOS 上的相機捕捉
語言標簽
同步案例學習
依賴注入和注解,為什么 Java 比你想象的要好
編譯器
基于 OpenCV 的人臉識別
玩轉字符串
相機工作原理
Build 過程

在沙盒中編寫腳本

Mac 應用間的腳本在桌面生態(tài)系統(tǒng)中已經(jīng)存在很長時日了。它最早于 1993 年 10 月作為 System 7 的一部分,用來為像 QuarkXPress 這樣的出版相關應用創(chuàng)建復雜的工作流,以方便使用。從那以后,很多應用通過使用腳本字典來支持 AppleScript (Brent 的文章向你展示了如何實現(xiàn))。在這篇文章里,我會解釋如何使用腳本字典里的命令和對象來與其他的應用進行通訊。

但在我們開始之前,我們需要看看最近在 Mac 平臺發(fā)生的一些事情。在 2010 年底 Mac App Store 開張之后,Apple 宣布所有開發(fā)者所提交的應用必須在 2011 年 11 月之前跑在沙盒里。這個最后期限被推遲了數(shù)次,最后他在 2012 年的六月一號成為了現(xiàn)實。

想讓 Mac 應用運行在沙盒中實屬不易,相信這一點從不斷被推遲的最后期限中你就可以尋得蛛絲馬跡。與之相對,iOS 應用一直就需要運行在沙盒里。一些資深的開發(fā)者會明白,一個安全的環(huán)境同時也意味著他們的應用要做重大的改變。我從一個 Apple 的負責安全的工程師那里聽到過一句話:“我們正在試圖把打開的潘多拉盒子關上,這可不是一件容易事兒?!?/p>

其中一個主要的挑戰(zhàn)就是要處理那些使用了 AppleScript 的應用。很多本來很簡單的功能突然就變得很困難了,有些事情甚至完全不可能完成。造成這么讓人沮喪的主要原因是應用不再能任意地通過腳本來控制其他的應用了。為了安全考慮,有非常多的理由可以證明允許這么做并不是什么好主意。但是從開發(fā)者或者顧客的視角來看的話,就是很多東西用不了了。

起初,Apple 通過在應用的權限聲明里引入并許可'暫時例外'的方式來幫助過渡。這些例外可以允許應用維持本來已經(jīng)應該喪失了的功能。但是也正如名字所示,它們中的很多特例也正在消失,因為在最近版本的 OS X 中,一些作為替代手段的控制別的應用的方法已經(jīng)可以使用了。

這個教程將向您展示現(xiàn)在使用 AppleScript 來控制別的應用的最佳方式。我也會告訴您一些小技巧以幫助您和您的用戶用最小的努力就架設起 AppleScript。

第一步

你需要學習的第一件事是如何在你自己的應用里跑 AppleScript。通常來說,最困難的部分是寫出 AppleScript 的代碼。來看看吧:

on chockify(inputString)
    set resultString to ""

    repeat with inputStringCharacter in inputString
        set asciiValue to (ASCII number inputStringCharacter)
        if (asciiValue > 96 and asciiValue < 123) then
            set resultString to resultString & (ASCII character (asciiValue - 32))
        else
            if ((asciiValue > 64 and asciiValue < 91) or (asciiValue = 32)) then
                set resultString to resultString & inputStringCharacter
            else
                if (asciiValue > 47 and asciiValue < 58) then
                    set numberStrings to {"ZERO", "ONE", "TWO", "THREE", "FOR", "FIVE", "SIX", "SEVEN", "EIGHT", "NINE"}
                    set itemIndex to asciiValue - 47
                    set numberString to item itemIndex of numberStrings
                    set resultString to resultString & numberString & " "
                else
                    if (asciiValue = 33) then
                        set resultString to resultString & " DUH"
                    else
                        if (asciiValue = 63) then
                            set resultString to resultString & " IF YOU KNOW WHAT I MEAN"
                        end if
                    end if
                end if
            end if
        end if
    end repeat

    resultString
end chockify

在我看來,AppleScript 最大的優(yōu)點并不在于它的語法,也不是它在處理字符串上的能力,雖然這些都做得超級棒。

當開發(fā)像這樣的腳本時,我通常會去查閱 AppleScript 腳本指南。好消息是與其他應用進行通訊的腳本一般來說都很短,也容易理解。AppleScript 可以被想做一種傳送的機制,而不是一種處理環(huán)境。上面展示的腳本就很典型。

一旦你寫好了腳本并且完成了測試,你就可以回到你的 Objective-C 的舒適的小窩了。你可能會寫下的第一行代碼會將你通過時光旅行帶回到 Carbon 年代:

#import <Carbon/Carbon.h> // for AppleScript definitions

放輕松,你不需要做一些比如將整個 Carbon 框架導入項目的瘋狂舉動。你所需要的只是 Carbon.h,因為它有關于所有的 AppleEvent 的定義。記住,這些代碼已經(jīng)在那兒超過 20 年了!

在有了定義之后,你就可以創(chuàng)建事件描述符 (event descriptor) 了。這是可以在你的腳本和應用之間互相傳遞的一個數(shù)據(jù)塊?,F(xiàn)在的話,你可以把它想象成一個封裝好的會去執(zhí)行某個事件的目標,一個將被調用的函數(shù),以及這個函數(shù)的參數(shù)。在這里我們?yōu)樯厦娴?"chockify" 創(chuàng)建了事件描述符,使用一個 NSString 作為參數(shù):

- (NSAppleEventDescriptor *)chockifyEventDescriptorWithString:(NSString *)inputString
{
    // parameter
    NSAppleEventDescriptor *parameter = [NSAppleEventDescriptor descriptorWithString:inputString];
    NSAppleEventDescriptor *parameters = [NSAppleEventDescriptor listDescriptor];
    [parameters insertDescriptor:parameter atIndex:1]; // you have to love a language with indices that start at 1 instead of 0

    // target
    ProcessSerialNumber psn = {0, kCurrentProcess};
    NSAppleEventDescriptor *target = [NSAppleEventDescriptor descriptorWithDescriptorType:typeProcessSerialNumber bytes:&psn length:sizeof(ProcessSerialNumber)];

    // function
    NSAppleEventDescriptor *function = [NSAppleEventDescriptor descriptorWithString:@"chockify"];

    // event
    NSAppleEventDescriptor *event = [NSAppleEventDescriptor appleEventWithEventClass:kASAppleScriptSuite eventID:kASSubroutineEvent targetDescriptor:target returnID:kAutoGenerateReturnID transactionID:kAnyTransactionID];
    [event setParamDescriptor:function forKeyword:keyASSubroutineName];
    [event setParamDescriptor:parameters forKeyword:keyDirectObject];

    return event;
}

注意: 這些代碼放在了 GitHub 上。Automation.scpt 文件包含了 chockify 函數(shù)和本教程中用到的其他腳本。Objective-C 的代碼全都在 AppDelegate.m 中。

現(xiàn)在你有一個事件描述符了。它用來告訴 AppleScript 你想做的事情,你還得讓它有個什么去處吧。這意味著你需要從你的應用包 (application bundle) 里加載 AppleScript。

NSURL *URL = [[NSBundle mainBundle] URLForResource:@"Automation" withExtension:@"scpt"];
if (URL) {
    NSAppleScript *appleScript = [[NSAppleScript alloc] initWithContentsOfURL:URL error:NULL];

    NSAppleEventDescriptor *event = [self chockifyEventDescriptorWithString:[self.chockifyInputTextField stringValue]];
    NSDictionary *error = nil;
    NSAppleEventDescriptor *resultEventDescriptor = [appleScript executeAppleEvent:event error:&error];
    if (! resultEventDescriptor) {
        NSLog(@"%s AppleScript run error = %@", __PRETTY_FUNCTION__, error);
    }
    else {
        NSString *string = [self stringForResultEventDescriptor:resultEventDescriptor];
        [self updateChockifyTextFieldWithString:string];
    }
}

通過應用包的一個 URL 可以創(chuàng)建 NSAppleScript 的實例。而反過來,腳本也要和上面創(chuàng)建的 chockify 事件描述符一起使用、如果一切正常的話,你會得到另一個事件描述符。如果出錯了,你會得到一個包含了描述錯誤信息的字典。雖說這個模式和很多其他 Foundation 類很相似,但是返回的錯誤并不是一個 NSError 的實例。

現(xiàn)在就剩從描述符中抽取出你想要的結果了:

- (NSString *)stringForResultEventDescriptor:(NSAppleEventDescriptor *)resultEventDescriptor
{
    NSString *result = nil;

    if (resultEventDescriptor) {
        if ([resultEventDescriptor descriptorType] != kAENullEvent) {
            if ([resultEventDescriptor descriptorType] == kTXNUnicodeTextData) {
                result = [resultEventDescriptor stringValue];
            }
        }
    }

    return result;
}

InputString 輸入可以被正確整形輸出,并且你現(xiàn)在也看到想在你的應用里運行 AppleScripts 的方法了。

曾經(jīng)的方式

曾經(jīng)有一段時間你是可以將 AppleEvents 發(fā)送到任意應用的,而不僅僅是當前運行的應用,就像我們上面的 chockify 里做的那樣。

比如你想知道 Safari 里現(xiàn)在最前面的窗口加載的 URL 地址是什么,你需要做的就是通過 tell application "Safari" 告訴 Safari 要做什么。

on safariURL()
    tell application "Safari" to return URL of front document
end safariURL

現(xiàn)在的話,可能得到的就只有 Debug Console 中的下面的輸出了:

AppleScript run error = {
    NSAppleScriptErrorAppName = Safari;
    NSAppleScriptErrorBriefMessage = "Application isn\U2019t running.";
    NSAppleScriptErrorMessage = "Safari got an error: Application isn\U2019t running.";
    NSAppleScriptErrorNumber = "-600";
    NSAppleScriptErrorRange = "NSRange: {0, 0}";
}

就算其實 Safari 是在運行的。買了個表..

沙盒限制

這是因為正嘗試在應用的沙盒中運行腳本??紤]到在沙盒里,Safari 事實上確實沒有在運行。

問題在于沒有人授予你訪問 Safari 的權限。這其實和一個很大的安全漏洞相關:一段腳本可以輕易地拿到瀏覽器當前頁面上的內(nèi)容,甚至是在任意標簽和窗口運行 JavaScript。想象一下如果這些頁面里有你的銀行賬號,或者包含你的信用卡信息什么的。好疼..

這也就是為什么從 Mac App Store 獲取的應用的腳本不能隨便執(zhí)行的原因。

不過事情在最近的 OS X 版本中有所改善。在 10.8 山獅中, Apple 引入了一個新的抽象類 NSUserScriptTask。有三個具體的子類實現(xiàn)讓你分別可以運行 Unix shell 命令 (NSUserUnixTask),Automator 工作流 (NSUserAutomatorTask) 以及我們最喜愛的 AppleScript(NSUserAppleScriptTask)。教程的接下來的部分將會專注于最后一類,因為這也是最常用的類。

對于沙盒應用,Apple 所提倡的是通過用戶的需要來驅動安全策略。實際操作上來說,這意味著用戶需要決定是否想要運行你的腳本。這些腳本可能是來自互聯(lián)網(wǎng),也可能是你的應用的一部分,這并不關鍵。唯一相關的事情是你的用戶表示“好的,我想要運行這個腳本”。一旦得到了權限,腳本就可以以一種受限的方式與系統(tǒng)其他部分進行交互了。NSUserScriptTask 類使這一切變得可能。

安裝腳本

那么,想要運行腳本的應用要怎么向用戶請求許可呢?

機制超級簡單:你的應用只能從用戶的賬戶里的一個特定的文件夾中運行腳本。而腳本想要進入這個文件夾的唯一方式就是用用戶把它們復制到那里。本質上來說,OS X 將這些腳本通過只讀的方式提供給你使用。

現(xiàn)在的挑戰(zhàn)是:這個特定的文件夾是 User > Library > Application Scripts 然后跟著是應用的 bundle identifier。對于我們的 Scriptinator,文件夾的名字大概只有程序員會喜歡:com.iconfactory.Scriptinator。兩者對用戶都很不友好,特別是 Library 文件夾在 OS X 里默認還是隱藏的。

解決這個問題的一個方法是實現(xiàn)一些代碼來為用戶打開這個隱藏文件夾,比如:

NSError *error;
NSURL *directoryURL = [[NSFileManager defaultManager] URLForDirectory:NSApplicationScriptsDirectory inDomain:NSUserDomainMask appropriateForURL:nil create:YES error:&error];
[[NSWorkspace sharedWorkspace] openURL:directoryURL];

這對于用戶自己寫的腳本來說是個很好的解決方案。用戶可以通過你的應用的某個控件打開這個文件夾,然后進行編輯。

但是有時候你想要幫助最終用戶將你已經(jīng)寫好的腳本安裝到這個目錄里。很自然的,作為程序員,你的編程水平應該比你的用戶的平均水平要強吧,你也更明白應該如何寫代碼讓你的應用與用戶的其他應用更好地工作在一起。很自然地,存放這些你自己的腳本的理想的地方是應用包里,但是要怎么樣才能把這些腳本放到用戶的腳本文件夾離去呢?

解決的方法是獲取對這個文件夾的寫入權限。在 Xcode 里,你需要更新你的應用的 Capabilities,讓其包括 "User Selected File to Read/Write"。你可以在 App Sandbox > File Access 里找到相關選項。再一次,用戶的意愿是關鍵,因為你需要獲取權限以將腳本添加到文件夾:

NSError *error;
NSURL *directoryURL = [[NSFileManager defaultManager] URLForDirectory:NSApplicationScriptsDirectory inDomain:NSUserDomainMask appropriateForURL:nil create:YES error:&error];
NSOpenPanel *openPanel = [NSOpenPanel openPanel];
[openPanel setDirectoryURL:directoryURL];
[openPanel setCanChooseDirectories:YES];
[openPanel setCanChooseFiles:NO];
[openPanel setPrompt:@"Select Script Folder"];
[openPanel setMessage:@"Please select the User > Library > Application Scripts > com.iconfactory.Scriptinator folder"];
[openPanel beginWithCompletionHandler:^(NSInteger result) {
    if (result == NSFileHandlingPanelOKButton) {
        NSURL *selectedURL = [openPanel URL];
        if ([selectedURL isEqual:directoryURL]) {
            NSURL *destinationURL = [selectedURL URLByAppendingPathComponent:@"Automation.scpt"];
            NSFileManager *fileManager = [NSFileManager defaultManager];
            NSURL *sourceURL = [[NSBundle mainBundle] URLForResource:@"Automation" withExtension:@"scpt"];
            NSError *error;
            BOOL success = [fileManager copyItemAtURL:sourceURL toURL:destinationURL error:&error];
            if (success) {
                NSAlert *alert = [NSAlert alertWithMessageText:@"Script Installed" defaultButton:@"OK" alternateButton:nil otherButton:nil informativeTextWithFormat:@"The Automation script was installed succcessfully."];
                [alert runModal];
            }
            else {
                NSLog(@"%s error = %@", __PRETTY_FUNCTION__, error);
                if ([error code] == NSFileWriteFileExistsError) {
                    // this is where you could update the script, by removing the old one and copying in a new one
                }
                else {
                    // the item couldn't be copied, try again
                    [self performSelector:@selector(installAutomationScript:) withObject:self afterDelay:0.0];
                }
            }
        }
        else {
            // try again because the user changed the folder path
            [self performSelector:@selector(installAutomationScript:) withObject:self afterDelay:0.0];
        }
    }
}];

這么一來,應用包中的 Automation.scpt 文件現(xiàn)在暴露在常規(guī)的文件系統(tǒng)中了。

在整個流程中讓你的用戶確實知道他們在做什么是很重要的。你必須記住,是你的用戶在控制你的腳本,而不是你。用戶可能會決定將所有腳本清理出腳本文件夾,而你必須做對應的處理。你可能會需要禁用某些依賴于這個腳本的特性,或者解釋為什么腳本需要在再次安裝。

注意: Scriptinator 的示例代碼包括了上面兩種策略。如果你想看看現(xiàn)實的例子,不妨研究下 Overlay xScope 的免費試用版。應用里有一個對用戶很友好的腳本設置步驟,以使應用能夠呵用戶的網(wǎng)頁瀏覽器進行通訊。還有一個好處是,你也許會發(fā)現(xiàn) xScope 真的是一個進行開發(fā)的很棒的工具?。ㄗg者注:本文作者是 xScope 的合作開發(fā)者,同時也是 Twitterrific 的開發(fā)者,所以這里也算個小的軟廣告)

腳本任務

現(xiàn)在你的自動化腳本在正確的位置了,你可以開始使用它們了。

在下面的代碼中,我們在上面創(chuàng)建的事件描述符沒有改變,唯一不一樣的是它們是如何被運行的:你需要使用 NSUserAppleScriptTask 來替代 NSAppleScript。

你大概會經(jīng)常用到這些腳本任務。文檔警告說對于給定的類的某個實例, NSUserAppleScriptTask 不應該被執(zhí)行多次。所以寫一個工廠函數(shù)來在需要的時候創(chuàng)建任務會是一個好主意:

- (NSUserAppleScriptTask *)automationScriptTask
{
    NSUserAppleScriptTask *result = nil;

    NSError *error;
    NSURL *directoryURL = [[NSFileManager defaultManager] URLForDirectory:NSApplicationScriptsDirectory inDomain:NSUserDomainMask appropriateForURL:nil create:YES error:&error];
    if (directoryURL) {
        NSURL *scriptURL = [directoryURL URLByAppendingPathComponent:@"Automation.scpt"];
        result = [[NSUserAppleScriptTask alloc] initWithURL:scriptURL error:&error];
        if (! result) {
            NSLog(@"%s no AppleScript task error = %@", __PRETTY_FUNCTION__, error);
        }
    }
    else {
        // NOTE: if you're not running in a sandbox, the directory URL will always be nil
        NSLog(@"%s no Application Scripts folder error = %@", __PRETTY_FUNCTION__, error);
    }

    return result;
}

如果你正在寫一個同時適用于沙盒和非沙盒的 Mac 應用的話,在獲取 directoryURL 時你需要特別小心。NSApplicationScriptsDirectory 只在沙盒中有效。

在創(chuàng)建腳本任務后,你需要使用 AppleEvent 并提供一個結束處理來執(zhí)行它:

NSUserAppleScriptTask *automationScriptTask = [self automationScriptTask];
if (automationScriptTask) {
    NSAppleEventDescriptor *event = [self safariURLEventDescriptor];
    [automationScriptTask executeWithAppleEvent:event completionHandler:^(NSAppleEventDescriptor *resultEventDescriptor, NSError *error) {
        if (! resultEventDescriptor) {
            NSLog(@"%s AppleScript task error = %@", __PRETTY_FUNCTION__, error);
        }
        else {
            NSURL *URL = [self URLForResultEventDescriptor:resultEventDescriptor];
            // NOTE: The completion handler for the script is not run on the main thread. Before you update any UI, you'll need to get
            // on that thread by using libdispatch or performing a selector.
            [self performSelectorOnMainThread:@selector(updateURLTextFieldWithURL:) withObject:URL waitUntilDone:NO];
        }
    }];
}

對于用戶寫的腳本,用戶可能期望你的應用只是簡單地'運行'腳本 (而不去調用事件描述符中指定的函數(shù))。在這種情況下,你可以為 event 傳遞一個 nil,腳本就會像用戶在 Finder 中雙擊那樣的行為進行執(zhí)行。

NSUserAppleScriptTask 中很好的一個東西就是結束時候的回調處理。腳本是異步執(zhí)行的,所以你的用戶界面并不會被一個 (比較長) 的腳本鎖住。要小心你在結束回調中做的事情,因為它并不是跑在主線程上的,所以你不能在那兒對你的用戶界面做更新。

幕后

背后發(fā)生了些什么事情呢?

也許你從腳本只能異步地運行一次這個事實中猜到了,這些代碼現(xiàn)在是通過 XPC 執(zhí)行的。就像 iOS 8 中使用 XPC 來確保擴展不會影響調用應用那樣,在 Mac 應用中運行的腳本也無法訪問調用應用的內(nèi)存地址空間。

如果你看看接收的事件描述符的 keySenderPIDAttr 屬性的話,你會發(fā)現(xiàn)進程 ID 屬于 /usr/libexec/lsboxd,而不是你的應用程序。這個迷之進程大概是 Launch Services 的沙盒守護進程。無論怎樣,你向其他進程的請求肯定基本都是要被封送 (marshalling) 的。

如果想在更高層級理解關于應用沙盒的安全目標的內(nèi)容,我推薦看看 Ivan Krsti? 在 WWDC 2012"The OS X App Sandbox" 的演講、很出乎意料的是,這個演講非常有意思,在 36 分鐘的演講中,他介紹了在上面提到的關于自動化的改變。同一次大會中,Sal Soghoian 和 Chris Nebel 帶來的 "Secure Automation Techniques in OS X" 深入講解了自動化的改變。如果你只想學習關于應用運行的用戶腳本方面的內(nèi)容,你可以跳過前 35 分鐘的內(nèi)容。

在這些演講中討論了另一個很重要的安全方面的內(nèi)容是訪問組 (access group),我們在這個教程中并沒有涉及這方面的內(nèi)容。如果你想要用腳本控制像郵件或者 iTunes 這樣的系統(tǒng)應用,你絕對需要特別關注一下上面提到的視頻中有關這方面的內(nèi)容。

同步

正如我上面提到的,NSAppleScriptNSUserAppleScriptTask 有一個微妙的區(qū)別:新的機制是異步執(zhí)行的。對于大部分情況,使用一個結束回調來處理會是一個好得多的方式,因為這樣就不會因為執(zhí)行腳本而阻礙你的應用。

然而有時候如果你想帶有依賴地來執(zhí)行任務的時候,事情就變得有些取巧了。比方說一個任務需要在另一個任務開始之前必須完成。這種情況下你就會想念 NSAppleScript 的同步特性了。

要獲得傳統(tǒng)方式的行為,一種簡單的方法是使用一個信號量 (semaphore) 來確保同時只有一個任務運行、在你的類或者應用的初始化方法中,使用 libdispatch 創(chuàng)建一個信號量:

self.appleScriptTaskSemaphore = dispatch_semaphore_create(1);

接下來在初始化腳本任務之前,簡單地等待信號量。當任務完成時,標記相同的這個信號量:

// wait for any previous tasks to complete before starting a new one — remember that you're blocking the main thread here!
dispatch_semaphore_wait(self.appleScriptTaskSemaphore, DISPATCH_TIME_FOREVER);

// run the script task
NSAppleEventDescriptor *event = [self openNetworkPreferencesEventDescriptor];
[automationScriptTask executeWithAppleEvent:event completionHandler:^(NSAppleEventDescriptor *resultEventDescriptor, NSError *error) {
    if (! resultEventDescriptor) {
        NSLog(@"%s AppleScript task error = %@", __PRETTY_FUNCTION__, error);
    }
    else {
        [self performSelectorOnMainThread:@selector(showNetworkAlert) withObject:nil waitUntilDone:NO];
    }

    // the task has completed, so let any pending tasks proceed
    dispatch_semaphore_signal(self.appleScriptTaskSemaphore);
}];

再強調一下,除非確實有所需要,否則最好別這么做。

你能寫什么樣的腳本

在最后這個例子中,系統(tǒng)偏好設置面板中的網(wǎng)絡面板通過下面的 AppleScript 代碼可以被打開:

tell application "System Preferences"
    launch
    activate

    reveal pane id "com.apple.preference.network"
end tell

很棒,但是你是怎么知道這些各種面板的 ID 的?如果想打開的不是網(wǎng)絡面板,輔助功能或者是安全性與隱私面板的話,要怎么做呢?

正如你在 Brent 的文章中看到的那樣,每一個支持 AppleScript 的應用都有一個腳本字典。這個字典描述了應用數(shù)據(jù)模型的對象和屬性。所以我們只需要查看數(shù)據(jù)模型就可以找到你想要的東西了!

首先從你的 應用 > 其他 文件夾中打開腳本編輯器。然后從文件菜單中,選取"打開字典..."。在這里,所有支持 AppleScript 的應用都會被陳列出來 - 可能比你想象的要多!選擇系統(tǒng)偏好設置,并且點選"選取"。

在這里,你將看到一個標準套件 (Standard Suite) 和系統(tǒng)偏好設置 (System Preferences) 的樹形瀏覽器。標準套件里列出了像 "open" 這樣的命令,"window" 這樣的類,以及其他一些對大多數(shù)腳本字典來說都通用的東西。有意思的是另一個腳本套件:System Preferences。當你選擇它后,你會看到一個叫做 "reveal" 的命令以及三個類 (對象類型),分別叫做 "application","pane" 和 "anchor"。

當你查看 "application" 的話,你會看到兩個東西:元素 (elements) 和屬性 (properties)。元素是被所選對象管理的一組對象集合。屬性列出了備選對象所維護的數(shù)據(jù)。

http://wiki.jikexueyuan.com/project/objc/images/14-1.png" alt="" />

applicaiton 里含有 panes,聽起來就是它。在一個新的腳本編輯器窗口中,創(chuàng)建一個簡單的腳本來顯示所有的面板對象:

tell application "System Preferences"
    panes
end tell

我們的目標是打開輔助功能界面的安全面板,于是我們可以在輸出結果中查找,知道我們看到類似這樣的有用的東西:

pane id "com.apple.preference.security" of application "System Preferences"

查看它的 "localized name" 屬性:

tell application "System Preferences"
    localized name of pane id "com.apple.preference.security"
end tell

輸出為 Security & Privacy。就是它!現(xiàn)在我們嘗試使用之前見過的 "reveal" 命令和 "pane id" 另外寫一個腳本

tell application "System Preferences"
    reveal pane id "com.apple.preference.security"
end tell

系統(tǒng)偏好設置為我們打開了這個面板?,F(xiàn)在讓我們來看看怎么打開特定的 tab 視圖。首先通過 pane 里包含的唯一的元素,anchor 對象,來查詢一下:

tell application "System Preferences"
    anchors of pane "com.apple.preference.security"
end tell

哈哈,我們看到:

anchor "Privacy_Accessibility" of pane id "com.apple.preference.security" of application "System Preferences"

這就是我們想要的。這里也顯示了系統(tǒng)偏好設置的結構:一個應用含有 pane,而 pane 含有 anchors。我們調整一下我們的腳本:

tell application "System Preferences"
    reveal anchor "Privacy_Accessibility" of pane id "com.apple.preference.security"
end tell

完成!現(xiàn)在想象一下如果你的應用需要用戶賦予控制電腦的權限的時候,比起告訴用戶如何在偏好設置面板里打開對應面板,你現(xiàn)在直接為用戶打開了這個面板,干得漂亮。

總結

你學到了通過你自己的應用去控制別的應用的時候所需要知道的一切。不管你是想讓用戶可以創(chuàng)建他們自己的自動化工作流,還是只是想在你的應用中啟用一些內(nèi)部功能,就算只能運行在沙盒里,AppleScript 依舊都是每個 Mac 應用的強有力的部件。希望這篇教程能給你帶來新的工具和視野,并且在之后你自己的項目中使用這些特性時有所幫助。

上一篇:回到 Mac下一篇:iOS 上的相機捕捉