zoukankan      html  css  js  c++  java
  • [Cocoa]深入浅出Cocoa之Plugin

    深入浅出Cocoa之Plugin

    罗朝辉 (http://www.cnblogs.com/kesalin/)

    本文遵循“署名-非商业用途-保持一致”创作公用协议

    在前文深入浅出Cocoa之Framework中讲解了 Framework,接下来讲解 plugin。如果你对 Framework 还不太熟悉的话,请阅读那篇文中,在本例中使用到了 framework,并在本文中没有详细讲述其创建和使用过程。

    本文代码下载:点击这里

    为什么要引入插件?

    我们知道编译程序时,会连接相关 framework,通常我们所连接的框架是 Foundation 和 Application 框架。当程序启动运行时,每个被连接到的 framework 都会被加载到该程序的 objc 运行时环境中。如果我们想向正在运行的程序加载新的 framework,那该怎么办呢?答案之一就是使用 plugin 机制。cocoa 的 plugin 机制通常由 NSBundle 类来实现,而实现动态加载的功能由函数 objc_addClass 来完成。一般我们无需与 objc_addClass 这个函数打交道,我们使用 NSBundle 来完成绝大部分与 plugin 相关的工作。


    plugin 机制能够让我们开发出高度模块化,可定制以及可扩展的应用程序,并能够让第三方为该应用程序添加新特性。想必很多人都熟悉 Eclipse,Eclipse 的 plugin 机制就非常方便与强大。

    NSBundle 简介

    束(bundle)是文件系统中的一个目录结构,它将程序会使用到的资源打包在一起。这些资源可包括编译好的代码,nib文件,配置文件,图像,声音,本地化资源等等。束是 Mac OS X 的一个核心特性,应用程序,Framework,插件都是一个束,只是扩展名各异,如应用程序的扩展名为 .app;Framework 的扩展名是 .framework;插件的扩展名默认为 .bundle。

    一个 plugin 就是一个 bundle(束),xcode 默认以 .bundle 为扩展名。通常我们使用我们自己定义的扩展名,以便与系统或其他人编写的 plugin 区分开来。我们通过 NSBundle 来载入 bundle,并把其中经过编译的类注册到 objc 运行时中,然后我们就能在程序中使用这些类了;我们也可以使用 bundle 中的所有资源。

    plugin 构架

    我们可以通过多种途径来实现一个 plugin:

    1,定义一个 objc protocol,让 plugin 遵守该 protocol;
    2,定义一个基类,让 plugin 继承该基类;
    3,定义一个 C 回调函数接口,让 plugin 实现改回调函数;
    4,使用 CFPlugIn 来创建 plugin 接口;


    在今天的例子中,使用的是第二种情况,这种情况稍稍复杂一些,我们需创建一个 framework 供宿主程序(使用插件的程序)和 plugin 使用,该 framework 的主要职责是提供基类接口。

    plugin 的存放目录
    通常 plugin 总是存放在以下三个位置:

    1,应用程序名.app/Contents/Plug-ins                               这是程序的开发者存放随产品发布的插件的地方。 
    2,~/Library/Application Support/应用程序名/Plug-ins       用户存放个人插件的地方。
    3,/Library/Application Support/应用程序名/Plug-ins         系统中供全部用户使用的插件。


    在今天的例子中,使用的是第一种情况,即将插件存放在应用程序包中。

    创建宿主程序
    我们来创建一个名为 PluginDemo 的 cocoa application,该程序含有一个显示已安装 plugin 的 popup button 以及一个执行选中 plugin 的 button。


    创建 framework

    1,创建名为 PluginFramework 的 framework,向其中添加 plugin 基类:AbstractPlugin。如果你忘记怎样创建和使用 framework,请参看前文:深入浅出Cocoa之Framework


    AbstractPlugin 类仅仅提供两个接口:

    #import "PluginOne.h"

    @implementation PluginOne

    @synthesize mainWindow;

    - (id)init
    {
    self = [super init];
    if (self) {
    // Initialization code here.

    [NSBundle loadNibNamed:@"PluginOneMainWindow" owner:self];
    }

    return self;
    }

    - (void)dealloc
    {
    mainWindow = nil;

    [super dealloc];
    }

    - (NSString *)name;
    {
    return @"Plugin One";
    }

    - (IBAction)run:(id)sender;
    {
    [mainWindow center];
    [mainWindow makeKeyAndOrderFront:sender];
    }

    - (IBAction)closeWindow:(id)sender;
    {
    [mainWindow orderOut:sender];
    }

    @end

    name 用来标识 plugin,run 用来供宿主程序运行插件。

    2,在 PluginDemo 中连接和使用该 framework 来运行插件。如果你忘记怎样连接和使用 framework,请参看前文:深入浅出Cocoa之Framework。我们在按钮响应函数中,运行选中的插件。

    - (IBAction)runPlugin:(id)sender
    {
    AbstractPlugin *plugin = [[pluginsController selectedObjects] lastObject];
    if (!plugin)
    return;

    [plugin run:sender];
    }

    创建 plugin

    1,创建 plugin;


    2,连接 PluginFramework;如果你忘记怎样连接和使用 framework,请参看前文:深入浅出Cocoa之Framework


    3,创建 UI 界面;


    4,创建继承自基类的 plugin 子类:PluginOne;

    PluginOne 类继承自 AbstractPlugin,它仅仅是显示和隐藏一个 window,其实现如下:

    #import "PluginOne.h"

    @implementation PluginOne

    @synthesize mainWindow;

    - (id)init
    {
    self = [super init];
    if (self) {
    // Initialization code here.

    [NSBundle loadNibNamed:@"PluginOneMainWindow" owner:self];
    }

    return self;
    }

    - (void)dealloc
    {
    mainWindow = nil;

    [super dealloc];
    }

    - (NSString *)name;
    {
    return @"Plugin One";
    }

    - (IBAction)run:(id)sender;
    {
    [mainWindow center];
    [mainWindow makeKeyAndOrderFront:sender];
    }

    - (IBAction)closeWindow:(id)sender;
    {
    [mainWindow orderOut:sender];
    }

    @end

    5,plugin 设置

    下面我们来对 plugin 进行设置,我们可以设置其 Principal class,Wrapper Extension(扩展名)。

    使用 plugin

    1,宿主程序设置

    前面说了,在这个例子中,我们打算将插件随宿主程序一起发布,所以其存放位置就在宿主应用程序包中。因此我们需要在宿主程序种添加一个 Add Copy Files 的 build phase,如下所示:


    2,载入plugin

    在正式的应用中,我们应该在前面提到的三个目录下去查找所有 plugin,因为这三个目录都是 Cocoa 所推荐的 plugin 目录。在这个例子中,演示的是随宿主应用程序一起发布的程序,所以我只扫描了应用程序包中的目录。

    - (NSArray *)loadPlugins
    {
    NSBundle *main = [NSBundle mainBundle];
    NSArray *allPlugins = [main pathsForResourcesOfType:@"bundle" inDirectory:@"../PlugIns"];

    NSMutableArray *availablePlugins = [[[NSMutableArray alloc] init] autorelease];

    id plugin = nil;
    NSBundle *pluginBundle = nil;

    for (NSString *path in allPlugins) {
    pluginBundle = [NSBundle bundleWithPath:path];
    [pluginBundle load];

    Class principalClass = [pluginBundle principalClass];
    if (![principalClass isSubclassOfClass:[AbstractPlugin class]]) {
    continue;
    }

    plugin = [[principalClass alloc] init];

    if ([plugin respondsToSelector:@selector(run:)])
    {
    [availablePlugins addObject:plugin];
    NSLog(@" >> loading plugin %@ from %@", [plugin name], path);
    }

    [plugin release];
    plugin = nil;
    pluginBundle = nil;
    }

    return availablePlugins;
    }


    该函数在 init 中被调用:

    - (id)init
    {
    self = [super init];
    if (self) {
    plugins = [[self loadPlugins] retain];
    }

    return self;
    }

    下面提供一个函数扫描前面提到的三个目录,你可以用这个函数提到上面代码中对 loadPlugins 的调用:

    - (NSArray *)loadAllPlugins
    {
    NSString *appName = @"PluginOne/Plugins";
    NSString *appSupport = @"Library/Application Support";
    appSupport = [appSupport stringByAppendingPathComponent:appName];

    NSString *appPath = [[NSBundle mainBundle] builtInPlugInsPath];
    NSString *userPath = [NSHomeDirectory() stringByAppendingPathComponent:appSupport];
    NSString *sysPath = [@"/" stringByAppendingPathComponent:appSupport];

    NSArray* paths = [NSArray arrayWithObjects:appPath, userPath, sysPath, nil];

    NSMutableArray * availablePlugins = [[[NSMutableArray alloc] init] autorelease];
    for (NSString * path in paths)
    {
    NSLog(@" >> Search in directory: %@", path);

    NSArray *contents = [[NSFileManager defaultManager] contentsOfDirectoryAtPath:path error:NULL];
    for (NSString *fileName in contents)
    {
    if ( [[fileName pathExtension] isEqualToString:@"plugin"] || [[fileName pathExtension] isEqualToString:@"bundle"])
    {
    NSString *fullPath = [path stringByAppendingPathComponent:fileName];
    NSBundle *pluginBundle = [NSBundle bundleWithPath:fullPath];
    if (pluginBundle && [pluginBundle load])
    {
    Class principalClass = [pluginBundle principalClass];
    if (![principalClass isSubclassOfClass:[AbstractPlugin class]]) {
    continue;
    }

    id plugin = [[principalClass alloc] init];

    if ([plugin respondsToSelector:@selector(run:)])
    {
    [availablePlugins addObject:plugin];
    NSLog(@" >> loading plugin %@ from %@", [plugin name], path);
    }

    [plugin release];
    plugin = nil;
    }
    }
    }
    }

    return availablePlugins;
    }

    显示 plugin 列表的 popupbutton 的内容被绑定到该 plugins 数组,所以程序启动之后,就能显示 plugin 的列表。运行结果如下:

    点击运行之后,就能显示出插件主界面:

    引用资源:
    Code Loading Programming Topics provides information about writing plug-ins using the Objective-C language.
    Bundle Programming Guide provides an overview to bundles, including their purpose, types, structure, and the API for accessing bundle resources.

  • 相关阅读:
    URL统一资源定位符的组成
    B/S与C/S的比较
    springmvc在处理请求过程中出现异常信息交由异常处理器进行处理,自定义异常处理器可以实现一个系统的异常处理逻辑。为了区别不同的异常通常根据异常类型自定义异常类,这里我们创建一个自定义系统异常,如果controller、service、dao抛出此类异常说明是系统预期处理的异常信息。
    springmvc中Controller方法的返回值
    springmvc中@RequestMapping的使用
    构造完全图---最小生成树
    poj
    叶子的颜色---经典树上dp
    花神游历各国
    CodeForces
  • 原文地址:https://www.cnblogs.com/kesalin/p/cocoa_plugin.html
Copyright © 2011-2022 走看看