Cordova 插件:使用弱链接添加自定义框架
Cordova Plugin: Add custom framework using weak linking
在我的 plugin.xml 中,我试图声明一个自定义 .framework 并将其弱链接,但是一旦我打开 Xcode 我看到添加的框架仍然标记为 "required" 而不是 "optional".
这是我的 plugin.xml 条目:
<framework src="ios/libs/BlaBla.framework" custom="true" weak="true" />
这是我收到的第 3 方自定义 .framework
,其中包含 Headers(显然)和共享动态库文件(我将在运行时使用 dlopen("TheDylib", RTLD_LAZY|RTLD_GLOBAL);
加载)。
我不能使用 <header-file src="BlaBla.framework/Headers/Bla.h" />
的原因是 .framework
中的 headers 本身引用内部 headers 和 #import <BlaBla.framework/SomeHeader.h>
所以 <header-file>
标签在这种情况下无能为力。
重要提示
最好使用 "Embedded Frameworks" 功能而不是此解决方案,因为 dlopen
自 non-mac/simulator 设备(真实 iPhones/iPads)上的 iOS 8.0 以来被禁止。
看看Custom Cordova Plugin: Add framework to "Embedded Binaries"
重要说明结束
我最后做了一些不同的事情,我没有将 .framework
声明为 <framework ... />
标签,而是做了以下事情。
我创建了一个插件挂钩,将插件目录添加到 FRAMEWORK_SEARCH_PATHS Xcode build 属性.
<hook type="after_platform_add" src="hooks/addPluginDirToFrameworkSearchPaths/hook.js" />
挂钩代码:
module.exports = function(context) {
const includesiOS = context.opts.platforms.indexOf('ios') != -1;
if(!includesiOS) return;
const
deferral = context.requireCordovaModule('q').defer(),
pluginId = context.opts.plugin.id;
const xcode = require('xcode'),
fs = require('fs'),
path = require('path');
function fromDir(startPath,filter, rec) {
if (!fs.existsSync(startPath)){
console.log("no dir ", startPath);
return;
}
const files=fs.readdirSync(startPath);
for(var i=0;i<files.length;i++){
var filename=path.join(startPath,files[i]);
var stat = fs.lstatSync(filename);
if (stat.isDirectory() && rec){
fromDir(filename,filter); //recurse
}
if (filename.indexOf(filter)>=0) {
return filename;
}
}
}
const xcodeProjPath = fromDir('platforms/ios','.xcodeproj', false);
const projectPath = xcodeProjPath + '/project.pbxproj';
const myProj = xcode.project(projectPath);
function unquote(str) {
if (str) return str.replace(/^"(.*)"$/, "");
}
function getProjectName(myProj) {
var projectName = myProj.getFirstTarget().firstTarget.name;
projectName = unquote(projectName);
return projectName;
}
function set_FRAMEWORK_SEARCH_PATHS(proj) {
const lineToAdd = '"\"' + getProjectName(proj) + '/Plugins/' + pluginId + '\""'
const FRAMEWORK_SEARCH_PATHS = proj.getBuildProperty("FRAMEWORK_SEARCH_PATHS");
if(FRAMEWORK_SEARCH_PATHS != null) {
const isArray = typeof FRAMEWORK_SEARCH_PATHS != 'string';
if(isArray) {
for(var entry of FRAMEWORK_SEARCH_PATHS) {
if(entry.indexOf(pluginId) != -1) {
return false; // already exists, no need to do anything.
}
}
} else { // string
if(FRAMEWORK_SEARCH_PATHS.indexOf(pluginId) != -1) {
return false; // already exists, no need to do anything.
}
}
var newValueArray = isArray?FRAMEWORK_SEARCH_PATHS:[FRAMEWORK_SEARCH_PATHS];
newValueArray.push(lineToAdd);
proj.updateBuildProperty("FRAMEWORK_SEARCH_PATHS", newValueArray);
} else {
proj.addBuildProperty("FRAMEWORK_SEARCH_PATHS", lineToAdd);
}
return true;
}
myProj.parse(function (err) {
if(err) {
deferral.reject('Error while parsing project');
}
if(set_FRAMEWORK_SEARCH_PATHS(myProj)) {
fs.writeFileSync(projectPath, myProj.writeSync());
console.log('Added Framework Search Path for ' + pluginId);
} else {
console.log('Framework Search Path was already added for ' + pluginId);
}
deferral.resolve();
});
return deferral.promise;
};
注意:挂钩依赖于一个名为 "xcode" 的 NPM 依赖项,因此之前 npm i xcode --save
也是如此(无需编辑挂钩代码)。
现在我们在 plugin.xml 中声明将 .framework
内容导入我们的项目的方式如下:
<source-file src="ios/libs/CameraWizard.framework" />
<resource-file src="ios/libs/CameraWizard.framework/CameraWizard" />
我们使用 source-file
标签简单地导入 .framework
因为我们只想将它复制到 iOS 平台插件目录,我们不希望拥有它 "strongly" 链接,我们需要它的原因只是因为它是 Headers
,而不是二进制的。我们的钩子将为它添加正确的框架搜索路径。
然后我们使用 resource-file
只导入 .framework
目录中的共享库文件,我们将其添加为资源,以便在应用程序启动时调用 dlopen(...)
,共享库将在运行时找到。
最后,现在要在您的插件代码中使用共享库,请执行以下操作:
#import <dlfcn.h>
(同时导入您的 .framework
的 headers)。
在-(void)pluginInitialize
方法下,加载共享库:
NSString* resourcePath = [[NSBundle mainBundle] resourcePath];
NSString* dlPath = [NSString stringWithFormat: @"%@/FrameworkFileNameInResourceTag", resourcePath];
const char* cdlpath = [dlPath UTF8String];
dlopen(cdlpath, RTLD_LAZY|RTLD_GLOBAL);
现在使用共享库中的 class:
SomeClassInFramework someInstance = [(SomeClassInFramework)[NSClassFromString(@"SomeClassInFramework") alloc] init];
在我的 plugin.xml 中,我试图声明一个自定义 .framework 并将其弱链接,但是一旦我打开 Xcode 我看到添加的框架仍然标记为 "required" 而不是 "optional".
这是我的 plugin.xml 条目:
<framework src="ios/libs/BlaBla.framework" custom="true" weak="true" />
这是我收到的第 3 方自定义 .framework
,其中包含 Headers(显然)和共享动态库文件(我将在运行时使用 dlopen("TheDylib", RTLD_LAZY|RTLD_GLOBAL);
加载)。
我不能使用 <header-file src="BlaBla.framework/Headers/Bla.h" />
的原因是 .framework
中的 headers 本身引用内部 headers 和 #import <BlaBla.framework/SomeHeader.h>
所以 <header-file>
标签在这种情况下无能为力。
重要提示
最好使用 "Embedded Frameworks" 功能而不是此解决方案,因为 dlopen
自 non-mac/simulator 设备(真实 iPhones/iPads)上的 iOS 8.0 以来被禁止。
看看Custom Cordova Plugin: Add framework to "Embedded Binaries"
重要说明结束
我最后做了一些不同的事情,我没有将 .framework
声明为 <framework ... />
标签,而是做了以下事情。
我创建了一个插件挂钩,将插件目录添加到 FRAMEWORK_SEARCH_PATHS Xcode build 属性.
<hook type="after_platform_add" src="hooks/addPluginDirToFrameworkSearchPaths/hook.js" />
挂钩代码:
module.exports = function(context) {
const includesiOS = context.opts.platforms.indexOf('ios') != -1;
if(!includesiOS) return;
const
deferral = context.requireCordovaModule('q').defer(),
pluginId = context.opts.plugin.id;
const xcode = require('xcode'),
fs = require('fs'),
path = require('path');
function fromDir(startPath,filter, rec) {
if (!fs.existsSync(startPath)){
console.log("no dir ", startPath);
return;
}
const files=fs.readdirSync(startPath);
for(var i=0;i<files.length;i++){
var filename=path.join(startPath,files[i]);
var stat = fs.lstatSync(filename);
if (stat.isDirectory() && rec){
fromDir(filename,filter); //recurse
}
if (filename.indexOf(filter)>=0) {
return filename;
}
}
}
const xcodeProjPath = fromDir('platforms/ios','.xcodeproj', false);
const projectPath = xcodeProjPath + '/project.pbxproj';
const myProj = xcode.project(projectPath);
function unquote(str) {
if (str) return str.replace(/^"(.*)"$/, "");
}
function getProjectName(myProj) {
var projectName = myProj.getFirstTarget().firstTarget.name;
projectName = unquote(projectName);
return projectName;
}
function set_FRAMEWORK_SEARCH_PATHS(proj) {
const lineToAdd = '"\"' + getProjectName(proj) + '/Plugins/' + pluginId + '\""'
const FRAMEWORK_SEARCH_PATHS = proj.getBuildProperty("FRAMEWORK_SEARCH_PATHS");
if(FRAMEWORK_SEARCH_PATHS != null) {
const isArray = typeof FRAMEWORK_SEARCH_PATHS != 'string';
if(isArray) {
for(var entry of FRAMEWORK_SEARCH_PATHS) {
if(entry.indexOf(pluginId) != -1) {
return false; // already exists, no need to do anything.
}
}
} else { // string
if(FRAMEWORK_SEARCH_PATHS.indexOf(pluginId) != -1) {
return false; // already exists, no need to do anything.
}
}
var newValueArray = isArray?FRAMEWORK_SEARCH_PATHS:[FRAMEWORK_SEARCH_PATHS];
newValueArray.push(lineToAdd);
proj.updateBuildProperty("FRAMEWORK_SEARCH_PATHS", newValueArray);
} else {
proj.addBuildProperty("FRAMEWORK_SEARCH_PATHS", lineToAdd);
}
return true;
}
myProj.parse(function (err) {
if(err) {
deferral.reject('Error while parsing project');
}
if(set_FRAMEWORK_SEARCH_PATHS(myProj)) {
fs.writeFileSync(projectPath, myProj.writeSync());
console.log('Added Framework Search Path for ' + pluginId);
} else {
console.log('Framework Search Path was already added for ' + pluginId);
}
deferral.resolve();
});
return deferral.promise;
};
注意:挂钩依赖于一个名为 "xcode" 的 NPM 依赖项,因此之前 npm i xcode --save
也是如此(无需编辑挂钩代码)。
现在我们在 plugin.xml 中声明将 .framework
内容导入我们的项目的方式如下:
<source-file src="ios/libs/CameraWizard.framework" />
<resource-file src="ios/libs/CameraWizard.framework/CameraWizard" />
我们使用 source-file
标签简单地导入 .framework
因为我们只想将它复制到 iOS 平台插件目录,我们不希望拥有它 "strongly" 链接,我们需要它的原因只是因为它是 Headers
,而不是二进制的。我们的钩子将为它添加正确的框架搜索路径。
然后我们使用 resource-file
只导入 .framework
目录中的共享库文件,我们将其添加为资源,以便在应用程序启动时调用 dlopen(...)
,共享库将在运行时找到。
最后,现在要在您的插件代码中使用共享库,请执行以下操作:
#import <dlfcn.h>
(同时导入您的.framework
的 headers)。在
-(void)pluginInitialize
方法下,加载共享库:NSString* resourcePath = [[NSBundle mainBundle] resourcePath]; NSString* dlPath = [NSString stringWithFormat: @"%@/FrameworkFileNameInResourceTag", resourcePath]; const char* cdlpath = [dlPath UTF8String]; dlopen(cdlpath, RTLD_LAZY|RTLD_GLOBAL);
现在使用共享库中的 class:
SomeClassInFramework someInstance = [(SomeClassInFramework)[NSClassFromString(@"SomeClassInFramework") alloc] init];