苹果手机上刻绘机加工输出中心app设计

   

因手机自身的普及度广,体积小,易携带,以手机作为刻绘机的加工输出中心,既提高了刻绘机工作的便捷性,又间接加强了数据传输的效率。蓝牙传输虽然在传输速率上没有明显的优势,但对于刻绘机来说基本满足,且其具有很好的适用性,协议较为简单,没有太大危害性等优点,被广泛应用于不同的传输场合中。本文设计了基于iOS系统,coreBluetooth蓝牙框架的刻绘机输出加工中心app,用于实现手机向刻绘机发送指令等的要求。

 本论文基于iOS系统,以xcode为设计平台,objective-c为app开发语言,采用了coreBluetooth蓝牙框架进行蓝牙连接,传输数据等,其中数据发送以每包最大20字节,延迟20ms的形式进行分包发送。此外,本次的app通过UItableview类的numberofRowsInSection及cellForRowAtIndexPath等代理方法创建了以表格为显示基础的app界面,采用系统自带文件预览控制器QLPreviewController设计了对所需发送的文件的预览功能。

实验结果表明:传输效率较高,蓝牙连接稳定,没有丢包现象,传输准确。

关键词:iOS;xcode;objective-c;coreBlue

目录

第一章  绪  论. 1

1.1 引言... 1

1.2 课题背景... 1

1.3刻绘机发展现状... 1

1.3课题研究主要内容... 2

第二章 app开发环境及工具. 3

2.1引言... 3

2.2objective-c. 3

2.3开发环境及工具... 3

2.4本章小结... 4

第三章 蓝牙及CoreBluetooth框架. 5

3.1蓝牙模块... 5

3.2蓝牙协议... 6

3.3蓝牙框架... 9

第四章Xcode实现. 12

4.1 app界面... 12

4.2蓝牙连接实现... 14

4.3文件的打开和发送... 15

4.4预览功能... 21

4.5动画显示功能... 23

第五章 校验及安全机制. 26

5.1 数据校验... 26

5.2安全机制... 27

第六章 试验结果及分析. 28

5.2实验结果... 28

6.2出现的问题... 30

6.3成本分析... 31

第七章 总结与展望. 32

7.1 总结... 32

                                         第一章  绪  论


1.1 引言

刻字机是一种通过电脑端控制,在纸张,塑料等柔性材质上刻绘出我们所需要的图形的设备。其主要原理是通过相关的绘图软件,绘制出图案,由电脑通过usb连接发出指令,控制刻刀绘出图形。

苹果手机是如今使用最多的手机机型之一,它具有完善的操作系统和良好的开发环境,适合各种应用的app开发。蓝牙则因其成本低,危害小,协议简单等优点适用于不同场合的中近距离传输。因此,本文决定以苹果手机为刻绘机的输出中心,蓝牙为它们间的连接传输通道。


1.2 课题背景

早期在计算机技术还未成熟时,数控系统是通过各种的硬件电路元件组成的,而自经典控制理论成型,现代控制理论的发展,电子计算机的问世,计算机已经逐渐取代了hard,成为了数控的主体,计算机数控系统也因此形成。技术发展至今,计算机的体积已经越来越小,甚至于出现了手机这样虽小却有着自己一套完善的操作系统的设备。

刻绘机作为一种小型的经济形数控机床,其体积小,低成本的优势使它快速占领了刻绘领域。但是在手机逐渐普及的新时期,依旧通过电脑端控制发送数据显然已经不足以突显其便捷的优势了,因此,本文主要研究将手机作为中心设备,向刻绘机发送数据的app设计[1]。

自1994年爱立信公司发明了蓝牙以来,通过蓝牙技术联盟的不断创新,如今的蓝牙已经发展到了4.2的版本,具有着低功耗,安全性高,传输速率快,不易被追踪,更加智能化等优势。


1.3刻绘机发展现状

1.日本的罗兰公司距今已经有了30多年,其在1982年便已经设计出了绘制音乐乐谱的刻绘机。如图1.1所示为罗兰公司所设计的CAMM-1 GX-500型号的刻绘机,350克的刀压,切割速度达到了850mm每秒,其附带着先进的VersaWorks RIP软件,能够自动校准和切割图形,且其装配功率较大的伺服电机,能够切割材质较厚的介质,具有着智能化,精度高,切割速度快等优点[2]。

2. 日本图技株式会社为知名的刻绘机生产商家,其在计策领域有着全球领先的技术研发能力,日本所研发的FC8000-60AP刻绘机,其5vv5具有如下所示的多个优点:

(1)高速度,高精度,高品质,高可靠;

(2)低成本,使用简单方便;

(3)内置2MB的缓存空间,可提高电脑的运行速度;

(4)配置着超大LCD液晶显示屏,便于操作;

(5)搭载着高精度的伺服电机;

(6)有着usb等串口,连接方便。

1.3课题研究主要内容

一般的刻绘机只能通过usb与电脑相连由电脑对其进行控制,而这样的方法局限性较大,为了让刻绘机的使用更加便捷,本次实验决定设计一款苹果手机上的刻绘机加工输出中心app。主要工作分为以下几个部分:

1、对本课题的背景及刻绘机的发展进行了简单的介绍,并确定了本课题中app所采用的平台为苹果手机。

2、对在设计中所需用到的开发工具Xcode,开发语言objective-c进行了进行了大致的介绍,并简单说明了一些app设计中所要用到的方法。

3、对实验所需用到的蓝牙模块作了分析,通过对蓝牙协议的研究理解,决定了app设计的思路方法,研读了苹果设计的蓝牙框架后确立了开发的大致流程。

4、详解了app中包含的每个功能在Xcode中的具体方法实现,并对安全机制等进行了研究。

第二章 app开发环境及工具

2.1引言

本次实验为app开发,在编程语言上,由于Xcode(苹果公司为程序员所设计的基于MAC系统的开发工具)所允许的编程语言只有objective-c及swift两种,而其它诸如c,java等语言需要经过文本编辑器进行编写,因此选择了在C语言和C++的基础上经过严谨的扩展而形成的objective-c语言。

2.2objective-c

因为对C有着一定的了解,所以实验初期主要研究了objective-c与C语言之间的不同之处,以便后期进行app的编程设计。

相较于C语言,Objective-c有着独特的如继承,复合,内存管理,对象初始化,协议等特性。通常在每个程序中都会包含头文件和定义文件,在C中,我们通过#include的方法对程序进行添加,而在objective-c中,则是#import。#import由Xcode内部的编辑器提供,它结合了C语言中#include和#idef这两种指令的功能,即能够查找头文件信息,还可以解决文件之间互相包含的问题。因此,在后面的编程环节中,头文件的引用一律采取了如#include <Foundation/Foundation.h>这样的形式加载头文件[3]。

由于实验的主要功能是实现文件传输,所以还对objective-c中的字符串,可变数组,URL的输出,字典的设立等做了研究。如在C语言中输出字符串时所使用的是printf指令,而在objective-c中,则使用的是归于Cocoa类的NSlog。使用pathforresource可以得到文件的路径,通过contentsatpath则可以读取文件内容,最后用UTF8编码将NSString转为NSData并发送出去。至于其它区别及编程的具体实现将在第四章进行详解。

2.3开发环境及工具

本次实验所需设计的app主要基于Xcode软件进行开发,通过它可以创建一份包含各种方法定义及声明的.h头文件和一份用于表示为objective-c语言设计的.m定义文件,在.h中用@interface对方法进行声明,为了创建一个界面,可以先设置@interface PeripheralViewController :UITableViewController的声明,因为本试验界面决定以表格为基础,于是用该方法声明了一个继承于视图控制器的界面,而在.m文件,通过@implementation描绘出各个方法的实现。

2.4本章小结

本章主要从app的开发语言objective-c及开发工具Xcode两方面阐述了app开发的基本前提,介绍了相关的语言和方法实现,为下文思路流程的讲解建立了概念基础。

第三章 蓝牙及CoreBluetooth框架

3.1蓝牙模块

蓝牙模块电路图如图3.1所示,部分管脚说明见表3.1。

  序号

     名称

             功能解释

1

TX

发送端

2

RX

接受端

3

CTS

删除输出

4

RTS

要求输出

5

PCM CLK

时钟输入

6

PCM OUT

输出端

8

PCM SYNC

频率的同步接口

11

RST

复位

13

GND

14

NC

悬空

15

USB D-

数据线的负极

16

CSB

片选

17

MOSI

主机输出从机输入

18

MISO

主机输入从机输出

20

USB D+

数据线的正极

表3.1 管脚说明

图3.1 蓝牙模块电路图

因刻绘机不具备蓝牙功能,本次试验选择了Risym HC-05主从机一体的蓝牙模块与芯片连接来为刻绘机提供蓝牙环境。其具有以下一些特性:

(1)具有防止电源接反的功能,当电源接反时将不会工作。

(2)电压最大不能超过6v,最少不低于3.6v。

(3)具有EN(使能端)、VCC(连接正极)、GND(连接负极)、RXD(接收)、TXD(发送)、STATE(显示状态,为连接为低电平,连接时为高电平)这6个引脚。

3.2蓝牙协议

3.2.1蓝牙连接协议

为了能够实现手机与刻绘机之间的蓝牙连接,首先需要了解蓝牙连接所需要的必要条件及规定,而这被定义在了蓝牙协议栈的Link Layer即链路层中。

链路层是蓝牙的底层协议,负责蓝牙的连接、商议和建立连接,它规定了控制设备准备(standby)、广播、监听/扫描(scan)、初始化、连接,这五种状态。在准备状态中,设备不接受也不发送,是最初始的状态,当设备由准备转为广播时,开始向外传输advertising的包,它被称为广播者。而另一个设备则开始进行监听或者按需扫描,即为发起者。之后两者建立初始化并建立连接,发起连接的规定为中心设备,被动接受连接的为外围设备。

由以上协议可知,在实现连接前,需先将手机与刻绘机分类,手机作为输出加工中心,主动发起连接,应当为中心设备Central。刻绘机被动连接,接受传输的指令,即为外围设备Peripheral。此外,因为链路层规定,在连接前双方设备需要进行初始化设置,于是,通过[ CBCentralManager alloc ]initWithDelegate : self的方法,对中心设备进行了初始化。同时,由它的5个状态可知,只有中心设备处于监听扫描的状态时才能够发起连接,在初始化后,还需对中心设备的状态进行判断,本实验用的是switch语句,如果case到中心设备已处于监听扫描状态便跳出,执行与外围设备的连接。

3.2.2设备属性协议

前文已经了解了蓝牙连接的基本要求,而蓝牙又是如何识别出不同的设备,设备中有哪些功能,如何判别外围设备是否支持写入或读取,对中心设备又有哪些限制,这些便是解决手机通过蓝牙向刻绘机发送数据这一功能的前提,而它由ATT协议管理。

ATT同样是蓝牙的底层协议,主要负责对数据的检索,它定义了BLE协议栈上层的数据结构和组织方式。ATT的核心即为attribute,它规定了从设备的属性功能,决定了主设备能否读取的权限。ATT的属性结构如图3.2所示。

图3.2 ATT属性结构

Attribute Handle指向了一个属性值的查询地址,Attribute Type存储着各个属性的UUID,用于区分该属性是service还是characteristic,外围设备的数据则存储在Attribute Value中,通过它,可以知道外围设备是否能够进行发送、接受、订阅的功能,而在Attribute Permission中,定义了各种权限,对中心设备进行诸如只读只写等限制。

一个外围设备所含的属性有4种:peripheral 、service、characteristic、Descriptor。它们呈现着上下级的包含关系,peripheral为属性的顶级,service并不储存设备的信息,只是characteristic的载体,而characteristic才是装载设备数据的属性,它的表示形式为声明加上特征值属性。Descriptor则是characteristic的补充信息,是characteristic的子集,可以单独扩出空间存储数据,但它并不具备Notify/write等读写属性。

通过上述ATT协议内容,可以知道,外围设备的信息描述可以分为外设,服务,特征值,描述这四个阶段,在进行连接时,首先发现的便是peripheral这一属性,接着层层递进,而它们又通过Attribute Type中的UUID进行区分。如果利用这样的关系设立一种跳转,由peripheral到characteristic,并分别在每个app界面显示属性的UUID及所带数据,那么外围设备的所有属性都可以被中心读取出来,此外,在Attribute Value中还包含了一个订阅功能,那么是否能够通过订阅来达到用单元格来实时显示外围设备接受到的内容,具体步骤将在下文进行讲解。

3.2.3数据发送协议

在能够成功读取外设属性之后便可以尝试进行数据的发送,而在实现发送数据之前,首先需要对协议中所规定的数据包格式进行一定的了解,如数据包的组成,数据包的大小规定等等,这样才能保证数据发送的有效性及正确性。

虽然蓝牙的空中包在诸如链路层、逻辑链路控制和适配层、安全管理层等都有着相关的应用,但其主要是由链路层进行定义。链路层定义的空中包格式如图3.3所示。

图3.3空中包格式

Preamble为前导码,用于进行同步和自动增益控制,Access Address用来区分空中包的类型为广播包还是数据包,本实验所需发送的便是数据包。PDU作为协议数据单元,又由Header、PayLoad Length、PayLoad三部分组成。而PayLoad正是数据的有效载荷,协议规定Payload最大为27字节,其中L2CAP规定为了给信道长度及信道标识符进行描述占用了PayLoad头部的4个字节,ATT规定,为了让外围设备随时都可以向中心设备发送属性值,需要占用PayLoad的3个字节来规定它的属性操作码及属性句柄。

由上述协议可知,虽然可以通过蓝牙发送大批量数据,但因其蓝牙内部接受数据的缓冲区的限制,在传输这块,通过writedata:直接发送便是行不通的,于是采取了以每包20字节的方式发送数据。但是经实验及研究发现这样依然会存在问题,如图3.4所示,当以每包20字节直接发送时,原本14000多个包的数据实际却只发送了13000左右个包,丢包现象严重。

    图3.4 数据包发送失败反馈图

经调查研究后发现,在进行数据传输的时候,中心设备和外围设备往往会因为相互间的处理速度的差异而产生缓冲区溢出,最终导致writeValue的数据被丢失的情况。虽然iOS本身具有着流控的功能,对于writeValue,BTserver进程会接管并暂存缓冲数据作RF流控,但这样确是不够的[4]。

一般的思路是,为了解决外围设备的应用层对数据的处理不足以支撑RF层接受数据的速度从而造成HCI缓冲区溢出丢包的问题,通过实测外围设备的应用层处理速度,然后增加射频的连接间隔来达到负载平衡的问题。虽然,从性能来说,可以通过修改波特率等方法来用好每一秒射频连接 ,但这种方法稍显繁琐,且不易实现。

而本文的思路是,根据上述方法,等待发送的数据被暂存在了BTserver中,那么是不是可以利用usleep的方法,设置一个延迟,将传送数据包的这个进程暂时停止,即将等待发送的数据暂存在app进程中,从而解决缓冲区溢出,数据包丢失的现象,这样不仅易修改,而且容易实现。经实验表明,该方法可行(具体实现方法见第4章),通过writeValue,成功将10000多个数据包发送了出去,成功发送反馈如图3.5所示。

   图3.5 数据包发送成功反馈图

3.3蓝牙框架

3.3.1框架介绍

如上所述,外围设备属性的读取,蓝牙的连接,大批量数据的发送等机制都已大致了解,但要在Xcode中实现蓝牙的连接及传输等功能还需选择相应的框架进行程序编写。

 苹果公司一共推出过3种蓝牙框架,其中,最早推出的是GameKit,GameKi只能够支持ios之间的相互连接,且双方必须安装相同的app才能够进行连接传输,传输的数据较小,速率较慢,一般只适合作为小游戏的相关开发。

MultipeerConnectivity则是在ios7开始逐渐推广的蓝牙框架,它在GameKit上做了许多的改进,虽然要求依旧是同一个系统,但是它不仅是一个蓝牙框架,同时也是wifi框架,有着统一设计的接口用来发送数据,提高了安全性[5]。

本次实验的目的是通过手机与蓝牙模块相互连接,并借此向刻绘机发送文件的功能,因此,选择了第三种框架CoreBluetooth。

3.3.2CoreBluetooth组成

CoreBluetooth于2013年正式被推出,是一款以BLE为基准开发的蓝牙框架,有着完善的封装。其工作原理如图3.6所示。

图3.6 CoreBluetooth工作原理

CoreBluetooth框架的两个核心便是Peripheral和Central。Central作为整个连接环节的中心设备,主要负责发起连接,并对Peripheral的功能如write、notify、response等进行解析。Peripheral则代表着外设,每一个Peripheral都含有许多的服务,而服务中又有多个特征值来更详细的描述服务,同时,分别用不同的UUID为之命名[6]。从这儿可以看出,CoreBluetooth的设定遵循着BLE4.0的蓝牙协议,核心的定义与ATT协议中规定的属性分类几乎无异。

3.3.3 CoreBluetooth的设计流程

在前文的链路层中,已经将手机定为了中心设备,刻绘机定为了外围设备,那么如何通过CoreBluetooth框架来逐一实现蓝牙连接传输的功能,以下分为Central和Peripheral这两部分进行流程阐述。

1、Central

Central即中心设备由CBCetnralManager进行管理,其主要工作是对一切已经扫描到的设备进行解析。在搜索设备之前,会先调用CentralManagerDidUpdate的方法来检查Central自身的蓝牙是否已经打开,接着开始扫描外围设备,如果搜索到,便进入centralManager:didDiscoverPeripheral:advertisementData:RSSI:这一方法回调,通过它,可以选择所需的Peripheral建立连接,同时可以对设备相互间的距离进行判断。同样,如果双方已经成功连接,使用centralManager : didConnectPeripheral:便可以访问外围设备的信息,当选中其中一个服务之后,会回调peripheral:didDiscoverService:以便进一步搜索它的特征值[7]。在特征值中,利用peripheral:didDiscoverCharacteristic,可以知道该服务所提供的具体功能并进行相关的write、read、notify等操作。notify可以对外围设备值的变化进行实时的监控并且反映出来。

2、 Peripheral

Peripheral即外围设备由CBPeripheralManager进行管理,首先,对服务和特征值进行设置,给它们生成特定的UUID,建立服务-特征值树,并把这一树形结构加载到CBPeripheralManager对象中。然后,通过peripheralManager : did Add Service :的回调来传输数据,接着,通过peripheral Manager Did Start Advertising,来检验是否成功向外广播了数据。如果成功便可以对Central所要求的write、read等进行回应,例如,peripheralManager : didReceiveReadRequest可以实现中心设备read的请求,peripheralManager : didSubscribeToCharacteristic则能够对其notify进行响应[8]。

第四章Xcode实现

4.1 app界面

在实现各类功能之前,首先决定界面的布局,设计app的人机界面。本设计的设想是一款不仅仅能够实现连接传输,还可以即时显现perpheral、service、characteristic的UUID值。因此选择以表格的方式呈现。

列表布局通常使用于显示较大规模信息的时候,工作于动态表视图中。表示图是app设计里运用最多的视图,它分节,分组的特点让信息变得简洁、整齐[8]。列表界面如图4.1所示。

                    

https://pic002.cnblogs.com/images/2012/49237/2012053118444611.png

                  图4.1 列表布局界面

一个表视图通常由5个部分组成,分别为表头视图(位于顶端,概括整个表格的信息)、表脚视图(位于尾端,显示其它信息)、单元格(每行即为一个单元格)和节(由cell组成,分为节头和节脚)。

表视图继承自UIScrollview,具有UITableViewDelegate的委托协议以及UITableViewDataSource数据源协议,包含了包括UITableViewCell单元格类和UITableViewController类等,具体结构如图4.2所示[9]。

 图4.2 UITableView

首先如图4.3所示创建.h头文件和.m实现文件,命名为Peripheral View Controller,在头文件中,通过@ interface Peripheral View Controller : UITable View Controller来声明它的视图控制器为PeripheralViewController,父类(即继承关系中的超类)为UITableViewController,@interface是用来描述对象的数据成员及其属性功能。为了实现表格显示Peripheral的功能,调用tableview : cellForRowAtIndexPath的方法向单元格提供数据[10]。图4.3实现了在单元格中显示外围设备的名称。

  图4.3 Peripheral显示界面

通常,一个外围设备会含有多个service,每个service又包含着不同的characteristic,为了将之全部显示在单元格中,可以利用for循环语句即for(int i=0;i<self.services.count;i++){PeripheralInfo*info = [self.services objectAtIndex:i];}和for (int row=0 ; row < service.characteristics.count ; row ++ ) { CBCharacteristic*c = service.characteristics [row] ; }这两种方法,其中self.services.count表示所有服务的个数,objectAtIndex:i可以通过顺序数i来获取self.services这个数组中的元素。图4.4为characteristic显示及功能集成界面。

图4.4 characteristic显示及功能集成界面

在该界面,从properties中可以知道外围设备所支持的功能,而最上方的2A25是外围设备characteristic的UUID,而read value栏的单元格则可以通过通知按钮的选择来决定是否显示外围设备所接受到的信息。为了使操作便捷化,将通知,选择文件,阅览文件,动画模型,usleep设置,发送等功能都集成在了characteristic属性显示界面。

4.2蓝牙连接实现

之前已经介绍过,蓝牙连接的实现主要基于CoreBluetooth的框架来完成。首先设立CBCentralManagerDelegate的委托,通过switch case语句,将中心设备unknown、resetting、supported、unauthorized、poweredoff、poweredon这6种状态加以区分,只有在状态为poweredon时才开始执行scan peripheral即搜索设备的方法。此外,还通过postNotificationName:@"didDiscoverPeripheral"object:nil userInfo:@(@"central":central)向通知中心传递已经发现外设的信息,其中postNotificationName是指只接受名为didDiscoverPeripheral的信息,而object:类似于密码,当内容为空时,通知中心按名接受信息,否则,还需具有与object相同的值才能接受信息,userInfo:是用于设置包含发现外设这个参数的字典。发现外设后,进入连接设备阶段,通常蓝牙的设备连接需要双方设好的UUID,但本次实验目的是显示所有外设、服务、特征的UUID,便设置了nil即任意外设。

为了防止出现连接时间过长,添加了connectTimer = [ NSTimer scheduled TimerwithInterval:5.0f target:self selector(disconnect) userInfo:peripheral repeats:NO]以创建定时器监控连接是否超时的情况。5.0f中f为浮点数,意为延迟时间5s,selector (disconnect)为引用的方法,定义如下- ( void ) disconnect : ( id ) sender {[bleManager stopScan];}即5s后停止搜索。图4.5为蓝牙连接成功提示栏。

图4.5 蓝牙连接成功界面

4.3文件的打开和发送

4.3.1 文件夹的建立

iOS只能够在自己创建的文件系统中读取文件,所以,首先创建一个文件夹,命名为LocailFile。xib为Xcode中的图形界面设计文档,其作用是为界面提供一个模板,模板如图4.6所示。

 图4.6 文件夹界面模板

文件夹的界面同样采用了表格的方式,在cellForRowAtIndexPath中,通过cell.textlabel.text = dict[kLocalFileNameKey]和cell.detailTextLabel.text = [NSString stringWithFormat:@"大小":%@B],dict[kLocalFileSizeKey]]这两种方法将文件的名称和大小显示在单元格中。其中kLocalFileNameKey和kLocalFileSizeKey为在接口中定义的两个全局变量(可以被所有方法引用的变量),此时两个变量并没有实际值。文件夹建好后,需要进行原界面与文件夹界面之间的相互跳转[11]。在文件夹界面端,设置了发送文件回调和取消按钮点击回调,具体方法代码为[ self dismissViewController:alertView animated:YES completion:^{} ],其中completion是一个块,用于视图关闭后的回调设计。present View Controller和dismiss View Controller是对用来可以自定义打开和关闭模态视图的方法。而在原界面,则通过UIAlertController创建了一个控制器实现跳转,其操作表如图4.7所示。

 图4.7 打开文件操作表

为了能够正确读取文件属性,首先创建一个字典(关键字及其定义的结合)和一个文件管理器NSFileManager(用来管理文件系统),通过文件管理器中attributesofItemAtPath:[self.localFilePath]的方法获取localfile文件的大小,内容等属性,用forKey将获得的属性与之前创建的两个全局变量相联系,文件夹打开功能如图4.8所示 。

图4.8 文件夹打开功能界面

4.3.2 文件的发送

计算机中所存储的文件一般分为两种,一种为二进制,一种为文本文件。中心设备和外设间的蓝牙传输所传的是NSData,NSData用于进行对数据的包装,它所存储的就是二进制数据。NSString归属于foundation框架,它所存储的是字符串数据,为了让人机交互更为简单,需要设置一种能够存储和处理人类可读文本的方式,而字符串便是一组人类可读的字符序列。在接受发送过程中,所需要的输入输出的是字符串,但在这过程中,它是以二进制来传输的,这就需要另一个知识即编码。

通常,会使用NSString*msg = [[NSString alloc] initwithData:subdata encoding : NSUTF8StringEncoding]这样的形式来实现NSString和NSData之间的相互转换。NSUTF8即名为UTF-8的编码方式。它为所有的编码制定统一的标准,用两个字节来表示一个字符,这也引发了存储空间占有过大的问题[12]。而UTF-8就是经过改良后的unicode,编码方式具体如表4.1所示。UTF-8是一种长度可调整的编码方式,能够用1到4个字节来表示字符,且随着字符的不同,它的大小也可以随之变化,UTF-8内容如下:

(1)当只用单一字节描述一个符号时,字节的首位规定为0,其它位则是符号本身的unicode码。

(2)当用多字节去描述一个字符时,如有a个字节,则首个字节的前a位设置为1,后一位设置为0,剩下的字节前两位都设置为10,余下的便是这个字符自身的unicode码。

表4.1 编码方式

     编码范围

                    编码方法

0000

0000-0000

007F

OXXXXXXXX

0000

0080-0000

07FF

110XXXXX

10XXXXXX

0000

0800-0000

FFFF

1110XXXX

10XXXXXX

10XXXXXX

0001

0000-0010

FFFF

11110XXX

10XXXXXX

10XXXXXXXX

1OXXXXXXXX

以汉字“严”为例,已知“严”的unicode是4E25(100111000100101),根据上表,可以发现4E25处在第三行的范围内(0000 0800-0000 FFFF),因此“严”的UTF-8编码需要三个字节,即格式是“1110xxxx 10xxxxxx 10xxxxxx”。然后,从“严”的最后一个二进制位开始,依次从后向前填入格式中的x,多出的位补0。这样就得到了,“严”的UTF-8编码是“11100100 10111000 10100101”,转换成十六进制就是E4B8A5[13]。

本次实验通过NSData * data = [ @ " IN ; PU0 , 0 ; PD200 , 300 ; PU0 , 0 ; PG ; " dataUsingEncoding : NSUTF8StringEncoding ]的方法将字符串转换成了NSData数据,并成功发送给了电机使之转动,之后便是进行文件的传输[14]。

因为蓝牙协议的规定,蓝牙物理链路分为ACL和SCO两种,ACL即能够实现对称连接,又可以进行非对称的连接,一般用于普通数据传输,SCO只支持对称连接,一般用于实时的语音传输,本次实验使用的是ACL物理链路[15]。L2CAP PDU即所谓的协议数据单元规定了属性协议最大传输单元,它的值默认为27字节,所以蓝牙传输的数据包的字节最大为27字节,但L2CAP Header将占4个字节,在属性层中,1个字节用于存储操作码,2个字节用于Attribute Handle。属性句柄是一个16进制,占了两字节的编码,大小范围为0x0001到0xFFFF。因此,蓝牙传输的数据包每包最大只能为20字节,对于较大的数据,需要进行分包发送。

首先定义每个数据包的最大字节,#define BLE_SEND_MAX_LEN 20,对于每包最大2字节的数据发送,同样可以利用for循环语句的方法。for(int i = 0;i < [msgData length];i+= BLE_SEND_MAX_LEN),当i小于发送数据的长度时,执行定义的方法并与最大包数相加[16]。定义方法为if(i + BLE_SEND_MAX_LEN < [ msgData length] )和else if(i + BLE_SEND_MAX_LEN > [msgData length]),为了实现分包,需要判断每次发送数据的大小,通过i作中间量,当i加上每包最大发送量的值却依旧小于需发数据的长度时,就可以判断出该数据剩余传输的量依旧够发一个20字节的数据包,接着通过stringwithformat:@" %i , %i " , i , BLE_SEND_MAX_LEN将i和每包最大发送量的值转为字符串并使用NSData*subData = [ msgData subdatawithRange : NSRangeFromstring ( rangestr ) ]这一方法实现分包[17]。其中NSRangeFromstring是指将之前变为字符串的i和20转换为数组的形式,subdatawithRange则用于数据的截取,以i为数据包发送的起始位置,20为数据包的大小,这样便实现了每包20字节的分包。但发送到最后,总会余下不足20字节的数据,这便要用到上文所提及的else if定义的方法解决。截取方法类似,只是需要通过(int)([msgData length] - i)将原来定义的每个数据包发送20字节改为发送数据所剩的字节。

4.3.2 数据包usleep的设置

因缓存空间大小的限制,一次性发送的数据包不能太多,否则会导致丢包的现象。在writeValue :subData之后,加入了延时功能usleep(20000),每发送一个数据包都会延长一定的时间。但经实验表明,延时时间的长短影响着刻绘机电机转动的连贯性,为了能够即时调整,加入了usleep可调功能。

首先建立一个局部变量value,将usleep(20000)跟改为usleep(value),通过UITextField*textField = [ [ UITextField alloc ]initwithFrame : rect ]创建一个文本框,设计之初,想将文本框的内容直接赋予给变量value,但两者的代码不再同一个方法里,value又是局部变量(只能在本方法被引用),于是,在建立文本框之后,加了一行[textField setTag :1]这样的代码[18],将textField文本框打上标签,在定义变量前再通过UITextField*myTextField = [self.view viewwithtag : 1]使被标记的文本框的值直接给myTextField,但当赋值给value时依然出现错误,因为局部变量value为int类型,而文本框的内容确是NSString类型的字符串,所以还需要进行字符串与整形的相互转换,即NSString*a = myTextField.text ; int ivalue = [ a intvalue ]这两个方法。延时调节功能如图4.9所示,在其中设置延时为20000ms。

图4.9 usleep调节功能

4.3.3进度条

对于进度条,苹果自从ios8版本开始,特地设计了NSProgress用来管理进度条, progress = [ NSProgress progressWithTotalUnitCount : 10 ],首先创建了一个任务进度管理对象,其中UnitCount是一个基于UI上的完整任务的单元数,接着开始对任务实行监听,[progress addObserver:selfforKey Path : @ " fraction Completed " options:NSKeyValueObservingOptionNew context:nil],如上所示的代码创建了一个进度的fractionCompleted属性的观察者即self,然后通过配置KVO观察进度的变化,对UI做出更新(NSProgress 类就是搭配 KVO 来操作的)。使用KVO的要求是对象必须能支持kvc机制。kvo是一种回调,某个对象注册成为监听者后,当被检测的对象出现变化的时候,对象会发送通知给监听者,以便监听者执行回调操作。它的工作机制为,设立了一个监听者之后,编译器对监听对象的isa指针进行修改,将之指向一个新生成的中间类。isa是一个指向class类的指针,用于指向类的类型,可以通过object_getClass的方法来得到这个值。一般情况,class方法内部的实现就是获取这个isa指针代表的元类,但在kvo机制中苹果注册监听对象后,通过objc_allocateClassPair动态重新创建了一个新类和元类,此时object_getClass()获取的就不是原来isa指向的元类,而是是新建的元类。接着,通过observeValueForKeyPath:ofObject:change:context:{}来判断接收数据的变化并在括号内定义需要的方法。当对象已经完成操作之后,可以通过removeObserver:self的方法来移出监听[19]。了解了监听的步骤,接下来需要将监听的数据转成百分比并用动态的方式在view中显示出来。

首先,通过_progressView = [ [ UIView alloc ] init]对界面进行初始化,之后用CGRectMake(origin.x,origin.y,size.width. Size.height)定义一个矩形,其中,origin.x和origin.y代表矩形在界面中的坐标位置,size.width和Size.height则表示矩形的高度和宽度,用_progressValue = MAX ( 0.0,MIN ( 1.0,value ))将进度条的范围设置在0到1之间,value则为变量。当开始发送数据时,添加set Progress Value : 0将进度条的值设为0,为了能够实时的显示进度,在上文提到的observe ValueForKeyPath接收数据变化后添加实现方法,运用for语句,当progress.fractionCompleted > 0时,所监听的对象还未传输完成,将fraction Completed的值赋给progressvalue,当progress.fractionCompleted = 1时,传输已经完成,移除监听。其中,fractionCompleted属性为0-1之间的浮点值,它显示了任务的完成比例。进度条功能如图4.10所示,黄色为底代表未完成的任务,红色代表已经完成的进度[20]。

图4.10 进度条功能

4.4预览功能

iOS中,文件预览的方法一共有四种:

(1)UIWebView。UIWebView使用简单,可以预览多种类型的文件,能够支持在线预览和本地预览,缺点是只能够浏览,不能收到回调。

(2)QLPreviewController。QLPreviewController是系统自带的文件预览控制器,QL英文全名为quick look,即快速查看的意思。在使用QLPreviewController之前,需要先在文件中导入头文件#import <QuickLook/QuickLook.h>,并且添加它的代理方法QLPreviewControllerDelegate。

(3)UIDocumentInteractionController。UIDocumentInteractionController同样需要在文件中添加其代理方法UIDocumentInteractionControllerDelegate,其本身并不能算是一个控制器类,它继承于NSObject,因此不能进行直接的push或者模态跳转,为了进入节面,需要加入其它类方法的模态跳转函数。

(4)CGContexDrawPDFPage。此方法利用了CGContexDrawPDFPage和UIPageViewController 来实现翻页浏览功能,这种方法虽然只能加载本地的PDF文件,但是可以实现如翻页,旋转等特殊功能。

因考虑到本设计的实际需求,选用了QLPreviewController的方法。按上文所说加入头文件和代理方法,首先进行文件预览控制器的初始化及设置代理,因为要进行多个文件的预览,在实现方法之前,先设一个声明,即@property( nonatomic strong )NSArray*files,创建一个不可变数组。@property即声明,它不仅仅会默认生成一个_类型的成员变量,同时也会生成setter/getter方法。括号中的strong代表着强引用,同样,还有一个weak代表着弱引用。 强引用也就是通常所说的引用,其存亡直接决定了所指对象的存亡。如果不存在指向一个对象的引用,并且此对象不再显示列表中,则此对象会被从内存中释放。弱引用除了不决定对象的存亡外,其他与强引用相同。即使一个对象被持有无数个弱引用,只要没有强引用指向它,那麽它还是会被清除。接着,通过NSString*filePath1 = [ [ NSBundle mainBundle ] pathForResource : @ "1" ofType :@ " pdf " ]将需要在文件预览控制器打开的文件路径转换为字符串的形式,Bundle即文件目录,[ NSBundle mainBundle ]是获得NSBundle的一个单例对象。之后用_files = @ [ filePath1 , filePath2 ]将所有所需的文件路径放在数组中,最后通过push的方法跳转到文件预览界面[21]。

起初,控制器获取的path值为空,后将文件拖入到项目中也无法解决,经查阅各种资料发现,当时添加是直接拖拽过去,没有真正加入到bundle中,需要在项目设置中,build phases-》copy bundle resources 下面添加自己的文件就可以了。文件预览列表如图4.11所示,文件预览内容如图4.12所示,文件预览分享如

            

         图4.11文件预览列表                     图4.12 文件预览内容            图4.13 文件预览分享

4.5动画显示功能

CAshapelayer继承自CALayer,是基于QuartzCore框架,通过设置shape(形状)的path(路径),利用贝塞尔曲线绘制各式不规则图形的一个子类。因此,CAshapelayer需要与UIBezierPath配合在一起使用。

UIBezierPath类允许我们在自定义的view中绘制渲染图形的路径,通常,在初始化的时候可以直接向UIBezierPath指定一个几何图形。简单点说,UIBezierPath是用来描绘图形的路径,CAshapelayer则是根据这个路径进行绘制图象。

贝塞尔曲线是又称作贝兹曲线,通过少量点的设定及方程式的计算实现在计算机中绘制不规则图形的功能。根据一次方程,二次方程及三次方程的运用,贝塞尔曲线又分为一阶、二阶、三阶等不同的曲线。

在一阶,通常需要设置两个坐标点p0和p1,当t由0到1开始递增时,可以得到多个点的坐标即B(t).x = (1-t)*P0.x + t*P1.x和B(t).y = (1-t)*P0.y + t*P1.y,一阶描绘的其实就是一条直线。

在二阶,需要设定三个坐标点,二阶看似有点复杂,但可以把它当作两个一阶曲线的结合。即由p0和p1确定的一条直线Pm上的点Pm(t) = (1-t)P0 + tP1与p1和p2确定的一条直线Pn上的点Pn(t) = (1-t)*P1 + t*P2相结合产生的曲线B(t) = (1-t)*Pm(t) + t*Pn(t)。

在三阶,需要设定4个坐标点,可以同样将之看作三个点在变化的二阶曲线,如图4.14,取AE:AB = BF:BC = CG:CD = EH:EF = FI:FG = HJ:HI,其中点 J 就是最终得到的贝塞尔曲线上的一个点[22]。

 图4.14 三阶曲线原理

本次实验,通过CAshapelayer绘制了一个较为简单的矩形刻绘动画。首先通过CGRectMake定义了矩形在界面的位置及大小,用layer.background、layer.strokeColor和layer.fillColor分别设置了矩形的背景色、描边色和填充色[14]。但在实际显示中,描边色与填充色并没有实现,后发现,这两个设置需要通过UIBezierPath来先绘制图形的路径,再使用CAShapeLayer进行渲染,于是在之后添加了一个UIBezierPath类路径,UIBezierPath *path = [ UIBezierPath bezierPathWithRect : CGRectMake ( 0, 0, 100, 100 ) ]。然而这些只是创建固定的图形,并不能形成动画,要实现还需加上CABasicAnimation的功能。

CABasicAnimation类的使用方式就是基本的关键帧动画,它将Layer的属性作为KeyPath来注册,指定动画的起始帧和结束帧,然后自动计算和实现中间的过渡动画。使用CABasicAnimation前,必须添加<QuartzCore/QuartzCore.h>头文件,它具有duration(动画时长)、repeatcount(重复次数)、beginTime(指定动画开场时间),timingFunction(设定动画的速度变化)、autoreverses(动画结束时是否执行逆动画)这5种属性[23]。与上文提过的贝塞尔曲线类似,先设定一个起始点、一个结束点和两个控制点,之后通过strokeStart或strokeEnd的方法让计算机自动实现动画。动画的播放共有如下4种方法:

(1)当KeyPath为strokeStart,动画的fromValue = 0即有一条完整的路径,toValue = 1即、没有路径时,动画从路径的起点到终点慢慢的消失。

(2)KeyPath不变, fromValue变为1,toValue变为0时,动画从路径的终点到起点慢慢的出现。

(3)当KeyPath为strokeEnd,动画的fromValue = 0即无路径,toValue = 1即有完整的路径时,动画从路径的起点到重点慢慢的出现。

(4)KeyPath不变,fromValue变为1,toValue变为0时,动画从路径的终点到起点慢慢消失。动画显示功能如图4.15所示[24]。

 图4.15图形显示

第五章 校验及安全机制

5.1 数据校验

为了保证蓝牙数据传输的完整及正确性,校验的环节必不可少。当在信息发送之前,可以先设计一套算法并将原信息带入得到一个确切的值,当发送完毕之后,用新数据进行计算,如果最后的值相同,则表示传输可靠。其中,数据的校验主要有4种方式:

(1)可以通过发送前的数据与发送后的数据直接进行对比,即每传输一个数据包便自动回调出来当与发送前数据相同时,传输下一个数据包。

(2)一个字节有8个比特,由0或1表示,首先定义整个字节1的个数为单数(双数同理),然后通过计算,如果的确是单数则添一个最高位0(如0 1000,0001),如果是双数,则添一个最高位1形成单数(如1 1010,0101),最后将每个最高位进行异或,从而间接检验数据的正确性。

(3)将传输的数据当做一个位数很长的数,将这个数除以另一个数,得到的余数作为校验数据附加到原数据后面,这就是CRC算法。每个二进制都有0和1组成,可以将它与多项式对应。例如,传输的信息为1011001,它所对应的多项式即为m(x)=x6+x4+x3+1,我们假设生成的多项式为g(x)=x4+x3+1,则它的代码为11001,x4m(x)=x10+x8+x7+x4对应的代码为10110010000,通过多项式相除得到余数为1010,那么,发送的字段即为10110011010。在接受数据时,采用相同的生成码进行校验,如果能够除尽,则数据传输正确。

(4)将要发送的数据和设好的值进行异或,到受到数据时再次进行异或,如果相同则数据传输正确。

本次实验的蓝牙数据包格式如表5.1所示。

  表5.1 蓝牙数据包格式

包头

1字节

命令

1字节

压力

2字节

速度

2字节

软件协议

1字节

设备编号

8字节

预留字段

1字节

校验

1字节

包尾

1字节

编码顺序是按照小段为标准的,即字节的高低位位置与高低地址相一致,发送数据的时候,优先发送低位的字节,而本次采用的蓝牙定义为大端,这与小段的格式恰恰相反,即字节的高低位置与低高低址一致,首先发送的是字节中的高位,然后才是低位[25]。

如果要向刻绘机发送0x20的命令,当前刻绘机压力为20(0x14),速度为5(0x05),加密协议为0x43,设备编号为0x01 0x01 0x01 0x01 0x01 0x01 0x01 0x03,预留字段为0x02,按照协议格式,整个传输的数据为 0xF00x20 0x00 0x14 0x01 0x05 0x43 0x01 0x01 0x01 0x01 0x01 0x01 0x01 0x02 0x03,我们可以设置一个算法,从0x20开始,依次每个字节进行异或的计算,即得校验为0x35。之后再对收到的数据进行校验即可。

5.2安全机制

虽然蓝牙通过跳频机制传送能够对外界的干扰起到一定的保护作用,却不能对窃听偷取等进行防范,这便需要用到鉴权和加密等技术。

每个蓝牙设备都会有它自己的识别标识即BD_ADDR,它是一个唯一的具有透明性的设备地址,在设备相互传输数据之前,都会产生一个完全随机且同样唯一的随机码即RAND,它是鉴权和加密的基础,可以被用作密钥。而密钥又分为KAB、KA、Kmaster、Kinit。KAB与KA的主要区别是一个由通过两个设备共同合成,另一个由一个设备生成。Kmaster则是一个临时密钥,一般用于主设备与多个其它设备相连时的临时加密[26]。

蓝牙加密前,首先由主设备生成RAND并传送给从设备,这时两边都具有相同的随机码,再根据从设备的设备地址及设好的识别密码用E22算法得到初始化需要的Kinit。接着,主设备与从设备各自生成一个随机码并与算出的Kinit进行异或,得到结果后两者相互交换数据并通过E21算法求得半永久密钥KAB,具体过程如图5.1所示。

图5.1 KAB产生过程

密钥生成后便进入认证阶段,主设备设置一个RAND码并传送给从设备,双方根据密钥,随机码和从设备的设备地址,用E1算法各自得出结果并交由主设备进行比较,如果相同则认证成功。

第六章 试验结果及分析

5.2实验结果

本次试验主要用了主芯片STM32,HC-05主从机一体的蓝牙模块,一台刻绘机,一台用于在Xcode设计app的MAC Pro,一部iOS系统的手机。

首先通过EN、VCC、GND、RXD、TXD、STATE这6个引脚将HC-05蓝牙模块与主芯片相连,将在Xcode中设计的app工程通过usb装入手机中,利用app,将手机作为中心设备,与主芯片相连的刻绘机作为外围设备,两者进行蓝牙连接。

打开手机app,自动搜索外围设备并在单元格成功读取显示设备的uuid及设备的信号强度。点击设备名,成功连接则跳转到service界面,连接超过5s会显示disconnect提示框,效果见图6.3。

                               图6.3连接成功                               

接下来选择一份plt文件并通过蓝牙分包发送给外围设备并成功刻绘出了神兽的图形,但是在打印过程中电机有些微的停顿感,通过预览功能得出的原图及打印出的图形。

后经过usleep设置功能,将原来的100ms延时更改为每包20ms,顿挫感有了较明显的减少。usleep设置界面如图6.6所示

   图6.6 延迟设置界面

在传输过程中为了掌握信息何时被发送,又是什么时候停止,需要通过建立监听对数据发送的过程进行显示,如图6.7所示。

 图6.7进度调显示

6.2出现的问题

在进行usleep的设计时,发现即使成功将变量与文本框建立了联系,但在数据传输时,主界面发生了卡屏的现象,无法点击文本框并输入字符,后发现,数据包的传输被放在了主队列进行,而主队列确是进行UI设置等相关工作的,于是便出现了堵塞,界面卡住的情况,这是,通过 dispatch_async ( dispatch_get_global_queue (0, 0))创立了一个全局队列,由全局队列对数据传输进行管理,而主队列照常工作。

在进行动画模拟功能设置时发现,当push动画界面时,出现黑屏的现象,无法正常显示动画,后发现,没有对新建的view设置模板即xib文件,加入之后已改善。

6.3成本分析

由于本实验大部分为软件设计,在硬件部分上所需的成本较低,购买HC-05蓝牙模块的费用为55元,工业级USB转串口线DB9针的费用为48元,总费用为103元。对于本次课题此前并未有过太多的接触,所以在用于学习的书本费中花费较多,objective-c基础教程的书本费为59元,IOS开发指南书本费为119元,书本费共为178元。两者合计,此次总成本为281元。

当今人们所使用的刻绘机普遍都是通过usb与电脑相连,由电脑控制进行文件的传输及打印。而本次实验通过极少的费用完成了由手机端向刻绘机传输文件并进行加工的功能,几乎节省了一台电脑的费用,缩小了刻绘机的使用成本,能够使产品更具竞争力。

第七章 总结与展望

7.1 总结

本设计围绕Xcode、objective-c、蓝牙、刻绘机进行了许多的研究,现在对这些研究做了如下的总结:

1、对设计的内容,设计的意义,刻绘机的背景,蓝牙的发展进行了较为详细的介绍。

2、对开发环境进行了简介,讲述了所用语言objective-c与所学的c语言之间的异同及设计所需的部分方法。

3、对所需要的硬件即蓝牙模块进行了详解,对所要使用的蓝牙协议进行了系统化的大致分析及从中所获得的app设计的大概思路,选择了适当的蓝牙框架并列出了设计大致流程。

5、对上述功能进行了实际应用,并做了一些修整。

7.2展望

苹果手机刻绘输出加工中心app的基本功能已大致完成,但因本人时间及水平的限制,对刻绘机的压力控制等还有些欠缺,对蓝牙的安全机制也只是进行了一定的了解,此外,还可以加入许多操作功能,后续将对之逐渐完善,添加更多的功能实现以至能够达到上架的程度。

参 考 文 献

[1] 王生铁,王志和,李巴津.伺服电机驱动的电脑刻绘机控制系统的设计[D].电气传动.2002

[2] 张莉松. 刻绘机控制原理与设计(第三版) [M].北京理工大学出版社.2006

[3]韩玉会.ios系统应用开发规范研究[D].西北大学.2017

[4]关东升.ios开发指南[M]. 人民邮电出版社.2017.6

[5] 谭浩强.c程序设计[M].清华大学出版社.2012.1

[6]金纯.蓝牙协议及其源代码分析[D].国防工业出版社.2006

[7] 吴宁.iOS APP开发安全框架设计与实现[D].浙江大学.2015

[8] 张丽,徐文平.人机界面设计[D].北京大学出版社.2012

[9] 谭颖,胡俊平,李结,等.基于iOS的同学帮移动应用软件设计与开发[J].软件导刊.2015

[10] 黄天柱,涂时亮.iOS开发UITableView加载图片的内存管理[D].复旦大学.2012

[11]Scott KnasterWaqar MalikMark Dalrymple.Objective-c基础教程[M].人民邮电出版社.2013.5

[12] 钟元生,曹权.app开发教学案例设计[D].江西财经大学.2015

[13] 王淑娟,金敬强,魏昆.电脑开发与应用[J].山西省邮政报刊发行局.2013

[14]萨丹著.iPhone开发秘籍[M].人民邮电出版社.2010

[15]黄俊萍.基于iOS的购物类电子商务app的研究与设计[J].齐齐哈尔大学学报.2017

[16]葛孟兰,金娇.基于iOS的App社交软件[D]. 安徽大学.2016

[17]田园.基于ios系统的休闲类游戏设计与实现[D].北京工业大学.2015

[18]高洋.iOS平台客户端应用UI创建方式的研究[D].广东工业大学.2015

[19]黄天柱.基于iOS的旅行游记移动应用软件设计与实现[D].复旦大学.2014

[20]Gary Bennett, Mitch Fisher, Brad Lees.Programming Basics in Objective-C[M]. Springer.2016

[20] 曹斌.渗透测试演练平台的设计与实验[D].北京邮电大学.2012

Tom.Research and Markets: Beginning Mac OS X Snow Leopard Programming[J]. ProQuest期刊.2016

[21] Gary Bennett, Brad Lees.Learning Swift and Xcode[M]. Springer图书.2015

[22] Chang,Tsui-Ping.XCode: a Novel Encoding Scheme of Frequent XML Query Pattern Mining[J]. ProQuest期刊.2014

[23] 李越. 低功耗无按键运动腕表的设计研究[D]. 浙江大学.2015

[24]张云光.基于iOS平台销售系统的设计与实现[D]. 北京交通大学.2014

[25] 滕启明,陈向群.XCODE:一种面向系统软件的可扩展构件描述语言.北京大学.2014

[26]Tom.Research and Markets: Beginning Mac OS X Snow Leopard Programming[J]. ProQuest期刊.2016

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

在梦里-119

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值