Block学习探究

代码及结果

  • 普通局部变量

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    - (void)block1 {

    NSInteger num = 100;

    // 定义一个block
    dispatch_block_t block = ^ {
    NSLog(@"block1 -- numer = %zd", num);
    };

    num = 200;

    block();
    }
  • 使用__block修饰变量

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    - (void)block2 {

    __block NSInteger num = 100;

    // 定义一个block
    dispatch_block_t block = ^ {
    NSLog(@"block2 -- numer = %zd", num);
    };

    num = 200;

    block();
    }
  • 全局变量

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    - (void)block3 {


    // 定义一个block
    dispatch_block_t block = ^ {
    NSLog(@"block3 -- numer = %zd", blockNum);
    };

    blockNum = 200;

    block();
    }
  • 静态常量

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    - (void)block4 {

    static NSInteger num = 100;

    // 定义一个block
    dispatch_block_t block = ^ {
    NSLog(@"block4 -- numer = %zd", num);
    };

    num = 200;

    block();
    }

运行结果

2018-01-19 00:04:13.759416+0800 CycleRetain[91251:2382380] block1 -- numer = 100
2018-01-19 00:04:13.760206+0800 CycleRetain[91251:2382380] block2 -- numer = 200
2018-01-19 00:04:13.760473+0800 CycleRetain[91251:2382380] block3 -- numer = 200
2018-01-19 00:04:13.760603+0800 CycleRetain[91251:2382380] block4 -- numer = 200

原理及本质

block 根据创建位置不同,共有三种:栈block,堆block,全局block

使用block本质就是为了保证栈上和堆上block内访问和修改的是同一个变量,具体的实现是将block 修饰的变动自动封装成一个结构体,让他在堆上创建
基于OC 底层的runtime 机制
block 在内部会有一个指向结构体的指针,当调用block的时候其实就是让block找出对应的指针所指的函数地址进行调用。并传入了block自己本身

面试中常见问题总结(二)

非正式协议和正式协议

非正式协议:凡是给NSObject或者其子类添加的类别都是非正式协议
正式协议:使用 @protocol 方式声明的方法列表,有 @require 和 @optional 两种类型

KVC 键值编码

之所以可以使用就是因为给 NObject 增加了 NSKeyValueCoding 非正式协议,NSObject 实现了协议,OC中几乎所有的对象都支持KVC,获取值的方法是不通过属性的setter、getter 方法,而是通过属性名称的key间接访问了实例变量,可以访问私有变量

1
2
3
4
 // 官方注释
/* Send -setObject:forKey: to the receiver, unless the value is nil, in which case send -removeObjectForKey:.
*/
- (void)setValue:(nullable ObjectType)value forKey:(NSString *)key;

如果为nil, 就会自动过滤掉, 此方法常用在字典中过滤空值

// forKeyPath: 用于符合属性,能够通过层层访问内部属性,相当于调用属性点语法, 拥有forKey 的所有功能

1
- (void)setValue:(nullable id)value forKeyPath:(NSString *)keyPath;
  • 实现原理
    • 利用 setValue: forKeyPath: 赋值
    1. 底层实现还是使用 runtime 给对象发消息,先去查找属性是否有setter方法,存在setter 就会通过 setter 方法复制,
    2. 如果没有setter方法,接着查找下是否划线的成员变量,如果有直接给赋值
    3. 如果没有下划线的成员变量,查找是否存在和key值相同的属性,如果有就直接给属性赋值
    4. 如果没有和key值相同的属性,就会执行 setValue: forUndefinedKey: 抛出一个异常程序奔溃,这也是字典转模型中需要实现这个方法的原因

KVO 键值监听

利用一个key值来找到某个属性并监听某个属性的值的变化发送给观察者,可以理解为简单的观察者模式

  • KVO 使用

    1. 为目标对象的属性添加观察者

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      - (void)testKVO {
      Person *p = [[Person alloc] init];
      self.person = p;
      p.name = @"哈哈";

      [p addObserver:self forKeyPath:@"name" options:NSKeyValueObservingOptionNew | NSKeyValueObservingOptionOld context:nil];

      p.name = @"我不是原来的我!你能监听到我 ???";
      p.name = @"看我七十二变";
      }
2. 实现监听的方法

1
2
3
4
5
#pragma mark - kvo 回调方法
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSKeyValueChangeKey,id> *)change context:(void *)context {

NSLog(@"KVO监听到的对象是 = %@, 值 = %@ , change = %@", [object class], keyPath, change);
}
3. 移除观察者
1
2
3
4
5
- (void)dealloc {

[self removeObserver:self forKeyPath:@"name"];
NSLog(@"-----控制器销毁-----");
}
  • KVO原理

    类的属性被添加了观察者后,OC底层就会通过Runtime 动态的创建一个这个类的派生类,当前类内部的isa 指针指向了这个派生类,在派生类中重写基类被观察属性的 setter 方法,调用前后会调用 willChangeValueForKey, setValueForKey, didChangeValue 方法, 从而达到监听属性值的方法

面试中常见问题总结

OC 的动态性

OC是基于C语言的,C语言在编译阶段就会确定了调用具体的那个函数,OC它的底层是通过runtime 运行时机制来调用函数,在OC中成为消息发送,具体来说就是在编译阶段OC可以调用任何函数,即时这个函数是没有实现的,只有在运行的时候才会根据isa指针和已经注册的方法列表中找到 IMP(函数指针)真正要调用的那个函数,我们平时写的OC代码,在运行的时候也都是转换成了runtime 的方式进行的,不管是什么方法本质都是发送一个消息

runtime

  • 消息机制原理: 根据方法编号列表 SEL 去映射表中查找对应方法的实现
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
Person *p = [Person allo] init];

// 底层的写法
// alloc
Person *p = objc_msgSend(objc_getClass("Person"), sel_registerName("alloc"),);

// init
p = objc_msgSend(p, sel-registerName("init"));

// 调用对象方法本质就是:让对象发消息
objc_msgSend(p, @selector(eat));

// 调用类方法本质: 让类发消息
objc_msgSend([Person class], @selector(run:), 20);

// alloc init 也可以理解为:

id objc = objc_msgSend([NSObject class], @selector(alloc));

objc = objc_msgSend(objc, @selector(alloc));
  • runtime 消息机制调用流程

    每个对象内部都有一个isa指针,指向他自己真实的类来确定调用的是那个类的方法; 每个方法sel都有一个IMP指针(指向现实函数的指针),不管是对象方法还是类方法,都会有一个方法列表

    1. 发消息时根据对象内部的isa 指针去该类所对应的方法类表中查找方法,如果没有去父类中查找
    2. 注册每个方法编号,为了以后快速查找调用
    3. 根据方法编号查找对应方法
    4. 通过isa指针和IMP指针确定最终调用的函数
  • runtime 常见使用

    1. 运行是动态添加方法
    2. 给分类添加属性,(关联属性)
    3. 字典转模型中从先遍历模型属性再从字典中查找相关值实现模型属性赋值
    4. NSCoding 自动归档解档时对象属性过多时 encodeWithCoder:initWithCoder 两个方法实现

Runloop

  • 概念

    NSRunloop 是封装 CFRunloopRef 一套纯C的函数,

  • 作用

    1. 保持程序持续运行
    2. 处理App中各种事件,具体的有:toches事件, NSTimer 事件, Selector 事件, PerformSelector 事件,source 源等等
    3. 有事件触发时就运行,没有就休眠
    4. 渲染app 中唯一一个主线程上所有UI界面的更新
  • 使用和要点

    1. Runloop 在程序入口 main 函数中就会开启,并且开启的是主线程不会销毁除非退出程序,保证了程序一直运行并能响应事件和交互
    2. 每一条线程都是唯一对应一个Runloop
    3. 主线程不需要创建,因为在程序启动的时候就已经创建了,而子线程的需要手动创建 直接获取currentRunloop 方式(本质是使用了懒加载)
    4. Runloop 对象是利用字典进行存储,因为一一对应,所有key 和value 就是线程和 他所对应的runloop

    5. Runloop 中 NSDefaultRunLoopMode 一般情况下主线程运行的 Mode

    6. UIInitializationRunLoopMode 初始化mode,通常不用
    7. UITrackingRunLoopMode 界面跟踪, 用于scrollView 追踪触摸滑动,保证界面不受其他mode影响
    8. GSEventReceiveRunLoopMode 用于接收系统事件内部 mode, 通常不用

    9. NSRunLoopCommonModes, 并不是某种具体的 mode ,开发中使用最多,他是一个组合 mode,组合了 NSDefaultRunLoopMode和 NSRunLoopCommonModes

  • Runloop 相关类

    1. Source 事件源/输入源可以理解为 激发源
    2. Timer 基于事件触发,CADisplayLink,NSTimer 都是加到了 Runloop,受 Mode 的影响,GCD 定时器不受Runloop 影响
    3. Observer 相当于runloop 循环中的监听器,时刻监听当前Runloop的运行状态
    4. 休眠没事情做的时候可以停下来,处于休眠状态
  • 使用场景

    1. 创建NSTimer
    2. PersforSelector方法
    3. 常驻线程:(创建后处于等待状态,有事件时响应事件)
      • 实现后台收集用户停留时间或者是某个按钮的点击次数,如果使用主线程会不方便,使用runloop解决
    4. AutoreleasePool 自动释放池
    5. UI更新

iOS即时通讯实现二

实现IM通信所用协议

几个协议理解

xmpp 是基于xml协议主要是易于扩展,早起用于PC时代的产品使用,流量大,耗电大交互复杂其实并不适合移动互联网时代的IM产品

MQTT 协议 适配多平台,需自己扩展好友,群组等功能

protobut 协议

封装socket,自己实现

所要考虑的问题

  • 传输协议选择

    • TCP ? 一般公司都选用(技术不是很成熟)
    • UDP ?QQ 腾讯增加自己的私有协议,保证数据传递的可靠性,解决了丢包,乱序的问题
  • 聊天协议选择及代表框架

    • Socket ? CocoaAsyncSocket
    • WebSockt ?是传输通讯协议,基于socket封装的一个协议 SocketRocket
    • MQTT ?MQTTKit 聊天协议为上层协议
    • protobut 协议 ?
    • XMPP ?XMPPFramework 聊天协议为上层协议
    • 自定义
  • 传输数据格式

    • Json?
    • XML?
    • ProtocolBuffer?
  • 细节相关

    • TCP 长连接如何保持,
    • 心跳机制
    • 重连机制
    • 数据安全机制

CocoaAsyncSocket

  • CocoaAsyncSocket 的 delegate 用来设置代理处理各个回调
  • delegateQueue
  • socketQueue 串行队列,贯穿全类并没有任何加锁,确保了socket 操作中每一步都是线程安全的
  • 创建了两个读写队列(本质是数组)

    • NSMutableArray *readQueue;
    • NSMutableArray *writeQueue;
  • 全局数据缓冲: 当前socket未获取完的数据大小

    • GCDAsyncSocketPreBuffer *preBuffer;
  • 交替延时变量: 用于进行另一服务端地址请求的延时

    • alternateAddressDelay
  • connect

  • disconnect

数据粘包,断包

  • 粘包: 如果客户端同一时间发送几条数据,而服务器只收到一大条数据

    • 原因:
    • 由于传输的是数据流,经过TCP传输后,TCP使用了优化算法将多次间隔较小且数据量小的数据合并成一个大的数据块,因此三条数据合并成了一条

      TCP,UDP都可能造成粘包的原因

    • 发送端需要等缓冲区满了才发送出去,做成粘包

    • 接收方不及时接收缓冲区的包,造成多个包接收
  • 断包: 因为一次发送很大的数据包,缓冲区有限,会分段发送或读取数据

CocoaAsyncSocket的封包,拆包处理

  • 读取数据方法:

    // 有数据时调用,读取当前消息队列中的未读消息
    - (void)readDataWithTimeout:(NSTimeInterval)timeout tag:(long)tag
    
**每次读取数据,每次都必须手动调用上述 readData 方法超时设置为不超时才能触发消息回调的代理**

**因此在第一连接时调用,再在接到消息时调用,上述方法就可以达到每次收到消息都会触发读取消息的代理**

以上做法存在的问题就是没有考虑数据的拆包会有粘包情况,这时候需要用下面两个`read`方法,1. 读取指定长度、2.读取指定边界

    // 读取特定长度数据时调用
    - (void)readDataToLength:(NSUInteger)length withTimeout:(NSTimeInterval)timeout tag:(long)tag

    // 读到特定的 data 边界时调用
    - (void)readDataToData:(NSData *)data withTimeout:(NSTimeInterval)timeout tag:(long)tag


**解决办法及具体思路**

- 封包:给每个数据包增加一个长度或者加一个开始结束标记,表明数据的长度和类型(根据自己的项目:文本、图片、语音、红包、视频、话题、提问等等)
    - 可以在数据包之后加一个结束标识符,解决了传输过程中丢包,丢失头部信息的错误包读取,读到这样的就丢弃直接读下一个数据包

- 拆包:获取每个包的标记,根据标记的数据长度获取数据,最后根据类型处理数据,最后读取数据包头部的边界
  • 利用缓冲区对数据进行读取

    • 创建读取数据包
    • 添加到读取的队列中
    • 从队列中取出读取任务包
    • 使用偏移量 maxLength 读取数据

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      21
      22
      23
      24
      25
      26
      27
      28
      29
      30
      31
      32
      33
      34
      35
      36
      		- (void)readDataWithTimeout:(NSTimeInterval)timeout
      buffer:(NSMutableData *)buffer
      bufferOffset:(NSUInteger)offset
      maxLength:(NSUInteger)length
      tag:(long)tag
      {
      if (offset > [buffer length]) {
      LogWarn(@"Cannot read: offset > [buffer length]");
      return;
      }

      // 1. 创建读取数据包
      GCDAsyncReadPacket *packet = [[GCDAsyncReadPacket alloc] initWithData:buffer
      startOffset:offset
      maxLength:length
      timeout:timeout
      readLength:0
      terminator:nil
      tag:tag];

      dispatch_async(socketQueue, ^{ @autoreleasepool {

      LogTrace();

      if ((flags & kSocketStarted) && !(flags & kForbidReadsWrites))
      {
      // 2. 向读的队列添加任务包
      [readQueue addObject:packet];
      // 3. 从队列中取出读取任务包
      [self maybeDequeueRead];
      }
      }});

      // Do not rely on the block being run in order to release the packet,
      // as the queue might get released without the block completing.
      }

doReadData 方法时读取数据的核心方法

  • 如果读取对列中没有数据,就去判是否是上次已经读取了数据,但是因为没有目前还没有读取到数据的标记,这是正好设置了 read 方法中的超时时间,所以要断开socket 连接

  • 接收到数据后在 socket系统进程的数据缓冲区中
    • (基于TLS的分为两种)他们在各自管道中流动,完成数据解密后又流向了全局数据缓冲区
      • CFStreem
      • SSLPrebuffer

处理数据逻辑: 如果当前读取包长度给明了,则直接流向currentRead,如果数据长度不清楚,那么则去判断这一次读取的长度,和currentRead可用空间长度去对比,如果长度比currentRead可用空间小,则流向currentRead,否则先用prebuffer来缓冲。

心跳机制

  • TCP 自带的keep-alive 使用空闲时间来发并且默认超时时间太长是2 小时,
  • 如果应用使用了socks ,socks proxy会让tcp keep-alive失效,因为socks 协议只管转发TCP层具体的数据包,并不会转发TCP协议内的实现具体细节数据包
  • TCP 的长连接理论上是一直保持连接的,可以设置 TCP keep-alive的时间 但是实际情况下中,可能出现故障或者是因为防火墙的原因会自动把一定时间段内没有数据交互的连接给断开,心跳机制就可以维持长连接,保持活跃状态

  • iOS 中使用 NSTimer scheduledTimerWithTimeInterval 需要在调用之前的关键位置设置 fireDate 为未来的某个时间, 然后再需要的时候开启

  • 或者直接使用 timerWithTimeInterval 创建,然后添加到runloop

iOS 即使通讯网络知识篇

网络七层协议

读《图解TCP/IP》第5版以及看过的关于网络文章之后自己的理解

OSI (Open System Interconnec) 译为 开发式系统互联 七层模型从下到上一次是:物理层 -> 数据链路层 —> 网络层 -> 传输层 -> 会话层 -> 表示层 -》 应用层

数据每次经过一层之后就会在数据头部增加一个首部信息

物理层:网卡,网线,集线器,中继器,调制解调器
  • 定义设备标准,如网线,光纤接口类型,将0和1转化为电信号的强和弱,达到传输比特流目的,这层数据叫比特
数据链路层:网桥,交换机
  • 传输的地址帧,并且有检测错误功能,保证数据可靠传输, 这层数据叫帧
网络层:路由器
  • 两个计算机通信时可能会经过多个数据链路或者通信子网,网络层将数据链路层的帧组成数据包,包中有封装好的包头,其中含有逻辑地址信息(源站点和目的站点),此层为数据包选择合适的路由和交换结点,IP协议产生,这层数据叫着数据包
传输层:
  • 解决数据如何在网络中传输,这个层负责获取全部信息,因此,它必须跟踪数据单元碎片、乱序到达的 数据包和其它在传输过程中可能发生的危险。第4层为上层提供端到端(最终用户到最终用户)的透明的、可靠的数据传输服务。所为透明的传输是指在通信过程中 传输层对上层屏蔽了通信传输系统的具体细节。传输层协议的代表包括:TCP、UDP、SPX等
会话层:
  • 这一层也可以称为会晤层或对话层,在会话层及以上的高层次中,数据传送的单位不再另外命名,而是统称为报文。会话层不参与具体的传输,它提供包括访问验证和会话管理在内的建立和维护应用之间通信的机制。如服务器验证用户登录便是由会话层完成的
表示层:
  • 这一层主要解决拥护信息的语法表示问题。它将欲交换的数据从适合于某一用户的抽象语法,转换为适合于OSI系统内部使用的传送语法。即提供格式化的表示和转换数据服务。数据的压缩和解压缩, 加密和解密等工作都由表示层负责。
应用层:HTTP 协议
  • 如何包装数据

通常从上到下会分成四层

TCP/IP 通常是指TCP/IP协议族,是一组不同协议组合在一起构成的协议族

  • 应用层:FTP 应用层协议
  • 传输层:TCP,UDP 传输层协议,TCP 提供了一个可靠的服务
  • 网络层:IP,ICMP 网络层协议,传输时不可靠的
  • 链路层:以太网协议

Socket

为了解决不同计算机上进程间通信问题
服务器在指定的端口上监听,然后生成一对儿新的socket 进行通讯,一个线程对应一个socket

英文翻译为“插座”通常称为”套接字”, 本质是对于TCP/IP的封装,有客户端 socket 和 服务端socket

  • 流式 Socket (STREAM): 是一种面向连接的socket,针对于面向连接的TCP服务应用,因为安全所以效率低
  • 数据报式Socket (DATAGRAM): 是一种无连接的SOcket,正对于无连接的UDP服务应用,无序,不安全,需要在接收端分析重排或者要求重发,所以效率高

Swift加密相关

最近公司项目开发中引用了 SwiftOC 混编, 涉及到了加密相关, 使用了第三方 CryptoSwift, 以下是使用过程中的简单记录

CryptoSwift简介

数组转换

  • data 转化为字节数组

    1
    2
    let bytes = data.bytes
    print(bytes)
  • 字节数组转化为 data

    1
    2
    let data = Data(bytes: [0x01, 0x02, 0x03])
    print(data)
  • 十六进制创建字节

    let bytes_Hex = Array<UInt8>(hex: "0x010203")
    print(bytes_Hex)
    
  • 字节转十六进制数

    let hex = bytes_Hex.toHexString()
    print(hex)       
    
  • 字符串生成字节数组

    let string2bytes = "string".bytes
    print(string2bytes)
    
  • 字节数组 base64

    let bytes1: [UInt8] = [1, 2, 3]
    let base64Str = bytes1.toBase64()
    print(base64Str!)
    
  • 字符串base64

    let str0 = "test"
    let base64Str1 = str0.bytes.toBase64()
    print(base64Str1!)
    

MD5加密

  • MD5(RFC1321)诞生于 1991 年,全称是“Message-Digest Algorithm(信息摘要算法)5”,由 MIT 的计算机安全实验室和 RSA 安全公司共同提出。
  • 之前已经有 MD2、MD3 和 MD4 几种算法。MD5 克服了 MD4 的缺陷,生成 128bit 的摘要信息串,出现之后迅速成为主流算法

  • 字节数组MD5

    let str = "测试test"
    let bytes = str.bytes
    
    // 写法一
    let digest = bytes.md5().toHexString()
    print(digest)
    // 04f47b69e3573867a5b3c1e5edb00789
    
    // 写法二
    let digest1 = Digest.md5(bytes).toHexString()
    print(digest1)
    // 04f47b69e3573867a5b3c1e5edb00789
    
  • Data的MD5值

    /// data MD5
    let data = Data(str.bytes)
    let digest3 = data.md5().toHexString()
    print(digest3)
    // 04f47b69e3573867a5b3c1e5edb00789
    
  • 字符串MD5

    ///写法 一
    let digest4 = str.md5()
    print(digest4)
    // 04f47b69e3573867a5b3c1e5edb00789
    
    // 写法二
    do {
        var digest4_2 = MD5()
    
        let _ = try digest4_2.update(withBytes: "测试".bytes)
        let _ = try digest4_2.update(withBytes: "test".bytes)
        let result = try digest4_2.finish()
        print(result.toHexString())
        //04f47b69e3573867a5b3c1e5edb00789
    } catch {
    
    }    
    

哈希算法

  • 字节数组SHA值

    let str = "测试test"
    let bytes = str.bytes
    
    //方式一
    let digest1 = bytes.sha1().toHexString()
    let digest2 = bytes.sha224().toHexString()
    let digest3 = bytes.sha256().toHexString()
    let digest4 = bytes.sha384().toHexString()
    let digest5 = bytes.sha512().toHexString()
    print(digest1, digest2, digest3, digest4 ,digest5, separator: "\n")
    
    //方式二
    let digest6 = Digest.sha1(bytes).toHexString()
    let digest7 = Digest.sha224(bytes).toHexString()
    let digest8 = Digest.sha256(bytes).toHexString()
    let digest9 = Digest.sha384(bytes).toHexString()
    let digest10 = Digest.sha512(bytes).toHexString()
    print(digest6, digest7, digest8, digest9 ,digest10, separator: "\n")
    
  • Data的SHA值

    let data = Data(bytes: str.bytes)
    let digest11 = data.sha1().toHexString()
    let digest12 = data.sha224().toHexString()
    let digest13 = data.sha256().toHexString()
    let digest14 = data.sha384().toHexString()
    let digest15 = data.sha512().toHexString()
    print(digest11, digest12, digest13, digest14 ,digest15, separator: "\n")
    
  • 字符串的SHA值

    let digest16 = str.sha1()
    let digest17 = str.sha224()
    let digest18 = str.sha256()
    let digest19 = str.sha384()
    let digest20 = str.sha512()
    print(digest16, digest17, digest18, digest19 ,digest20, separator: "\n")
    
// 方式一
let digest_str = str.sha1()
print(digest_str)

// 方式二
do {
    var digest_str = SHA1()
    let _ = try digest_str.update(withBytes: Array("测试".bytes))
    let _ = try digest_str.update(withBytes: Array("test".bytes))
    let result = try digest_str.finish()
    print(result.toHexString())
} catch {

}

AES 加密

  • key:公共密钥

  • IV: 密钥偏移量

  • padding:key 的补码方式

    key 长度不够16 字节时,需要手动填充, zeroPadding 将其补齐至 blockSize 的整数倍
    zeroPadding 补齐规则:
    - 将长度补齐至 blockSize 参数的整数倍。比如我们将 blockSize 设置为 AES.blockSize(16)
    - 如果长度小于 16 字节:则尾部补 0,直到满足 16 字节。
    - 如果长度大于等于 16 字节,小于 32 字节:则尾部补 0,直到满足 32 字节。
    - 如果长度大于等于 32 字节,小于 48 字节:则尾部补 0,直到满足 48 字节。 以此类推......
    
  • 补码方式padding, noPadding, zeroPadding, pkcs7, pkcs5, 默认使用 pkcs7,两者写法结果相同

let iv_paddding = "1234567890123456" // 默认是
let aes_padding1 = try AES(key: key.bytes, blockMode: .CBC(iv: iv_paddding.bytes))
let aes_padding2 = try AES(key: key.bytes, blockMode: .CBC(iv: iv_paddding.bytes), padding: .pkcs7)            
  • ECB 模式

    let key = "hangge.com123456"
    let str = "E蜂通信"
    print("加密前 \(str)")
    
    //MARK: 使用ECB 模式, , 使用aes128 加密
    let aes = try AES(key: key.bytes, blockMode: .ECB)
    let encrpyted = try aes.encrypt(str.bytes)
    print("加密后 \(encrpyted)", separator: "-----")
    
     /// 解密
     let decrypted = try aes.decrypt(encrpyted)
     print("解密后 \(decrypted)")
     let decryptedString = String(data: Data(decrypted), encoding: .utf8)!
     print("解密后字符串 \(decryptedString)")
    
  • CBC 模式

    //MARK: 使用CBC 模式, 需要提供一个额外的密钥偏移量 iv
    
        /// 写法 1 --  便捷写法
        // 偏移量
        let iv = "1234567890123456" // 默认是
    
        // TODO: 随机密钥偏移量
        let data = Data(bytes: AES.randomIV(AES.blockSize))
        let random_iv = String(data: data, encoding: .utf8)
    
/// 写法 2 --
// 创建密码器
let cbc_aes = try AES(key: key.bytes, blockMode: .CBC(iv: iv.bytes))

let cbc_aes2 = try AES(key: key, iv: iv)


let cbc_aes3 = try AES(key: key, iv: iv)

let cbc_encrypted = try cbc_aes.encrypt(str.bytes)

let cbc_encrypted2 = try cbc_aes2.encrypt(str.bytes)

let cbc_encrypted3 = try cbc_aes3.encrypt(str.bytes)


// 便捷写法解密
let cbc_decrypted = try cbc_aes.decrypt(cbc_encrypted)
let cbc_decryptedData = Data(cbc_decrypted)
let cbc_decryptedStr = String(data: cbc_decryptedData, encoding: .utf8)
print(cbc_decryptedStr)

// 正常写法2 解密
let cbc_decrypted2 = try cbc_aes2.decrypt(cbc_encrypted2)
let cbc_decryptedStr2 = String(data: Data(cbc_decrypted2), encoding: .utf8)
print(cbc_decryptedStr2)


// 随机密钥偏移量解密
let cbc_decrypted3 = try cbc_aes3.decrypt(cbc_encrypted3)
let cbc_decryptedStr3 = String(data: Data(cbc_decrypted3), encoding: .utf8)
print(cbc_decryptedStr3)

Mac 安装MySQL

搭建 XMPP 即使通讯

安装MySQL数据库

  • 安装MySQL 数据库

    • 官网 下载安装包

      特别强调:不要一直下一步最后的时候会有个弹窗提示你生成root的临时密码,记录一下

  • 安装数据库管理工具 MySQLWorkbench

    官网安装包下载安装

  • 修改数据库 root 密码,

    打开MySQLWorkbench 随便打开一个SQL 输入安装时记录的密码,确认后提示你修改密码,

  • 如果为记住安装时密码,参考下面链接修改

安装 openfire

  • 官网 下载安装包安装

  • 安装后需重启系统偏好设置

  • 如果出现右边的管理员配置按钮不能点击,请使用 brew 安装 或者更新 Java 环境,openfire 需要 Java 环境

本地已有项目添加Pod

上一篇中主要记录了如何使用 pod 工具自动创建 pod 库并且生成一个测试Demo,这边主要记录给已有的项目添加 pod

创建 .podsepc 文件

  • cd 到已有项目的根目录执行命令: pod spec create [ProjectName].podspec

    1
    pod spec create EmotionKeyboard.podspec

修改编辑 .podsepc 文件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
	s.name         = "EmotionKeyboard"
s.version = "0.0.1"
s.summary = "A Emotional Keyboard."

# 此处的格式不能修改
s.description = <<-DESC

Swift Emotional Keyboard
DESC

s.homepage = "https://github.com/aTreey/EmotionKeyboard"

# 两种写法,使用下面第二种
# s.license = "MIT (example)"
s.license = { :type => "MIT", :file => "LICENSE" }

s.author = { "aTree" => "480814177@qq.com" }

s.platform = :ios, "8.0"

s.source = { :git => "https://github.com/aTreey/EmotionKeyboard.git", :tag => "#{s.version}" }


# 从 .podspec 同级别的目录下起匹配
s.source_files = "EmotionKeyboard", "EmotionKeyboard/Classes/**/*.{h,m}"

# 设置自己项目中依赖的系统库
s.framework = "UIKit"

```

## 调整文件的目录结构

- 在项目根目录下新建一个放组件代码的文件夹
- 如果项目中包含 `bundle` 文件, 需要在组件文件夹下新建一个 `Assets` 文件夹,用来存放组件中的资源

最终结果如下:

![](https://ws1.sinaimg.cn/large/a1641e1bly1fm2jl7ykslj21gc0o80xt.jpg))

- 创建并添加必要文件

- `LICENSE` 文件: 开源许可文件, 如果创建项目的时候选择了开源许可就可以自动生成忽略此步骤,如果没有直接拷贝一份其他的工程中的即可
- `README.md` 文件: 主要用来说明你开源库的信息及使用方法


## 验证本地 `.podspec` 文件

- 错误1: `.podspec `中的文件路径设置错误

![](https://ws1.sinaimg.cn/large/a1641e1bly1fm2jlboq82j20v807ejug.jpg)

- 错误2: bundle 文件资源路径错误

![](https://ws1.sinaimg.cn/large/a1641e1bly1fm2jl4shnnj20zq0cwgqf.jpg)


- 本地验证结果

- **EmotionKeyboard passed validation.** 本地验证验证通过

## 网络验证 `.podspec` 文件

- 给 `master` 分支打上 `.podspec` 文件中对应的 `tag` 标签, 提交推送,
- 执行命令
pod spec lint
1
- 网络验证结果
EmotionKeyboard.podspec passed validation. ```

本文参考

给 Pod 添加资源文件

基于 Swift 创建 CocoaPods 完全指南

CocoaPods制作私有pod

CocoaPods原理

本文主要记录制作自己的pod库过程

CocoaPods 是自动化工具,主要用来管理一些开源的第三方框架,或者是制作自己的私有库,开源库,也可以使用它讲自己的项目组件化。

具体可以参考:

  1. 细聊 Cocoapods 与 Xcode 工程配置
  2. 你真的会用 CocoaPods 吗?

CocoaPods 安装

安装教程

创建私有索引仓库

每次使用第三方开源库的时候只需要在 Podfile 文件中指定所用的库然后 Pod install ,这是因为安装pod的时候 pod setup远程仓库(索引库) clone到了本地,此仓库中存放了所有支持pod框架的描述信息(包括每个库的各个版本)

文件目录:

~/.cocoapods/repos/master
  • 码云 或者 coding 上创建一个私有仓库

  • 添加私有库到CocoaPods

    • 格式 pod repo add [repoName] (URL)

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      	pod repo add PrivateSpec https://gitee.com/aapenga/PrivateSpec.git 
      ```


      ## 创建本地私有库

      - `cd` 到任意一个文件夹下, 执行 `pod lib create [项目名]`

      ```
      pod lib create PPExcelView
  • 将文件放入到生成的 Demo 文件夹 的 Classes 文件目录下
  • cdExample 下(含有 Podfile 文件) 执行

    1
    pod install

    或者

    1
    pod update

此时添加的文件出现在 Dome Pod 的 Development Pods 目录中

  • 从本地验证 pod 是否通过

    1
    pod lib lint

    如果有警告,可以使用 --private 或者 --allow-warnings 忽略

    1
    2
    pod lib lint --allow-warnings
    pod lib lint --private

    出现 PPExcelView passed validation. 为通过

  • 从远程仓库验证 pod

    1
    pod spec lint

    出现 PPExcelView.podspec passed validation 为通过

推送 .podspec 文件到私有仓库

cd.podspec 所在的文件下

pod repo push MyGitSpec PPExcelView.podspec

Alamofire源码学习总结

网络请求时Path和Query 之间是用 ‘?’ 号隔开,后边是传给服务器的参数,GET请求Query是放在URL之后,POST请求是放在Body中

  • 如果参数是一个 key-value 形式,Query 格式为:key=value

  • 如果参数是一个数组 key = [value1, value2, value3 ….],

  • Query 格式为 key[]=value1&key[]=value2&key[]=value3

  • 如果参数是一个字典

1
key = [“subKey1”:”value1”, “subKey2”:”value2”, “subKey3”:”value3”….],
  • Query 的格式为
1
key[subKey1]=value1&key[subKey2]=value2&key[subKey3]=value3
  • Alamfire中的编码
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
private func query(_ parameters: [String: Any]) -> String {
var components: [(String, String)] = []
for key in parameters.keys.sorted(by: <) {
let value = parameters[key]!
components += queryComponents(fromKey: key, value: value)
}
return components.map { "\($0)=\($1)" }.joined(separator: "&")
}
public func queryComponents(fromKey key: String, value: Any) -> [(String, String)] {
var components: [(String, String)] = [] // 元祖数组
if let dictionary = value as? [String: Any] { // value 为字典,key[subKey]=value 形式
for (nestedKey, value) in dictionary {
components += queryComponents(fromKey: "\(key)[\(nestedKey)]", value: value)
}
} else if let array = value as? [Any] { // value为数组, key[]=value 形式
for value in array {
components += queryComponents(fromKey: "\(key)[]", value: value)
}
} else if let value = value as? NSNumber { // value为 NSNumber
if value.isBool {
components.append((escape(key), escape((value.boolValue ? "1" : "0"))))
} else {
components.append((escape(key), escape("\(value)")))
}
} else if let bool = value as? Bool { // value 为 Bool
components.append((escape(key), escape((bool ? "1" : "0"))))
} else { // value 为字符串时 直接转义
components.append((escape(key), escape("\(value)")))
}
return components
}