0%

VSCode Snippets 使用手册

前言

当我们为了减少模板代码,我们第一个很可能想到使用Code Snippets,很多IDE和文件编辑器已经给我们提供了一个预装的代码片段,当预装的代码片段不能瞒着我们的需求,我们可能会自己定制一些自己的专有代码代码片段,下面就为大家介绍如何在VSCode定制自己的代码片段以及一些小技巧。

阅读全文 »

简介

最近在研究软件设计领域常用各种图解,发现Typorad对MarkDown渲染diagrams的支持特别好,于是整理了一下,方便以后查阅使用,目前(测试版本0.9.9.18.1(1088))支持以下几大类图解:

  • Sequence
  • Flowchart
  • Mermaid
    • sequence (时序图)
    • flowchart (流程图)
    • gantt (甘特图)
阅读全文 »

问题引出

在开发中,涉及价格金额处理,后台会返回Number类型的数据,打印或者经过Json转Model后的NSString可能出现精度丢失的问题,如果涉及到金额的加减乘除运算问题将暴露得更为明显。这里就iOS数据精度处理做一个总结。

问题复现

NSNumber转NSSting

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
NSArray *numbers = @[
@99.00,
@99.09,
@99.19,
@99.29,
@99.39,
@99.49,
@99.59,
@99.69,
@99.79,
@99.89,
@99.99,
];

for (int i = 0; i < numbers.count; i++) {
NSNumber *number = numbers[i];
NSString *strValue = [number stringValue];

NSLog(@"strValue:%@",strValue);
}

/*
oldVlue:99.00 strValue:99
oldVlue:99.09 strValue:99.09
oldVlue:99.19 strValue:99.19
oldVlue:99.29 strValue:99.29000000000001
oldVlue:99.39 strValue:99.39
oldVlue:99.48 strValue:99.48999999999999
oldVlue:99.59 strValue:99.59
oldVlue:99.69 strValue:99.69
oldVlue:99.79 strValue:99.79000000000001
oldVlue:99.89 strValue:99.89
oldVlue:99.99 strValue:99.98999999999999
*/

在这里我们发现将NSNumber转换成NSSting的过程中可能会出现精度丢失。

Json到Model

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
//出现BUG的条件必须是两位数,且带两位小数,类型还必须是float
//两位数:十位必须是7、8、9;个位数随意
//两位小数:个位数随意;十位数必须是0
NSString *jsonStr = @"{\"71.40\":71.40, \"97.40\":97.40, \"80.40\":80.40, \"188.40\":188.40}";
NSLog(@"json:%@", jsonStr);

NSData *jsonData = [jsonStr dataUsingEncoding:NSUTF8StringEncoding];
NSError *jsonParsingError = nil;
NSDictionary *dic = [NSMutableDictionary dictionaryWithDictionary:[NSJSONSerialization JSONObjectWithData:jsonData options:0 error:&jsonParsingError]];

NSLog(@"dic:%@", dic);
/*
2017-10-14 18:29:19.434 FloatTransferDemo[62722:3093992] dic:{
"188.40" = "188.4";
"71.40" = "71.40000000000001";
"80.40" = "80.40000000000001";
"97.40" = "97.40000000000001";
}
*/

在这里我们发现将Json解析成Model的过程中可能会出现精度丢失。

问题分析

因为浮点数在计算机中是采用IEEE规定的标准浮点格式,即二进制科学表示法。
在这种表示法中,一个数 S = M * 2 ^ N

其中N表示阶码,M表示位数(有效数字位)。
例如一个float类型的浮点,在32bit位上,占4个字节,字节表示为

【31】N:【30 ~ 23】 M:【22~0】
  • 31位表示符号位: 0正,1负
  • 中间8位是阶码位: 表示范围【-128 ~ 127】,对于float类型数据规定其偏移量为127
  • 后面23位是有效数字位: 因为科学计数法,整数位定死了是1,所以这里记录的是小数点后面的二进制为

指数N决定它的范围,因为M总是一个以1开头的小数,以float来说即是:-2 ^ 128 ~ 2 ^ 128,即float能表示的数的大小的范围。

而它的精度是由位数(也就是有效的数据位)来决定的, 2 ^ 23 = 8388608,总共7位,表示最多能用7位有效数字,最多能表示到.8388708即小数点后7位,由于不能完全表示全部的7位数,所以它的精度范围是6位~7位。

同理可得double的精度是2 ^ 52 = 4503599627370496, 共16位,所以精度为15 ~ 16位。

总结:float/double类型的范围和精度的计算方式

不同机器字节序的规定
公式: S = M * 2 ^ N
二进制在内存中是以补码形式存储,负数要对其二进制绝对值按位取反再加一,正数的补码与原码形式相同

也就是说float和doublel类型数据在计算机中存储可能是不精确的。
当我们需要转换成浮点类型是数据时,最好用double,因为double的精度更高,出现丢精度的概率相对是较小的。

在iOS中提供一个专用的类来处理浮点数据相关的运算:NSDecimalNumber

解决方案

使用NSDecimalNumber来进行浮点数处理。
我们给NSString添加一个分类来处理浮点运算问题

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
///.h
@interface NSString (DecimalNumber)

+ (NSString *)decimalNumberWithNSNumber:(NSNumber * )number;
+ (NSString *)decimalNumberWithDouble:(double)conversionValue;

@end

///.m
#import "NSString+DecimalNumber.h"

@implementation NSString (DecimalNumber)

+ (NSString *)decimalNumberWithNSNumber:(NSNumber * )number
{
double conversionValue = [number doubleValue];
NSString *doubleString = [NSString stringWithFormat:@"%lf", conversionValue];
NSDecimalNumber *decNumber = [NSDecimalNumber decimalNumberWithString:doubleString];
return [decNumber stringValue];
}

+ (NSString *)decimalNumberWithDouble:(double)conversionValue
{
NSString *doubleString = [NSString stringWithFormat:@"%lf", conversionValue];
NSDecimalNumber *decNumber = [NSDecimalNumber decimalNumberWithString:doubleString];
return [decNumber stringValue];
}

@end

如何使用

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
NSArray *numbers = @[
@99.00,
@99.09,
@99.19,
@99.29,
@99.39,
@99.49,
@99.59,
@99.69,
@99.79,
@99.89,
@99.99,
];

for (int i = 0; i < numbers.count; i++) {
NSNumber *number = numbers[i];
NSString *strValue = [NSString decimalNumberWithNSNumber:number];

NSLog(@"strValue:%@",strValue);
}

/*
oldVlue:99.00 strValue:99
oldVlue:99.09 strValue:99.09
oldVlue:99.19 strValue:99.19
oldVlue:99.29 strValue:99.29
oldVlue:99.39 strValue:99.39
oldVlue:99.48 strValue:99.49
oldVlue:99.59 strValue:99.59
oldVlue:99.69 strValue:99.69
oldVlue:99.79 strValue:99.79
oldVlue:99.89 strValue:99.89
oldVlue:99.99 strValue:99.99
*/

问题得以解决。☕️

参考资料:

排序是应用常见需求之一,如何正确优雅的实现一个排序,NSSortDescriptor或许是一个非常好的选择。

NSSortDescriptor由3个参数组成:

  • key(键):对于一个给定的集合,将对集合种的每个对象按照该键来进行排序
  • ascending(升序):指定集合按照升序(YES)还是降序(NO)进行排序
  • selector(方法子):排序时的比较函数,当对字符串进行排序时,应当加入localizedStandardCompare:选择器,它将根据语言规则进行排序(例如大小写,变音符号等等的顺序)

如何使用

NSSortDescriptor主要针对各种集合排序,NSArray,NSMutableArray,NSSet,NSOrderedSet,NSMutableOrderedSet

如果集合是可变的,则对集合本身排序例如

  • NSMutableOrderedSetsortUsingDescriptors:
  • NSMutableArraysortUsingDescriptors:

如果集合是可变的,则返回一个排好序的新集合例如

  • NSArraysortedArrayUsingDescriptors:
  • NSSetsortedArrayUsingDescriptors:
  • NSOrderedSetsortedArrayUsingDescriptors:

注意排序描述是一个数组,也就是排序可以支持按照多个描述综合排序。如果存在多个规则先满足前面的规则再满足后面的规则。例如两个元素按照第一天规则顺序一致,那么如果还存在第二天规则,它们将按照第二条规则继续排序,如果前面的规则已经区分出顺序后面的规则将失效。

实例说明

为了更好的描述,假如我们有一个Person对象,它有**NSString * **类型的姓和名属性,以及一个NSNumber类型的年龄属性。

1
2
3
4
5
6
7
@interface Person : NSObject

@property (nonatomic, copy ) NSString *firstName;
@property (nonatomic, copy ) NSString *lastName;
@property (nonatomic, strong) NSNumber *age;

@end

给定以下数据集:

index 0 1 2 3 4
firstName Alice Bod Charlie Quentin
lastName Smith Jones Smith Alberts
age 24 27 33 31 19

使用不同的NSSortDescriptor的不同组合来将它们排序:

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
NSArray *firstNames = @[@"Alice", @"Bod", @"Charlie", @"Quentin", @""];
NSArray *lastNames = @[@"Smith", @"Jones", @"Smith", @"", @"Alberts"];
NSArray *ages = @[@24, @27, @33, @31, @12];

NSMutableArray *people = [NSMutableArray array];
[firstNames enumerateObjectsUsingBlock:^(id _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) {
Person *person = [[Person alloc] init];
person.firstName = [firstNames objectAtIndex:idx];
person.lastName = [lastNames objectAtIndex:idx];
person.age = [ages objectAtIndex:idx];

[people addObject:person];
}];

NSSortDescriptor *firstNameSortDescriptor = [NSSortDescriptor sortDescriptorWithKey:@"firstName" ascending:YES selector:@selector(localizedStandardCompare:)];
NSSortDescriptor *lastNameSortDescriptor = [NSSortDescriptor sortDescriptorWithKey:@"lastName" ascending:YES selector:@selector(localizedStandardCompare:)];
NSSortDescriptor *ageSortDescriptor = [NSSortDescriptor sortDescriptorWithKey:@"age" ascending:NO];

NSLog(@"By age: %@",[people sortedArrayUsingDescriptors:@[ageSortDescriptor]]);
/*
2017-06-06 23:15:02.101 NSSort​Descriptor_Demo[9103:1255482] By age: (
"Charlie Smith 33",
"Quentin 31",
"Bod Jones 27",
"Alice Smith 24",
" Alberts 12"
)
*/

NSLog(@"By first name: %@",[people sortedArrayUsingDescriptors:@[firstNameSortDescriptor]]);
/*
2017-06-06 23:15:02.102 NSSort​Descriptor_Demo[9103:1255482] By first name: (
" Alberts 12",
"Alice Smith 24",
"Bod Jones 27",
"Charlie Smith 33",
"Quentin 31"
)
*/

NSLog(@"By last name, first name: %@",[people sortedArrayUsingDescriptors:@[lastNameSortDescriptor ,firstNameSortDescriptor]]);
/*
2017-06-06 23:15:02.102 NSSort​Descriptor_Demo[9103:1255482] By last name, first name: (
"Quentin 31",
" Alberts 12",
"Bod Jones 27",
"Alice Smith 24",
"Charlie Smith 33"
)
*/

注意事项

  • 空字符是排在普通字符之前的
  • 基本数据类型需要包装成NSNumber对象来排序
  • 字符串类型排序需要加入localizedStandardCompare:选择器

参考链接

枚举

概述

Swift中枚举和结构体也上升到对象的位置,但是却不具备完整的对象特征,比如说他们不能继承。

在OC中枚举本质其实是整数类型,只是给他们取了一些意义直观的名称而已,而Swift完全摆脱以前的思想的束缚编的更自由,拥有了以下特征。

  • 类型更广,不限于整形,可以是CharacterStringFloatDouble等,这些叫做原始值
  • 可以拥有自己的静态成员变量来储存一些常量
  • 可以拥有自己的方法和变量
  • case条件匹配可以定义参数,可以使用where来过滤
  • 通过rawValue可以拿到枚举的原始值

原始值

如果提供了原始值,就可以在case声明成员时提供默认值,这样跟OC的枚举在结构上一些类似

1
2
3
4
5
6
7
enum WeekDays : Int {
case Monday = 0
case Tuesday = 1
case Wednesday = 2
case Thursday = 3
case Friday = 4
}

1.声明一个枚举值

1
let monday = WeekDays.Monday

2.获取原始值

1
let rawVlaue = WeekDays.Tuesday.rawValue

3.通过原始值构造某个枚举变量

1
let wednesday = WeekDays.init(rawValue: 2)

相关值

和C的联合体有点类似

1.声明

1
2
3
4
enum Figure {
case Rectangle(Int, Int)
case Circle(Int)
}

2.使用

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
func printFigure(figure: Figure) {
switch figure {
case .Rectangle(let width, let height):
print("width:\(width), height:\(height)")
case .Circle(let radius):
print("the circle's radius is:\(radius)")
}
}


var figure = Figure.Rectangle(1024, 768)
printFigure(figure)

figure = .Circle(600)
printFigure(figure)

OC与Swift中Options枚举的不同定义

OC版本NS_OPTIONS
1
2
3
4
5
6
7
8
9
typedef NS_OPTIONS(NSUInteger, NSVolumeEnumerationOptions) {
/* The mounted volume enumeration will skip hidden volumes.
*/
NSVolumeEnumerationSkipHiddenVolumes = 1UL << 1,

/* The mounted volume enumeration will produce file reference URLs rather than path-based URLs.
*/
NSVolumeEnumerationProduceFileReferenceURLs = 1UL << 2
}
Swift版本OPTIONS
1
2
3
4
5
struct NSVolumeEnumerationOptions : OptionSetType {
init(rawValue rawValue: UInt)
static var SkipHiddenVolumes: NSVolumeEnumerationOptions { get }
static var ProduceFileReferenceURLs: NSVolumeEnumerationOptions { get }
}
使用
1
2
3
4
5
UIView.animateWithDuration(0.3,
delay: 0.0,
options: [.CurveEaseIn, .AllowUserInteraction],
animations: {},
completion: nil)

结构体

Swift中加强了结构体的能力,可以定义和使用属性,方法,下标,构造器等,但是不能被继承,没有强制类型抓换,使用析构器和引用计数等能力

通常使用结构体来充当数据模型,常量可以使用静态关键词static标示

循环引用

Swift中解决循环强引用的方式

  • 弱引用 – weak
  • 无主引用 – unowned

解决闭包中循环引用

[unowned 捕获对象]或者
[weak 捕获对象]

使用实例

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
class Person {
let name: String

// 写法1
// lazy var printName: () ->() = {
// [weak self] in if let strongSelf = self {
// print("The name is \(strongSelf.name)")
// }
// }

// 写法2
lazy var printName: () ->() = {
[unowned self] in print("The name is \(self.name)")
}

init(personName: String) {
name = personName
}

deinit {
print("Person deinit \(self.name)")
}
}

var xiaoMing: Person? = Person(personName: "xiaoMing")
xiaoMing!.printName
xiaoMing = nil
二者的区别

如果我们可以确定在整个过程中 self 不会被释放的话,我们可以将上面的 weak 改为 unowned,这样就不再需要 strongSelf 的判断。但是如果在过程中 self 被释放了而 printName 这个闭包没有被释放的话 (比如 生成 Person 后,某个外部变量持有了 printName,随后这个 Persone 对象被释放了,但是 printName 已然存在并可能被调用),使用 unowned 将造成崩溃。在这里我们需要根据实际的需求来决定是使用 weak 还是 unowned。

OC和Swift中常用数据类型

字符串

  • OC: NSString, NSMutableString
  • Swift: String

NSString和String的关系:在Swift中,使用字符串可以使用Foundatio中的NSString和Swift中的String。

Swift在底层能够将String与NSString无缝地桥接起来,String可以调用NSString的全部API。

数组类

  • OC: NSArray, NSMutableArray
  • Swift: Array [AnyObject]

NSArray和Array的关系:Swift能在底层将他们自动桥接起来,一个NSArray对象桥接后的结果是[AnyObject]。

字典

  • OC: NSDictionary, NSMutableDictionary
  • Swift: Dictionary [Object: AnyObject]

NSDictionary和Dictionary的关系: 底层自动桥接,一个NSDictionary对象桥接后的结果是[Object: AnyObject]。

类型转换

类的转换使用 as
数据类型加括号 Int()

OC使用 (新的类型)原来的变量

类型判断

is关键词

相当于OC的isKindofClass:

辅助命令

  • #file –> _FILE_
  • #line –> _LINE_
  • #function –> _func_ 或者 _FUNCTION_

退出程序

  • C: exit(0)
  • Swift: fatalError() 或者 fatalError(message: String)

断言

  • OC: NSAssert
  • Swift: assert()

beginTime的妙用

创建一个动画,添加到不同的图层上,可以实现复用,通过调节beginTime可以调节动画开始执行的时间。

例如,我们创建一个水平位移动画,添加到不同的图层对象上,并且让他们轮流执行。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
let flyRight = CABasicAnimation(keyPath: "position.x")
flyRight.fromValue = 0
flyRight.toValue = 100
flyRight.duration = 0.5
flyRight.fillMode = kCAFillModeBoth

firstView.layer.addAnimation(flyRight, forKey: nil)

// make secondView perform animation on delay 0.3 seconds.
flyRight.beginTime = CACurrentMediaTime() + 0.3
secondView.layer.addAnimation(flyRight, forKey: nil)
secondView.layer.position.x = 100

// make thirdView perform animation on delay 0.4 seconds.
flyRight.beginTime = CACurrentMediaTime() + 0.4
thirdView.layer.addAnimation(flyRight, forKey: nil)
thirdView.layer.position.x = 100

动画的beginTime属性可以设置将要执行的动画的绝对开始时间,通过CACurrentMediaTime()函数获取当前时间再加上你想要延迟执行的时间的,单位秒。

动画代理

动画代理可以监听动画的进行状态,在动画开始和结束的时候回通知代理.
也就是会调用以下两个函数:

1
2
func animationDidStart(anim: CAAnimation)
func animationDidStop(anim: CAAnimation, finished flag: Bool)

CAAnimation

CAAnimation和它的子类遵循KVO,也就意味着你可以把它们当成字典一样使用来添加新的接口在运行时。

例如你可以使用这种机制来给某个动画指定一个名字,以便你能够把它和其它动画区分开。

1
flyRight.setValue("somename", forKey: "name")

CASpringAnimation

Property Default Value
damping 10.0
mass 1.0
stiffness 100.0
initialVelocity 0.0

参数说明:

  • damping - 应用给系统的阻尼
  • mass - 在系统中重物的质量
  • stiffness - 附加在重物上的弹簧的硬度
  • initialVelocity - 附加在重物上的初始速度

使用实例:

1
2
3
4
5
6
7
8
9
10
let jump = CASpringAnimation(keyPath: "position.y")
jump.initialVelocity = 100.0
jump.mass = 10.0
jump.stiffness = 1500.0
jump.damping = 50.0

jump.fromValue = textField.layer.position.y + 1.0
jump.toValue = textField.layer.position.y
jump.duration = jump.settlingDuration
textField.layer.addAnimation(jump, forKey: nil)

备注:
此动画的duration参数可以通过前面几个参数自动计算得出

jump.duration = jump.settlingDuration

使用CAGradientLayer的locations实现滑动解锁

CAGradientLayerlocations属性做动画轻松的实现iPhone自带的滑动解锁效果

a.创建一个渐变图层

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
let gradientLayer: CAGradientLayer = {
let gradientLayer = CAGradientLayer()

// Configure the gradient here

gradientLayer.startPoint = CGPoint(x: 0.0, y: 0.5)
gradientLayer.endPoint = CGPoint(x: 1.0, y: 0.5)

let colors = [
UIColor.blackColor().CGColor,
UIColor.whiteColor().CGColor,
UIColor.blackColor().CGColor
]
gradientLayer.colors = colors

let locations = [
0.25,
0.5,
0.75
]
gradientLayer.locations = locations

return gradientLayer
}()

b.创建一个文本样式字典

1
2
3
4
5
6
7
8
9
let textAttributes : [String: AnyObject] = {
let style = NSMutableParagraphStyle()
style.alignment = .Center

return [
NSFontAttributeName:UIFont(name: "HelveticaNeue-Thin", size: 28.0)!,
NSParagraphStyleAttributeName:style
]
}()

c.当设置文本时绘制内容,添加@IBInspectable方便看实时效果

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
@IBInspectable var text: String! {
didSet {
setNeedsDisplay()

UIGraphicsBeginImageContextWithOptions(frame.size, false, 0)
text.drawInRect(bounds, withAttributes: textAttributes)
let image = UIGraphicsGetImageFromCurrentImageContext()
UIGraphicsEndImageContext()

let maskLayer = CALayer()
maskLayer.backgroundColor = UIColor.clearColor().CGColor
maskLayer.frame = CGRectOffset(bounds, bounds.size.width, 0)
maskLayer.contents = image.CGImage

gradientLayer.mask = maskLayer
}
}

d.重新布局gradientLayer

1
2
3
4
5
6
7
override func layoutSubviews() {
gradientLayer.frame = CGRect(
x: -bounds.size.width,
y: bounds.origin.y,
width: 3 * bounds.size.width,
height: bounds.size.height)
}

e.添加到window时向渐变层添加动画

1
2
3
4
5
6
7
8
9
10
11
12
13
override func didMoveToWindow() {
super.didMoveToWindow()

layer.addSublayer(gradientLayer)

let gradientAnimation = CABasicAnimation(keyPath: "locations")
gradientAnimation.fromValue = [0.0, 0.0, 0.25]
gradientAnimation.toValue = [0.75, 1.0, 1.0]
gradientAnimation.duration = 3.0
gradientAnimation.repeatCount = Float.infinity

gradientLayer.addAnimation(gradientAnimation, forKey: nil)
}