创建好的framework工程模板,会生成一个和工程名相同的头文件,以及一个Resources资源文件夹,我们可以创建新的功能类文件,例如可以新建一个命名为MyLog的类和一个MyTool的类,代码如下:
MyLog.h
// MyLog.h
#import <Foundation/Foundation.h>
@interface MyLog : NSObject
+ (void)log:(NSString *)str;
@end
MyLog.m
#import "MyLog.h"
@implementation MyLog
+ (void)log:(NSString *)str {
NSLog(@"MyLog:%@",str);
}
@end
MyTool.h
#import <Foundation/Foundation.h>
@interface MyTool : NSObject
+ (NSInteger)add:(NSInteger)a another:(NSInteger)b;
@end
MyTool.m
#import "MyTool.h"
@implementation MyTool
+ (NSInteger)add:(NSInteger)a another:(NSInteger)b {
return a + b;
}
@end
在默认生成的库头文件中,引入这两个功能头文件,如下:
#import <Foundation/Foundation.h>
//! Project version number for MyStatic.
FOUNDATION_EXPORT double MyStaticVersionNumber;
//! Project version string for MyStatic.
FOUNDATION_EXPORT const unsigned char MyStaticVersionString[];
#import "MyLog.h"
#import "MyTool.h"
在构建framewrok前,我们可以设置此framework构建成动动态库还是静态库,我们先将其构建成静态库,设置编译选项的Mach-o Type为Static Library,如下:
之后,可以让Xcode进行Build,之后在对应的Products文件夹中可以找到生成的framework文件,如下图所示:
如果你查看此framework文件的包内容,会发现其中有5类文件,如下:
其中,_CodeSignature中存放的是framework的签名文件。
Headers中存放的是头文件,需要注意,在编译framework工程时,要将需要暴露的头文件设置为public。
Info.plist文件是当前framework的配置文件。
Modules中的modulemap文件用来管理LLVM的module map,定义组件结构。
下面,我们可以尝试使用下此静态库,使用Xcode新建一个名为LibDemo的iOS工程,将前面构建的MyStatic.framework文件直接拖入此工程中,在工程的编译选项中,找到Framework Search Paths和Header Search Paths中分别将此framework的路径与头文件的路径进行配置,如下图所示:
修改测试项目的ViewController.m文件如下:
#import "ViewController.h"
#import "MyStatic.framework/Headers/MyStatic.h"
@interface ViewController ()
@end
@implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
NSInteger a = 100;
NSInteger b = 200;
NSInteger c = [MyTool add:a another:b];
[MyLog log:[NSString stringWithFormat:@"%ld", c]];
}
@end
运行代码,从控制台可以看到,我们的静态库已经可以正常工作了。你可能会觉得上面的头文件引入方式非常的丑陋,你完全可以在工程中新建一个文件夹,将framework包内的头文件拷贝过来,如下图:
这样你就可以像引用工程内的头文件一样的使用framework中的功能了:
#import "ViewController.h"
#import "MyStatic.h"
@interface ViewController ()
@end
@implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
NSInteger a = 100;
NSInteger b = 200;
NSInteger c = [MyTool add:a another:b];
[MyLog log:[NSString stringWithFormat:@"%ld", c]];
}
@end
#import <Foundation/Foundation.h>
@interface MyObjectOne : NSObject
@property(copy) NSString *name;
@end
MyObjectOne.m
#import "MyObjectOne.h"
@implementation MyObjectOne
@end
MyObjectTwo.h
#import <Foundation/Foundation.h>
@interface MyObjectTwo : NSObject
@property(copy) NSString *title;
@end
MyObjectTwo.m
#import "MyObjectTwo.h"
@implementation MyObjectTwo
@end
按照同样的方式,将构建好的framework文件拖入到测试工程中,配置头文件路径,添加测试代码如下:
#import "ViewController.h"
#import "MyStatic.h"
#import "MyDylib.framework/Headers/MyDylib.h"
@interface ViewController ()
@end
@implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
NSInteger a = 100;
NSInteger b = 200;
NSInteger c = [MyTool add:a another:b];
[MyLog log:[NSString stringWithFormat:@"%ld", c]];
MyObjectOne *one = [[MyObjectOne alloc] init];
one.name = @"Hello";
[MyLog log:one.name];
}
试下编译运行,目前为止,看上去一切正常,但是当程序运行起来后会崩溃,控制台会输出如下信息:
dyld[72035]: Library not loaded: @rpath/MyDylib.framework/MyDylib
产生这个异常的原因是没有找到动态库文件,静态库的动态库的区别出现了,怎么解决这个问题呢,其实很简单,我们找到当前测试工程编译的产出可执行文件,点击显示包内容,在其中新建一个Frameworks的文件夹,将MyDylib.framework文件拷贝进入,如下图所示:
现在再运行工程,你会发现程序已经可以正常执行了。但是手动拷贝动态库到可执行文件的操作非常不优雅,如果真的要在项目中使用动态库,我们更多时候会通过自动化的脚本来实现复制库文件这一步操作。
通过这些实践,我们好像能感觉到静态库和动态库之间有些什么不同,但究竟哪里不同呢?我们带着疑问继续探索。
可以看到,静态库的结构其实是比较简单的,除了库本身的一些描述文件,符号表外,基本就是其他可执行文件的集合了,在图中可以看到,每个可执行文件都会有一些头数据,这些头数据记录了可执行未见的名字,大小等信息。可以点开任意一个可执行文件,其中就是我们熟悉的各种代码段,数据段等数据了:
我们再来看动态库,其结构如下:
可以看到动态库本身就是一个可执行文件,其并不是将内部的所有.o文件做简单的集合,而是一个最终链接完成的镜像文件。由于动态库是运行时进行链接的,其无法做编译时的优化,看上去可能会增加应用包的大小,但是实际应用中,我们大多会采用-Objc参数来强制静态库链接所有OC文件,并且静态库中每一个.o文件都会有一个头信息,而动态库则省略了这部分信息,因此最终对影响应用包大小这一方面来说,并不一定静态库更优。但是有一点是确定了,静态库是编译时链接,会节省应用启动时间。往往在做优化类的项目时,没有固定的方案,我们要根据实际情况,选择最合适自己的方案。
可以看到,其中有一些动态库的加载指令,Foundation,UIKit等都是系统的动态库,我们可以在其详情中看到详细的加载路径,如下:
对于我们自己的MyDylib库,其加载路径如下:
可以看到,这个动态库是从@rpath/MyDylib.framework/MyDylib这个路径来加载的,这个加载路径的设置在动态库编译时就已经确定,我们可以看下MyDylib这个工程,在Xcode的编译配置选项中,找到Dynamic Library Install Name选项,如下所示:
这里的@rpath实际上是一个环境变量,在应用工程中可以配置@rpath的值,在LibDemo工程的编译选项中搜rpath,可以看到这个环境变量的配置:
现在我们清楚了,其实动态库文件不一定要放入Frameworks文件夹下,修改@rpath变量的路径即可修改动态库的加载路径。
对于动态库的这种加载方式,原则上,我们可以修改此二进制文件的加载路径,也可以直接替换包内的动态库文件,实现一些逆向注入的功能,非常酷。
修改ViewController类的代码如下:
#import "ViewController.h"
#import "MyStatic.h"
#import <dlfcn.h>
@interface ViewController ()
@end
@implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
NSInteger a = 100;
NSInteger b = 200;
NSInteger c = [MyTool add:a another:b];
[MyLog log:[NSString stringWithFormat:@"%ld", c]];
NSString *path = [[[NSBundle mainBundle] pathForResource:@"MyDylib" ofType:@"framework"] stringByAppendingString:@"/MyDylib"];
// 载入动态库
void * p = dlopen([path cStringUsingEncoding:NSUTF8StringEncoding], RTLD_LAZY);
if (p) {
// 加载动态库成功 直接使用
Class cls = NSClassFromString(@"MyObjectOne");
NSObject *obj = [[cls alloc] init];
[obj performSelector:@selector(setName:) withObject:@"Hello"];
[MyLog log:[obj performSelector:@selector(name)]];
}
}
@end
此时,再次编译运行此工程,如果你观察测试项目的二进制文件,里面的加载命令中已经没有了MyDylib的加载,但是程序依然可以正常的执行,dlopen函数的作用就是在运行时载入动态链接库,载入成功后,我们可以借助OC的运行时方法,直接调用到动态库中的代码。通过这种方式,我们实际上可以实现插件动态下载与使用,使得应用有非常高的热更新能力,但是需要注意,动态下载动态库的方式并不允许在AppStore上架,我们只能在测试的App或企业的App中使用。
再进一步说,其实动态库的读取并不一定是从本地沙盒中,在本地调试时,你可以从任何位置读取动态库文件进行加载,这可以在本地实现很多非常酷的功能,比如Injection工具,它通过一个服务监听代码文件的变化,之后将其打包成动态库注入到程序中,再通过运行时替换类和方法,从而实现本地开发iOS项目的热更新效果,非常好用。
专注技术,懂的热爱,愿意分享,做个朋友 QQ:316045346
这里有一个很好的答案解释了如何在Ruby中下载文件而不将其加载到内存中:https://stackoverflow.com/a/29743394/4852737require'open-uri'download=open('http://example.com/image.png')IO.copy_stream(download,'~/image.png')我如何验证下载文件的IO.copy_stream调用是否真的成功——这意味着下载的文件与我打算下载的文件完全相同,而不是下载一半的损坏文件?documentation说IO.copy_stream返回它复制的字节数,但是当我还没有下
我正在尝试解析一个文本文件,该文件每行包含可变数量的单词和数字,如下所示:foo4.500bar3.001.33foobar如何读取由空格而不是换行符分隔的文件?有什么方法可以设置File("file.txt").foreach方法以使用空格而不是换行符作为分隔符? 最佳答案 接受的答案将slurp文件,这可能是大文本文件的问题。更好的解决方案是IO.foreach.它是惯用的,将按字符流式传输文件:File.foreach(filename,""){|string|putsstring}包含“thisisanexample”结果的
1.错误信息:Errorresponsefromdaemon:Gethttps://registry-1.docker.io/v2/:net/http:requestcanceledwhilewaitingforconnection(Client.Timeoutexceededwhileawaitingheaders)或者:Errorresponsefromdaemon:Gethttps://registry-1.docker.io/v2/:net/http:TLShandshaketimeout2.报错原因:docker使用的镜像网址默认为国外,下载容易超时,需要修改成国内镜像地址(首先阿里
有没有办法在Ruby中动态创建数组?例如,假设我想遍历用户输入的书籍数组:books=gets.chomp用户输入:"TheGreatGatsby,CrimeandPunishment,Dracula,Fahrenheit451,PrideandPrejudice,SenseandSensibility,Slaughterhouse-Five,TheAdventuresofHuckleberryFinn"我把它变成一个数组:books_array=books.split(",")现在,对于用户输入的每一本书,我想用Ruby创建一个数组。伪代码来做到这一点:x=0books_array.
我想在IRB中浏览文件系统并让提示更改以反射(reflect)当前工作目录,但我不知道如何在每个命令后进行提示更新。最终,我想在日常工作中更多地使用IRB,让bash溜走。我在我的.irbrc中试过这个:require'fileutils'includeFileUtilsIRB.conf[:PROMPT][:CUSTOM]={:PROMPT_N=>"\e[1m:\e[m",:PROMPT_I=>"\e[1m#{pwd}>\e[m",:PROMPT_S=>"FOO",:PROMPT_C=>"\e[1m#{pwd}>\e[m",:RETURN=>""}IRB.conf[:PROMPT_MO
print"Enteryourpassword:"pass=STDIN.noecho(&:gets)puts"Yourpasswordis#{pass}!"输出:Enteryourpassword:input.rb:2:in`':undefinedmethod`noecho'for#>(NoMethodError) 最佳答案 一开始require'io/console'后来的Ruby1.9.3 关于ruby-为什么不能使用类IO的实例方法noecho?,我们在StackOverflow上
首先,我使用的是rails3.1.3和来自master的carrierwavegithub仓库的分支。我使用after_init钩子(Hook)来确定基于属性的字段页面模型实例并为这些字段定义属性访问器将值存储在序列化哈希中(希望它清楚我是什么谈论)。这是我正在做的事情的精简版:classPage省略mount_uploader命令让我可以访问我想要的属性。但是当我安装uploader时出现错误消息说“nil类的未定义新方法”我在源代码中读到有方法read_uploader和扩展模块中的write_uploader。我如何必须覆盖这些来制作mount_uploader命令使用我的“虚拟
我正在尝试动态构建一个多维数组。我想要的基本上是这样的(为简单起见写出来):b=0test=[[]]test[b]这给了我错误:NoMethodError:undefinedmethod`test=[[],[],[]]而且它工作正常,但在我的实际使用中,我不会事先知道需要多少个数组。有一个更好的方法吗?谢谢 最佳答案 不需要像您正在使用的索引变量。只需将每个数组附加到您的test数组:irb>test=[]=>[]irb>test[["a","b","c"]]irb>test[["a","b","c"],["d","e","f"]]
如何只加载map边界内的标记gmaps4rails?当然,在平移和/或缩放后加载新的。与此直接相关的是,如何获取map的当前边界和缩放级别? 最佳答案 我是这样做的,我只在用户完成平移或缩放后替换标记,如果您需要不同的行为,请使用不同的事件监听器:在你看来(index.html.erb):{"zoom"=>15,"auto_adjust"=>false,"detect_location"=>true,"center_on_user"=>true}},false,true)%>在View的底部添加:functiongmaps4rail
如何在对象上调用方法名称的嵌套哈希?例如,给定以下哈希:hash={:a=>{:b=>{:c=>:d}}}我想创建一个方法,给定上面的散列,执行以下操作:object.send(:a).send(:b).send(:c).send(:d)我的想法是我需要从一个未知的关联中获取一个特定的属性(这个方法不知道,但程序员知道)。我希望能够指定一个方法链来以嵌套哈希的形式检索该属性。例如:hash={:manufacturer=>{:addresses=>{:first=>:postal_code}}}car.execute_method_hash(hash)=>90210