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

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

插件

插件是給你已經(jīng)發(fā)布的 App 增加功能的一個好辦法,Mac 上的 App 支持插件已經(jīng)有很長的歷史了,比如 Adobe Photoshop,在 1991 年的 version 2.0 就開始支持了。

在以前的 OS X 系統(tǒng)中,給你的 App 在運(yùn)行時動態(tài)載入可執(zhí)行代碼比較困難?,F(xiàn)在,在 NSBundle 的幫助和你的一些前瞻性思維的幫助下下,它從未如此簡單。

包 (Bundles) 和接口 (Interfaces)

如果你打開 Xcode 5 并且創(chuàng)建一個新項(xiàng)目,你會看見 OS X 選項(xiàng)卡下有一個 "Application Plug-in" 的分類和 "System Plug-in" 的分類,從 Screen Savers 到 Image Units,在 Xcode 里面一共有 12 中不同的模板可以編寫 App 的插件。如果你點(diǎn)擊 "Framework & Library" 的選項(xiàng)卡,你將可以看見一個 Bundle 條目。我會在今天探索一個非常簡單的的項(xiàng)目,那就是在一個修改過的 TextEdit 里面加入加載 bundle 的功能。

注意:Apple 稱這些為 plug-ins ,而通常大家更喜歡用 plugins 稱呼。為了一致性,在開發(fā)和 UI 相關(guān)的東西的時候,我想用和平臺一致的 plug-in 稱呼會更好。雖然在應(yīng)用的 UI 里你會看到 "plug-ins",但是在這篇文章和代碼里面,我會用 plugin。(同時我偶爾會混用 bundle 和 plugin 這兩個詞。)(譯者注:在本譯文中會把 plugin 統(tǒng)一翻譯成插件,偉大的中文)

什么是 bundle ?如果你創(chuàng)建一個 Xcode 的 bundle 模版項(xiàng)目,你會發(fā)現(xiàn)它內(nèi)容并不多。當(dāng)構(gòu)建它的時候你會得到一個很像構(gòu)建 App 時產(chǎn)生的目錄 —— 一個 Contents 目錄,里面包含了 Info.plist 和 Resource 目錄。如果你在你的項(xiàng)目下加入了新的類,你可以看見包含一個可執(zhí)行文件的 MacOS 目錄。Bundle 工程里缺少的一個東西是 main() 函數(shù)。它是被宿主 App 調(diào)用執(zhí)行的。

為 TextEdit 加入 Plugin 支持

我會介紹兩種插件的方式,第一個用最少的工作來為你的 app 加入插件支持,希望讓你知道實(shí)現(xiàn)這個有多簡單。

第二個技術(shù)有點(diǎn)復(fù)雜,它展現(xiàn)來一個為你的 app 加入插件的合理的方式,這可以使你不會在未來陷入到被鎖死在某一種實(shí)現(xiàn)的窘境中。

本文章的項(xiàng)目文件仍然會放在 GitHub 供大家參考。

在 TextEdit 中掃描 Bundle

請打開 "01 TextEdit" 目錄下面的 TextEdit.xcodeproj 工程,同時瀏覽它里面包含的代碼。

這個改寫過的 TextEdit 里面有三個簡單的組成部分:掃描 bundle,加載 bundle,并且添加了調(diào)用 bundle 的 UI。

打開 Controller.m,你可以看見 -(void)loadPlugins 方法 (它在 applicationDidFinishLaunching: 中被調(diào)用)。

loadPlugins 方法在你的界面菜單右側(cè)加入了一個新的 NSMenuItem,來為你調(diào)用你的插件提供一個入口(通常你會在 MainMenu.xib 做這件事情并且鏈接 outlets,但是我們這次偷下懶)。然后獲得你的插件目錄(在 ~/Library/Application Support/Text Edit/Plug-Ins/ )下,并且掃描這個目錄。

 NSString *pluginsFolder = [self pluginsFolder];
 NSFileManager *fm = [NSFileManager defaultManager];

 NSError *outErr;
 for (NSString *item in [fm contentsOfDirectoryAtPath:pluginsFolder error:&outErr]) {

     if (![item hasSuffix:@".bundle"]) {
         continue;
     }

     NSString *bundlePath = [pluginsFolder stringByAppendingPathComponent:item];

     NSBundle *b = [NSBundle bundleWithPath:bundlePath];

     if (!b) {
         NSLog(@"Could not make a bundle from %@", bundlePath);
         continue;
     }

     id <TextEditPlugin> plugin = [[b principalClass] new];

     NSMenuItem *item = [pluginsMenu addItemWithTitle:[plugin menuItemTitle] action:@selector(pluginMenuItemCalledAction:) keyEquivalent:@""];

     [item setRepresentedObject:plugin];

 }

到目前,看起來是非常簡單的。掃描插件目錄,確保得到的是一個 .bundle 文件(你當(dāng)然不希望載入 .DS_Store 文件),然后用 NSBundle 載入你找到的 bundle 并且實(shí)例化里面的類。

你會注意到一個 TextEditPlugin 的 protocol 的引用。在 TextEditMisc.h 能找它的定義:

 @protocol TextEditPlugin <NSObject>
 - (NSString*)menuItemTitle;
 - (void)actionCalledWithTextView:(NSTextView*)textView inDocument:(id)document;
 @end

這說明你實(shí)例化的類需要響應(yīng)這兩個方法。你可以驗(yàn)證這個類是否響應(yīng)這兩個方法(這是一個好主意),但是簡單起見,我們現(xiàn)在就不這樣做了。

OK,你在 bundle 里面調(diào)用的 principalClass 方法是什么呢?當(dāng)你創(chuàng)建一個 Bundle 的時候,你可以在里面創(chuàng)建一個或者多個類,同時你需要讓 TextEdit 知道哪一個類需要被實(shí)例化。為了幫助宿主 App 調(diào)用,你可以在 Info.plist 文件加入一個 NSPrincipalClass 的鍵,同時設(shè)置它的值為實(shí)現(xiàn)插件方法的類的名字。你可以用 [NSBundle principalClass] 方便地從 NSPrincipalClass 的值里面尋找并創(chuàng)建這個類。

繼續(xù):在 Plug-Ins 菜單加入一個新的按鈕,設(shè)置 action 為 pluginMenuItemCalledAction:,并且設(shè)置它表示你已經(jīng)實(shí)例化的對象。

注意你沒有在 menu item 里面設(shè)置一個target。如果一個menu item的目標(biāo)是nil,那么它會尋找響應(yīng)鏈,來尋找第一個實(shí)現(xiàn) pluginMenuItemCalledAction: 方法的對象。如果它找不到,那么這個菜單選項(xiàng)將會不能用。

舉一個例子,實(shí)現(xiàn) pluginMenuItemCalledAction 的最好的地方是在 Document 的 window controller 類中。打開 DocumentWindowController.m,然后定位到到 pluginMenuItemCalledAction

- (void)pluginMenuItemCalledAction:(id)sender {
    id <TextEditPlugin>plugin = [sender representedObject];
    [plugin actionCalledWithTextView:[self firstTextView] inDocument:[self document]];
}

代碼本身很清晰,搜集插件實(shí)例,調(diào)用 actionCalledWithTextView:inDocument: 方法(被定義在 protocol 里面的),運(yùn)行你插件里面的代碼。

插件一瞥

打開 "01 MarkYellow" 工程看一下。這是一個 Xcode (通過OS X ? Framework & Library ? Bundle template 建立) 的標(biāo)準(zhǔn)工程,里面只添加了一個類:TEMarkYellow。

如果你打開 MarkYellow-Info.plist,你可以看到 NSPrincipalClass 的值設(shè)置成了上面提到的 TEMarkYellow

接著,打開 TEMarkYellow.m,你將會看見定義在協(xié)議里面的方法。一個返回你插件的名字,就是在 menu 里面顯示的那個,更有意思的是另外一個方法 (actionCalledWithTextView:inDocument:),它把所有選中的文字變成黃色的背景。

- (void)actionCalledWithTextView:(NSTextView*)textView inDocument:(id)document {
    if ([textView selectedRange].length) {

        NSMutableAttributedString *ats = [[[textView textStorage] attributedSubstringFromRange:[textView selectedRange]] mutableCopy];

        [ats addAttribute:NSBackgroundColorAttributeName value:[NSColor yellowColor] range:NSMakeRange(0, [ats length])];

        //  先測試text view是否能改變文字內(nèi)容,這樣可以自動做正確的撤銷操作。

        By asking the text view if you can change the text first, it will automatically do the right thing to enable undoing of attribute changes
        if ([textView shouldChangeTextInRange:[textView selectedRange] replacementString:[ats string]]) {
            [[textView textStorage] replaceCharactersInRange:[textView selectedRange] withAttributedString:ats];
            [textView didChangeText];
        }
    }
}

運(yùn)行 TextEdit (它會創(chuàng)建Plug-Ins目錄),然后構(gòu)建 MarkYellow 工程。把 MarkYellow.bundle 丟到你的 ~/Library/Application Support/Text Edit/Plug-Ins/ 目錄下面,重啟你的 TextEdit 應(yīng)用。

一切看起來都很好,掃描,加載,插入一個菜單,然后,當(dāng)你使用菜單項(xiàng)的時候,傳遞到參數(shù)到插件里面。試一試,點(diǎn)擊 Plug-Ins ? Mark Selected Text Yellow,選擇的文字的背景顏色就變成黃色的了。

這真是令人驚嘆,但是其實(shí)它很脆弱,也不夠先進(jìn)。

所以關(guān)掉這兩個項(xiàng)目,扔進(jìn)廢紙簍,然后嘗試忘掉它們吧。

好的,但是如何改進(jìn)呢

上述的途徑有什么問題?

  • Bundle 中只有一個方法被調(diào)用。對于插件的作者來說太不方便了。有沒有更簡單的方法為 bundle 加入更多功能和菜單按鈕呢?

  • 這不是一個有前瞻性的做法,在插件里面硬編碼特定的方法名字固定了一些操作,讓我們重新來寫這個工程,讓插件能做的事情更多吧。

這一次,我們先從 bundle 開始探究。打開 02 MarkYellow 里面的 xcodeproj 工程,定位到 TEMarkYellow.m, 你馬上可以看見這里有更多代碼,同時它也做了更多事情。

這里實(shí)現(xiàn)了一個接收一個 interface 作為參數(shù)的 pluginDidLoad: 方法,而不是返回插件名字的方法。你可以用它來告訴 TextEdit 你的方法名字和調(diào)用它們的時候使用的 selector ,以及一個幫助存儲一些特別的文本操作的狀態(tài)的 user object。

這個插件從單一行為變成了實(shí)現(xiàn)了三個操作:一個把你的文本變成黃色,一個把你的文字變成藍(lán)色,一個把你選中的文本作為 AppleScript 運(yùn)行。我們充分發(fā)揮了 userObject 這個參數(shù)的優(yōu)點(diǎn),所以只需要實(shí)現(xiàn)兩個方法。

這個方法比第一種有擴(kuò)展性。然而,它也增加了 app 端的復(fù)雜度。

為TextEdit加入更多功能

打開 02 TextEdit 看看 Controler.m , 它沒有做太多事情。但是它在 applicationDidFinishLaunching: 設(shè)置了一個新的類,叫 PluginManager,打開 PluginManager.m 并且導(dǎo)航到 -loadPlugins 里面。

這個和剛才的 -loadPlugins 幾乎一樣,只不過代替了原來使用 for 循環(huán)加入菜單項(xiàng),現(xiàn)在只對從 bundle 中取到的 principalClass 進(jìn)行初始化,然后調(diào)用了 pluginDidLoad:,以此來驅(qū)動影響 TextEdit 的執(zhí)行。

看一下 -addPluginsMenuWithTitle:…,我們在這里創(chuàng)建了菜單項(xiàng)。并且這里不再設(shè)置菜單項(xiàng)的 representedObject 為插件實(shí)例本身,而是實(shí)例化一個 helper 類 (PluginTarget),同時關(guān)聯(lián)了對 text aciton 和 friends 的引用,然后設(shè)置它為菜單項(xiàng)的 representedObject 以備使用。

然而,設(shè)置到菜單項(xiàng)的 selector 仍然還是 pluginMenuItemCalledAction:,可以在 DocumentWindowController.m 里面看看這個方法到底干了什么:

- (void)pluginMenuItemCalledAction:(id)sender {

    PluginTarget *p = [sender representedObject];

    NSMethodSignature *ms = [[p target] methodSignatureForSelector:[p action]];
    NSInvocation *invocation = [NSInvocation invocationWithMethodSignature:ms];

    NSTextView *tv = [self firstTextView];
    id document = [self document];
    id userObject = [p userObject];

    [invocation setTarget:[p target]];
    [invocation setSelector:[p action]];
    [invocation setArgument:&tv atIndex:2];
    [invocation setArgument:&document atIndex:3];
    [invocation setArgument:&userObject atIndex:4];
    [invocation invoke];
}

因?yàn)槟阋幚砀嘈畔ⅲ赃@個版本相比之前的實(shí)現(xiàn)有一點(diǎn)復(fù)雜,創(chuàng)建一個 NSInvocation,設(shè)置它的參數(shù),然后從插件的實(shí)例里面調(diào)用它。

宿主 (app) 端需要更多工作,但是對于插件的作者來說寫插件更加靈活了。

下一步干什么

基于這個接口,你可以寫一個插件,加載其他的自定義的插件。假設(shè)你想要加入讓你的用戶用 Javascript 寫插件的功能,那么在 pluginDidLoad 調(diào)用之后,掃描指定目錄下面的 js 文件,在 addPluginsMenuWithTitle:… 中為每一個 js 文件增加對應(yīng)的條目,然后,當(dāng)插件被調(diào)用的時候,可以用 JavaScriptCore 來執(zhí)行對應(yīng)的腳本 。你也可以用 Python,Ruby,Lua 來做這些事情(我之前做過這些事情)。

最后,關(guān)于安全

“插件讓安全的人抽搐” — 匿名

一個顯而易見但是容易被忽略的事情是安全。當(dāng)你在你的進(jìn)程里面加載一個可執(zhí)行的 bundle 的時候,你相當(dāng)于在說:“這里有一把我房間的鑰匙,確保走的時候關(guān)上燈燈,不要把牛奶喝光,無論你干什么都請把火盆放在外面。” 你需要相信插件的作者不會犯錯,但是有可能事與愿違。

可能會發(fā)生什么糟糕的情況呢?一個實(shí)現(xiàn)的不好的的插件可以占用所有可用的內(nèi)存,讓 CPU 占用始終保持 100%,crash 一大堆東西?;蛟S有的家伙寫了一個看起來很好的插件,但是一個月以后,它的代碼把你的聯(lián)系人數(shù)據(jù)庫偷偷發(fā)給第三方……我還能舉出很多例子,我相信你懂的...

如何解決這個問題?你可以在單獨(dú)的地址空間運(yùn)行你的插件(解決 crash 問題,同時可能可以解決內(nèi)存和 cpu 問題),同時強(qiáng)制插件到沙盒里面運(yùn)行。(如果你正確地確認(rèn)插件的權(quán)限,那么你的聯(lián)系人數(shù)據(jù)庫就不會被讀取了)。我一時就能想到很多方法,但是最好的解決方法是使用蘋果的 XPC。

我把探索的過程留給讀者,但是你在處理插件的時候應(yīng)該一直有安全性的觀念。當(dāng)然,把一個插件放在沙盒里面或者另外一個進(jìn)程里面會缺少一些樂趣,并且增加一些工作量,所以這對于你的 App 或許沒那么重要。