诚如iOS开辟者做APP开辟半数以上时候都以通过Http央浼跟后台服务器打交道,做一些新闻体现和客商交互。比比较少涉及到去跟外界硬件配备连接的开辟。随着近来车联网和物联网的起来,智能家居和智能硬件的日益火爆,愈来愈多的app被开荒出来,用来跟硬件设备进行来三番五次,获取硬件相关音信体现可能发送指令调节硬件来提供劳务。

C本身是三个静态语言,数据类型和代码运营的结果都以在编写翻译的时候鲜明的。而Objective-C的runtime机制赋予了C贰个新的活力,即运营时机制。那也正是,OC代码或许C代码在编写翻译过后的机器码并不可能得出运营结果。而以此结果须求在运转的时候手艺得到,那样就给了我们一个新的主宰代码的上空,也等于运转时。在OC中,运营时是一段提前写完的叁个模块的代码。能够如此说,OC的运作时正是这段代码赋予的。前几篇小说中,小编提到了objc_msgSend的流水生产线,是为着让大家对runtime的历程有多个光景的打听。不过,对于绝大许多人的话,比起原理,更关切的是怎么用。所以本章的剧情就是自家在runtime小序曲,从运转时多态看那股神秘力量中提到的runtime的除了objc_msgSend的其他二种接纳:NSObject的主意和runtime的函数。学习进度:

CATransform3D 旋转 缩放 平移 动画 矩阵 绘制
图层 转换 形变

在行业内部学习以前自身看了三个扶植机构的摄像教学:装X特殊技艺蓝牙5.0,喜欢的朋友可以先今后看一下,那么您在读书蓝牙( Bluetooth® )的时候不会那么困难
  • runtime小序曲,从运转时多态看那股神秘力量
  • runtime进行曲,objc_msgSend的前生今生
  • runtime进行曲,objc_msgSend的前生今生
  • runtime变奏曲,那个藏在runtime中的接口
  • runtime变奏曲,那个藏在runtime中的接口

图片 1

蓝牙( Bluetooth® )常见名称和缩写

MTI:make for ipad ,iphone, itouch
专们为苹果设备创立的配备关于MFi认证你所必须要明了的事情BLE:buletouch
low energy,Bluetooth4.0设施因为低功耗,所以也称之为BLEperipheral,central:
peripheral被连接的装置为peripheral;central:发起链接时的设置service
and
characteristic:服务和本性,类似于服务端的API,可是机关不一致。每种外设会有为数非常多的服务,每三个劳务都饱含了多数的字段,这么些字段的权位一般分为
读read 写write
通知notify,正是我们连年装置或具体要求操作的从头到尾的经过Description
每种characteristic能够对应三个或多个Description客户描述characteristic的新闻或品质MFI
=== 开采使用ExternalAccessory 框架4.0 BLE === 开拓使用Core蓝牙( Bluetooth® )框架

前几天,和群里的一人骚年商量了runtime的难题。他的眼光是,runtime并未有怎么用,不用runtime照样能够付出。其实,前半句并不曾什么样病魔,runtime确实并没有何样用,因为一大半开荒职业着力用不着(其实大家公司用的蛮多的)。难题出在其次句,不用runtime就能够支付。OC作为一种尖端语言,能让您方便的采取它的片段接口。举例:

本文全部示例代码或德姆o能够在此赢得:

CoreBluetooth框架的中坚其实是多少个东西,peripheral和central, 能够通晓成外设和宗旨。对应他们分别有一组有关的API和类

图片 2CoreBluetoothFramework.jpeg

这两组api分别对应分歧的作业场景,侧面叫做中央方式,就是以你的app作为主导,连接其余的外设的光景,而右侧称为外设形式,使用手提式有线电话机作为外设别别的大旨设备操作的场地。

- respondsToSelector:aSelector;

万一本文对您持有扶助,请给个Star

蓝牙着力情势流程

主干格局的选取场景:主设备(手提式有线话机去扫描连接外设,开掘外设服务和天性,操作服务和属性的行使。一般的话,外设(蓝牙5.0设备,举个例子智能手环之类的事物),
会由硬件程序员开采好,并定义好设备提供的服务,各种服务对于的天性,各类特征的特性(只读,只写,通告等等),本文例子的事情场景,正是用花招机app去读写Bluetooth设备。

其一措施大家应该时时利用,特别是在动用delegate的场子,基本是必用的。那么,大家从逻辑上看那些方法。从runtime小序曲,从运维时多态看那股神秘力量中自身就说过,OC没有办法在编写翻译时刻明确贰个对象的品种。而以此点子是判断一个卫冕自NSObject的class有未有落到实处四个SEL。很明显,编写翻译时刻做不了那事,它是在运维时刻做的。所以,不用runtime就能够支付是破绽百出的。其实,这种工作并不稀罕,抢先1/4人不想深造runtime的缘由也是那般。

CATransform3DQuartzCore下申明的一个结构体,文档对它的叙说:

ios连接外设的代码达成流程
  1. 创立基本剧中人物
  1. 扫描外设
  2. 连天外设
  3. 围观外设中的服务和特色–4.1 获取外设的services–4.2
    获取外设的Characteristics,获取Characteristics的值,获取Characteristics的Descriptor和Descriptor的值
  4. 与外设做多少交互(explore and interact)
  5. 订阅Characteristic的通知
  6. 断开连接(disconnect)
  • runtime是C和汇编写的,看不懂。
  • runtime开垦用不着。

The transform matrix is used to rotate, scale, translate, skew, and
project the layer content. Functions are provided for creating,
concatenating, and modifying CATransform3D data.

1 导入Core蓝牙头文件,创建主设备管理类,设置主设备委托
 #import <CoreBluetooth/CoreBluetooth.h> @interface ViewController : UIViewController<CBCentralManagerDelegate> @interface ViewController (){ //系统蓝牙设备管理对象,可以把他理解为主设备,通过他,可以去扫描和链接外设 CBCentralManager *manager; //用于保存被发现设备 NSMutableArray *peripherals; } - viewDidLoad { [super viewDidLoad]; /* 设置主设备的委托,CBCentralManagerDelegate 必须实现的: - centralManagerDidUpdateState:(CBCentralManager *)central;//主设备状态改变的委托,在初始化CBCentralManager的适合会打开设备,只有当设备正确打开后才能使用 其他选择实现的委托中比较重要的: - centralManager:(CBCentralManager *)central didDiscoverPeripheral:(CBPeripheral *)peripheral advertisementData:(NSDictionary *)advertisementData RSSI:(NSNumber *)RSSI; //找到外设的委托 - centralManager:(CBCentralManager *)central didConnectPeripheral:(CBPeripheral *)peripheral;//连接外设成功的委托 - centralManager:(CBCentralManager *)central didFailToConnectPeripheral:(CBPeripheral *)peripheral error:(NSError *)error;//外设连接失败的委托 - centralManager:(CBCentralManager *)central didDisconnectPeripheral:(CBPeripheral *)peripheral error:(NSError *)error;//断开外设的委托 */ //初始化并设置委托和线程队列,最好一个线程的参数可以为nil,默认会就main线程 manager = [[CBCentralManager alloc]initWithDelegate:self queue:dispatch_get_main_queue()];

换个点子加以一下,runtime赋予C面向对象的工夫,所以有了OC。那么说实话,只要您用到class,其实都以和runtime相关的,怎么可能回避。好像跑题了,未来我们回去。其实,和respondsToSelector:类似的OC方法还恐怕有很多,下述小编会收拾一些常用的。

它是用来对一个layer的开始和结果张开旋转、缩放、平移、扭调换化的变形矩阵,它提供了有的创建、叠合和修改CATransform3D数据的函数。

2 扫描外设,扫描外设的点子我们位于centralManager成功张开的寄托中,因为唯有设备成功开采,技巧最初扫描,不然会报错。
 -centralManagerDidUpdateState:(CBCentralManager *)central{ switch (central.state) { case CBCentralManagerStateUnknown: NSLog(@">>>CBCentralManagerStateUnknown"); break; case CBCentralManagerStateResetting: NSLog(@">>>CBCentralManagerStateResetting"); break; case CBCentralManagerStateUnsupported: NSLog(@">>>CBCentralManagerStateUnsupported"); break; case CBCentralManagerStateUnauthorized: NSLog(@">>>CBCentralManagerStateUnauthorized"); break; case CBCentralManagerStatePoweredOff: NSLog(@">>>CBCentralManagerStatePoweredOff"); break; case CBCentralManagerStatePoweredOn: NSLog(@">>>CBCentralManagerStatePoweredOn"); //开始扫描周围的外设 /* 第一个参数nil就是扫描周围所有的外设,扫描到外设后会进入 - centralManager:(CBCentralManager *)central didDiscoverPeripheral:(CBPeripheral *)peripheral advertisementData:(NSDictionary *)advertisementData RSSI:(NSNumber *)RSSI; */ [manager scanForPeripheralsWithServices:nil options:nil]; break; default: break; } } //扫描到设备会进入方法 -centralManager:(CBCentralManager *)central didDiscoverPeripheral:(CBPeripheral *)peripheral advertisementData:(NSDictionary *)advertisementData RSSI:(NSNumber *)RSSI{ NSLog(@"当扫描到设备:%@",peripheral.name); //接下来可以连接设备 }
// 在usr/include中的objc/runtime.h可以查看// 获取对象对应的class- class;// 判断一个对象或者类是不是某个class或者这个class的派生类- isKindOfClass:aClass;// 判断一个对象或者类是不是某个class- isMemberOfClass:aClass;// 判断一个对象或者类对应的objc_class里面是否实现了某个协议- conformsToProtocol:(Protocol *)aProtocol;// 判断一个对象或者类对应的objc_class里面有没有某个方法- respondsToSelector:aSelector;

它作为CALayer的贰本性质对外访谈,注释表达:

3 连接外设
 //扫描到设备会进入方法 -centralManager:(CBCentralManager *)central didDiscoverPeripheral:(CBPeripheral *)peripheral advertisementData:(NSDictionary *)advertisementData RSSI:(NSNumber *)RSSI{ //接下连接我们的测试设备,如果你没有设备,可以下载一个app叫lightbule的app去模拟一个设备 //这里自己去设置下连接规则,我设置的是P开头的设备 if ([peripheral.name hasPrefix:@"P"]){ /* 一个主设备最多能连7个外设,每个外设最多只能给一个主设备连接,连接成功,失败,断开会进入各自的委托 - centralManager:(CBCentralManager *)central didConnectPeripheral:(CBPeripheral *)peripheral;//连接外设成功的委托 - centralManager:(CBCentralManager *)central didFailToConnectPeripheral:(CBPeripheral *)peripheral error:(NSError *)error;//外设连接失败的委托 - centralManager:(CBCentralManager *)central didDisconnectPeripheral:(CBPeripheral *)peripheral error:(NSError *)error;//断开外设的委托 */ //找到的设备必须持有它,否则CBCentralManager中也不会保存peripheral,那么CBPeripheralDelegate中的方法也不会被调用!! [peripherals addObject:peripheral]; //连接设备 [manager connectPeripheral:peripheral options:nil]; } } //连接到Peripherals-成功 - centralManager:(CBCentralManager *)central didConnectPeripheral:(CBPeripheral *)peripheral { NSLog(@">>>连接到名称为的设备-成功",peripheral.name); } //连接到Peripherals-失败 -centralManager:(CBCentralManager *)central didFailToConnectPeripheral:(CBPeripheral *)peripheral error:(NSError *)error { NSLog(@">>>连接到名称为的设备-失败,原因:%@",[peripheral name],[error localizedDescription]); } //Peripherals断开连接 - centralManager:(CBCentralManager *)central didDisconnectPeripheral:(CBPeripheral *)peripheral error:(NSError *)error{ NSLog(@">>>外设连接断开连接 %@: %@\n", [peripheral name], [error localizedDescription]); }

有好几非常轻便出错,我们请小心。在
didDiscoverPeripheral这一个委托中有这一行

//找到的设备必须持有它,否则CBCentralManager中也不会保存peripheral,那么CBPeripheralDelegate中的方法也不会被调用!! [peripherals addObject:peripheral];

请特别注意,假若不保留,会影响到前面包车型地铁艺术实行,那个地点重重人出错,在自个儿的蓝牙5.0交换群中天天差不离都会因为这么些主题材料产生心有余而力不足连接和对外设后续的操作。

世家也足以看一下那个委托在xcode中的表明,重视看@discussion中的内容,里面极度建议了亟待retained对象

/*! * @method centralManager:didDiscoverPeripheral:advertisementData:RSSI: * * @param central The central manager providing this update. * @param peripheral A <code>CBPeripheral</code> object. * @param advertisementData A dictionary containing any advertisement and scan response data. * @param RSSI The current RSSI of <i>peripheral</i>, in dBm. A value of <code>127</code> is reserved and indicates the RSSI * was not available. * * @discussion This method is invoked while scanning, upon the discovery of <i>peripheral</i> by <i>central</i>. A discovered peripheral must * be retained in order to use it; otherwise, it is assumed to not be of interest and will be cleaned up by the central manager. For * a list of <i>advertisementData</i> keys, see {@link CBAdvertisementDataLocalNameKey} and other similar constants. * * @seealso CBAdvertisementData.h * */- centralManager:(CBCentralManager *)central didDiscoverPeripheral:(CBPeripheral *)peripheral advertisementData:(NSDictionary<NSString *, id> *)advertisementData RSSI:(NSNumber *)RSSI;

一中举出了一部分OC的runtime方法,很易懂,因为OC就是大家的开支语言,上面作者会贰个个解读runtime中的C语言的接口。因为C中没有class的定义,独有struct,所以在介绍C语言接口此前,这里笔者将次第介绍在runtime中利用到的广泛结构体。

A transform applied to the layer relative to the anchor point of its
bounds rect. Defaults to the identity transform. Animatable.

4 扫描外设中的服务和性格

设施连接成功后,就能够扫描设备的服务了,同样是经过信托情势,扫描到结果后会步入委托方法。但是那个委托已经不复是主设备的委托(CBCentralManagerDelegate),而是外设的委托(CBPeripheralDelegate),那些委托富含了主设备与外设交互的重再次回到叫方法,包含获取services,获取characteristics,获取characteristics的值,获取characteristics的Descriptor,和Descriptor的值,写多少,读rssi,用文告的办法订阅数据等等。

猎取外设的services

//连接到Peripherals-成功 - centralManager:(CBCentralManager *)central didConnectPeripheral:(CBPeripheral *)peripheral { NSLog(@">>>连接到名称为的设备-成功",peripheral.name); //设置的peripheral委托CBPeripheralDelegate //@interface ViewController : UIViewController<CBCentralManagerDelegate,CBPeripheralDelegate> [peripheral setDelegate:self]; //扫描外设Services,成功后会进入方法:-peripheral:(CBPeripheral *)peripheral didDiscoverServices:(NSError *)error{ [peripheral discoverServices:nil]; } //扫描到Services -peripheral:(CBPeripheral *)peripheral didDiscoverServices:(NSError *)error{ // NSLog(@">>>扫描到服务:%@",peripheral.services); if  { NSLog(@">>>Discovered services for %@ with error: %@", peripheral.name, [error localizedDescription]); return; } for (CBService *service in peripheral.services) { NSLog(@"%@",service.UUID); //扫描每个service的Characteristics,扫描到后会进入方法: -peripheral:(CBPeripheral *)peripheral didDiscoverCharacteristicsForService:(CBService *)service error:(NSError *)error [peripheral discoverCharacteristics:nil forService:service]; } }

赢得外设的Characteristics,获取Characteristics的值,获取Characteristics的Descriptor和Descriptor的值

 //扫描到Characteristics -peripheral:(CBPeripheral *)peripheral didDiscoverCharacteristicsForService:(CBService *)service error:(NSError *)error{ if  { NSLog(@"error Discovered characteristics for %@ with error: %@", service.UUID, [error localizedDescription]); return; } for (CBCharacteristic *characteristic in service.characteristics) { NSLog(@"service:%@ 的 Characteristic: %@",service.UUID,characteristic.UUID); } //获取Characteristic的值,读到数据会进入方法:-peripheral:(CBPeripheral *)peripheral didUpdateValueForCharacteristic:(CBCharacteristic *)characteristic error:(NSError *)error for (CBCharacteristic *characteristic in service.characteristics){ { [peripheral readValueForCharacteristic:characteristic]; } } //搜索Characteristic的Descriptors,读到数据会进入方法:-peripheral:(CBPeripheral *)peripheral didDiscoverDescriptorsForCharacteristic:(CBCharacteristic *)characteristic error:(NSError *)error for (CBCharacteristic *characteristic in service.characteristics){ [peripheral discoverDescriptorsForCharacteristic:characteristic]; } } //获取的charateristic的值 -peripheral:(CBPeripheral *)peripheral didUpdateValueForCharacteristic:(CBCharacteristic *)characteristic error:(NSError *)error{ //打印出characteristic的UUID和值 //!注意,value的类型是NSData,具体开发时,会根据外设协议制定的方式去解析数据 NSLog(@"characteristic uuid:%@ value:%@",characteristic.UUID,characteristic.value); } //搜索到Characteristic的Descriptors -peripheral:(CBPeripheral *)peripheral didDiscoverDescriptorsForCharacteristic:(CBCharacteristic *)characteristic error:(NSError *)error{ //打印出Characteristic和他的Descriptors NSLog(@"characteristic uuid:%@",characteristic.UUID); for (CBDescriptor *d in characteristic.descriptors) { NSLog(@"Descriptor uuid:%@",d.UUID); } } //获取到Descriptors的值 -peripheral:(CBPeripheral *)peripheral didUpdateValueForDescriptor:(CBDescriptor *)descriptor error:(NSError *)error{ //打印出DescriptorsUUID 和value //这个descriptor都是对于characteristic的描述,一般都是字符串,所以这里我们转换成字符串去解析 NSLog(@"characteristic uuid:%@ value:%@",[NSString stringWithFormat:@"%@",descriptor.UUID],descriptor.value); }

类结构体,对应class。前几篇小说提到最多的三个布局。objc_class

它以一个layer的锚点为准,将形变效用于此layer的限量内。暗中认可是伊始值,Animatable意味着此属性能够功能于动画。

5 把多少写到Characteristic中
 //写数据 -writeCharacteristic:(CBPeripheral *)peripheral characteristic:(CBCharacteristic *)characteristic value:value{ //打印出 characteristic 的权限,可以看到有很多种,这是一个NS_OPTIONS,就是可以同时用于好几个值,常见的有read,write,notify,indicate,知知道这几个基本就够用了,前连个是读写权限,后两个都是通知,两种不同的通知方式。 /* typedef NS_OPTIONS(NSUInteger, CBCharacteristicProperties) { CBCharacteristicPropertyBroadcast = 0x01, CBCharacteristicPropertyRead = 0x02, CBCharacteristicPropertyWriteWithoutResponse = 0x04, CBCharacteristicPropertyWrite = 0x08, CBCharacteristicPropertyNotify = 0x10, CBCharacteristicPropertyIndicate = 0x20, CBCharacteristicPropertyAuthenticatedSignedWrites = 0x40, CBCharacteristicPropertyExtendedProperties = 0x80, CBCharacteristicPropertyNotifyEncryptionRequired NS_ENUM_AVAILABLE = 0x100, CBCharacteristicPropertyIndicateEncryptionRequired NS_ENUM_AVAILABLE = 0x200 }; */ NSLog(@"%lu", (unsigned long)characteristic.properties); //只有 characteristic.properties 有write的权限才可以写 if(characteristic.properties & CBCharacteristicPropertyWrite){ /* 最好一个type参数可以为CBCharacteristicWriteWithResponse或type:CBCharacteristicWriteWithResponse,区别是是否会有反馈 */ [peripheral writeValue:value forCharacteristic:characteristic type:CBCharacteristicWriteWithResponse]; }else{ NSLog(@"该字段不可写!"); } }
struct objc_class { // 指向元类的的指针,如果本身是元类,则指向rootMeta Class isa OBJC_ISA_AVAILABILITY;#if !__OBJC2__ // 指向父类 Class super_class OBJC2_UNAVAILABLE; // 类名 const char *name OBJC2_UNAVAILABLE; // 版本号,可以用runtime方法set,get long version OBJC2_UNAVAILABLE; long info OBJC2_UNAVAILABLE; long instance_size OBJC2_UNAVAILABLE; // 成员变量列表,存储成员变量 struct objc_ivar_list *ivars OBJC2_UNAVAILABLE; // 方法列表 struct objc_method_list **methodLists OBJC2_UNAVAILABLE; // 方法cache,msgSend遍历继承链的时候需要辅助使用 struct objc_cache *cache OBJC2_UNAVAILABLE; // 协议列表 struct objc_protocol_list *protocols OBJC2_UNAVAILABLE;#endif} OBJC2_UNAVAILABLE;

CATransform3D能够用来落到实处以下职能

6 订阅Characteristic的通知
 //设置通知 -notifyCharacteristic:(CBPeripheral *)peripheral characteristic:(CBCharacteristic *)characteristic{ //设置通知,数据通知会进入:didUpdateValueForCharacteristic方法 [peripheral setNotifyValue:YES forCharacteristic:characteristic]; } //取消通知 -cancelNotifyCharacteristic:(CBPeripheral *)peripheral characteristic:(CBCharacteristic *)characteristic{ [peripheral setNotifyValue:NO forCharacteristic:characteristic]; }

OBJC2_UNAVAILABLE宏定义是苹果在 Objc
中对系统运维版本举办封锁的操作,为的是包容非Objective-C
2.0的遗留逻辑,但我们还可以从中获得部分有价值的音讯,有乐趣的能够查看源代码,见尾巴部分文献。

图片 3两种布满的行使措施

7 断开连接(disconnect)
 //停止扫描并断开连接 -disconnectPeripheral:(CBCentralManager *)centralManager peripheral:(CBPeripheral *)peripheral{ //停止扫描 [centralManager stopScan]; //断开连接 [centralManager cancelPeripheralConnection:peripheral]; }

那一个中举出了仓库储存于上述objc_class结构体之中的片段有关结构体。objc_method

先是来看它的概念:

app作为外设被三翻五次的兑现

  1. 打开peripheralManager,设置peripheralManager的委托
  1. 创建characteristics,characteristics的description
    创建service,把characteristics添加到service中,再把service添加到peripheralManager中
  2. 翻开广播advertising
  3. 对central的操作举行响应
    • 4.1 读characteristics请求
    • 4.2 写characteristics请求
    • 4.4 订阅和注销订阅characteristics
// 存在objc_method_list里面struct objc_method { // 方法名 SEL method_name OBJC2_UNAVAILABLE; // 参数,返回值编码 char *method_types OBJC2_UNAVAILABLE; // 方法地址指针 IMP method_imp OBJC2_UNAVAILABLE;} 
public struct CATransform3D { public var m11: CGFloat public var m12: CGFloat public var m13: CGFloat public var m14: CGFloat public var m21: CGFloat public var m22: CGFloat public var m23: CGFloat public var m24: CGFloat public var m31: CGFloat public var m32: CGFloat public var m33: CGFloat public var m34: CGFloat public var m41: CGFloat public var m42: CGFloat public var m43: CGFloat public var m44: CGFloat public init() public init(m11: CGFloat, m12: CGFloat, m13: CGFloat, m14: CGFloat, m21: CGFloat, m22: CGFloat, m23: CGFloat, m24: CGFloat, m31: CGFloat, m32: CGFloat, m33: CGFloat, m34: CGFloat, m41: CGFloat, m42: CGFloat, m43: CGFloat, m44: CGFloat)}
1. 打开peripheralManager,设置peripheralManager的委托

设置当前ViewController达成CBPeripheralManagerDelegate委托

 @interface BePeripheralViewController : UIViewController<CBPeripheralManagerDelegate>

初始化peripheralManager

 /* 和CBCentralManager类似,蓝牙设备打开需要一定时间,打开成功后会进入委托方法 - peripheralManagerDidUpdateState:(CBPeripheralManager *)peripheral; 模拟器永远也不会得CBPeripheralManagerStatePoweredOn状态 */ peripheralManager = [[CBPeripheralManager alloc]initWithDelegate:self queue:nil];

objc_method_description

属性

它有15个存款和储蓄属性,含义如下:

m11:x轴方向扩充缩放m12:和m21一同决定z轴的旋转m13:和m31一齐决定y轴的旋转m14:

m21:和m12一同决定z轴的旋转m22:y轴方向扩充缩放m23:和m32一同决定x轴的旋转m24:

m31:和m13一齐决定y轴的旋转m32:和m23一齐决定x轴的旋转m33:z轴方向拓宽缩放m34:透视效果,m34
= -1 /
D,D越小,透视效果越鲜明,必得在有旋转效果的前提下,才会看出透视效果。

m41:x轴方向拓宽平移m42:y轴方向举行平移m43:z轴方向拓宽平移m44:开头为1

图片 4矩阵乘法总括同不时间它注明了多少个默认构造器和一个成员逐一构造器,但那并不经常用。

发表评论

电子邮件地址不会被公开。 必填项已用*标注