在 App 运行时通过 dlopen 和 dlsym 链接加载 bundle 里的动态库。

我们一般引入某个 framework 可能是直接使用#import <AVFoundation/AVFoundation.h>其实还有另一种办法就是使用函数啊 dlopen

函数定义: 
  void * dlopen( const char * pathname, int mode ); 
  函数描述: 
  在dlopen的()函数以指定模式打开指定的动态连接库文件,并返回一个句柄给调用进程。使用dlclose()来卸载打开的库。 
  mode:分为这两种 
  RTLD_LAZY 暂缓决定,等有需要时再解出符号 
  RTLD_NOW 立即决定,返回前解除所有未决定的符号。 
  RTLD_LOCAL 
  RTLD_GLOBAL 允许导出符号 
  RTLD_GROUP 
  RTLD_WORLD 

  返回值: 
  打开错误返回NULL 
  成功,返回库引用 
  编译时候要加入 -ldl (指定dl库) 

dlsym()

 
 功能:

根据动态链接库操作句柄与符号,返回符号对应的地址。
包含头文件:
#include <dlfcn.h>
函数定义:
void*dlsym(void* handle,const char* symbol)
函数描述:
dlsym根据动态链接库操作句柄(handle)与符号(symbol),返回符号对应的地址。使用这个函数不但可以获取函数地址,也可以获取变量地址。
handle是由dlopen打开动态链接库后返回的指针,symbol就是要求获取的函数或全局变量的名称。
dlclose()

 

dlclose用于关闭指定句柄的动态链接库,只有当此动态链接库的使用计数为0时,才会真正被系统卸载。
 

示例函数

-(void)mwviewDidLoad{
    //打开动态库
    NSString *path = [[NSBundle mainBundle] pathForResource:@"Kidswaste-Sans Toi" ofType:@"mp3"];
    // 加载库到当前运行程序
    void *lib = dlopen("/System/Library/Frameworks/AVFoundation.framework/AVFoundation", RTLD_LAZY);
    if (lib) {
        // 获取类名称
        Class playerClass = NSClassFromString(@"AVAudioPlayer");
        // 获取函数方法
        SEL selector = NSSelectorFromString(@"initWithData:error:");
        // 调用实例化对象方法
        _runtime_Player = [[playerClass alloc] performSelector:selector withObject:[NSData dataWithContentsOfFile:path] withObject:nil];
        // 获取函数方法
        selector = NSSelectorFromString(@"play");
        // 调用播放方法
        [_runtime_Player performSelector:selector];
        NSLog(@"动态加载播放");
    }
    dlclose(lib);
    NSLog(@"hello world from mwviewDidLoad");
    [self mwviewDidLoad];
    
}

2018/7/5 posted in  iOS

Swift 和 OC 实现“热加载”

众所周知,Google 推出的跨平台框架Flutter 有着很好的编译优势Flutter Hot Reload,每次改变之后,点击 reload 就会实现实时编译。那么其实我们也可以实现SwiftOC的实时编译。

InjectionIII

通过这个工具可以实现类似Flutter 的快速编译。

实现思路:

Injection 会监听源代码文件的变化,如果文件被改动了,Injection Server 就会执行 rebuildClass 重新进行编译、打包成动态库,也就是 .dylib 文件。编译、打包成动态库后使用 writeSting 方法通过 Socket 通知运行的 App。

安装方式

  1. Mac App Store下载安装

    屏幕快照 2019-03-27 下午4.26.20

  2. Github

    这个工具开发作者是John Holdsworth,作者已经开源了这个工具,地址是https://github.com/johnno1962/InjectionIII

我使用的是在Mac App Store安装的方式,这样比较方便一些,如果你想专研源码,可以去到 GitHub 去研究。

使用指南
  1. 打开工程

    安装之后打开会出现在 mac 的 menubar 点击打开你目标的工程文件的根目录。

    屏幕快照 2019-03-27 下午5.57.39

  2. 在 applicationDidFinishLaunching加载Injection包

    Xcode 10.1:

    #if DEBUG
    Bundle(path: "/Applications/InjectionIII.app/Contents/Resources/iOSInjection10.bundle")?.load()
    //for tvOS:
    Bundle(path: "/Applications/InjectionIII.app/Contents/Resources/tvOSInjection10.bundle")?.load()
    //Or for macOS:
    Bundle(path: "/Applications/InjectionIII.app/Contents/Resources/macOSInjection10.bundle")?.load()
    #endif

    Xcode 10.2 and later:

    #if DEBUG
    Bundle(path: "/Applications/InjectionIII.app/Contents/Resources/iOSInjection.bundle")?.load()
    //for tvOS:
    Bundle(path: "/Applications/InjectionIII.app/Contents/Resources/tvOSInjection.bundle")?.load()
    //Or for macOS:
    Bundle(path: "/Applications/InjectionIII.app/Contents/Resources/macOSInjection.bundle")?.load()
    #endif

上面是swift版本的NSBundle加载包,下面的代码是oc的实现方法

[[NSBundle bundleWithPath:@"/Applications/InjectionIII.app/Contents/Resources/iOSInjection10.bundle"] load];
  1. 运行工程

    选择模拟器,cmd+R启动编译

  2. 修改文件注入代码

    每当对代码进行修改之后,一定要cmd+S保存(不然Injection不会分析变动以及替换符号)实现快速编译。不用重新 cmd+R。

注意事项

屏幕快照 2019-03-27 下午5.56.38

还有一点就是如果你在 menubar 打开了这个程式的File Watcher那么就只需要cmd+S 如果你没有开启这个功能,还要多一步命令control+=(同时按住 control 和 =)。

还有就是由于沙盒机智,这个方法只能使用在模拟器上,对真机无效。

2018/6/20 posted in  iOS

What's New in LLVM(2017)

Xcode 9中的Apple LLVM编译器具有新的语言特性、改进的诊断和更强大的优化。了解Objective-C和c++的最新添加,了解新的和改进的警告和静态分析器检查,了解LLVM编译器技术如何为应用程序提供更快的构建时间和更好的运行时性能。

1.不同语言的api检查

@available(iOS 11,*)--oc
#available  --swift

便捷的方法检查(检查可用性,如果你要给某个新的系统加入新功能)

@interface MyAblumController : UIViewController

-(void)showFaces API_AVALABILITY(ios(11.0));

@end

还可以移动整个类的范围

API_AVALABILITY(ios(11.0))
@interface MyAblumController : UIViewController

-(void)showFaces ;

@end

2.不要把Number元素和标量进行比较

@property NSNumber * photoCount;
-(BOOL)hasPhotos{
return self.photoCount > 0
}

因为这个比较相当于是把photoCount和指针nil相比,返回为YES,所以这个错误很严重。

屏幕快照 2018-12-12 下午4.26.59

编译器会报错,提醒你出现了问题,你可以把NSNumber转成interger之后再做判断。

屏幕快照 2018-12-12 下午4.28.18

Control Check in Build Setting

设置Suspicious Conversions 为YES(aggressive)当编译器不确定的时候会弹出如上提示。
屏幕快照 2018-12-12 下午5.06.22

3. copy修饰NSMutableArray

赋值的时候等于是复制了一个不可变对象,所以以后运行时的时候会报错(如果你对这个数组进行修改)

屏幕快照 2018-12-12 下午6.45.36

这个时候就会报错,因为给self.titieArr赋值之后变成了不可变的,所以接下来的操作会导致错误。

解决方法:
//添加setter方法
-(void)setTitleArr:(NSMutableArray *)titleArr{
    
    self.titleArr= [titleArr mutableCopy];
}

每次赋值的时候给数组赋值可变对象。

另外为了及时发现bug可以开启每次build的时候analyze而不是每次都要去手动analyze(如果不开启的话上面的错误不会提示)

屏幕快照 2018-12-12 下午6.48.45
设置Analyze During Build 的值为Yes.

屏幕快照 2018-12-12 下午6.43.05

4. 声明无参数的函数

Non-prototype declarations in C and Objective-C

非原型声明

屏幕快照 2018-12-13 下午3.47.06

在C和OC中意味着foo可以跟任何参数调用,这个行为可以追溯到1977年C语言,不匹配类型的调用会导致运行时的崩溃。
在Xcode9 中编译器带有一个新的警告加强严格原型,会报出警告。

屏幕快照 2018-12-13 下午3.50.45

通常只需要在括号中添加void表示不接收任何参数传递。

屏幕快照 2018-12-13 下午3.51.41

任何带有参数的调用都会直接报错。

另外block中不接收参数的时候也会出现同样的警告,如下所示。

屏幕快照 2018-12-13 下午3.52.47

屏幕快照 2018-12-13 下午3.53.57

升级验证警告设置

屏幕快照 2018-12-13 下午4.08.45

勾选Yes(Error)

Strict Prototypes 勾选Yes(Error)之前 and 勾选之后

Artboard 2@2x

勾选之后就不只是警告了,直接是错误提醒,让你开发的时候不得不注意,也是为了代码的安全性。

结尾

WWDC2017剩余部分是关于C++的优化,如果感兴趣的话可以移步开发者网站观看。

2018/6/10 posted in  iOS