周精益分享 - 后端开发

Swift学习之二 - 张超耀

深度理解”?” && “!”

1
Optional其实是个enum,里面有None和Some两种类型。 nil就是Optional.None, 非nil就是Optional.Some, 然后会通过Some(T)包装(wrap)原始值,这也是为什么在使用Optional的时候要拆包(从enum里取出来原始值)的原因。声明为Optional只需要在类型后面紧跟一个"?"即可
1
2
var strValue: String?  //?相当于下面这种写法的语法糖
var strValue1: Optional<Int>

一旦声明为Optional的,如果不显式的赋值就会有个默认值nil。判断一个Optional的值是否有值,可以用if来判断:

1
2
3
if (strValue != nil) {
//do sth with strValue
}

怎么使用Optional值呢?
在使用Optional值的时候只前面需要加上一个”?”,就是这么简单。

  • 这个”?”什么意思?:
    1、 询问是否响应后面这个方法,如果是nil值,也就是Optional.None,就不响应后面的方法,直接跳过,
    2、 如果有值,就是Optional.Some,可能就会拆包(unwrap),然后对拆包后的值执行后面的操作,比如
1
let hashValue = strValue?.hashValue
  • strValue是Optional的字符串,如果strValue是nil,则hashValue也为nil,如果strValue不为nil,hashValue就是strValue字符串的值

  • 到这里我们看到了?的两种使用场景:

    1.声明Optional值变量
    2.用在对Optional值操作中,用来判断是否能响应后面的操作
    
  • 对于Optional值,不能直接进行操作,否则会报错
1
let hashValue1 = strValue!.hashValue

上面错误提示需要拆包(unwrap)后才能得到值,然后才能对其操作,那怎么来拆包呢?拆包提到了几种方法,一种是Optional Binding

1
2
3
if let str = strValue {
let hashValue = str.hashValue
}

还有一种是在具体的操作前添加”!”符号。

1
let hashValue2 = strValue!.hashValue

这里的”!”表示我确定肯定这里的的strValue一定是非nil的,随便用吧,木事de 就像下面:

1
2
3
if (strValue != nil) {
let hashValue = strValue!.hashValue
}
1
2
{}里的strValue一定是非nil的,所以就能直接加上!,强制拆包(unwrap)并执行后面的操作。
当然如果不加判断,strValue不小心为nil的话,就会出错,crash掉。

51offer例子解释『隐式拆包的Optional』

1
这种是特殊的Optional,称为Implicitly Unwrapped Optionals, 直译就是隐式拆包的Optional,就等于说你每次对这种类型的值操作时,都会自动在操作前补上一个!进行拆包,然后在执行后面的操作,当然如果该值是nil,也一样会报错crash掉。
  • “!”也有两种使用场景
    1、强制对Optional值进行拆包(unwrap)
    2、声明Implicitly Unwrapped Optionals值,一般用于类中的属性
    

swift 断言

1
2
3
4
断言(Assertions)
Optionals可以让我们检测值是否存在。在某些情况下,如果某个值不存在或者没有提供特定的满足条件,代码不应该继续往下执行。
在这些情况下,可以使用触发断言来终止执行并提供调试。
断言是在运行时检测条件是否为true,如果为true,就继续往下执行,否则就在这里中断。
1
2
3
var jobs = "我是一个好人"
assert(jobs == "我是一个好人", "我是一个好人")
assert(jobs == "我是一个坏人", "我是一个坏人么?")
1
2
3
4
5
什么时候使用断言呢?
包含下面的情况时使用断言:
1、整型下标索引作为值传给自定义索引实现的参数时,但下标索引值不能太低也不能太高时,使用断言
**2、传值给函数但如果这个传过来的值无效时,函数就不能完成功能时,使用断言。
3、Optional值当前为nil,但是后面的代码成功执行的条件是要求这个值不能为nil,使用断言

IM中输入框的优化方案实现

以下图的输入框为例

stand

输入框的呈现方式选择

以下两个方法对比

  • Keyboard的inputAccessoryView

相对推荐的方法,有更好的丰富的交互效果

可以与scrollView的一些属性直接绑定

  • InputView的frame适应

传统的方法

优点:灵活控制显示位置

缺点:过多的计算frame

InputAccessoryView方式

案例见GitHub的Demo

iOS的输入源都有输入源(keyboard及keyboard的配件InputAccessoryView),那通常的方法是输入源控件(Textfield、TextView等)使用一般的InputView的frame适应

解释几个技巧点

  • InputView( 输入源 )的父视图作为该InputView的InputAccessoryView,要避免相互引用
  • 巧用第三方不可见的InputView在适当时间点转移第一响应者给可见的InputView
  • 完成编辑时去除第一响应者(注意iOS9下键盘响应逻辑视图层级都发生了变化)

主要代码

simulator screen shot nov 27 2015 4 17 38 pm

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
// 单例初始化
+ (UUInputAccessoryView*)sharedView {
static dispatch_once_t once;
static UUInputAccessoryView *sharedView;
dispatch_once(&once, ^ {
sharedView = [[UUInputAccessoryView alloc] init];
sharedView->btnBack = [UIButton buttonWithType:UIButtonTypeCustom];
sharedView->btnBack.frame = CGRectMake(0, 0, UUIAV_MAIN_W, UUIAV_MAIN_H);
[sharedView->btnBack addTarget:sharedView action:@selector(dismiss) forControlEvents:UIControlEventTouchUpInside];
sharedView->btnBack.backgroundColor=[UIColor clearColor];
UIToolbar *toolbar = [[UIToolbar alloc]initWithFrame:CGRectMake(0, 0, UUIAV_MAIN_W, 44)];
sharedView->inputView = [[UITextField alloc]initWithFrame:CGRectMake(UUIAV_Edge_Hori, UUIAV_Edge_Vert, UUIAV_MAIN_W-UUIAV_Btn_W-4*UUIAV_Edge_Hori, UUIAV_Btn_H)];
sharedView->inputView.borderStyle = UITextBorderStyleRoundedRect;
sharedView->inputView.returnKeyType = UIReturnKeyDone;
sharedView->inputView.clearButtonMode = UITextFieldViewModeWhileEditing;
sharedView->inputView.enablesReturnKeyAutomatically = YES;
sharedView->inputView.delegate = sharedView;
[toolbar addSubview:sharedView->inputView];
sharedView->assistView = [[UITextField alloc]init];
sharedView->assistView.delegate = sharedView;
sharedView->assistView.returnKeyType = UIReturnKeyDone;
sharedView->assistView.enablesReturnKeyAutomatically = YES;
[sharedView->btnBack addSubview:sharedView->assistView];
sharedView->assistView.inputAccessoryView = toolbar;
sharedView->BtnSave = [UIButton buttonWithType:UIButtonTypeCustom];
sharedView->BtnSave.frame = CGRectMake(UUIAV_MAIN_W-UUIAV_Btn_W-2*UUIAV_Edge_Hori, UUIAV_Edge_Vert, UUIAV_Btn_W, UUIAV_Btn_H);
sharedView->BtnSave.backgroundColor = [UIColor clearColor];
[sharedView->BtnSave setTitle:@"确定" forState:UIControlStateNormal];
[sharedView->BtnSave setTitleColor:[UIColor redColor] forState:UIControlStateNormal];
[sharedView->BtnSave addTarget:sharedView action:@selector(Done) forControlEvents:UIControlEventTouchUpInside];
[toolbar addSubview:sharedView->BtnSave];
});
CGRectGetHeight([UIScreen mainScreen].bounds);
return sharedView;
}

实现逻辑代码

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
+ (void)showKeyboardType:(UIKeyboardType)type content:(NSString *)content Block:(UUInputAccessoryBlock)block
{
[[UUInputAccessoryView sharedView] show:block
keyboardType:type
content:content];
}

- (void)show:(UUInputAccessoryBlock)block keyboardType:(UIKeyboardType)type content:(NSString *)content
{
UIWindow *window=[UIApplication sharedApplication].keyWindow;
[window addSubview:btnBack];

inputBlock = block;
inputView.text = content;
inputView.keyboardType = type;
assistView.keyboardType = type;
[assistView becomeFirstResponder];
shouldDismiss = NO;

[[NSNotificationCenter defaultCenter] addObserverForName:UIKeyboardDidShowNotification
object:nil
queue:nil
usingBlock:^(NSNotification * _Nonnull note) {
if (!shouldDismiss) {
[inputView becomeFirstResponder];
}
}];
}

- (void)Done
{
[inputView resignFirstResponder];
!inputBlock ?: inputBlock(inputView.text);
[self dismiss];
}

- (BOOL)textFieldShouldReturn:(UITextField *)textField
{
[self Done];
return NO;
}

- (void)dismiss
{
shouldDismiss = YES;
[inputView resignFirstResponder];
[btnBack removeFromSuperview];
[[NSNotificationCenter defaultCenter] removeObserver:self];
}

InputView的frame适应

案例见GitHub的Demo

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
// 键盘响应布局
@objc func keyboardFrameChanged(notification: NSNotification) {

let dict = NSDictionary(dictionary: notification.userInfo!)
let keyboardValue = dict.objectForKey(UIKeyboardFrameEndUserInfoKey) as! NSValue
let bottomDistance = mainScreenSize().height - keyboardValue.CGRectValue().origin.y
let duration = Double(dict.objectForKey(UIKeyboardAnimationDurationUserInfoKey) as! NSNumber)

UIView.animateWithDuration(duration, animations: {
self.inputViewConstraint!.constant = -bottomDistance
self.view.layoutIfNeeded()
}, completion: {
(value: Bool) in
self.chatTableView.scrollToBottom(animation: true)
})

}

ScrollView下拉动态修改keyboard(InputView)的frame

常见隐藏keyboard的一些方式

  • TouchBeigin
  • DidDrag
  • EndDrag
  • Interactive

iOS7 开始,ScrollView提供

1
2
3
4
5
6
@available(iOS 7.0, *)
public enum UIScrollViewKeyboardDismissMode : Int {
case None
case OnDrag // dismisses the keyboard when a drag begins
case Interactive // the keyboard follows the dragging touch off screen, and may be pulled upward again to cancel the dismiss
}

所以在ScrollView上添加scrollView.keyboardDismissMode = UIScrollViewKeyboardDismissMode.Interactive就可以了。
效果详见iOS7及以上原生设备的短信滑动消失键盘的交互

写负责任的代码之异常处理 - 曾铭

千万不要以为你可以忽视这个特殊的返回值,因为它是一种“可能性”。代码漏掉任何一种可能出现的情况,都可能产生意想不到的灾难性结果。

  • 重视代码(及业务)逻辑的异常情况,将其视为『正常流程』的一部分。
  • 重视函数的抛出异常,将其视为返回值之一(union类型)。

政策之后的对策

1
2
String foo() throws MyException {
}
  • catch 全部异常
  • try 全部代码
1
2
3
4
5
6
7
try {
...
...
... foo() ...
...
...
} catch (Exception e) {}

我们 API 为什么难调试

  • Service 层未声明抛出异常,也会抛出异常
  • 抛出的异常 API 层记入日志,向上抛时却丢弃了异常信息
  • API 层方法代码全部 try,很难定位到是哪一行出问题

推荐的对策

  • 只 catch 指定的 exception
  • try 进可能的小
1
2
3
4
5
try {
foo();
} catch (MyException e) {
Log.warning(e);
}
  • 源头思考全部可能性,内部的问题不抛给别人
  • 不要试图忽略(隐藏)错误,每次出错都是优化代码(反思自己局限)的好机会。否则,你终将付出代价。