第2节:详解TextField

[TOC]

参考文章:

一、TextField class

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
const TextField({
Key key,
this.controller,// 文本控制器,详见下文
this.focusNode, // 焦点,详见下文
this.decoration = const InputDecoration(), // 输入框装饰器,详见下文
TextInputType keyboardType,
this.textInputAction, //更改键盘本身的操作按钮,详见下文
this.textCapitalization = TextCapitalization.none, //提供了一些有关如何使用户输入中的字母大写的选项
this.style,
this.textAlign = TextAlign.start,
this.textDirection,
this.autofocus = false, // 是否自动获取焦点
this.obscureText = false, // 是否隐藏正在编辑的文本,如用于输入密码的场景等,文本内容会用“•”替换。
this.autocorrect = true,
this.maxLines = 1, // 能写入的最大行数
this.maxLength, // 能写入的最大字符数
this.maxLengthEnforced = true,
this.onChanged, // 文本框事件:文字改变触发
this.onEditingComplete, // 文本框事件:当用户提交可编辑内容时调用(常用于“焦点(FocusNode)”变更)
this.onSubmitted, // 文本框事件:文字提交触发(键盘按键)
this.inputFormatters,
this.enabled,
this.cursorWidth = 2.0, // 光标宽度 Colors.red
this.cursorRadius, // 光标半径 Radius.circular(16.0)
this.cursorColor, // 光标颜色 16.0
this.keyboardAppearance,
this.scrollPadding = const EdgeInsets.all(20.0),
this.enableInteractiveSelection = true,
this.onTap, // 文本框事件:
}) : assert(textAlign != null),
assert(autofocus != null),
assert(obscureText != null),
assert(autocorrect != null),
assert(maxLengthEnforced != null),
assert(scrollPadding != null),
assert(maxLines == null || maxLines > 0),
assert(maxLength == null || maxLength > 0),
keyboardType = keyboardType ?? (maxLines == 1 ? TextInputType.text : TextInputType.multiline),
assert(enableInteractiveSelection != null),
super(key: key);

二、文本框的输入器装饰 InputDecoration decoration

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
const InputDecoration({
this.icon,
this.labelText,
this.labelStyle,
this.helperText,
this.helperStyle,
this.hintText,
this.hintStyle,
this.errorText,
this.errorStyle,
this.errorMaxLines,
this.hasFloatingPlaceholder = true,
this.isDense,
this.contentPadding,
this.prefixIcon,
this.prefix,
this.prefixText,
this.prefixStyle,
this.suffixIcon,
this.suffix,
this.suffixText,
this.suffixStyle,
this.counterText,
this.counterStyle,
this.filled,
this.fillColor,
this.errorBorder,
this.focusedBorder,
this.focusedErrorBorder,
this.disabledBorder,
this.enabledBorder,
this.border,
this.enabled = true,
this.semanticCounterText,
}) : assert(enabled != null),
assert(!(prefix != null && prefixText != null), 'Declaring both prefix and prefixText is not allowed'),
assert(!(suffix != null && suffixText != null), 'Declaring both suffix and suffixText is not allowed'),
isCollapsed = false;

图解如下:

Flutter TextField

输入器装饰 InputDecoration decoration的其他参数

参数 作用 备注
contentPadding 内容的边距,默认是有一个边距的 contentPadding: new EdgeInsets.all(0.0)

2、文本控制器 TextEditingController controller

controller.clear() 清空了用户名输入框中的内容

3、键盘输入类型 TextInputType keyboardType

键盘输入类型(数字,文本等各种类型):设置TextField获得焦点的时候弹出的键盘

类型 作用
TextInputType.number 数字键盘
TextInputType.text 普通完整键盘
TextInputType.emailAddress 带有“@”的普通键盘
TextInputType.datetime 带有“/”和“:”的数字键盘
TextInputType.multiline 带有选项以启用有符号和十进制模式的数字键盘

4、键盘本身的操作按钮 TextInputAction

1
2
3
TextField(
textInputAction: TextInputAction.search,
),

5、TextCapitalization

类型 作用
TextCapitalization.characters 大写句子中的所有字符
TextCapitalization.words 将每个单词的首字母大写

6、文本框的其他参数

见类结构

二、键盘

Flutter | 滚动 PageView 自动关闭键盘

1
2
3
4
5
PageView(
onPageChanged: (index) {
WidgetsBinding.instance?.focusManager.primaryFocus?.unfocus();
}
)

第1节:详解Button

参考文章:

一、基础知识

1、继承关系

Object > Diagnosticable > DiagnosticableTree > Widget > StatelessWidget > MaterialButton > RaisedButton、FlatButton、OutlineButton

  • 2021-12-18

TextButton 是 1.20.0 推出的一个新的按钮

Flutter TextButton 详细使用配置、Flutter ButtonStyle概述实践

flutter 2.0版本新增了三个按钮

TextButton、OutlinedButton、ElevatedButton

2、类

RaisedButton Material Design中的button, 一个凸起的材质矩形按钮。
FlatButton Material Design中的button,一个没有阴影的材质设计按钮。
OutlineButton Material Design中的button,RaisedButton和FlatButton之间的交叉:一个带边框的背景透明的按钮,当按下按钮时,其高度增加,背景变得不透明。
IconButton 用于创建仅包含图标的按钮,参数就不再讲解
DropdownButton 一个显示可供选择的选项的按钮
FloatingActionButton 材质应用程序中的圆形按钮
InkWell 实现平面按钮的墨水飞溅部分

RaisedButton、FlatButton、OutlineButton三个控件都继承于MaterialButton,查看源码会发现MaterialButton由RawMaterialButton(无主题Button)构建的。而RawMaterialButton与CupertinoButton是一对button组合,都继承于StatefulWidget,前者是google风格,后者iOS风格!

附:使用CupertionBUtton要注意导入库:import ‘package:flutter/cupertino.dart’;

二、 MaterialButton参数详解

这里我们仅对个别不好理解的做解释。

属性 作用 备注
VoidCallback onPressed 点击激活按钮时调用的方法
ValueChanged onHighlightChanged 按下和抬起时都会调用的方法,详看后面示例
ButtonTextTheme textTheme 定义按钮的基色,以及按钮的最小尺寸,内部填充和形状的默认值。
Color disabledTextColor 未设置按钮点击回调时使用的文本颜色
Color splashColor 按钮被按下的水波纹颜色,默认是有值的,不要要水波纹效果设置透明颜色即可!
colorBrightness 按钮的主题亮度,当设置了textColor、color颜色,此值无效!

更多内容参考:Flutter RaisedButton、FlatButton、OutlineButton 参数详解

第1节:Swift基础知识

[TOC]

入门

  • 一、基础部分
  • 二、基本运算符

OC与Swift混编

原本在 swift 3 中除了手动添加 @objc 声明函数支持 OC 调用还有另外一种方式:继承 NSObject。

即class 继承了 NSObject 后,编译器就会默认给这个类中的所有函数都标记为 @objc ,支持 OC 调用。

然而在实际项目中,一个 swift 类虽然继承了 NSObject,但是其中还是有很多函数不会在 OC 中被调用,这里有很大的优化空间。

于是根据 SE160 的建议,苹果修改了自动添加 @objc 的逻辑:现在一个继承 NSObject 的 swift 类不再默认给所有函数添加 @objc。只在实现 OC 接口和重写 OC 方法时才自动给函数添加 @objc 标识。

XCode 9会在运行过程中自行检测类中函数是被 OC 调用,然后提示添加 @objc。下图中的 vc 是 swift 中的类,showStatus 也是 swift 函数,现在编译器会提示需要手动添加 @objc:

Swift概念新增/区别

swift的guard语句的时候,我当时不太理解以后会有什么理由能用到它。这个语句的介绍如下:

与if语句相同的是,guard也是基于一个表达式的布尔值去判断一段代码是否该被执行。与if语句不同的是,guard只有在条件不满足的时候才会执行这段代码。你可以把guard近似的看做是Assert,但是你可以优雅的退出而非崩溃。

一、基础部分

Swift基础部门:http://www.swift51.com/swift4.0/chapter2/01_The_Basics.html

1、声明常量和变量

声明变量时在类型后添加?或者!,就是告诉编译器这是一个Optional的变量,如果没有初始化,你就将其初始化为nil

常量和变量必须在使用前声明,用 let 来声明常量,用 var 来声明变量。

1
2
3
4
5
let maximumNumberOfLoginAttempts = 10
var currentLoginAttempt = 0

// 可以在一行中声明多个常量或者多个变量,用逗号隔开:
var x = 0.0, y = 0.0, z = 0.0

与变量不同,常量的值一旦被确定就不能更改了。尝试这样做会导致编译时报错:

1
2
3
let languageName = "Swift"
languageName = "Swift++"
// 这会报编译时错误 - languageName 不可改变

附:类型标注

Swift可以根据赋值推断出类型。

1
2
let PI = 3.1415
var Str = "string"

你也可以使用类型标注(type annotation)来指定类型。在常量或者变量名后面加上一个冒号和空格,然后加上类型名称。声明中的冒号代表着“是…类型”

1
2
3
4
var welcomeMessage: String = "string"

// 可以在一行中定义多个同样类型的变量,用逗号分割,并在最后一个变量名之后添加类型标注:
var red, green, blue: Double

2、输出print

Swift使用字符串插值(string interpolation)的方式将常量或变量当作占位符加入到长字符串中,我们可以借此拼接长字符。

1
2
3
4
let value1 = "123"
var value2 = 345
print("value1 = \(value1) , value2 = \(value2)")
// 输出: value1 = 123 , value2 = 345

3、数据类型(布尔值、数组、字典、元组、可选类型)

3.1、布尔值

和OC不同,Swift中的布尔值使用truefalse表示真、假

1
2
3
4
let boolValue = true
if boolValue {
print("value is true")
}

这里需要说明的是,Swift不能像OC中那样,数值为0即表示布尔值NO,非0即为YES,因此下面OC代码可以通过

1
2
3
4
5
float value = 0.001;
if (value) {
NSLog(@"value:%f",value);
}
// 输出: value:0.001000

而在Swift中会编译报错

1
2
3
4
let boolValue = 0.001
if boolValue {
print("value is true")
}

3.2、可选类型

3.2.1、可选类型

使用可选类型(optionals)来处理值可能缺失的情况。

1
2
3
let possibleNumber = "123"
let convertedNumber = Int(possibleNumber)
// convertedNumber 被推测为类型 "Int?", 或者类型 "optional Int"

因为该构造器可能会失败,所以它返回一个可选类型(optional)Int,而不是一个 Int。一个可选的 Int 被写作 Int? 而不是 Int。问号暗示包含的值是可选类型,也就是说可能包含 Int 值也可能不包含值。(不能包含其他任何值比如 Bool 值或者 String 值。只能是 Int 或者什么都没有。)

3.2.2、隐式解析可选类型

可选类型 String 和隐式解析可选类型 String 之间的区别:

1
2
3
4
5
let possibleString: String? = "An optional string."
let forcedString: String = possibleString! // 需要感叹号来获取值

let assumedString: String! = "An implicitly unwrapped optional string."
let implicitString: String = assumedString // 不需要感叹号

你可以把隐式解析可选类型当做一个可以自动解析的可选类型。你要做的只是声明的时候把感叹号放到类型的结尾,而不是每次取值的可选名字的结尾。

nil

Swift 的 nil 和 Objective-C 中的 nil 并不一样。

Objective-C Swift
nil 在 Objective-C 中,nil 是一个指向不存在对象的指针。
只有对象类型能被设置为nil
在 Swift 中,nil 不是指针——它是一个确定的值,用来表示值缺失。
任何类型的可选状态都可以被设置为 nil,不只是对象类型。

你可以给可选变量赋值为nil来表示它没有值:

1
2
3
4
var serverResponseCode: Int? = 404
// serverResponseCode 包含一个可选的 Int 值 404
serverResponseCode = nil
// serverResponseCode 现在不包含值

强制解析

1
2
3
4
5
6
7
//let possibleNumber = "123"
//let convertedNumber = Int(possibleNumber)
var convertedNumber: Int? = 123
if convertedNumber != nil {
print("convertedNumber has an integer value of \(convertedNumber!).") //当你确定可选类型确实包含值之后,你可以在可选的名字后面加一个感叹号(!)来获取值。这个惊叹号表示“我知道这个可选有值,请使用它。”这被称为可选值的强制解析(forced unwrapping):
}
// 输出 "convertedNumber has an integer value of 123."

可选绑定

if 语句中写一个可选绑定:

1
2
3
if let constantName = someOptional {
statements
}

举例:

1
2
3
4
5
6
if let actualNumber = Int(possibleNumber) {
print("\'\(possibleNumber)\' has an integer value of \(actualNumber)") //转换成功,因为它已经被可选类型 包含的 值初始化过,所以不需要再使用 ! 后缀来获取它的值。
} else {
print("\'\(possibleNumber)\' could not be converted to an integer")
}
// 输出 "'123' has an integer value of 123"

这段代码可以被理解为:“如果 Int(possibleNumber) 返回的可选 Int 包含一个值,创建一个叫做 actualNumber 的新常量并将可选包含的值赋给它。”

二、基本运算符

1、赋值运算符

与 C 语言和 Objective-C 不同,Swift 的赋值操作并不返回任何值。所以以下代码是错误的:

1
2
3
if x = y {
// 此句错误, 因为 x = y 并不返回任何值
}

这个特性使你无法把(==)错写成(=),由于 if x = y 是错误代码,Swift 能帮你避免此类错误发生。

如果赋值的右边是一个多元组,它的元素可以马上被分解成多个常量或变量:

1
2
let (x, y) = (1, 2)
// 现在 x 等于 1,y 等于 2

2、算术运算符

加法运算符也可用于 String 的拼接:

1
"hello, " + "world"  // 等于 "hello, world"

在新版Swift中,++ 和 – 运算符被取消,因此 i++ 这种形式的累加需要换成 i += 1 这种形式。

3、一元正号/负号运算符

1
2
3
4
let tempValue = -6
let plusSix = -tempValue // plusSix 等于 6
let minusSix = +tempValue // minusSix 等于 -6
//当你在使用一元负号来表达负数时,你可以使用一元正号来表达正数,如此你的代码会具有对称美。

4、比较运算符(Comparison Operators)

比较运算多用于条件语句,如if条件:

1
2
3
4
5
6
7
let name = "world"
if name == "world" {
print("hello, world")
} else {
print("I'm sorry \(name), but I don't recognize you")
}
// 输出 "hello, world", 因为 `name` 就是等于 "world"

5、空合运算符(Nil Coalescing Operator)

空合运算符a ?? b)将对可选类型 a 进行空判断,如果 a 包含一个值就进行解封,否则就返回一个默认值 b。表达式 a 必须是 Optional 类型。默认值 b 的类型必须要和 a 存储值的类型保持一致。

1
2
3
a ?? b	// 空合运算符
// 等价于
a != nil ? a! : b // 三目运算符

注意: 如果 a 为非空值(non-nil),那么值 b 将不会被计算。这也就是所谓的短路求值

6、区间运算符(Range Operators)

写法 示例
闭区间运算符 a…b for index in 1…5 {}
闭区间运算符的单侧形式 [a…]或[…b] for name in names[2…] { print(name) }
半开区间运算符 a..<b for i in 0..<count {}
半开区间运算符的单侧形式 [..<b]

7、逻辑运算符(Logical Operators)

注意: Swift 逻辑操作符 &&|| 是左结合的,这意味着拥有多元逻辑操作符的复合表达式优先计算最左边的子表达式。为使得代码更加可读,建议使用括号来明确优先级

1
2
3
4
5
6
7
8
9
10
11
if enteredDoorCode && passedRetinaScan || hasDoorKey || knowsOverridePassword {
print("Welcome!")
} else {
print("ACCESS DENIED")
}
// 等价于
if (enteredDoorCode && passedRetinaScan) || hasDoorKey || knowsOverridePassword {
print("Welcome!")
} else {
print("ACCESS DENIED")
}

8、溢出运算符

1
2
3
4
5
6
7
8
9
10
11
var maxInt = Int8.max	// Int8 型整数能容纳的最大值是 +127,以二进制表示即 01111111。
var minInt = Int8.min // Int8 型整数能容纳的最小值是 -128,以二进制表示即 10000000。
print("Int8.max:\(maxInt), Int8.min:\(minInt)")
// 输出 Int.max:127, Int.min:-128

// 运算的错误写法:
maxInt += 1 // 编译错误
minInt -= 1 // 编译错误

// 运算的正确写法:
minInt = minInt &- 1 // 当使用溢出减法运算符对其(二进制10000000)进行减 1 运算时,得到二进制数值 01111111,也就是十进制数值的 127(符号位被翻转了),这个值也是 Int8 型整数所能容纳的最大值。

三、函数

Swift的函数使用 func 声明一个函数,形式为:

1
2
3
func name(parameters) -> return type {
function body
}

常用的函数形式

1
2
3
4
5
6
7
8
9
10
11
12
13
14
// 无参数函数
func sayHello() -> String {
return "hello, world"
}
// 多参数函数,无返回值( -> Void 可以省略 )
func personInfo(name: String, age: Int) -> Void {
// function body
}
// 注意:严格上来说,虽然没有返回值被定义,greet(person:) 函数依然返回了值。没有定义返回类型的函数会返回一个特殊的Void值。它其实是一个空的元组(tuple),没有任何元素,可以写成()。

// 多重返回值,这里返回一个元组
func minMax(array: [Int]) -> (min: Int, max: Int) {
return (array.min()!, array.max()!)
}
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
// 可选返回类型,‘_’ 表示忽略参数标签
func max(_ array: [Int]) -> Int? {
return array.max()
}
// 指定参数标签(参数说明,和参数名以空格分隔),区别于参数名,默认参数名就是外部的标签名
func someFunction(parameterDescription parameter: String) {
print(parameter)
}
someFunction(parameterDescription: "parameter")
// 可以看到参数标签只供外部调用显示使用,而参数名parameter可以供内部函数使用
// 默认参数
func sumFunc(a: Int, b: Int = 12) -> Int {
return a &+ b
// 如果你在调用时候不传第二个参数,parameterWithDefault 会值为 12 传入到函数体中。
}
print(sumFunc(a: 3)) // 输出 15
// 可变参数,使用(...)的方式接收可变参数,并且必须作为左后一个参数
func sumFunc2(_ numbers: Int ...) -> Int {
var total: Int = 0
// 和OC实现不定参数不同,Swift已将不定参转换为数组
for number in numbers {
total += number
}
return total
}
print("\(sumFunc2(1,2,3,4,5))") // 输出 15
/*
输入输出参数
如果你想要一个函数可以修改参数的值,并且想要在这些修改在函数调用结束后仍然存在,那么就可以将这个参数定义为输入输出参数
这有点类似OC中常见的NSError的使用,他们的本质是接受一个地址,直接操作内存地址进行修改和访问
可是使用 inout 关键字来定义输入输出参数,需要注意的是,在新版本中,已经将 inout 放在了 '参数名:' 后面,而不是前面
*/
func swapTwoObj(_ a: inout String, _ b : inout String) {
(a, b) = (b, a)
}
var str1 = "123"
var str2 = "456"
swapTwoObj(&str1, &str2)
print("str1:\(str1),str2:\(str2)") // 输出 str1:456,str2:123

[TOC]

入门

  • 一、修饰符
  • 二、通知、代理、block

一、修饰符

Swift基础部门:http://www.swift51.com/swift4.0/chapter2/01_The_Basics.html

修饰符:

修饰符 所修饰的属性或者方法的作用范围
private 只能在当前类里访问
fileprivate 在当前的Swift源文件里可以访问
internal
(默认访问级别)
此修饰符可写可不写
在源代码所在的整个模块都可以访问。
①如果是框架或者库代码,则在整个框架内部都可以访问,框架由外部代码所引用时,则不可以访问。
②如果是App代码,也是在整个App代码,也是在整个App内部可以访问。
public 可以被任何人访问。
①其他module中不可以被override和继承,
②而在module内可以被override和继承
open 可以被任何人使用,包括override和继承

访问顺序:
现在的访问权限则依次为:open > public > internal > fileprivate > private。

swift – 静态变量static

1
2
3
4
5
6
7
8
9
10
11
12
class ViewController: UIViewController {
//静态变量 swift中的static静态变量,只能在这里声明,不能在方法中声明,会报错
static var i : Int = 1

override func viewDidLoad() {
super.viewDidLoad()

//调用静态变量
//print(self.i) 错
print(ViewController.i) //这样调用
}
}

懒加载 & Readonly

1、lazy关键字修饰一个变量就可以实现懒加载
2、懒加载的本质是,在第一次使用的时候使用调用,只会赋值一次

示例:

1
2
3
4
5
//lazy var btn : UIButton = UIButton();
lazy var btn : UIButton = {
let btn = UIButton()
return btn
}()

ReadOnly

Swift如何优雅的的设置只读(readOnly)属性

优雅地使用 Selector - Swift

截取字符串

参考文章:

修改函数参数的值/让方法参数可修改方法

在swift中,我们常常对数据进行一些处理。因为swift的计算属性,所以如果不是大量重复性处理,基本可以在set及didSet中改变原数据的一些状态。但需要用到同样的算法处理大量数据的时候,仍然需要写方法来解决。在如C一类的传统语言中,指针变量能轻易帮我们处理并直接修改掉原数据,而apple本身期望siwft中尽量减少指针的出现,因此,swift常规方法中经常用到的是值传递。值传递最明显的后果便是无法对原数据进行直接修改。如果我们需要处理后的数据结果,那么就需要重新定义一个变量来接收值。在原数据被废弃的情况下,这样既增多了代码量,也产生了空间大量浪费。因此 siwft提供了关键字修饰inout来申明数据地址传递,也称之为引用传递。在siwft3.0中 inout的位置发生了改变,处于标签位置,不过其作用相同。具体作用如下图代码:

其他参考:swift之inout

举例:

1
2
3
4
5
6
func show(with fixHeight: inout CGFloat, blankBGColor: UIColor) {
let maxHeight: CGFloat = UIScreen.main.bounds.height-60
if fixHeight > maxHeight {
fixHeight = maxHeight
}
}
1
2
3
4
5
6
func addTitle(text: String? = "") {
// 直接在方法中参数里赋默认值就好
//if text == nil {
// text = ""
//}
}

二、通知、代理、block

什么时候用通知,什么时候用代理,什么时候用block
通知 : 两者关系层次太深,八竿子打不着的那种最适合用通知.因为层级结构深了,用代理要一层一层往下传递,代码结构就复杂了

代理 : 父子关系,监听的方法较多的时候使用

block : 父子关系,监听的方法较少的时候使用

1、代理

  • 定义swift中代理的协议
1
2
3
4
5
6
7
// swift 中的代理必须继承自NSObjectProtocol
protocol VisitorViewDelegate : NSObjectProtocol
{
// 代理中的方法默认必须实现,有可以不实现的情况,以后整理
func visitorViewDidClickRegisterBtn(visitorView : VisitorView)
func visitorViewDidClickLoginBtn(visitorView : VisitorView)
}
  • 声明代理
1
2
// 代理属性,与OC一样,用weak修饰
weak var delegate : VisitorViewDelegate? // 可选类型,代理可以有也可以没有
  • 在按钮点击的事件中执行代理方法
1
2
3
4
5
6
7
8
9
10
11
12
// 注册按钮的点击
@IBAction func registerBtnClick(sender: UIButton) {
// 监听到点击,通知代理做事情
// 代理中的方法默认是必须实现的(也有可选的,后面再说),所以这里没有判断代理有没有实现相应的方法
delegate?.visitorViewDidClickRegisterBtn(self)
}

// 登录按钮的点击
@IBAction func loginBtnClick(sender: UIButton) {
// 监听到点击,通知代理做事情
delegate?.visitorViewDidClickLoginBtn(self)
}
  • 代理的具体实现

    swift中为了让方法分类更清晰,实现代理或者数据源的方法单独写到分类中

1
2
3
4
5
6
7
8
9
10
11
12
13
// MARK: - VisitorViewDelegate代理方法
// swift 中为了区分不同类型的代理方法或者数据源方法,通过extension实现了更好的区分
extension BaseTableViewController: VisitorViewDelegate
{
func visitorViewDidClickRegisterBtn(visitorView : VisitorView) {
ChaosLog("")
}

func visitorViewDidClickLoginBtn(visitorView : VisitorView) {

ChaosLog("")
}
}

2、block

作为类属性:

1
var clickHandle: ((_ sheetModel: CJDemoActionSheetModel, _ selectIndex: NSInteger)->())?

作为函数变量:

1
2
3
init(sheetModels:[CJDemoActionSheetModel], clickHandle: @escaping (_ sheetModel: CJDemoActionSheetModel, _ selectIndex: NSInteger)->()) {

}

其他参考文章:

其他

获取类名

1
2
CJDemoActionSheetTableViewCell.self
UIViewController.self

如何在int和float之间的算术在Swift?

1
2
3
4
var myInt = 5;
var myDouble = 3.4;
var doubleResult = Double(myInt) + myDouble;
var intResult = myInt + Int(myDouble)

内存管理

CFRelease

苹果的一些底层框架返回的对象有的是自动管理内存的(annotated APIs),有的是不自动管理内存。

如果一个函数名中包含CreateCopy,则调用者获得这个对象的同时也获得对象所有权,返回值Unmanaged需要调用takeRetainedValue()方法获得对象。调用者不再使用对象时候,Swift代码中不需要调用CFRelease函数放弃对象所有权,这是因为Swift仅支持ARC内存管理,这一点和OC略有不同。

上文摘自:https://www.jianshu.com/p/103591adc3d1 中 CFRoopLoop使用 的使用

Swift GCD延迟加载

详细参考:

swift 自定义view的初始方法

swift中的@objc的作用

Swift枚举的全用法

iOS 国际化全解-swift与OC

常见痛点

[toc]

Swift和OC有什么区别?

弄清 SwiftUI,才看得懂苹果的强大

在Swift中,struct(结构体)和class(类)都是创建自定义数据类型的方式,它们都支持许多相同的功能,例如属性、方法、下标、初始化、扩展以及协议。但是,它们之间也存在一些关键的区别:

  1. 存储和内存管理

    • struct 是值类型,意味着当你将一个结构体赋值给一个变量或者作为参数传递给函数时,它的值会被复制。这使得结构体在多线程环境下更安全,因为它们的值不会意外地被改变。
    • class 是引用类型,意味着当你将一个类的实例赋值给一个变量或者作为参数传递给函数时,你实际上是传递了一个指向对象内存地址的引用。因此,多个变量可能引用同一个对象,并且任何对该对象的修改都会影响到所有引用它的变量。
  2. 继承

    • class 支持继承,这意味着你可以创建一个新类来继承一个现有类的属性和方法。
    • struct 不支持继承,因为它们是值类型,继承可能会引入复杂的内存管理问题。
  3. 方法重写

    • class中,你可以重写父类的方法。
    • struct中,你不能重写方法,但你可以使用方法重载(方法签名不同)。
1
2
3
4
5
6
7
8
let person1 = Person(name: "John")

// 将person1赋值给另一个变量person2、// 将person1赋值给另一个变量person2
let person2 = person1
person2.name = "Jane"

class: person1person2都指向同一个Person实例,当person2name属性被修改时,person1name属性也会随之改变。
struct:person1person2是两个不同的Person实例。当person2name属性被修改时,person1name属性保持不变,因为结构体是值类型,赋值操作会创建一个新的实例副本。

在 Swift 中,structclass 的复制行为是不同的:

  1. Struct(结构体):当你复制一个 struct 时,你得到的是原始数据的深复制(deep copy)。这意味着新变量和原始变量之间没有共享任何数据。对新变量所做的任何修改都不会影响原始变量。
  2. Class(类):对于 class 来说,复制行为取决于你是如何实现复制的。如果你只是使用赋值操作符(=),那么新变量和原始变量会指向同一个对象,这意味着它们共享相同的内存地址,并且任何对新变量的修改也会影响原始变量(浅复制)。但是,如果你实现了 NSCopying 协议并提供了一个 copy(with:) 方法,那么你可以控制复制行为,实现深复制。

总结来说,struct 默认是深复制,而 class 默认是浅复制,但可以通过实现 NSCopying 协议来改变这个行为。

在Flutter开发中,列表页图片在返回时刷新是一个常见的问题,这通常是因为图片资源被释放或重建导致的。

1、图片没缓存? 使用有缓存功能的图片库 cached_network_image

2、缓存空间太小释放了?->

图片太大导致缓存空间不够? -> 图片数据万象,加载空间优化

图片视图重建:图片加载动画导致的?-> 未列表项增加唯一的key标识符,重用

Flutter中的列表项Key与iOS中UITableViewreuseIdentifier在概念上类似,但它们的使用和行为上有一些差异。以下是两者的主要区别:唯一性要求iOS:在UITableView中,reuseIdentifier(重用标识符)是用于指定一个可重用的单元格的。同一个reuseIdentifier可以被多个单元格共用,只要它们是相同类型的单元格。系统通过reuseIdentifier来识别和重用单元格,而不是通过它来区分不同的单元格。Flutter:每个列表项的Key必须是唯一的。如果两个列表项使用了相同的Key,Flutter会抛出异常,因为它违反了Key的唯一性原则。Key的唯一性确保了框架能够正确地识别和重用列表项。

flutter与iOS的重用区别:iOS列表的重用标识符reuseIdentifier VS Flutter列表的唯一的Key

1
2
3
4
5
> 		return TIMUIKitGroupTrtcTipsElem(
> key: ValueKey(messageItem.msgID), // 使用key可以帮助Flutter识别哪些列表项是重复的,从而避免不必要的重建,提高性能。
> customMessage: messageItem,
> );
>

在iOS中,UITableView通过重用标识符(reuseIdentifier)来重用离开屏幕的单元格。当单元格滚动出屏幕时,它们会被放入一个重用队列中,当需要新的单元格时,系统会尝试从这个队列中获取一个已经存在的单元格,而不是每次都创建一个新的单元格。

在Flutter中,虽然没有直接的重用标识符概念,但是通过为每个列表项指定一个唯一的Key,可以帮助Flutter识别哪些列表项是相同的,从而实现重用。当列表项离开屏幕并且需要重新进入屏幕时,如果它们有相同的Key,Flutter会重用这些项,而不是重新创建它们。这可以减少不必要的widget重建,从而提高性能。

当用户滚动列表时,不可见的列表项(列表项离开屏幕时)会被”回收”,此处是回收站概念,并不意味着它们完全被销毁;因为Flutter的列表组件使用一种称为“虚拟滚动”的技术,这意味着它们除保留当前屏幕上可见的列表项外,还可能额外保留一些列表项以提高滚动性能,具体是否有保留Flutter系统会根据当时的内存使用情况等处理,即不代表未设置cacheExtent,因其默认值是0,而就说离开屏幕的列表项就会被销毁。

当用户滚动列表,使得之前不可见的项目再次变得可见时,Flutter会检查是否已经有一个具有相同Key的项目在内存中。如果找到了,Flutter将重用这个项目,而不是创建一个新的,即从之前的回收站里重新使用。

(附:Flutter的ListView(包括ListView.builder)使用了一种“懒加载”机制,这意味着它只会构建和渲染当前可见的列表项,而不是一次性构建所有列表项。)

如果还希望提高滚动性能,可以在Flutter的ListView或类似的滚动组件中设置addAutomaticKeepAlives: falseaddRepaintBoundaries: false时,你实际上是在告诉Flutter框架不要为列表项自动添加保持活动状态(keepalive)或重绘边界(repaint boundaries)。虽然都设置成了flase,但不代表当列表项再次滚动回屏幕时,一定会重建。它只是增加列表项在滚动回屏幕时重建的可能性,但是否一定会重建还取决于其他因素。比如列表项有唯一的Key,就不会重建,而是重用它们。

内存:图片大小卡顿、视频压缩崩溃、

热修复

原理:js -> native (swizzed)

优化:

页面加载优化

列表优化、图片优化、内存优化、web资源优化、卡顿优化

web

大文件上传优化、

内容发布崩溃:

连续异步压缩导致的崩溃: https://github.com/dvlproad/001-UIKit-CQDemo-Flutter/blob/ce55cf403a45f05f2c2bfe22c849de268c455246/flutter_image_process/lib/src/images_compress_util.dart#L24

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
class ImageCompressUtil {
/// 异步压缩所添加的文件
static Future compressMediaBeans<T extends ImageChooseBean>(
List<T> addMeidaBeans,
) async {
int count = addMeidaBeans.length;
for (var i = 0; i < count; i++) {
T bean = addMeidaBeans[i];
_log("bean.assetEntity.id=${bean.assetEntity?.id} begin...");
bean.checkAndBeginCompressAssetEntity();
if (i < count - 1) {
// 异步压缩,并且不是最后一个时候,每个压缩间隔一下,防止内存还没释放,导致积累过多崩溃
await Future.delayed(const Duration(milliseconds: 500));
}
_log("bean.assetEntity.id=${bean.assetEntity?.id} finish...");
}
}
}

webview白屏 –> 白屏使用可使用不存在WKCompositingView类型的视图来检测 –> webview中的WKCompositingView视图也是用做同层渲染的原理 –> 同层渲染的场景:有个话题活动页,页面上有一个已经用原生方式实现过列表。(h5说还要实现列表开发来不及,原生说这种活动用原生做一遍到时候还得废弃,无用功。所以折中页面还是网页实现,但列表部分用web同层渲染替换成原生。)更多使用可参考微信小程序。 –> 如果页面上这是小部分内容需要用html,则可以用原生的lable或textview等显示html富文本,如iOS的iOS中支持HTML文本的标签控件——MDHTMLLabelflutter_html iOS UITextView 加载 HTML 时的问题与优化 。注意此场景不适合将该部分使用单独的webview显示,效果不好。 –> 插入说明,如果前面那个列表是h5实现的,由于已经晓得它和原生实现过的一样,那这两个地方的列表内容肯定重合度高,尤其是图片,进而导致同一张图片已经在原生加载过了,还得在h5上再加载一遍,而影响加载效率,所以可以考虑对h5中的图片通过WKURLSchemeHandler 进行拦截,使其直接使用原生中的图片。

问题排查

错误

web白屏、

问题复现:

在iOS开发中,崩溃排查是常见的问题解决过程。以下是一个典型的崩溃排查案例,包括遇到的困难和解决方法:

崩溃案例:野指针导致崩溃

遇到的问题: 在一次版本更新后,用户开始报告应用在浏览图片时偶尔会出现崩溃。崩溃日志显示是EXC_BAD_ACCESS错误,但具体的堆栈跟踪信息不是很清晰,没有直接指向崩溃的代码行。

困难:

  1. 崩溃不复现:崩溃不是每次都发生,难以复现具体场景。
  2. 日志信息不足:崩溃日志没有提供足够的信息来定位问题。
  3. 多线程环境:应用中有多线程操作,不确定是否与线程安全有关。

解决方法:

  1. 符号化崩溃日志:首先,使用atos工具对崩溃日志进行符号化,获取更详细的堆栈跟踪信息。
  2. 代码审查:根据符号化后的日志,检查相关代码逻辑,特别是图片加载和缓存的部分。
  3. 增加日志输出:在怀疑有问题的代码区域增加更多的日志输出,尝试引导出更多的崩溃信息。
  4. 使用调试工具:使用Xcode的Debug和Release模式进行测试,开启Zombie Objects来检测野指针,使用Thread Sanitizer检查线程安全问题。
  5. 重现崩溃:通过不断测试和调整,最终发现崩溃发生在一个特定的图片处理操作后,且与图片的来源有关。
  6. 定位问题:最终定位到问题是由于一个图片处理函数在异线程中被调用,且处理的图片对象已经被释放,导致野指针访问。
  7. 代码修复:修改代码,确保图片对象在异步操作中不会被提前释放,或者在异步操作完成前保持对图片对象的强引用。
  8. 编写单元测试:为了防止未来再次出现类似问题,编写了单元测试来覆盖图片处理的逻辑。
  9. 灰度发布:在修复后,先进行灰度发布,监控是否有新的崩溃报告。
  10. 全量发布:在灰度发布稳定后,将修复版本全量发布给所有用户。

通过这个过程,我们不仅解决了崩溃问题,还增强了代码的稳定性和测试覆盖率。在处理iOS崩溃问题时,耐心和细致的调试是关键,同时,良好的日志记录和崩溃监控系统也能帮助快速定位和解决问题。

公司业务转型,融资待岗

个人优势:

slogan:做一个的项目,而不仅仅是能跑没问题的项目。
十年移动端开发,拥有千万级用户app开发经验及团队管理经验(>10人);
丰富的面向对象编程经验,精通常用设计模式;熟练数据库、多线程开发;
熟练模块化,精通组件开发,有丰富的SDK开发经验;
具有多媒体应用、视频直播、IM即时通讯等相关开发经验;
丰富的页面加载体验优化经验,页面关键内容”秒显”;
擅长图片加载优化、网络请求优化,减少加载时间,提升渲染速度;
优秀的跨平台开发能力,熟练掌握iOS与H5的混合开发及React Native、Flutter等跨平台技术;
熟练运用shell、python脚本语言,进行各种自动化打包、信息提取等处理,简化重复性工作,提高开发和运维效率;
敏锐的问题预见与解决能力,提前发现潜在问题并处理,确保项目的顺利进行和高质量输出;
良好的自驱和追求卓越力,不断对项目进行优化,让应用体验更好。

wcdb 相比fmdb

为什么要从FMDB迁移到WCDB

表结构

WCDB提供了ORM的功能,将类的属性绑定到数据库表的字段。在日常实践中,类的属性名和表的字段名通常不一致。因此,WCDB提供了WCDB_SYNTHESIZE_COLUMN(className, propertyName, columnName)宏,用于映射属性名。

易用:对于等价的功能,WCDB所需的代码量往往会比FMDB少很多。而更少的代码量通常意味着更快的开发效率和更少的错误。

性能:更好

业绩:

  1. APP安全合规上架成功率75%->100%;
  2. 页面加载用户体验的全面优化,”秒显”用户关心的页面内容;
  3. 应用内Cocoas游戏优化,平均打开时间3000ms->300ms;
  4. 监控系统开发和问题处理,错误率81%->3%,崩溃率<0.03%;
  5. 风险设备识别,减少公司被薅羊毛的经济损失。
  6. 移动端js自测平台开发,与游戏和web交互工作的闭环效率提升50%+。
  7. 自动化打包扩展,打包资源有效利用率提高99%+,测试前的沟通成本减少90%+,测试效率侧面提升10%+;
  1. 提升APP安全合规上架成功率:从75%提升至100%的成功率,确保了用户对新版本的及时体验。
  2. 页面加载速度革命性提升:通过全面优化,页面加载速度实现了质的飞跃,用户体验得到极大改善,页面关键内容”秒显”。
  3. Cocoas游戏加载速度显著缩短:应用内Cocoas游戏的加载时间从3000ms优化至300ms,提升了10倍,极大增强了用户的游戏体验。
  4. 监控系统开发与问题处理:通过开发先进的监控系统,错误率从81%降至3%,崩溃率控制在0.03%以下,显著提升了应用的稳定性和可靠性。
  5. 提高公司运营的有效投入产出比:进行风险设备识别,有效减少了因被薅羊毛而造成的经济损失,确保了资源的合理分配和利用率,进一步提高公司运营的有效投入产出比。
  6. 移动端js自测平台创新开发:开发了移动端js自测平台,实现了与游戏和web交互工作的闭环效率提升超过50%,大幅提升了app和web的协同效率。
  7. 自动化打包流程革新:通过自动化打包扩展,资源有效利用率提高至99%以上,测试前的沟通成本大幅减少90%以上,同时测试效率侧面提升超过10%。

1.对APP启动流程进行重构规范,彻底解决安卓用户隐私协议前的安全合规问题,APP上架成功率75%->100%;
2.对主页、首页等重要页面以及列表等进行预加载,减少用户等待时间,提高应用的响应速度和用户体验。对列表进行预加载、按需加载优化,实现丝滑的无限滚动列表;
3.对图片进行万象优化,提高图片加载速度,减少图片磁盘占用;解决底层库在图片高磁盘占用情况下的卡顿问题;
4.对页面加载的用户体验进行全面优化,让用户关心的页面内容达到”秒显”效果。
5.制定网络的弱网优化、加密优化方案,使得网络处理又快又安全。
6.搭建页面框架、路由框架,统一规范,提高开发效率。
7.发掘和注重用户体验的持续改进,通过迭代设计和功能增强,让应用使用更加便捷和高效。如大文件上传优化,视频封面截取,视频边下边播、视频预加载、打赏礼物优化、视频播放中暗掉等问题。
8.对承载Cocoas游戏的WebView进行整体优化,解决游戏加载慢、白屏、发热、并实现秒开,让用户对游戏的体验得到一个质的提升。
9.开发移动端的js自测平台,有效提升与游戏和web交互工作的闭环效率50%+。
10.搭建并扩展自动化打包工程,编写shell、python等脚本,进行打包前分支允许性、完整性的检查,避免无效包的产生,减少打包资源的浪费(提高打包资源的有效利用率);打包时分支功能信息的采集和整理,并及时输出日志,避免多余功能的合入,让测试人员更直观对安装包内容进行测试,减少测试前的沟通成本达90%+。;同时加入测试进度,让开发负责人更能及时了解和跟进当前测试进度,侧面的促进了测试效率的提升10%+。
11.捕获并及时抛出异常,提高开发人员对异常的关注和处理率,保证了代码质量。同时进行空安全升级,提前发现潜在问题,将错误率从81%降到3%。修复崩溃问题,崩溃率<0.03%
12.建立埋点框架,提供埋点数据(曝光),帮助产品分析用户行为,优化产品体验。
13.建立日志系统,提高各类问题的定位效率,节省了不必要的调试和沟通成本。通过滚动存储、日志回捞,进一步帮助开发人员对极限问题的排查处理。
14.建立灰度系统,实现应用内功能回滚,有效避免集体性功能异常问题的发生。
15.进行风险设备识别(数美、同盾),减少公司被薅羊毛的经济损失。
16.实现各种开发工具,并保证开发工具的安全性。

  1. 预加载、按需加载优化,实现丝滑的无限滚动列表。
  2. 对图片进行万象优化,提高图片加载速度,减少图片磁盘占用;解决底层库在图片高磁盘占用情况下的卡顿问题;

5.制定网络的弱网优化、加密优化方案,使得网络处理又快又安全;
6.整个项目开发规范的统一,提高开发效率和维护性;
7.发掘和注重用户体验的持续改进,让应用使用更加便捷和高效。

12.建立埋点框架,提供埋点数据(曝光),帮助产品分析用户行为,优化产品体验。

13.建立日志系统,提高各类问题的定位效率,节省了不必要的调试和沟通成本。通过滚动存储、日志回捞,进一步帮助开发人员对极限问题的排查处理。

  1. 建立灰度系统,实现应用内功能回滚,有效避免集体性功能异常问题的发生。

16.实现各种开发工具,并保证开发工具的安全性。

常见痛点

码上格言slogan:做一个好的项目,而不仅仅是能跑没问题的代码。

高质量的项目.md

如何进行0到1的架构设计?

1、搭建项目结构。其主要的组成部分有:应用入口、资源文件、三方SDK、工具类、功能模块(数据管理中心+业务功能)等。

2、在功能模块中,每个模块有独立的数据层(manager)和业务层。模块内:按照MVC、MVVM+S等设计模式。模块间:模块化方案、依赖注入?;

文档:《架构模式-①概览.md》、《架构模式-④MVC与MVVM.md》、《5框架设计模式-⑦组件化.md》、《设计模式-①概览.md

架构的演变

用户在view上的操作,会通过vm对model进行数据的修改;
数据的修改引起的属性变化会通知到vm上;
vm根据变化进行view UI的更新;

组件化的各种方案、系统通知的优化方案

发布订阅模式的实现方式:类似Notification(页面需要订阅数据变化、线程管理

3、页面内结构设计(与用户数据管理模块进行模块化通信)

页面执行自己的动作,调用Service服务类。无需关心数据请求、数据请求结束后是否需要缓存等等。

Service服务中心,内部包含请求和可能的数据存储等处理,不对外暴露。

文档:项目目录结构规范 见《架构模式-①概览》

4、容灾能力

异常捕获/监控(自研、bugly)、埋点、日志预警、应用内回滚、灰度发布

5、自动构建与持续集成

6、分支管理

面向对象OOP和面向协议POP分别在不同场景下怎么选择,后者有什么优点?

定义共享行为:当你有多个不同的类需要实现相同的行为,但这些类之间没有继承关系时,可以使用协议来定义共享的行为。eg:磁盘和内存的存储行为。网络请求行为定义;场景:遵循协议定义行为的实例可被其他也同样遵循协议的实例替换,这种替换满足了开闭原则:对扩展开放,对修改封闭。里氏替换原则:应用程序中任何父类对象出现的地方,我们都可以用其子类的对象来替换,并且可以保证原有程序的逻辑行为和正确性。因为这里父类是抽象类,所以肯定遵守里氏替换原则。

解耦:当你想要解耦组件,使得组件之间的依赖关系更加松散时,面向协议可以提供帮助。使用场景:模块化;

月OKR

对xxx进行yyy,

OOM 内存泄露

怎么保障一个项目的稳定性?

稳定性:崩溃

通过runtime及消息转发机制,提高项目容错性和稳定性。同时接入Bugly,发现并解决真机在生产环境下可能出现的问题,提高产品的质量,保证用户的体验。

通过多线程、GCD、队列、信号量Semaphore等解决项目中的多任务问题。如解决讯飞语音的顺序播放,使收到的信息能够顺序且完整的播报,而不会因为新信息的提前播报使得旧信息播报未全或者丢失等情况。

一、对APP启动流程进行重构规范,彻底解决安卓用户隐私协议前的安全合规问题,提高APP上架成功率从75%到100%。

旧:原本启动流程中各种三方库的初始化顺序不是太严谨。安全合规的错误根源:同意用户协议前有些三方库有概率①去调用获取设备敏感信息AndroidID,因为安卓未同意隐私政策前不可获取deviceId(其中也包含我们的业务接口需要上报deviceId);②调用获取粘贴板内容。通讯录?

新:同意用户协议前,避免其他三方库去调用获取敏感信息的可能。即三方库初始化统一到同意用户协议之后。至于网络库是否也放到同意协议后可用有两种方式,其中如果不放到同意协议后,则一定要注意获取敏感信息的时候一定要增加判断是否同意了协议。

文档:《网络框架.md》、《分享业务规范.md》粘贴板?、短链?

流程图:《网络框架的一生.graffle》中的【一、网络与用户协议流程】

进行Android加固,构建安全的APP安装包,解决上架及软件管理局

iOS APP安全加固方案(一、代码混淆CodeObfuscation)

github CJStandardProject 项目中的 CodeObfuscation

在系统网络授权弹窗基础上,加入自定义的网络引导设置弹窗,提升用户网络设置的体验。

要点:系统网络权限弹窗机制:有代码执行到网络相关事项时候才会系统自动弹出。常见的场景:1监听网络状态变化;2进行任一网络请求。

二、对主页、首页等重要页面以及列表等进行预加载,减少用户等待时间,提高应用的响应速度和用户体验。对列表进行预加载、按需加载优化,实现丝滑的无限滚动列表。

文档:《列表优化.md》、iOS视频列表滚动自动播放的实现(Swift)

主页:通过 vm / manager 预加载数据

列表:监听滚动的滑动距离,判断阈值,提前加载下一页数据(阈值的变化逐渐增长var threshold = (curPageIndex+0.7)/(curPageIndex+1.0),页数越大,越靠近1.0)。滚动过程不加载图片、滚动结束加载。

三个player

iOS性能优化相关问题

Flutter的长列表优化

ListView.builder会按需构建列表元素,也就是只有那些可见得元素才会调用itemBuilder 构建元素

三、对图片进行万象优化,提高图片加载速度,减少图片磁盘占用;解决底层库在图片高磁盘占用情况下的卡顿问题;其他卡顿优化?

只加载与视图大小相近的合适图片,避免下载的图片太大而占用磁盘空间。

设置阶梯,避免同一张图片加载的规格太多份,而导致占用不必要的磁盘空间。

真出现图片导致的高磁盘占用引起下滑卡顿时候,通过fork图片库,修改图片的保存路径,使其根据图片 URL 的 MD5 值进行散列,然后根据这个散列值来创建子目录和文件名,从而在磁盘上形成一个树状的目录结构。为了避免生成的文件夹数量太多,可以使用一致哈希算法进行优化和方便以后扩展。

磁盘大小的其他优化:图片缓存池、删除掉ttf字体文件

卡顿的检测可用FPS来衡量(CADisplayLink计算),《性能监控-①卡顿监控.md》,也可用主线程卡顿监控的方案(如果发现主线程runloop的状态卡在为BeforeSources或者AfterWaiting超过88毫秒时,即表明主线程当前卡顿。)。

iOS内存泄露的定位:Analyze 静态分析 和 Leak工具内存泄露检查,以及 MLeaksFinder 库内存泄露检查方式。

文档1:《图片框架.md

文档2磁盘:《高磁盘占用的排查与优化.md

文档3内存:《2内存-③内存泄漏定位.md》、《app内存问题.md

3视图-更新机制.md

深入探索Flutter性能优化

离屏渲染:

https://github.com/SDWebImage/SDWebImage

四、对页面加载的用户体验进行全面优化,让用户关心的页面内容出来前的初始空白时间肉眼可见的达到”秒显”效果。

文档:《页面加载体验优化.md

问题背景:前期后台网络接口耗时长,导致页面初始的空白时间很长。

从页面进入开始优化到内容渲染完成。

其他:网路质量的获取?页面加载时长?技术选型?

2.1、对图片进行万象优化,见上文

2.2、对接口进行缓存优化,见下文

移动端页面加载耗时监控方案

五、制定网络的弱网优化、加密优化方案,使得网络处理又快又安全。

快:①网络缓存;②弱网情况下加载低质量的图片,当然有缓存的情况下还是优先取缓存。③ip直连,可以省去请求时候的dns解析,节省部分请求时间。

安全:通过RSA+AES进行数据的加密。

弱网优化,请查看我的另一篇文章:《弱网优化空间探索.md

4、处理各种安全问题:设计网络接口加密方案,为数据安全保驾护航。

文档:《网络框架.md》、《网络加密.md》、《网络耗时及其优化.md

流程图:《网络框架的一生.graffle》中的【三、网络缓存优化】、《网络加密.graffle

六、搭建页面框架、路由框架,统一规范,提高开发效率。

生命周期???

viewDidAppearviewDidDisappear

1、页面跳转

同级页面跳转(tab切换): visibility_detector

不同级页面跳转:监视页面的切换(RouteObserver & RouteAware)didPop\didPopNext、didPush\didPushNext

2、App 的生命周期(前后台切换):

WidgetsBindingObserver@override void didChangeAppLifecycleState(AppLifecycleState state)

拦截器???

路由规范(含点击属性规范).md.md)》

妙用依赖注入.md

六、发掘和注重用户体验的持续改进,通过迭代设计和功能增强,让应用使用更加便捷和高效。如大文件上传优化,视频封面截取,视频边下边播、视频预加载、打赏礼物优化、视频播放暗掉问题。

文档:《视频优化.md》、《文件分片上传与分片下载.md

添加视频封面功能,从视频中截取封面流程,实现丝滑的任意位置视频帧获取。(核心:上面是视频,下面进度条变化时候相当于seek视频)

优化大文件的上传:添加断点续传功能,

七、对承载Cocoas游戏的WebView进行整体优化,解决游戏加载慢、白屏、发热、并实现秒开,让用户对游戏的体验得到一个质的提升。

加载慢:

阶段 优化方案
(初次/重复)打开时,加载慢 webview提前(尽可能多的)初始化+全局化
加载中,加载慢 拦截加载自定义图片(webview_flutter暂不支持)

加载慢:游戏自身加载慢的问题原因:游戏首屏请求太多,下载资源太多,未分帧。使用不再维护的cocos2d缺少cocos对web的优化。

接受游戏自身问题导致的加载慢,但避免每次进入游戏都慢。游戏的游戏首页能否提前初始化:不能。app只是个游戏链接,不涉及到游戏引擎的加载。

发热:离开web游戏页面停止计时器、停止渲染

白屏:回前台时候通过app调用js方法做web白屏检查,若白屏执行reload。也可以通过webview截图 ==> 缩放图片,为了减少待会遍历的像素点 ==> 遍历像素点,判断白色像素占比超过95%则认定为白屏。在iOS上还可以通过 webView的子视图WKCompositingView 存不存在来判断白屏。

文档:《WebView优化.md》、《app发热问题.md

八、开发移动端的js自测平台,有效提升与游戏和web交互工作的闭环效率50%+。

js注入:在页面指定位置(top(默认) \ bottom \ overlay)注入按钮

WebView优化.md》中【四、使用中的交互】

WKWebView 注入js代码: 001-UIKit-CQDemo-Flutter 中的 flutter_webview_kit

js调试

js 规范:code msg result

请求规范_JS.md

webview声音?

九、搭建并扩展自动化打包工程,编写shell、python等脚本,进行打包前分支允许性、完整性的检查,避免无效包的产生,减少打包资源的浪费(提高打包资源的有效利用率);打包时分支功能信息的采集和整理,并及时输出日志,避免多余功能的合入,让测试人员更直观对安装包内容进行测试,减少测试前的沟通成本达90%+。;同时加入测试进度,让开发负责人更能及时了解和跟进当前测试进度,侧面的促进了测试效率的提升10%+。

建立分支合并规范(分支创建工具),

打包日志并加入打包信息,。

打包资源的利用率,缺少经常新发布的缺少主功能(之前的分支很早就开出来),缺少上次打包的内容(打包的不是打包的分支,只是单个功能的分支),蒲公英包缺少合入蒲公英功能的分支代码;

搭建移动端日常自动化

文档:《Git Rebase.md

脚本:https://github.com/dvlpCI/script-branch-json-filehttps://github.com/dvlpCI/script-qbase

游戏toast 提取脚本

Webhook

Webhook的工作原理是基于HTTP请求的。当GitLab中的某个事件发生时,GitLab会向预先配置的URL发送一个HTTP请求。这个请求包含了事件的详细信息,如事件类型、触发事件的Git对象等。接收请求的服务可以根据这些信息执行相应的操作,如拉取代码、构建项目、发布应用等。

如何在Jenkins上关联Gitee的Webhook

GitLab Webhook配置详解

webhook_gitee_1

webhook_gitee_2

监控相关

异常/错误捕获、崩溃处理、数据埋点(含页面信息注解)=> 日志输出(滚动存储、日志回捞、主动上报) => 埋点上报 => 灰度实现

解决错误、崩溃率

发热问题

十、捕获并及时抛出异常,提高开发人员对异常的关注和处理率,保证了代码质量。同时进行空安全升级,提前发现潜在问题,将错误率从81%降到3%。修复崩溃问题,崩溃率<0.03%

剩下的主要是与h5交互时候,h5通过js返回的结构问题。

iOS崩溃:95%腾讯播放器

Android崩溃:60%+模拟器

文档:《BUGLY崩溃问题汇总.md》、《异常与崩溃.md》、《app发热问题.md

错误率的处理,技术债务管理:识别、区分优先级、监控效果

日志 注解

十一、建立埋点框架,提供埋点数据(曝光),帮助产品分析用户行为,优化产品体验。

提供优雅的技术方案,利用注解,提供埋点所需的页面信息。

曝光埋点:

文档:《埋点原理与实现.md》、《妙用注解.md

注解:埋点所需的pageKey 、pageDesc

十二、建立日志系统,提高各类问题的定位效率,节省了不必要的调试和沟通成本。通过滚动存储、日志回捞,进一步帮助开发人员对极限问题的排查处理。

文档:《日志系统.md

十三、建立灰度系统,实现应用内功能回滚,有效避免集体性功能异常问题的发生。

文档:《灰度系统.md

十四、对安装包进行瘦身,从400多M降到180M左右。

代码:《package-size-resource 中的 example_base_remove_unused_resources.py》执行结果 APP资源清单表.xlsx

处理的内容:远程化字体、打赏动画、图片(手写脚本)

编写脚本提取冗余资源

功能重复的框架

地图:百度 VS 高德

风险设备识别:数美 VS 数盟

人脸核身:阿里 VS 腾讯

十五、进行风险设备识别(数美、同盾),减少公司被薅羊毛的经济损失。

至少几十万的损失。

十六、实现各种开发工具,并保证开发工具的安全性。

实现iOS重签名,实现在不允许切环境下,安装相同app,提高测试人员测试效率。

文档:《开发工具.md》、《开发工具安全性处理

iOS的重签名.md

面试后问题提问

1、人工分工情况

目前项目有遇到什么问题吗

比较希望什么方面的能力

团队目前的阶段性目标是什么,如果加入需要提前做什么准备吗

多久能反馈面试结果,如果通过后续流程是怎样的?

Code Reviewer

正常的异常现象汇总

技术输出:

  • 优化进入新版本游戏的地址处理。旧完整的新版本地址,新web自己判断新版本地址。即最后只进入baidu.com

发现潜在危险:

  • 商品价格计算优化,本来是前端计算价格

进行功能抽象化探索及实现,减少重复开发,提高开发效率

  • 分享页(海报内容+按钮) 思考海报的内容一般都与H5落地页一致,那能否app中不做海报内容,直接webview加载海报内容?答:系统的截屏截取不到webView上的内容,webView上的内容要用webView自己截取,所以还得考虑怎么整合。方案暂未继续调研过。

消息转发

防抖、节流

ios性能优化文章 iOS-Performance-Optimization

大厂常问iOS面试题–性能优化篇

iOS性能检测优化

MVVM和MVC有什么区别

Flutter Dart语法(1):extends 、 implements 、 with的用法与区别

flutter doctor出现 Unable to find bundled Java version

AGP(Android Gradle Plugin)是Android构建系统的一部分,用于帮助我们构建和打包Android应用程序。

各个 Android Gradle 插件版本所需的 Gradle 版本

6.3 ListView

shrinkWrap:该属性表示是否根据子组件的总长度来设置ListView的长度,默认值为false 。默认情况下,ListView会在滚动方向尽可能多的占用空间。当ListView在一个无边界(滚动方向上)的容器中时,shrinkWrap必须为true。

addAutomaticKeepAlives:该属性我们将在介绍 PageView 组件时详细解释。

addRepaintBoundaries:该属性表示是否将列表项(子组件)包裹在RepaintBoundary组件中。RepaintBoundary 读者可以先简单理解为它是一个”绘制边界“,将列表项包裹在RepaintBoundary中可以避免列表项不必要的重绘,但是当列表项重绘的开销非常小(如一个颜色块,或者一个较短的文本)时,不添加RepaintBoundary反而会更高效(具体原因会在本书后面 Flutter 绘制原理相关章节中介绍)。如果列表项自身来维护是否需要添加绘制边界组件,则此参数应该指定为 false。

6.12 嵌套可滚动组件NestedScrollView

内部的可滚动组件(body的)不能设置 controller 和 primary,这是因为 NestedScrollView 的协调器中已经指定了它的 controller,如果重新设定则协调器将会失效。

在flutter中,使用NestedScrollView和CustomScrollView+SliverListView有什么区别?

在 Flutter 中使用 NestedScrollView 與 CustomScrollView

页面退出时候,如果无脑执行关闭键盘,会触发 build 的执行

Android开发关于高德地图POI不返回区域编码AdCode的问题 2020.10最新

Telegram 创建 bot 获取 token 和 chatId 以及发送消息简明教程

Telegram机器人Token和ChatID获取教程

Telegram 聊天机器人中获取照片

Android合规安全

[toc]

Android合规安全

一、先上结论

1
2
javaCmdPath=/Library/Java/JavaVirtualMachines/jdk-17.jdk/Contents/Home/bin/java
jiaguJarFilePath=${jiaguDic}/jiagu.jar

问题1,通过勾选资源加密策略,对资源目录中明文证书加密保护

1
2
3
4
#高级加固服务:
# 资源文件保护: -assets
# 配置需要排除的资源文件列表(多个文件使用英文逗号分隔): -assets=文件1,文件2
${javaCmdPath} -jar ${jiaguJarFilePath} -config-jiagu add -assets

问题2,通过勾选日志防泄露策略,防止日志泄露

1
2
3
#高级加固服务:
# 日志防泄漏: -anti-log
${javaCmdPath} -jar ${jiaguJarFilePath} -config-jiagu add -anti-log

问题3,勾选本地数据文件加密策略,对XML存储明文数据进行加密保护

1
2
3
#高级加固服务:
# 文件完整性校验: -file-check
${javaCmdPath} -jar ${jiaguJarFilePath} -config-jiagu add -file-check

问题4,勾选ptrace防注入策略,为应用添加防注入保护

1
2
3
#高级加固服务:
# Ptrace防注入: -ptrace
${javaCmdPath} -jar ${jiaguJarFilePath} -config-jiagu add -ptrace

二、APP安全漏洞项的原因分析与加固前后对比

(1)****证书明文存储风险****

image-20231025192829546

bid-log-key-public.key 加固前后数据对比(加固使用360企业版)

image-20231025192211991

(2)****日志函数泄漏风险****

image-20231025193458250

对安装包中的 classes.dex 进行反编译(使用qtool 下的 decompile_dex.py 脚本),分别得到如下结果

未加固前:

image-20231025213337351

加固后:

image-20231025215240636

(3)****XML存储明文数据风险****

image-20231025191441524

加固前后数据对比(加固使用360企业版)

image-20231025191112052

(4)****动态注入检测****

image-20231025191712639