yytmzys / blog Goto Github PK
View Code? Open in Web Editor NEW我的博客
License: MIT License
我的博客
License: MIT License
复制文件内容, 修改三项
master改为main, 因为新建的工程仓库默认是主分支名字main
env:
GITHUB_NAME: qq
GITHUB_EMAIL: [email protected]
UIKit
// 函数的定义与调用
func greet(person: String) -> String {
let greeting = "Hello, " + person + "!"
return greeting
}
print(greet(person: "Anna"))
// 为了简化这个函数的定义, 可以将问候消息的创建和返回写成一句
func greetAgain(person: String) -> String {
return "Hello, again, " + person + "!"
}
print(greetAgain(person: "Anna"))
// 函数参数与返回值
// 无参数函数
func sayHelloWorld() -> String {
return "Hello, World!"
}
print(sayHelloWorld())
// 多参数函数, 函数可以有多种输入参数, 包含在函数括号中, 以逗号分隔
func greet2(person: String, alreadyGreeted: Bool) -> String {
if alreadyGreeted {
return greetAgain(person: person)
} else {
return greet(person: person)
}
}
print(greet2(person: "Tim", alreadyGreeted: true))
// 无返回值函数, 函数可以没有返回值, 不需要返回箭头 -> 和返回类型
// 严格上来说, 虽然没有返回值被定义, greet2(person:) 函数依然返回了值. 没有定义返回值类型的函数会返回一个特殊的Void值. 它其实是一个空的元祖 touple, 没意义任何元素, 可以写成 ()
func greet3(person: String) {
print("Hello, \(person)!")
}
greet3(person: "Dave")
// 被调用时, 一个函数的返回值可以被忽略
func printAndCount(string: String) -> Int {
print(string)
return string.count
}
func pringWithoutCounting(string: String) {
let _ = printAndCount(string: string)
}
printAndCount(string: "Hello, World!") // 打印, 并返回值
pringWithoutCounting(string: "Hello, World!")
// 多重返回值函数
func minMax(array: [Int]) -> (min: Int, max: Int) {
var currentMin = array[0]
var currentMax = array[0]
for value in array[1..<array.count] {
if value < currentMin {
currentMin = value
} else if value > currentMax {
currentMax = value
}
}
return(currentMin, currentMax)
}
let bounds = minMax(array: [8, -9, 2, 109, 2, 76])
print("min is \(bounds.min) and max is \(bounds.max)")
// 可选元祖返回类型,
func minMax2(array: [Int]) -> (min: Int, max: Int)? {
if array .isEmpty { return nil }
var currentMin = array[0]
var currentMax = array[0]
for value in array[1..<array.count] {
if value < currentMin {
currentMin = value
} else if value > currentMax {
currentMax = value
}
}
return(currentMin, currentMax)
}
let bounds2 = minMax2(array: [8, -9, 2, 109, 2, 76])
print("min is \(bounds2?.min) and max is \(bounds2?.max)")
// 函数参数标签和参数名称, 你可以在参数名称前指定它的参数标签, 中间以空格分割,
// 参数标签的使用能够让一个函数在调用时更有表达力, 更类似自然语言, 并且扔保持了函数内部的可读性以及清晰的意图
func greet4(person: String, from hometown: String) -> String {
return "Hello \(person)! Glad you could visit from \(hometown)."
}
print(greet4(person: "Bill", from: "Cupertino"))
// Hello Bill! Glad you could visit from Cupertino.
// 忽略函数标签, 如果你不希望为某个参数添加一个标签, 可以使用一个下划线 _ 来代替一个明确的参数标签,
// 如果一个参数有一个标签, 那么在调用的时候必须用标签来标记这个参数
func greet5(_ person: String, from hometown:String) -> String {
return "Hello \(person)! Glad you could visit from \(hometown)."
}
print(greet5("Bill", from: "Cupertino"))
// 默认参数值 , 你可以在函数体中通过给参数值来为任意一个参数定义默认值, 当默认值被定以后, 调用这个函数时就可以忽略这个参数
func greet6(person: String, hometown: String = "earth") -> (String){
return "Hello \(person)! Glad you could visit from \(hometown)."
}
print(greet6(person: "Bill"))
// Hello Bill! Glad you could visit from earth.
// 可变参数, 可以接受零个或多个值. 函数调用时, 你可以用可变参数来指定函数参数可以被传入不确定数量的输入值. 通过在变量类型名后面加入 ... 的方式来定义可变参数
// 可变参数的传入值在函数体重变为此类型的一个数组. 例如, 一个叫做numbers 的 Double... 型可变参数, 在函数体内可以当做一个叫numbers 的 [Double]型的数组常量
func arithmeticMean(_ numbers: Double...) -> Double {
var total: Double = 0
for number in numbers {
total += number
}
return total / Double( numbers.count )
}
print(arithmeticMean(1, 2, 3, 4, 5)) // 3.0
print(arithmeticMean(1, 2, 3, 4, 5, 7)) // 3.66666666666667
// 输入输出参数
func swapTwoInts(_ a: inout Int, _ b: inout Int ) -> (Int, Int) {
let temp = a
a = b
b = temp
return (a, b)
}
var a = 3
var b = 5
print(swapTwoInts(&a, &b)) // (5, 3)
// 函数类型. 每个函数都有种特定的函数类型, 函数的类型都由函数的参数类型和返回值类型组成
func addTwoInts(_ a: Int, _ b: Int) -> Int{
return a + b
}
func multiplyTwoInts(_ a: Int, _ b: Int) -> Int {
return a * b
}
// 这两个函数的类型是 (Int, Int) -> Int, 可以解读为" 这个函数类型有两个 Int 型的参数并返回一个 Int 型的值".
// 使用函数类型, 在Swift中, 使用函数类型就像使用其他类型一样.
var mathFunction: (Int, Int) -> Int = addTwoInts
// 这段代码可以理解为: 定义一个叫做 mathFunction的常量, 类型是'一个有两个Int型的参数并返回一个Int型的值得函数', 并让这个新变量指向 addTwoInts 函数, addTwoInts 和 mathFunction 有同样的类型, 所以这个赋值 在 Swi 类型检查(type-check) 中是允许的
print(mathFunction( 2, 3)) // 5
// 有相同匹配类型的不同函数可以被赋值给同一个变量,
mathFunction = multiplyTwoInts
print(mathFunction( 2, 3)) // 6
// 函数类型作为参数类型, 你可以用 (Int, Int) -> Int 这样的函数类型最为另一个函数的参数类型. 这样可以将函数的一部分实现留给函数的调用者来提供
func printMathResult(_ mathFunction: (Int, Int) -> Int, a: Int, _ b: Int) {
print("Result: \(mathFunction(a, b))")
}
printMathResult(addTwoInts, a: 2, 3) // Result: 8
// 函数类型作为返回类型, 你可以用函数类型作为另一个函数的返回类型, 你需要做的就是在返回箭头 -> 后写一个完整的函数类型
func stepForward(_ input: Int) -> Int{
return input + 1
}
func stepBackward(_ input: Int) -> Int {
return input - 1
}
// 如下名为chooseStepFunction的函数, 它的返回值类型是 (Int) -> int 类型的函数
func chooseStepFunction(backward: Bool) -> (Int) -> Int {
return backward ? stepForward : stepBackward
}
var currentValue = 3
var moveNearerToZero = chooseStepFunction(backward: currentValue > 5)
// moveNearerToZero 现在指向 stepBackward
while currentValue != 0 {
print("\(currentValue)")
currentValue = moveNearerToZero(currentValue)
}
print("zero!")
// 嵌套函数, 可以把函数定义在别的函数体中. 默认情况下, 嵌套函数对外界是不可见的, 但是可以被它们的外围函数(enclosing function) 调用. 一个外围函数也可以返回它的某一个嵌套函数, 使得这个函数可以在其他领域中被使用
func chooseStepFunction2(backward: Bool) -> (Int) -> Int {
func stepForward(input: Int) -> Int { return input + 1}
func stepBackward(input: Int) -> Int { return input - 1}
return backward ? stepForward : stepBackward
}
currentValue = -4
moveNearerToZero = chooseStepFunction2(backward: currentValue < 0)
// moveNearerToZero 指向 stepForward
while currentValue != 0 {
print("\(currentValue)")
currentValue = moveNearerToZero(currentValue)
}
print("zero!")
1 非逃逸闭包(Non-Escaping Closure):
func performOperation(completion: () -> Void) {
print("Performing operation...")
completion()
}
performOperation {
print("Operation completed.")
}
2 逃逸闭包(Escaping Closure):
var completionHandlers: [() -> Void] = []
func appendCompletionHandler(handler: @escaping () -> Void) {
completionHandlers.append(handler)
}
func performOperation(completion: () -> Void) {
print("Performing operation...")
completionHandlers.append(completion)
}
performOperation {
print("Operation completed.")
}
completionHandlers.forEach { $0() }
3 自动闭包(Autoclosure):
func printAndRemove(element: @autoclosure () -> Int) {
print("Element:", element())
}
printAndRemove(element: 10)
这些示例展示了不同类型的闭包在 Objective-C 和 Swift 中的用法。全局块类似于 Swift 中的全局函数,而栈块和堆块用于捕获局部变量并在不同的作用域中使用。在 Swift 中,根据闭包是否允许逃逸,以及是否自动封装表达式,有更多的灵活性和功能。
UIKit
表格视图
我们可以为结构体定义属性(常量、变量)和添加方法,从而扩展结构体的功能。
与 C 和 Objective C 不同的是:
1 结构体不需要包含实现文件和接口。
2 结构体允许我们创建一个单一文件,且系统会自动生成面向其它代码的外部接口。
结构体总是通过被复制的方式在代码中传递,因此它的值是不可修改的。
语法
我们通过关键字 struct 来定义结构体:
UIKit
页面视图控制器
导航控制器
数据存储和持久化
变量、常量和数据类型 #3 (comment)
运算符 #3 (comment)
表达式 #3 (comment)
控制流程:if、switch、循环 #3 (comment)
函数 #3 (comment)
闭包 #3 (comment)
类、结构体和枚举
属性和方法
协议和扩展
可选项(Optionals)
强制解包和可选项绑定
错误处理(Error Handling)
数组(Array)
字典(Dictionary)
集合(Set)
高阶函数
map、filter、reduce
尾随闭包
Grand Central Dispatch(GCD)
Operation 和 OperationQueue
异步编程
引用类型和值类型
自动引用计数(ARC)
内存管理模式面向对象编程:
继承和多态
初始化器和反初始化器
访问控制
单例模式
工厂模式
观察者模式等
SwiftUI 和 UIKit
视图控制器和视图
自定义视图和界面布局
网络请求和 URLSession
JSON 解析和编码
数据持久化:Core Data、UserDefaults
单元测试和 UI 测试
断言和调试技巧
Instruments 工具
MVC、MVVM、VIPER
依赖注入、装饰器等
Xcode 使用技巧
Swift Package Manager
调试器和性能优化
Swift 语言的版本更新和变化
iOS中,有GCD、NSOperation、NSThread等几种多线程技术方案。
而GCD共有三种队列类型:
main queue:通过dispatch_get_main_queue()获得,这是一个与主线程相关的串行队列。
global queue:全局队列是并发队列,由整个进程共享。存在着高、中、低三种优先级的全局队列。调用dispath_get_global_queue并传入优先级来访问队列。
自定义队列:通过函数dispatch_queue_create创建的队列。
死锁就是队列引起的循环等待
- (void)viewDidLoad {
[super viewDidLoad];
dispatch_sync(dispatch_get_main_queue(), ^{
NSLog(@"deallock");
});
// Do any additional setup after loading the view, typically from a nib.
}
在主线程中运用主队列同步,也就是把任务放到了主线程的队列中。
同步对于任务是立刻执行的,那么当把任务放进主队列时,它就会立马执行,只有执行完这个任务,viewDidLoad才会继续向下执行。
而viewDidLoad和任务都是在主队列上的,由于队列的先进先出原则,任务又需等待viewDidLoad执行完毕后才能继续执行,viewDidLoad和这个任务就形成了相互循环等待,就造成了死锁。
想避免这种死锁,可以将同步改成异步dispatch_async,或者将dispatch_get_main_queue换成其他串行或并行队列,都可以解决。
dispatch_queue_t serialQueue = dispatch_queue_create("test", DISPATCH_QUEUE_SERIAL);
dispatch_async(serialQueue, ^{
dispatch_sync(serialQueue, ^{
NSLog(@"deadlock");
});
});
外面的函数无论是同步还是异步都会造成死锁。
这是因为里面的任务和外面的任务都在同一个serialQueue队列内,又是同步,这就和上边主队列同步的例子一样造成了死锁
解决方法也和上边一样,将里面的同步改成异步dispatch_async,或者将serialQueue换成其他串行或并行队列,都可以解决
dispatch_queue_t serialQueue = dispatch_queue_create("test", DISPATCH_QUEUE_SERIAL);
dispatch_queue_t serialQueue2 = dispatch_queue_create("test", DISPATCH_QUEUE_SERIAL);
dispatch_async(serialQueue, ^{
dispatch_sync(serialQueue2, ^{
NSLog(@"deadlock");
});
});
这样是不会死锁的,并且serialQueue和serialQueue2是在同一个线程中的。
dispatch_queue_t serialQueue = dispatch_queue_create("test", DISPATCH_QUEUE_SERIAL);
NSLog(@"1");
dispatch_async(serialQueue, ^{
NSLog(@"2");
});
NSLog(@"3");
dispatch_sync(serialQueue, ^{
NSLog(@"4");
});
NSLog(@"5");
打印顺序是13245
原因是:
首先先打印1
接下来将任务2其添加至串行队列上,由于任务2是异步,不会阻塞线程,继续向下执行,打印3
然后是任务4,将任务4添加至串行队列上,因为任务4和任务2在同一串行队列,根据队列先进先出原则,任务4必须等任务2执行后才能执行,又因为任务4是同步任务,会阻塞线程,只有执行完任务4才能继续向下执行打印5
所以最终顺序就是13245。
这里的任务4在主线程中执行,而任务2在子线程中执行。
如果任务4是添加到另一个串行队列或者并行队列,则任务2和任务4无序执行(可以添加多个任务看效果)
dispatch_async(dispatch_get_global_queue(0, 0), ^{
[self performSelector:@selector(test:) withObject:nil afterDelay:0];
});
这里的test方法是不会去执行的,原因在于
- (void)performSelector:(SEL)aSelector withObject:(nullable id)anArgument afterDelay:(NSTimeInterval)delay;
这个方法要创建提交任务到runloop上的,而gcd底层创建的线程是默认没有开启对应runloop的,所有这个方法就会失效。
而如果将dispatch_get_global_queue改成主队列,由于主队列所在的主线程是默认开启了runloop的,就会去执行(将dispatch_async改成同步,因为同步是在当前线程执行,那么如果当前线程是主线程,test方法也是会去执行的)。
多读单写的意思就是:可以多个读者同时读取数据,而在读的时候,不能去写入数据。并且,在写的过程中,不能有其他写者去写。即读者之间是并发的,写者与读者或其他写者是互斥的。
这里的写处理就是通过栅栏的形式去写。
就可以用**dispatch_barrier_sync(栅栏函数)**去实现
dispatch_queue_t concurrentQueue = dispatch_queue_create("test", DISPATCH_QUEUE_CONCURRENT);
for (NSInteger i = 0; i < 10; i++) {
dispatch_sync(concurrentQueue, ^{
NSLog(@"%zd",i);
});
}
dispatch_barrier_sync(concurrentQueue, ^{
NSLog(@"barrier");
});
for (NSInteger i = 10; i < 20; i++) {
dispatch_sync(concurrentQueue, ^{
NSLog(@"%zd",i);
});
}
这里的dispatch_barrier_sync上的队列要和需要阻塞的任务在同一队列上,否则是无效的。
从打印上看,任务0-9和任务任务10-19因为是异步并发的原因,彼此是无序的。而由于栅栏函数的存在,导致顺序必然是先执行任务0-9,再执行栅栏函数,再去执行任务10-19。
- (id)readDataForKey:(NSString *)key
{
__block id result;
dispatch_sync(_concurrentQueue, ^{
result = [self valueForKey:key];
});
return result;
}
- (void)writeData:(id)data forKey:(NSString *)key
{
dispatch_barrier_async(_concurrentQueue, ^{
[self setValue:data forKey:key];
});
}
场景:在n个耗时并发任务都完成后,再去执行接下来的任务。比如,在n个网络请求完成后去刷新UI页面。
dispatch_queue_t concurrentQueue = dispatch_queue_create("test1", DISPATCH_QUEUE_CONCURRENT);
dispatch_group_t group = dispatch_group_create();
for (NSInteger i = 0; i < 10; i++) {
dispatch_group_async(group, concurrentQueue, ^{
sleep(1);
NSLog(@"%zd:网络请求",i);
});
}
dispatch_group_notify(group, dispatch_get_main_queue(), ^{
NSLog(@"刷新页面");
});
GCD 中的信号量是指 Dispatch Semaphore,是持有计数的信号。
Dispatch Semaphore 提供了三个函数
Dispatch Semaphore 在实际开发中主要用于:
dispatch_semaphore_t semaphore = dispatch_semaphore_create(0);
__block NSInteger number = 0;
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
number = 100;
dispatch_semaphore_signal(semaphore);
});
dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
NSLog(@"semaphore---end,number = %zd",number);
dispatch_semaphore_wait加锁阻塞了当前线程,dispatch_semaphore_signal解锁后当前线程继续执行
在线程安全中可以将dispatch_semaphore_wait看作加锁,而dispatch_semaphore_signal看作解锁
首先创建全局变量
_semaphore = dispatch_semaphore_create(1);
注意到这里的初始化信号量是1。
- (void)asyncTask
{
dispatch_semaphore_wait(_semaphore, DISPATCH_TIME_FOREVER);
count++;
sleep(1);
NSLog(@"执行任务:%zd",count);
dispatch_semaphore_signal(_semaphore);
}
异步并发调用asyncTask
for (NSInteger i = 0; i < 100; i++) {
dispatch_async(dispatch_get_global_queue(0, 0), ^{
[self asyncTask];
});
}
然后发现打印是从任务1顺序执行到100,没有发生两个任务同时执行的情况。
原因如下:
在子线程中并发执行asyncTask,那么第一个添加到并发队列里的,会将信号量减1,此时信号量等于0,可以执行接下来的任务。而并发队列中其他任务,由于此时信号量不等于0,必须等当前正在执行的任务执行完毕后调用dispatch_semaphore_signal将信号量加1,才可以继续执行接下来的任务,以此类推,从而达到线程加锁的目的。
dispatch_after能让我们添加进队列的任务延时执行,该函数并不是在指定时间后执行处理,而只是在指定时间追加处理到dispatch_queue
//第一个参数是time,第二个参数是dispatch_queue,第三个参数是要执行的block
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
NSLog(@"dispatch_after");
});
由于其内部使用的是dispatch_time_t管理时间,而不是NSTimer。
所以如果在子线程中调用,相比performSelector:afterDelay,不用关心runloop是否开启
+ (instancetype)shareInstance {
static dispatch_once_t onceToken;
static id instance = nil;
dispatch_once(&onceToken, ^{
instance = [[self alloc] init];
});
return instance;
}
Alamofire 或其他网络库
UIKit
UserDefaults
网络通信和数据交互
协议和扩展
后台任务和异步操作
Size Classes
Swift 占位
一、HTTPS和HTTP的区别
HTTPS协议 = HTTP协议 + SSL/TLS协议
SSL的全称是Secure Sockets Layer,即安全套接层协议,是为网络通信提供安全及数据完整性的一种安全协议。TLS的全称是Transport Layer Security,即安全传输层协议。
即HTTPS是安全的HTTP。
二、HTTPS的连接建立流程
HTTPS为了兼顾安全与效率,同时使用了对称加密和非对称加密。在传输的过程中会涉及到三个密钥:
服务器端的公钥和私钥,用来进行非对称加密
客户端生成的随机密钥,用来进行对称加密
如上图,HTTPS连接过程大致可分为八步:
1、客户端访问HTTPS连接。
客户端会把安全协议版本号、客户端支持的加密算法列表、随机数C发给服务端。
2、服务端发送证书给客户端
服务端接收密钥算法配件后,会和自己支持的加密算法列表进行比对,如果不符合,则断开连接。否则,服务端会在该算法列表中,选择一种对称算法(如AES)、一种公钥算法(如具有特定秘钥长度的RSA)和一种MAC算法发给客户端。
服务器端有一个密钥对,即公钥和私钥,是用来进行非对称加密使用的,服务器端保存着私钥,不能将其泄露,公钥可以发送给任何人。
在发送加密算法的同时还会把数字证书和随机数S发送给客户端
3、客户端验证server证书
会对server公钥进行检查,验证其合法性,如果发现发现公钥有问题,那么HTTPS传输就无法继续。
4、客户端组装会话秘钥
如果公钥合格,那么客户端会用服务器公钥来生成一个前主秘钥(Pre-Master Secret,PMS),并通过该前主秘钥和随机数C、S来组装成会话秘钥
5、客户端将前主秘钥加密发送给服务端
是通过服务端的公钥来对前主秘钥进行非对称加密,发送给服务端
6、服务端通过私钥解密得到前主秘钥
服务端接收到加密信息后,用私钥解密得到主秘钥。
7、服务端组装会话秘钥
服务端通过前主秘钥和随机数C、S来组装会话秘钥。
至此,服务端和客户端都已经知道了用于此次会话的主秘钥。
8、数据传输
客户端收到服务器发送来的密文,用客户端密钥对其进行对称解密,得到服务器发送的数据。
同理,服务端收到客户端发送来的密文,用服务端密钥对其进行对称解密,得到客户端发送的数据。
总结:
会话秘钥 = random S + random C + 前主秘钥
HTTPS连接建立过程使用非对称加密,而非对称加密是很耗时的一种加密方式
后续通信过程使用对称加密,减少耗时所带来的性能损耗
其中,对称加密加密的是实际的数据,非对称加密加密的是对称加密所需要的客户端的密钥。
三、对称加密和非对称加密
1、对称加密
用同一套密钥来进行加密解密。
对称加密通常有 DES,IDEA,3DES 加密算法。
2、非对称加密
用公钥和私钥来加解密的算法。
公钥(Public Key)与私钥(Private Key)是通过一种算法得到的一个密钥对(即一个公钥和一个私钥),公钥是密钥对中公开的部分,私钥则是非公开的部分,私钥通常是保存在本地。
用公钥进行加密,就要用私钥进行解密;反之,用私钥加密,就要用公钥进行解密(数字签名)。
由于私钥是保存在本地的,所以非对称加密相对与对称加密是安全的。
但非对称加密比对称加密耗时(100倍以上),所以通常要结合对称加密来使用。
常见的非对称加密算法有:RSA、ECC(移动设备用)、Diffie-Hellman、El Gamal、DSA(数字签名用)
而为了确保客户端能够确认公钥就是想要访问的网站的公钥,引入了数字证书的概念,由于证书存在一级一级的签发过程,所以就出现了证书链,在证书链中的顶端的就是根CA
标签栏控制器
SQLite数据库
Interface Builder (IB)
Swift 占位
使用ASP.NET/PHP/JSP 或者javascript都会用到http的不同状态,一些常见的状态码为:
status code | 状态描述 |
---|---|
200 | 服务器成功返回网页 |
404 | 请求的网页不存在 |
503 | 服务不可用 |
status code | 状态 | 描述 |
---|---|---|
100 | 继续 | 请求者应当继续提出请求。 服务器返回此代码表示已收到请求的第一部分,正在等待其余部分 |
101 | 切换协议 | 请求者已要求服务器切换协议,服务器已确认并准备切换。 |
102 | 扩展码 | 处理将被继续执行。 |
status code | 状态 | 描述 |
---|---|---|
200 | 成功 | 服务器已成功处理了请求。 |
201 | 已创建 | 请求成功并且服务器创建了新的资源。 |
202 | 已接受 | 服务器已接受请求,但尚未处理。 |
203 | 非授权信息 | 服务器已成功处理了请求,但返回的信息可能来自另一来源。 |
204 | 无内容 | 服务器成功处理了请求,但没有返回任何内容。 |
205 | 重置内容 | 服务器成功处理了请求,但没有返回任何内容。 |
206 | 部分内容 | 服务器成功处理了部分 GET 请求。 |
207 | 状态码 | 代表之后的消息体将是一个XML消息,并且可能依照之前子请求数量的不同,包含一系列独立的响应代码 |
status code | 状态 | 描述 |
---|---|---|
300 | 多种选择 | 针对请求,服务器可执行多种操作。 |
301 | 永久移动 | 请求的网页已永久移动到新位置。 |
302 | 临时移动 | 请求的资源现在临时从不同的URI响应请求。由于这样的重定向是临时的,客户端应当继续向原有地址发送以后的请求。。 |
303 | 查看其他位置 | 对应当前请求的响应可以在另一个URI上被找到,而且客户端应当采用GET的方式访问那个资源。 |
304 | 未修改 | 304响应禁止包含消息体,因此始终以消息头后的第一个空行结尾。 |
305 | 使用代理 | 被请求的资源必须通过指定的代理才能被访问。 |
306 | 无 | 在最新版的规范中,306状态码已经不再被使用。 |
307 | 临时重定向 | 请求的资源现在临时从不同的URI响应请求 |
这类的状态码代表了客户端看起来可能发生了错误,妨碍了服务器的处理。)
status code | 状态 | 描述 |
---|---|---|
400 | 语法错误 | 由于包含语法错误,当前请求无法被服务器理解。 |
401 | 验证错误 | 当前请求需要用户验证。 |
402 | 该状态码是为了将来可能的需求而预留的。 | |
403 | 拒绝 | 服务器已经理解请求,但是拒绝执行它。。 |
404 | 未发现 | 请求失败,请求所希望得到的资源未被在服务器上发现。 |
405 | 请求行中指定的请求方法不能被用于请求相应的资源。 | |
406 | 请求的资源的内容特性无法满足请求头中的条件,因而无法生成响应实体。 | |
407 | 验证 | 与401响应类似,只不过客户端必须在代理服务器上进行身份验证。 |
408 | 超时 | 请求超时。 |
409 | 无法完成 | 由于和被请求的资源的当前状态之间存在冲突,请求无法完成。 |
410 | 响应的目的主要是帮助网站管理员维护网站,通知用户该资源已经不再可用,并且服务器拥有者希望所有指向这个资源的远端连接也被删除。 | |
411 | 无定义 | 服务器拒绝在没有定义Content-Length头的情况下接受请求。 |
412 | 不满足 | 服务器在验证在请求的头字段中给出先决条件时,没能满足其中的一个或多个。 |
413 | 拒绝 | 服务器拒绝处理当前请求,因为该请求提交的实体数据大小超过了服务器愿意或者能够处理的范围。 |
414 | URI 过长 | 请求的URI长度超过了服务器能够解释的长度,因此服务器拒绝对该请求提供服务。 |
415 | 不支持的媒体类型 | 请求的格式不受请求页面的支持。 |
416 | 请求范围不符合要求 | 如果页面无法提供请求的范围,则服务器会返回此状态代码。 |
417 | 未满足期望值 | 服务器未满足”期望”请求标头字段的要求。 |
Size Classes
弹出视图
图片选择器
code | url |
---|---|
SwiftUI基础 | #85 |
文本视图 Text, Label, TextField, TextEditor, SecureField | #86 |
图片 Image AsyncImage | #87 |
按钮-Button | |
导航 Link, NavigationLink | https://www.jianshu.com/p/74ed753feb40 |
TabView | #90 |
选择器 Picker | https://www.jianshu.com/p/4b9365d5bed5 |
开关 指示器 计数器-Toggle ProgressView Stepper | #92 |
弹窗, Alert, ActionSheet | https://www.jianshu.com/p/c4edd212f59d |
地图 Map | https://www.jianshu.com/p/bc25595e59f1?v=1690899147140 |
视图和布局 | |
HStack VStack ZStack | #99 |
List, ScrollView, LazyVGrid, Form, Spacer, Divider, NavigationView, TabView | https://www.jianshu.com/p/9f6383144133 |
数据绑定和状态管理 | |
自定义视图和组件 | |
SwiftUI与UIKit混编 |
code | url |
---|---|
Swift语法和基础 | #3 |
函数和闭包 | #4 |
类和结构体 | #5 |
枚举 | #6 |
协议和扩展 | #7 |
泛型 | #8 |
错误处理 | #9 |
code | 链接 |
---|---|
视图控制器(UIViewController) | |
视图(UIView) | |
控件(UIButton、UILabel、UITextField等) | |
表格视图(UITableView) | |
集合视图(UICollectionView) | |
导航控制器(UINavigationController) | |
标签栏控制器(UITabBarController) | |
页面视图控制器(UIPageViewController) | |
弹出视图(UIAlertController) | |
图片选择器(UIImagePickerController) | |
等等... |
code | 链接 |
---|---|
AutoLayout | https://www.jianshu.com/p/e5ca657d6495?v=1690895203958 |
Size Classes | |
Size Classes | |
Interface Builder (IB) | |
Storyboards 和 XIBs |
code | 链接url |
---|---|
进程 线程 多线程 任务 队列 | https://www.jianshu.com/p/afef02f0e4cc |
GCD | https://www.jianshu.com/p/5a2b1ae59b5e |
NSOperation 和 NSOperationQueue | https://www.jianshu.com/p/b77ddeb5e1eb?v=1690973440713 |
后台任务和异步操作 |
code | 链接 |
---|---|
Core Data | |
SQLite数据库 | |
UserDefaults | |
文件系统操作 |
code | 链接 |
---|---|
URLSession | |
Alamofire 或其他网络库 | |
HTTPS、对称加密、非对称加密 | https://www.jianshu.com/p/2d024ca4baf2?v=1690981970791 |
JSON解析和序列化 | |
JSON解析和序列化 | |
HTTP协议常见错误码 | https://www.jianshu.com/p/959318890a4e |
code | 链接 |
---|---|
用户交互和事件处理 | |
手势识别器(Gesture Recognizers) |
code | 链接 |
---|---|
Core Graphics | |
Core Image | |
图片和视频处理 | |
音频播放和录制 |
code | 链接 |
---|---|
Core Location | |
MapKit |
code | 链接 |
---|---|
远程推送通知 | #61 |
本地通知 | #62 |
code | 链接 |
---|---|
AVFoundation(多媒体处理) | #63 |
Core Animation(动画) | |
Core Bluetooth(蓝牙通信) | |
Core Motion(传感器数据) | #66 |
Core NFC(近场通信) | #67 |
Core Spotlight(搜索功能) | #68 |
Core Telephony(电话和网络信息) | #69 |
code | 链接 |
---|---|
单元测试(Unit Testing) | #76 |
UI测试(UI Testing) | #77 |
调试技巧和性能优化 | #78 |
code | 链接 |
---|---|
应用打包和签名 | #79 |
App Store上架和审核 | #80 |
企业分发 | 企业分发 |
code | 链接 |
---|---|
相机和相册访问 | #82 |
地理位置权限 | |
通知权限 | |
蓝牙和NFC权限 |
视图控制器
打开mac的活动监视器,可以看到很多个进程同时运行
1.同一时间,CPU只能处理1条线程,只有1条线程在执行。多线程并发执行,其实是CPU快速地在多条线程之间调度(切换)。如果CPU调度线程的时间足够快,就造成了多线程并发执行的假象
2.如果线程非常非常多,CPU会在N多线程之间调度,消耗大量的CPU资源,每条线程被调度执行的频次会降低(线程的执行效率降低)
3.多线程的优点:
能适当提高程序的执行效率
能适当提高资源利用率(CPU、内存利用率)
4.多线程的缺点:
开启线程需要占用一定的内存空间(默认情况下,主线程占用1M,子线程占用512KB),如果开启大量的线程,会占用大量的内存空间,降低程序的性能
线程越多,CPU在调度线程上的开销就越大
程序设计更加复杂:比如线程之间的通信、多线程的数据共享
就是执行操作的意思,也就是在线程中执行的那段代码。在 GCD 中是放在 block 中的。执行任务有两种方式:同步执行(sync)和异步执行(async)
队列(Dispatch Queue):这里的队列指执行任务的等待队列,即用来存放任务的队列。队列是一种特殊的线性表,采用 FIFO(先进先出)的原则,即新任务总是被插入到队列的末尾,而读取任务的时候总是从队列的头部开始读取。每读取一个任务,则从队列中释放一个任务
在 GCD 中有两种队列:串行队列和并发队列。两者都符合 FIFO(先进先出)的原则。两者的主要区别是:执行顺序不同,以及开启线程数不同。
串行队列(Serial Dispatch Queue):
同一时间内,队列中只能执行一个任务,只有当前的任务执行完成之后,才能执行下一个任务。(只开启一个线程,一个任务执行完毕后,再执行下一个任务)。主队列是主线程上的一个串行队列,是系统自动为我们创建的
并发队列(Concurrent Dispatch Queue):
同时允许多个任务并发执行。(可以开启多个线程,并且同时执行任务)。并发队列的并发功能只有在异步(dispatch_async)函数下才有效
是我们自己手动开辟的子线程,如果使用的是初始化方式就需要我们自己启动,如果使用的是构造器方式它就会自动启动。只要是我们手动开辟的线程,都需要我们自己管理该线程,不只是启动,还有该线程使用完毕后的资源回收
NSThread *thread = [[NSThread alloc] initWithTarget:self selector:@selector(testThread:) object:@"我是参数"];
// 当使用初始化方法出来的主线程需要start启动
[thread start];
// 可以为开辟的子线程起名字
thread.name = @"NSThread线程";
// 调整Thread的权限 线程权限的范围值为0 ~ 1 。越大权限越高,先执行的概率就会越高,由于是概率,所以并不能很准确的的实现我们想要的执行顺序,默认值是0.5
thread.threadPriority = 1;
// 取消当前已经启动的线程
[thread cancel];
// 通过遍历构造器开辟子线程
[NSThread detachNewThreadSelector:@selector(testThread:) toTarget:self withObject:@"构造器方式"];
//在当前线程。延迟1s执行。响应了OC语言的动态性:延迟到运行时才绑定方法
[self performSelector:@selector(aaa) withObject:nil afterDelay:1];
// 回到主线程。waitUntilDone:是否将该回调方法执行完在执行后面的代码,如果为YES:就必须等回调方法执行完成之后才能执行后面的代码,说白了就是阻塞当前的线程;如果是NO:就是不等回调方法结束,不会阻塞当前线程
[self performSelectorOnMainThread:@selector(aaa) withObject:nil waitUntilDone:YES];
//开辟子线程
[self performSelectorInBackground:@selector(aaa) withObject:nil];
//在指定线程执行
[self performSelector:@selector(aaa) onThread:[NSThread currentThread] withObject:nil waitUntilDone:YES]
需要注意的是:如果是带afterDelay的延时函数,会在内部创建一个 NSTimer,然后添加到当前线程的Runloop中。也就是如果当前线程没有开启runloop,该方法会失效。在子线程中,需要启动runloop(注意调用顺序)
[self performSelector:@selector(aaa) withObject:nil afterDelay:1];
[[NSRunLoop currentRunLoop] run];
而performSelector:withObject:只是一个单纯的消息发送,和时间没有一点关系。所以不需要添加到子线程的Runloop中也能执行
我们要明确NSOperationQueue与GCD之间的关系
GCD是面向底层的C语言的API,NSOpertaionQueue用GCD构建封装的,是GCD的高级抽象。
1、GCD执行效率更高,而且由于队列中执行的是由block构成的任务,这是一个轻量级的数据结构,写起来更方便
2、GCD只支持FIFO的队列,而NSOperationQueue可以通过设置最大并发数,设置优先级,添加依赖关系等调整执行顺序
3、NSOperationQueue甚至可以跨队列设置依赖关系,但是GCD只能通过设置串行队列,或者在队列内添加barrier(dispatch_barrier_async)任务,才能控制执行顺序,较为复杂
4、NSOperationQueue因为面向对象,所以支持KVO,可以监测operation是否正在执行(isExecuted)、是否结束(isFinished)、是否取消(isCanceld)
实际项目开发中,很多时候只是会用到异步操作,不会有特别复杂的线程关系管理,所以苹果推崇的且优化完善、运行快速的GCD是首选
如果考虑异步操作之间的事务性,顺序行,依赖关系,比如多线程并发下载,GCD需要自己写更多的代码来实现,而NSOperationQueue已经内建了这些支持
不论是GCD还是NSOperationQueue,我们接触的都是任务和队列,都没有直接接触到线程,事实上线程管理也的确不需要我们操心,系统对于线程的创建,调度管理和释放都做得很好。而NSThread需要我们自己去管理线程的生命周期,还要考虑线程同步、加锁问题,造成一些性能上的开销
造成一些性能上的开销
更多关于GCD:2019 iOS面试题-----多线程相关之GCD、死锁、dispatch_barrier_async、dispatch_group_async、Dispatch Semaphore
Core Data
Storyboards 和 XIBs
NSOperation、NSOperationQueue 是苹果提供给我们的一套多线程解决方案。实际上 NSOperation、NSOperationQueue 是基于 GCD 更高一层的封装,完全面向对象。但是比 GCD 更简单易用、代码可读性也更高。
如果只是重写NSOperation的main方法,由底层控制变更任务执行及完成状态,以及任务退出
如果重写了NSOperation的start方法,自行控制任务状态
系统通过KVO的方式移除isFinished==YES的NSOperation
执行操作的意思,换句话说就是你在线程中执行的那段代码。
在 GCD 中是放在 block 中的。在 NSOperation 中,使用 NSOperation 子类 NSInvocationOperation、NSBlockOperation,或者自定义子类来封装操作。
这里的队列指操作队列,即用来存放操作的队列。不同于 GCD 中的调度队列 FIFO(先进先出)的原则。NSOperationQueue 对于添加到队列中的操作,首先进入准备就绪的状态(就绪状态取决于操作之间的依赖关系),然后进入就绪状态的操作的开始执行顺序(非结束执行顺序)由操作之间相对的优先级决定(优先级是操作对象自身的属性)。
操作队列通过设置最大并发操作数(maxConcurrentOperationCount)来控制并发、串行。
NSOperationQueue 为我们提供了两种不同类型的队列:主队列和自定义队列。主队列运行在主线程之上,而自定义队列在后台执行。
NSThread在实际开发中比较常用到的场景就是去实现常驻线程。
首先常驻线程既然是常驻,那么我们可以用GCD实现一个单例来保存NSThread
+ (NSThread *)shareThread {
static NSThread *shareThread = nil;
static dispatch_once_t oncePredicate;
dispatch_once(&oncePredicate, ^{
shareThread = [[NSThread alloc] initWithTarget:self selector:@selector(threadTest) object:nil];
[shareThread setName:@"threadTest"];
[shareThread start];
});
return shareThread;
}
这样创建的thread就不会销毁了吗?
[self performSelector:@selector(test) onThread:[ViewController shareThread] withObject:nil waitUntilDone:NO];
- (void)test
{
NSLog(@"test:%@", [NSThread currentThread]);
}
并没有打印,说明test方法没有被调用。
那么可以用runloop来让线程常驻
+ (NSThread *)shareThread {
static NSThread *shareThread = nil;
static dispatch_once_t oncePredicate;
dispatch_once(&oncePredicate, ^{
shareThread = [[NSThread alloc] initWithTarget:self selector:@selector(threadTest2) object:nil];
[shareThread setName:@"threadTest"];
[shareThread start];
});
return shareThread;
}
+ (void)threadTest
{
@autoreleasepool {
NSRunLoop *runLoop = [NSRunLoop currentRunLoop];
[runLoop addPort:[NSMachPort port] forMode:NSDefaultRunLoopMode];
[runLoop run];
}
}
这时候再去调用performSelector就有打印了。
是一种用于保护多线程共享资源的锁,与一般互斥锁(mutex)不同之处在于当自旋锁尝试获取锁时以忙等待(busy waiting)的形式不断地循环检查锁是否可用。当上一个线程的任务没有执行完毕的时候(被锁住),那么下一个线程会一直等待(不会睡眠),当上一个线程的任务执行完毕,下一个线程会立即执行。
在多CPU的环境中,对持有锁较短的程序来说,使用自旋锁代替一般的互斥锁往往能够提高程序的性能。
当上一个线程的任务没有执行完毕的时候(被锁住),那么下一个线程会进入睡眠状态等待任务执行完毕,当上一个线程的任务执行完毕,下一个线程会自动唤醒然后执行任务。
自旋锁会忙等: 所谓忙等,即在访问被锁资源时,调用者线程不会休眠,而是不停循环在那里,直到被锁资源释放锁。
互斥锁会休眠: 所谓休眠,即在访问被锁资源时,调用者线程会休眠,此时cpu可以调度其他线程工作。直到被锁资源释放锁。此时会唤醒休眠线程。
自旋锁的优点在于,因为自旋锁不会引起调用者睡眠,所以不会进行线程调度,CPU时间片轮转等耗时操作。所有如果能在很短的时间内获得锁,自旋锁的效率远高于互斥锁。
缺点在于,自旋锁一直占用CPU,他在未获得锁的情况下,一直运行--自旋,所以占用着CPU,如果不能在很短的时 间内获得锁,这无疑会使CPU效率降低。自旋锁不能实现递归调用。
HTTP协议常见错误码
错误处理
集合视图
数据存储和持久化
文件系统操作
枚举
网络通信 URLSession
Autolayout 是 iOS 开发中的自动布局系统,用于对视图进行灵活的布局。
Autolayout 使用约束(Constraints)来描述视图之间的关系,包括视图的位置、大小和间距等。通过添加约束,实现视图的自适应布局。
Autolayout 在设备旋转、屏幕尺寸发生变化变化时,会自动重新计算视图的布局,调整位置和大小,确保视图正确呈现。
网络通信和数据交互
手势识别器
泛型
控件
视图
Swift 占位
用户交互和事件处理
A declarative, efficient, and flexible JavaScript library for building user interfaces.
🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
An Open Source Machine Learning Framework for Everyone
The Web framework for perfectionists with deadlines.
A PHP framework for web artisans
Bring data to life with SVG, Canvas and HTML. 📊📈🎉
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
Some thing interesting about web. New door for the world.
A server is a program made to process requests and deliver data to clients.
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
Some thing interesting about visualization, use data art
Some thing interesting about game, make everyone happy.
We are working to build community through open source technology. NB: members must have two-factor auth.
Open source projects and samples from Microsoft.
Google ❤️ Open Source for everyone.
Alibaba Open Source for everyone
Data-Driven Documents codes.
China tencent open source team.