从一个蓝牙打印说起

Author Avatar
XibHe 5月 28, 2018
  • 在其它设备中阅读本文章

遇到在 Release 模式下 80mm蓝牙打印机 无法打印的问题,最终的解决方式都是围绕 DebugRelease 这两种模式展开的。

可能打造无法打印的原因

1.需要打印的处方药品过多,排列太过复杂造成

于是,构建一个最简单的打印模板,打印一行最简单的日期,将打印功能尽可能的简单化,看是否是由打印功能本身引起的。结果,仍然无法打印。

2.线程问题,未在主线程刷新UI导致

每次选择蓝牙打印机打印处方单时,控制台就会输出线程相关的错误,如下:

2018-05-18 15:52:30.568985+0800 CloudOfficeTest[3340:1191905] [reports] Main Thread Checker: UI API called on a background thread: -[UIApplication windows]
PID: 3340, TID: 1191905, Thread name: (none), Queue name: com.apple.root.default-qos.overcommit, QoS: 21
Backtrace:
4   LZBasisComponents                   0x0000000101baad4c +[MBProgressHUD hideHUDForView:animated:] + 132
5   LZBasisComponents                   0x0000000101baab9c +[MBProgressHUD(MJ) hideHUDForView:] + 72
6   LZBasisComponents                   0x0000000101baabf0 +[MBProgressHUD(MJ) hideHUD] + 48
7   CloudOfficeTest                     0x0000000100aee2c8 -[LZBluetoothPrintView startPrint] + 300
8   Foundation                          0x00000001828de860 <redacted> + 996
9   libsystem_pthread.dylib             0x0000000181b1831c <redacted> + 308
10  libsystem_pthread.dylib             0x0000000181b181e8 <redacted> + 0
11  libsystem_pthread.dylib             0x0000000181b16c28 thread_start + 4
2018-05-18 15:52:38.876328+0800 CloudOfficeTest[3340:1191725] This application is modifying the autolayout engine from a background thread after the engine was accessed from the main thread. This can lead to engine corruption and weird crashes.
 Stack:(
    0   Foundation                          0x00000001829f266c <redacted> + 96
    1   Foundation                          0x00000001829f2350 <redacted> + 76
    2   Foundation                          0x0000000182822da0 <redacted> + 132
    3   Foundation                          0x00000001829f0458 <redacted> + 112
    4   UIKit                               0x000000018b610b18 <redacted> + 172
    5   UIKit                               0x000000018b32234c <redacted> + 1348
    6   QuartzCore                          0x0000000185edbec8 <redacted> + 184
    7   QuartzCore                          0x0000000185edffa8 <redacted> + 332
    8   QuartzCore                          0x0000000185e4ea98 <redacted> + 336
    9   QuartzCore                          0x0000000185e74eb4 <redacted> + 540
    10  QuartzCore                          0x0000000185e7559c <redacted> + 244
    11  libsystem_pthread.dylib             0x0000000181b17680 <redacted> + 572
    12  libsystem_pthread.dylib             0x0000000181b173d0 <redacted> + 88
    13  libsystem_pthread.dylib             0x0000000181b17168 _pthread_wqthread + 1340
    14  libsystem_pthread.dylib             0x0000000181b16c20 start_wqthread + 4
)

这里报错是由于 MBProgressHUD 导致的,将打印时的 MBProgressHUD 都注释掉,仍然无法打印。 又因为在打印时使用了 NSThread 去更新打印状态,就怀疑是 NSThread 导致的问题。

    if (thread == NULL) {
        [GlobalMethod showName:@"正在打印中" inView:_contentView delay:3];
        thread = [[NSThread alloc] initWithTarget:self selector:@selector(startPrint) object:nil];
        [thread start];
    } else {
        [GlobalMethod showName:@"正在打印中" inView:_contentView delay:3];
    }

这里索性就把 NSThread 的代码也注释掉。此时,编译后再次调用打印
功能,后台虽然不会输出线程相关的错误了,但连接上的蓝牙打印机仍然没有任何反应。

3.当 Edit SchemeBuild ConfigurationDebug 时,就可以调用蓝牙打印机进行打印。

选择Xcode中,Product -> Scheme -> Edit Scheme,注意 RunArchive 这两种编译方式,选择 Build Configuration 就可以切换 DebugRelease 这两种不同的模式。

这就需要对比 releasedebug 两种模式的不同,尤其是在调用 CoreBluetooth 时的不同。

Release 是发行版本,比 Debug 版本多一些优化,文件比 Debug 文件小。 Debug 是调试版本,DebugRelease 调用两个不同的底层库。通俗点讲,我们开发者自己内部真机或模拟器调试时,使用 Debug 模式就好,等到想要发布时,也就是说需要大众客户使用时,需要构建 Release 版本,具体区别如下:

  1. Debug 是调试版本,包括的程序信息更多;
  2. 只有 Debug 版的程序才能设置断点、单步执行、使用 TRACE/ASSERT 等调试输出语句;
  3. Release 不包含任何调试信息,所以体积小、运行速度快。

简而言之,Release 版本会对最终发布的项目做优化,以提高 App 的运行速度。

//读取特征中的数据
- (void)peripheral:(CBPeripheral *)peripheral didUpdateValueForCharacteristic:(CBCharacteristic *)characteristic error:(NSError *)error {

    NSLog(@"enter didUpdateValueForCharacteristic!");
    NSLog(@"read data=%@!",characteristic.value);
}

如果将此处的 NSLog 输出注释掉是否也可以打印?

控制台会一直输出打印类 LZBluetoothPrintView 的一个方法

<LZBluetoothPrintView.m : 382> -[LZBluetoothPrintView peripheral:didUpdateValueForCharacteristic:error:]
2018-05-18 15:52:38.778826+0800 CloudOfficeTest[3340:1191349] enter didUpdateValueForCharacteristic!
- (void)viewDidLoad
{
    [super viewDidLoad];
#ifdef DEBUG
    NSLog(@"Test DEBUG mode");
#else
    NSLog(@"Test Release mode");
#endif
}

不同的解决方案

方案一:将 Build Settings 里面的 Apple LLVM 9.0 - Preprocessing - Preprocessor Macros 这里。在 release 下增加配置项 DEBUG = 1,即可打印。

只是暂时解决了打印问题,慎用!因为默认情况下 release 模式是不需要设置为 DEBUG 模式的。

这里有一个疑问?当设置 Edit SchemeArchiveBuild ConfigurationRelease 时,此时,Archive 后生成一个 ipa包,导出并安装后发现程序中调用的仍然 DEBUG 模式下的程序。代码如下:

- (void)viewDidLoad {
    [super viewDidLoad];

    UIView *backView1 = [[UIView alloc] initWithFrame:CGRectMake(self.view.center.x - 20, self.view.center.y - 20, 40, 40)];
    [self.view addSubview:backView1];

#ifdef DEBUG
    backView1.backgroundColor = [UIColor redColor];
    NSLog(@"Test DEBUG mode");
#else
    backView1.backgroundColor = [UIColor greenColor];
    NSLog(@"Test Release mode");
#endif
}

此时,打开通过 ipa 包安装的程序,展示的为红色的视图。

方案二:将 Build Settings 里面的 Optimization Levelrelease 对应的配置项改为 None[-OO]

修改 release 模式下的编译策略为 -OO 即,不做任何编译优化。编译策略是对代码编译过程的优化(c->汇编),优化后的代码效率比较高,但是可读性比较差,且编译时间更长。

GCC_OPTIMIZATION_LEVEL = Fastest, Smallest [-OS] 优化级别:

  • None: 不做优化使用这个设置,编译器的目标是减少编译成本,使调试产生预期的结果。
  • Fast:优化编译将为大函数占用更多的时间和内存使用这个设置,编译器将尝试减少代码的大小和执行时间,不进行任何优化,需要大量编译时间。
  • Faster:编译器执行几乎所有支持的优化,它不考虑空间和速度之间的平衡与“Fast”设置相比,该设置会增加编译时间和生成代码的性能。编译器不进行循环展开、内联函数和寄存器变量的重命名。
  • Fastest:开启“Faster”支持的所有的优化,同时也开启内联函数和寄存器变量的重命名选项
  • Fastest,smallest:优化代码大小这个设置启用“Faster”所有的优化,一般不增加代码大小,它还执行旨在减小代码大小的进一步优化。

方案三:将蓝牙打印页面的预览视图中所有 #define NSLog(format, …) do 对应的 NSLog 注释掉。

最后发现将打印页面的所有输出语句注释掉不会起到任何作用,因此,只剩下 方案二 可行了。

方案四:从源码编译入手,利用 Clang 在编译为可执行文件前,过滤编译过程中影响打印的操作。

相对而言,这种方式是最复杂,最有难度的一种方式。但却能追本溯源,从根本上明白为什么无法打印?

从源码到可执行文件

Low Level Virtual Machine (LLVM) 是一个开源的编译器架构,Clang 是 LLVM 的一个编译器前端,它目前支持 C, C++, Objective-C 以及 Objective-C++ 等编程语言。Clang 对源程序进行词法分析和语义分析,并将分析结果转换为 Abstract Syntax Tree ( 抽象语法树 ) ,最后使用 LLVM 作为后端代码的生成器。

利用LLVM 将源码编译为可被不同语言识别的中间表示(IR)。

可以用Clang做什么?

1. libclang进行语法分析

可以使用libclang里面提供的方法对源文件进行语法分析,分析它的语法树,遍历语法树上面的每一个节点。可以用于检查拼写错误,或者做字符串加密。

2. LibTooling

对语法树有完全的控制权,可以作为一个单独的命令使用,如:clang-format

3. ClangPlugin

对语法树有完全的控制权,作为插件注入到编译流程中,可以影响build和决定编译过程。目录:llvm/tools/clang/examples

这里蓝牙打印的在release模式下无法打印的问题,涉及到编译器编译至可执行文件时,中间所做的优化处理。可以尝试从编译流程入手,进行相应的语法分析,过滤掉非必须但影响蓝牙打印的优化操作。

通过遍历语法树,去修改里面的方法名和返回变量名。

.cpp 文件,在编译源文件时,C++编译器会对符号(函数或变量)名作某些修正,修正后生成目标文件的后缀为.cpp。

基于 Pass,我们可以做什么? 我们可以编写自己的 Pass 去混淆代码,以增加他人反编译的难度。

  1. LLVM 编译一个源文件的过程:
    预处理 -> 词法分析 -> Token -> 语法分析 -> AST -> 代码生成 -> LLVM IR -> 优化 -> 生成汇编代码 -> Link -> 目标文件
  2. 基于 LLVM,我们可以做什么?
    1. 做语法树分析,实现语言转换 OC 转 Swift、JS or 其它语言,字符串加密。
    2. 编写 ClangPlugin,命名规范,代码规范,扩展功能。
    3. 编写 Pass,代码混淆优化”

参考资料

手把手教你给一个iOS app配置多个环境变量

XCode debug vs release build when debugging

初识 LLVM

结构化编译器前端 Clang 介绍

–EOF–

若无特别说明,本站文章均为原创,转载请保留链接,谢谢