0%

MVVM

MVVM (Swift UI)
转载自:2020 Spring CS193p

Model 模型

UI Independent: e.g. 模型不会import SwiftUI
Data + logic: 模型封装了应用的数据和逻辑,其中逻辑是指数据的调用时机,数据的匹配等
“The Truth”: 模型的数据版本唯一

View 视图

Reflects the Model: 视图反应模型的内容
Stateless: 视图是无状态的,即它不需要任何state,因为它只是展示模型的state
Declared: 与imperative相比较,声明式vs命令式。声明式只需要声明控件,而命令式则不止需要生成控件,还需要安排和管理控件。声明式的好处在于不需要理解控件如何被创建
Reactive: 视图随模型的改变而改变

ViewModel 视图模型

Bind View to Model:绑定视图和模型,一旦模型发生改变,视图会得到反应
Interpreter: 解析模型数据
Process Intent: 视图改变模型的方法

View随Model改变

1、Model发生改变
2、Model发送消息给ViewModel
3、ViewModel再发送给View
4、View收到消息后
5、View从ViewModel拉取数据并重绘

View改变Model

1、View发送手势
2、View调用ViewModel的intent function
3、ViewModel修改Model

因为想要购买显示器,而自己完全是小白,因此做了一些新手入门指南

需求分析

①家用、办公、看电影、上网只主要侧重清晰度和流畅度就可以了,而且要求不高。

②如果是游戏娱乐主要侧重刷新率、响应时间等关乎流畅度的参数就可以了,要求较高。

③如果是平面设计、印刷行业、影视调色等主要侧重清晰度和色彩精度就可以了。

不管是办公、看电影、游戏、还是图形设计、剪辑调色,都要关注面板材质、尺寸、分辨率、显示器接口、支架等扩展功能。

PS:我的需求是进行代码开发,显示屏要求尺寸适中

屏幕面板

TN屏(延迟低,色彩差,便宜)
VA屏(延迟最高,色彩好,价格中等,曲面屏居多)
IPS(延迟较好,色彩最好,价格最贵)

PS:非游戏建议VA、IPS

分辨率和尺寸

1080p(1920X1080)适合24寸
2k(2560X1440)适合27寸
4k(3840X2160)适合32寸

PS:我选择27寸以上,2K以上

刷新率

60HZ(主流),75HZ,120HZ,144HZ(主流),165HZ,240HZ

PS:我选择非游戏60HZ

延迟(响应时间)

1ms,2ms,3ms,5ms

PS: 我选择非游戏5ms左右

曲率

显示器曲率为4000R、3000R的,有轻微的弯曲,沉浸感稍弱。
显示器曲率2000R、1800R的弯曲明显,是主流的曲面屏。
更好的1500R,沉浸感、包围感最强,是高级的曲面屏。

PS:我不需要曲率

支架

可旋转
可升降
PS:我的需求是可旋转

接口

VGA
HDMI
USB
雷电
Type-C

以下非制图、平面设计人员无需考虑

色域

现在普遍的是srgb,ntsc,dci-p3,adobe,不同显示器他宣传的不同,一般而言99%srgb就足够使,99%srgb≈72%ntsc,100%dci-p3≈90%ntsc,100%ntsc≈98%adobe,这么大体有个数

色深

有8bit,对应1670万色,和10bit,对应10.7亿色,一般而言8bit足够足够使,低价位的10bit显示器一般是8bit抖上去的

色准

就是那个△E,一般而言△E<3你的眼睛就识别不出来显示器颜色和现实颜色的差别了

亮度

一般显示器都小于350cd/m2,如果带hdr的话起码得400cd/m2以上

题目描述

编写一个 StockSpanner 类,它收集某些股票的每日报价,并返回该股票当日价格的跨度。

今天股票价格的跨度被定义为股票价格小于或等于今天价格的最大连续日数(从今天开始往回数,包括今天)。

例如,如果未来7天股票的价格是 [100, 80, 60, 70, 60, 75, 85],那么股票跨度将是 [1, 1, 1, 2, 1, 4, 6]。

示例:

输入:[“StockSpanner”,”next”,”next”,”next”,”next”,”next”,”next”,”next”], [[],[100],[80],[60],[70],[60],[75],[85]]
输出:[null,1,1,1,2,1,4,6]
解释:
首先,初始化 S = StockSpanner(),然后:
S.next(100) 被调用并返回 1,
S.next(80) 被调用并返回 1,
S.next(60) 被调用并返回 1,
S.next(70) 被调用并返回 2,
S.next(60) 被调用并返回 1,
S.next(75) 被调用并返回 4,
S.next(85) 被调用并返回 6。

注意 (例如) S.next(75) 返回 4,因为截至今天的最后 4 个价格
(包括今天的价格 75) 小于或等于今天的价格。

提示:

调用 StockSpanner.next(int price) 时,将有 1 <= price <= 10^5。
每个测试用例最多可以调用 10000 次 StockSpanner.next。
在所有测试用例中,最多调用 150000 次 StockSpanner.next。
此问题的总时间限制减少了 50%。

来源:力扣(LeetCode)
链接:https://leetcode-cn.com/problems/online-stock-span
著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。

解题思路

总思路:当前股价往前搜索,遇到往日股价更大的则停止

设计两个全局变量stack,用于存储股价 & 跨度
维护一个价格的单调栈
若当前股价 > 栈顶股价,则当前股价的跨度应加上栈顶股价的跨度

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
class StockSpanner {

var priceStack: [Int]
var spanStack: [Int]

init() {
priceStack = [Int]()
spanStack = [Int]()
}

func next(_ price: Int) -> Int {
var span = 1
while let lastPrice = priceStack.last, let lastSpan = spanStack.last, price >= lastPrice {
span += lastSpan
spanStack.removeLast()
priceStack.removeLast()
}

priceStack.append(price)
spanStack.append(span)
return span
}
}

/**
* Your StockSpanner object will be instantiated and called as such:
* let obj = StockSpanner()
* let ret_1: Int = obj.next(price)
*/

其他思路

https://www.bilibili.com/video/BV1kW411k77K?from=search&seid=15780837099295797199

题目描述

给定两个 没有重复元素 的数组 nums1 和 nums2 ,其中nums1 是 nums2 的子集。找到 nums1 中每个元素在 nums2 中的下一个比其大的值。

nums1 中数字 x 的下一个更大元素是指 x 在 nums2 中对应位置的右边的第一个比 x 大的元素。如果不存在,对应位置输出 -1 。

示例 1:

输入: nums1 = [4,1,2], nums2 = [1,3,4,2].
输出: [-1,3,-1]
解释:
对于num1中的数字4,你无法在第二个数组中找到下一个更大的数字,因此输出 -1。
对于num1中的数字1,第二个数组中数字1右边的下一个较大数字是 3。
对于num1中的数字2,第二个数组中没有下一个更大的数字,因此输出 -1。
示例 2:

输入: nums1 = [2,4], nums2 = [1,2,3,4].
输出: [3,-1]
解释:
对于 num1 中的数字 2 ,第二个数组中的下一个较大数字是 3 。
对于 num1 中的数字 4 ,第二个数组中没有下一个更大的数字,因此输出 -1 。

提示:

nums1和nums2中所有元素是唯一的。
nums1和nums2 的数组大小都不超过1000。

来源:力扣(LeetCode)
链接:https://leetcode-cn.com/problems/next-greater-element-i

解题思路

遍历num2,维护一个单调栈(单调递减栈)

若栈为空,或者当前元素小于或等于栈顶元素,则直接入栈
反之,若当前元素大于栈顶元素,则说明栈顶的下一大元素已经找到了,则将栈顶元素出栈。
⚠️ 单调栈中存的是索引

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
class Solution {

func nextGreaterElement(_ nums1: [Int], _ nums2: [Int]) -> [Int] {

var stack: [Int] = [] // Int是存数字在nums2的位置
var pairs: [Int: Int] = [:] // 存储元素&元素下一大元素
var result: [Int] = [] // 存储下一大元素

// 1、遍历nums2,找出每个数字的对应的下一大元素,并储存为键值对pairs
for now in 0 ..< nums2.count {

// 找到下一大元素时,遍历单调栈
while let last = stack.last, nums2[last] < nums2[now] {
let number = nums2[last]
let numberLastBig = nums2[now]
pairs[number] = numberLastBig
stack.removeLast()
}

stack.append(now)

}

// 2、遍历nums1,将nums1作为键,在pairs中寻找其对应的值
for num in nums1 {
let temp = pairs[num] ?? -1
result.append(temp)
}

return result

}
}

Root、父类和子类

一般,NSObject是我们所说的Root类
父类和子类的相关术语:superclass/parent class, subclass/child class
子类可以继承父类的非私有实例变量和方法
⚠️ 在父类的interface中申明,并且implementation中不申明,子类才能访问其实例变量
⚠️ 在父类的implementation中申明或合成的实例变量,子类无法直接访问;需要用自定义的getter或setter方法来访问

例子:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
@interface ClassA: NSObject {   
int x;
}
-(void) initVar;
@end

@implementation ClassA
-(void) initVar {
x = 100;
}
@end

@interface ClassB: ClassA // ClassB继承了ClassA
-(void) printVar;
@end

@implementation ClassB
-(void) printVar {
NSLog (@"x = %i", x); // print x 的值,它是在ClassA中申明,ClassB中未申明的
}
@end

OC寻找方法的机制

首先,检查对象所属的类,以查看是否在该类中使用特定名称显式地定义了方法。如果是,这就是我们使用的方法。如果没有定义,则选中父类。如果这里定义了方法,就用它。如果没有,搜索仍在继续。
父类会被检查,直到发生以下两种情况之一:要么找到包含指定方法的类,要么在返回根类之后没有找到该方法。如果第一个发生了,一切都准备好了;如果出现第二种情况,就会出现问题,并生成如下警告消息: warning: ‘ClassB’ may not respond to ‘-inity’

通过继承扩展

点类和对象分配

测试

@class 指令

拥有对象的类

override方法

抽象类

题目描述

请根据每日气温 列表,重新生成一个列表。对应位置的输出为:要想观测到更高的气温,至少需要等待的天数。如果气温在这之后都不会升高,请在该位置用 0 来代替。

例如,给定一个列表 temperatures = [73, 74, 75, 71, 69, 72, 76, 73],你的输出应该是 [1, 1, 4, 2, 1, 1, 0, 0]。

提示:气温 列表长度的范围是 [1, 30000]。每个气温的值的均为华氏度,都是在 [30, 100] 范围内的整数。

来源:力扣(LeetCode)
链接:https://leetcode-cn.com/problems/daily-temperatures

解题思路(不通过)

先遍历温度列表,在循环中再次遍历温度列表
判断当日温度后若干天的温度,是否比当日温度高
是则插入waitDays,不是则寻找下一个
若内遍历结束仍没有,则waitDays插入0

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
class Solution {
func dailyTemperatures(_ T: [Int]) -> [Int] {

var waitDays = [Int]()
var temperatures = T

for t in temperatures {

// 遍历第一天以外的温度
temperatures.removeFirst()
var hasHigherTemp = false
for index in temperatures.indices {
let otherT = temperatures[index]
if (otherT > t) {
hasHigherTemp = true
waitDays.append(index+1)
break
}

}

// 判断是否有更高温度
if (!hasHigherTemp) {
waitDays.append(0)
}
}

return waitDays

}
}

问题分析:
时间复杂度:O(n^2)

算法优化(单调栈)

遍历每日温度,维护一个单调栈(单调递减栈)

若栈为空,或者当日温度小于或等于栈顶温度,则直接入栈
反之,若当日温度大于栈顶温度,则说明栈顶的升温日已经找到了,则将栈顶元素出栈,计算其与当日温度差即可
⚠️ 题目要求的是升温的天数,因此栈中应该存下标而不是温度

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
class Solution {
func dailyTemperatures(_ T: [Int]) -> [Int] {

var waitDays: [Int] = Array(repeating: 0, count: T.count)
var stack: [Int] = []

for now in 0 ..< T.count {

// 当栈不为空 && 当日温度>栈顶温度,即找到index的升温日,则将栈顶温度移除且储存对应升温天数
while let last = stack.last, T[now] > T[last] {
stack.removeLast()
waitDays[last] = now - last // 下标差
}

// 当栈为空 || 当日温度<栈顶温度,入栈
stack.append(now)

}

return waitDays

}
}

时间复杂度:O(n)
空间复杂度:O(n)

官方解释

https://www.bilibili.com/video/BV1ov411z7rM?from=search&seid=11178657533565450143

分离Interface和Implementation文件

section 文件后缀 import
类申明(@interface section) class.h import <Foundation/Foundation.h>
类实施(@implementation section) class.m import “class.h” (引入local file)
主程序(main program section) main.m import “class.h” (只需要@interface就够了)

可以把 @interface 看作 public information
可以把@implementation 看作 private information

合成访问方法

自动生成setter和getter方法
在interface中申明:@property + 实例变量名
在implementation中自动合成:@synthesize + 实例变量名 (现在不需要再申明实例变量了)
⚠️ 若在implementation中不使用@synthesize,系统会自动生成带有_的实例变量,如_numerator

使用点操作符访问属性

之前学习过访问实例的某个方法如下:
[instance property]

现在也可以用点操作符表示
instance.property

原本的[instance setProperty: value] 等价于 instance.property = value

多个参数的方法

@interface

1
-(void) setTo: (int) n over: (int) d;

@implementation

1
2
3
4
-(void) setTo: (int) n over: (int) d {
numerator = n;
denominator = d;
}

program section

1
[aFraction setTo: 100 over: 200]

无参数名方法:外部参数名都可省略,如:
- (int) set: (int) n: (int) d;
[aFraction set:1 :3]
但不推荐,因为这样可读性很差

static 关键字

static关键字声明的变量必须放在implementation外面,或者方法中,如果不为它赋值默认为0,它只在程序开机初始化一次,之后会储存维护一个值

声明后的static静态变量在其他类中是不能通过类名直接访问的,它的作用域只能是在声明的这个.m文件中

举例:

1
2
3
4
5
6
7
8
9
@implementation MyClass

+ (void)addCount {
static NSInteger count = 100;
count ++;
NSLog(@"static value is %ld", (long)count);
}

@end

其输出结果为:

1
2
3
4
5
2015-12-07 17:41:33.397 StaticDemo[80984:2870442] static value is 101
2015-12-07 17:41:33.398 StaticDemo[80984:2870442] static value is 102
2015-12-07 17:41:33.398 StaticDemo[80984:2870442] static value is 103
2015-12-07 17:41:33.398 StaticDemo[80984:2870442] static value is 104
2015-12-07 17:41:33.398 StaticDemo[80984:2870442] static value is 105

count的值是会变化的,而不是一直输出101

self 关键字

本章中未详细介绍,自行百度了相关学习内容:
https://www.jianshu.com/p/1adc99ab62c2

题目描述

判断一个整数是否是回文数。回文数是指正序(从左向右)和倒序(从右向左)读都是一样的整数。

示例 1:

输入: 121
输出: true

示例 2:

输入: -121
输出: false
解释: 从左向右读, 为 -121 。 从右向左读, 为 121- 。因此它不是一个回文数。

示例 3:

输入: 10
输出: false
解释: 从右向左读, 为 01 。因此它不是一个回文数。

进阶:

你能不将整数转为字符串来解决这个问题吗?

来源:力扣(LeetCode)
链接:https://leetcode-cn.com/problems/palindrome-number

解题思路

个人思路:将整型数字转为字符串,再逆转字符串后,每个字符一一比较
解决方案如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
class Solution {
func isPalindrome(_ x: Int) -> Bool {

let number = String(x)
let number_reversed = String(number.reversed())
var isPalindrome = true

for index in number.indices {
if (number[index] != number_reversed[index]) {
isPalindrome = false
}
}

return isPalindrome

}
}

算法问题:虽然能够解决问题,但遍历了整个字符串,时间复杂度为O(n)

算法优化

基本思路:将数字本身反转
特殊限制:
1、数字大小限制:<0的数不满足回文,0到9的数字直接满足回文,>=10的数需有分偶数位和奇数位
2、内存限制:int.Max=2^31-1=2147483647->7463847421,反转整个数字会超过整型内存限制,因此只转一半
(对于非负数x,num为偶数位,则迭代终止条件为num=revertedNumber;若x为奇数位,中间的数字转完即结束,因此迭代终止条件为num<revertedNumber)
主要思路:
1、判断是否转了一半数字(利用上面的迭代终止条件num<=revertedNumber)
1、取余拿到最低位
2、整除拿到除最低位的其他高位
3、revertedNumber原本的位数升一位+最低位
4、其他高位变为num
5、继续迭代

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
class Solution {
func isPalindrome(_ x: Int) -> Bool {
var num = x;
var revertedNumber = 0;

// x 为非负数,或x大于等于10时,最后一位数字为0肯定不是回文
if (x < 0 || (x >= 10 && x % 10 == 0)) {
return false;
}

// 数字反转
while (revertedNumber < num) {
let onesPlace = num % 10;
let tensPlace = num / 10;
revertedNumber = revertedNumber * 10 + onesPlace;
num = tensPlace;
}

// 满足(偶数位相同条件 || 奇数位相同条件)则是回文
return (num == revertedNumber) || (num == (revertedNumber / 10));
}
}

if 语句

1
2
3
4
5
6
7
8
9
10
11
int main (int argc, char * argv[]) { 
@autoreleasepool {
int number;
NSLog (@"Type in your number: ");
scanf ("%i", &number);
if ( number < 0 )
number = -number;
NSLog (@"The absolute value is %i", number);
}
return 0;
}

switch 语句

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
int main (int argc, char * argv[]) {   
@autoreleasepool {
double value1, value2;
char operator;
Calculator *deskCalc = [[Calculator alloc] init];

NSLog (@"Type in your expression.");
scanf ("%lf %c %lf", &value1, &operator, &value2);
[deskCalc setAccumulator: value1];

switch ( operator ) {
case '+':
[deskCalc add: value2];
break;
case '-':
[deskCalc subtract: value2];
break;
case '*':
[deskCalc multiply: value2];
break;
case '/':
[deskCalc divide: value2];
break;
default:
NSLog (@"Unknown operator.");
break;
}
NSLog (@"%.2f", [deskCalc accumulator]);
}
return 0;
}

Boolean变量

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
int main (int argc, char * argv[]) {   
@autoreleasepool {
int p, d, isPrime;

for ( p = 2; p <= 50; ++p ) {
isPrime = 1;
for ( d = 2; d < p; ++d )
if ( p % d == 0 )
isPrime = 0;
if ( isPrime != 0 )
NSLog (@"%i ", p);
}
}
return 0;
}

条件操作符

condition ? expression1 : expression2

特殊扩展
condition ?: expression
如果condition是true,那么value是condition
如果condition是false,那么value是expression

for语句

1
2
3
4
5
6
7
8
9
10
11
int main (int argc, char * argv[]) {    
@autoreleasepool {
int n, triangularNumber;
triangularNumber = 0;
for ( n = 1; n <= 200; n = n + 1 )
triangularNumber += n;

NSLog (@"The 200th triangular number is %i", triangularNumber);
}
return 0;
}

其中
for ( n = 1; n <= 200; n = n + 1 )
也可以改为
for ( n = 1; n <= 200; ++n)

while语句

1
2
3
4
5
6
7
8
9
10
11
int main (int argc, char * argv[]) {   
@autoreleasepool {
int count = 1;

while ( count <= 5 ) {
NSLog (@"%i", count);
++count;
}
}
return 0;
}

do语句

1
2
3
4
5
6
7
8
9
10
11
12
13
14
int main (int argc, char * argv[]) {   
@autoreleasepool {
int number, right_digit;
NSLog (@"Enter your number.");
scanf ("%i", &number);
do {
right_digit = number % 10;
NSLog (@"%i", right_digit);
number /= 10;
}
while ( number != 0 );
}
return 0;
}

break语句

终止循环,离开整个循环嵌套

continue语句

离开当前循环,继续下一个循环