iOS基础

RunLoop

runloop是一个事件循环对象

do {

//接受消息->等待->处理

}while(message != quit)

  • 概念:事件循环对象,在循环过程中处理各种事件(点击、刷新等),从而保持程序持续运行;在没有事件处理的时候,会进入睡眠模式,从而节省CPU资源,提高程序性能。
  • 为什么需要:一个线程只能执行一个任务,执行完就会退出,如果我们需要一种机制,让线程能随时处理时间但并不退出,那么 RunLoop 就是这样的一个机制。Runloop是事件接收和分发机制的一个实现。
  • Runloop 和线程是绑定在一起的。每个线程(包括主线程)都有一个对应的 Runloop 对象。我们并不能自己创建 Runloop 对象,但是可以获取到系统提供的 Runloop 对象。

是否接触TableView渲染性能相关的东西?

  • cell的数目,配置tableview数据

    重用单元格的形式,数据成千上万行,最终渲染个数为屏幕上显示的数目。

    往下拉的时候,最上面的cell到最下面来,放置重复渲染,提高手机性能。

  • 刷新页面的两种方法

    • 无动画效果:tableView.reloadData(),就是相当于执行 cell for row的方法,将结果取出来,再更新视图
    • 将刷新语句放在View.beginUpdates()、tableView.endUpdates()中间,可以提高app的性能

事件处理

触摸屏幕、晃动设备、通过遥控设施控制设备。对应的事件类型有以下三种:

  1. 触屏事件(Touch Event)
  2. 运动事件(Motion Event)
  3. 远端控制事件(Remote-Control Event)

响应者链

当发生事件响应时,必须知道由谁来响应事件。在 iOS 中,由响应者链来对事件进行响应。

所有事件响应的类都是 UIResponder 的子类,响应者链是一个由不同对象组成的层次结构,其中的每个对象将依次获得响应事件消息的机会。

1
First Responser --> The Window --> The Application --> nil(丢弃)

我们可以通过 [responder nextResponder] 找到当前 responder 的下一个 responder,持续这个过程到最后会找到 UIApplication 对象。

通常情况下,我们在 First Responder (一般也就是用户当前触控的 View )这里就会响应请求,进入下面的事件分发机制。

事件分发

第一响应者(First responder)指的是当前接受触摸的响应者对象(通常是一个 UIView 对象),即表示当前该对象正在与用户交互,它是响应者链的开端。响应者链和事件分发的使命都是找出第一响应者。

  • iOS 系统检测到手指触摸 (Touch) 操作时会将其打包成一个 UIEvent 对象,并放入当前活动 Application 的事件队列,
  • 单例的 UIApplication 会从事件队列中取出触摸事件并传递给单例的 UIWindow 来处理,
  • UIWindow 对象首先会使用 hitTest:withEvent:方法寻找此次 Touch 操作初始点所在的视图(View),即需要将触摸事件传递给其处理的视图,这个过程称之为 hit-test view。

内存管理机制:ARC自动引用计数\MRC

创建的时候为1,使用的时候+1,不需要的时候-1,为0的时候系统知道其不需要了,则release。

class和struct

相同点

  • 可以定义存储属性
  • 能够定义方法
  • 通过下标操作访问实例所包含的值
  • 通过扩展来增加默认实现功能

不同点

  • 类可以继承
  • 类型转换允许在运行时检查和解释一个类实例的类型
  • 析构器允许一个类实例释放任何它所被分配的资源
  • 引用计数允许一个类的多次引用
  • class是引用类型,struct是值类型
  • struct会自动生成一个初始化所有属性的构造器

多线程

谈及 iOS 中的多线程,一般说的是 pthread,NSthread,GCD,NSOperation 这四种, 用的最多也最方便的就是 GCD 了。

四、线程安全问题

当多个线程访问同一块资源时,很容易引发数据错乱和数据安全问题。就好比几个人在同一时修改同一个表格,造成数据的错乱。

解决多线程安全问题的方法

  • 方法一:互斥锁(同步锁)
1
`@synchronized(锁对象) {``    ``// 需要锁定的代码``}`

加了互斥做的代码,当新线程访问时,如果发现其他线程正在执行锁定的代码,新线程就会进入休眠。

  • 方法二:自旋锁

加了自旋锁,当新线程访问代码时,如果发现有其他线程正在锁定代码,新线程会用死循环的方式,一直等待锁定的代码执行完成。相当于不停尝试执行代码,比较消耗性能。

3873004-d66485790959dcf5.png

GCD 中两个重要重要概念 —— 队列 & 任务

队列是一种特殊的线性表,采用FIFO(先进先出)的原则,队列的主要作用是用来存放任务。

GCD会自动将队列中的任务取出,放到对应的线程中执行。

串行队列(Serial Dispatch Queue): 让任务一个接着一个地执行(一个任务执行完毕后,再执行下一个任务

并发队列(Concurrent Dispatch Queue): 可以让多个任务并发(同时)执行(自动开启多个线程同时执行任务), 并发功能只有在异步(dispatch_async)函数下才有效

队列执行任务的方式:

  • 同步:在当前线程中执行,当前代码不执行完,就不能够执行下一条代码。会阻塞当前线程。
  • 异步:在另一条线程中执行(不用等待当前代码执行完,就能够执行下一条),不会阻塞当前线程。

  • 主队列 (串行)

1
let mainQueue = DispatchQueue.main
  • 全局队列 (并发)
1
let globalQueue = DispatchQueue.global()

全局队列默认是并发队列,在不进行第三方框架或者大型的商业应用开发,全局队列基本够用。

  • 主队列 dispatch_main_queue(); 串行 ,更新UI
  • 全局队列 dispatch_global_queue(); 并行,四个优先级: background,low,default,high
  • 自定义队列 dispatch_queue_t queue; 可以自定义是并行: DISPATCH_QUEUE_CONCURRENT 或者串行 DISPATCH_QUEUE_SERIAL

同步不开异步开,串行开1条,并行开多条。

队列中的任务同步执行,队列就不具有开启线程的能力, 队列中的任务异步执行,队列就具有开启线程的能力。
(同步和异步执行决定的是否有开启线程的能力)

如果队列具 有开启线程的能力 (队列任务异步执行) 且队列是 串行队列 ,那么将会 开启 1 条线程
如果队列具 有开启线程的能力 (队列任务异步执行) 且队列是 并发队列 ,那么将会 开启 多 条线程 。开启线程的条数由 GCD 决定。

2.1 全局队列

全局队列是获取的,不是程序员创建的。
为了方便 GCD 的使用,apple 默认为我们提供的。
全局队列默认是并发队列,在不是进行第三方框架或者大型的商业应用开发,全局队列基本够用。

全局 ( 并发 ) 队列异步执行 :

并发队列异步(不阻塞当前线程)执行(队列就具有开启线程的能力), 队列会开启多条线程。

1
2
3
4
5
6
7
8
任务异步执行不会阻塞当前线程, 
befor 在最前,
after 在任意位置,
task 执行顺序不确定 —— 并发执行(index可以确认)。
task 并发执行 —— 并发执行(number可以确认)。

异步开线程 number 可以确定开启了多条线程
开的线程数由 GCD 决定。 可以看到线程的 number 有重复,是 GCD 对线程进行了复用。
1
2
3
4
5
6
7
8
9
10
11
12
func async() {
print("DispatchQueue.global().async: befor", Thread.current)
// 全局队列进行 10次异步
for index in 0..<10 {
DispatchQueue.global().async {
print("DispatchQueue.global().async: task:(taskIndex:\(index)", Thread.current)
}
}
print("DispatchQueue.global().async: after", Thread.current)
}

打印:

全局 ( 并发 ) 队列同步执行 :

并发队列同步(阻塞当前线程)执行(队列就不具有开启线程的能力), 队列不会开启线程(代码都在主线程中执行)。

1
2
3
4
5
任务同步执行会阻塞当前线程, 
befor 在最前,
after 在最后,
task 执行顺序确定 —— 阻塞。
同步没有开启线程 number 可以确定没有开启多条线程。所有的代码都在 主线程中执行。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
func sync() {
print("DispatchQueue.global().sync: befor", Thread.current)
for index in 0..<10 {
DispatchQueue.global().sync {
print("DispatchQueue.global().sync: task:(taskIndex:\(index))", Thread.current)
}
}
print("DispatchQueue.global().sync: after", Thread.current)
}


打印:
DispatchQueue.global().sync: befor <NSThread: 0x60800007fa80>{number = 1, name = main}
DispatchQueue.global().sync: task:(taskIndex:0) <NSThread: 0x60800007fa80>{number = 1, name = main}
DispatchQueue.global().sync: task:(taskIndex:1) <NSThread: 0x60800007fa80>{number = 1, name = main}
DispatchQueue.global().sync: task:(taskIndex:2) <NSThread: 0x60800007fa80>{number = 1, name = main}
DispatchQueue.global().sync: task:(taskIndex:3) <NSThread: 0x60800007fa80>{number = 1, name = main}
DispatchQueue.global().sync: task:(taskIndex:4) <NSThread: 0x60800007fa80>{number = 1, name = main}
DispatchQueue.global().sync: task:(taskIndex:5) <NSThread: 0x60800007fa80>{number = 1, name = main}
DispatchQueue.global().sync: task:(taskIndex:6) <NSThread: 0x60800007fa80>{number = 1, name = main}
DispatchQueue.global().sync: task:(taskIndex:7) <NSThread: 0x60800007fa80>{number = 1, name = main}
DispatchQueue.global().sync: task:(taskIndex:8) <NSThread: 0x60800007fa80>{number = 1, name = main}
DispatchQueue.global().sync: task:(taskIndex:9) <NSThread: 0x60800007fa80>{number = 1, name = main}
DispatchQueue.global().sync: after <NSThread: 0x60800007fa80>{number = 1, name = main}

2.2 主队列

主队列是获取的,不是程序员创建的,apple 默认为我们提供的。
(app 开发中,所有的 UI 更新操作都应该在主线程中进行)

主队列(串行)异步执行

主队列异步(不会阻塞当前线程)执行(队列就具有开启线程的能力), 队列会开启线程(开启的线程就是主线程)。

1
2
3
4
5
6
7
8
9
10
11
> 有朋友问我,异步会开启线程, 主队列异步就不会开启线程。 
> 我当时还信以为真。认为自己错误,说特殊情况特殊处理。 其实说白了就是学艺不精。
> 由于主队列是我们获取的,不是我们创建的,在某种意识中会认为主线程不是在主队列中创建的。(认为一开始就存在的。)


> 任务异步执行不会阻塞当前线程,
befor 在最前,
after 在第二,
task 执行顺序确定 —— 串行执行(index可以确认)。

同步没有开启线程 number 可以确定没有开启多条线程。所有的代码都在 主线程中执行。

主队列异步的操作主要用在更新 UI 操作中。 具体参考 项目开发中 GCD 代码使用。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
func async() {

print("DispatchQueue.main.async: befor", Thread.current)
for index in 0..<10 {
DispatchQueue.main.async {
print("DispatchQueue.main.async: task:(taskIndex:\(index)", Thread.current)
}
}
print("DispatchQueue.main.async: after", Thread.current)
}

打印:
DispatchQueue.main.async: befor <NSThread: 0x60800006ddc0>{number = 1, name = main}
DispatchQueue.main.async: after <NSThread: 0x60800006ddc0>{number = 1, name = main}
DispatchQueue.main.async: task:(taskIndex:0 <NSThread: 0x60800006ddc0>{number = 1, name = main}
DispatchQueue.main.async: task:(taskIndex:1 <NSThread: 0x60800006ddc0>{number = 1, name = main}
DispatchQueue.main.async: task:(taskIndex:2 <NSThread: 0x60800006ddc0>{number = 1, name = main}
DispatchQueue.main.async: task:(taskIndex:3 <NSThread: 0x60800006ddc0>{number = 1, name = main}
DispatchQueue.main.async: task:(taskIndex:4 <NSThread: 0x60800006ddc0>{number = 1, name = main}
DispatchQueue.main.async: task:(taskIndex:5 <NSThread: 0x60800006ddc0>{number = 1, name = main}
DispatchQueue.main.async: task:(taskIndex:6 <NSThread: 0x60800006ddc0>{number = 1, name = main}
DispatchQueue.main.async: task:(taskIndex:7 <NSThread: 0x60800006ddc0>{number = 1, name = main}
DispatchQueue.main.async: task:(taskIndex:8 <NSThread: 0x60800006ddc0>{number = 1, name = main}
DispatchQueue.main.async: task:(taskIndex:9 <NSThread: 0x60800006ddc0>{number = 1, name = main}

主队列(串行)同步执行

执行的效果就俩字 死锁

主线程同步,在 Swift 中,编译阶段就报错,在 oc 中是在运行的时候才能发现。体现的主要是界面的 “假死”。

UITableView

要实现Tableview必须要签订2个协议

UITableViewDelegate:数据视图的普通协议,作用是:处理数据视图事件.
UITableViewDataSource:数据视图的数据代理协议,作用是:处理视图的数据代理.

实现签订的协议的2个协议方法:
返回数字,每个section有多少个row

返回cell,cell就是TableView显示时候的格子内容,根据indexPath和数据决定;

观察者设计模式、KVO

设计模式——观察者设计模式

A对B的变化感兴趣,就注册为B的观察者,当B发生变化时通知A,告知B发生了变化。

指定一个被观察对象(例如 A 类),当对象某个属性(例如 A 中的字符串 name)发生更改时,对象会获得通知,并作出相应处理;

在 MVC 设计架构下的项目,KVO 机制很适合实现 mode 模型和 view 视图之间的通讯。比如label中的对象为 num=0 ,我们按下按钮之后对象的num++,则label改变。

设计模式——通知机制

当用户从后台进入,从非活跃的状态进入活跃状态的时候,系统自动发送“becomeActive”通知,如果有注册监听者(观察者),则执行回调方法。【天气预报需要用】

网络

浏览器进行一次网络请求都需要哪些步骤?

  • 1、域名解析
    • 浏览器查找域名的 IP 地址、
    • 没找到后发送给 DNS 服务器(本地host,电信),让它解析为 IP 地址
  • 2、TCP的三次握手
  • 3、建立TCP连接后发起HTTP请求,浏览器向 web 服务器发送一个 HTTP 请求
    • HTTP请求格式:是请求方法(GET/POST/DELETE/PUT/HEAD)、URI路径、HTTP版本号。
    • 3. 2 服务器响应HTTP请求:状态行、响应头、空行、消息体。
  • 4、浏览器解析html代码,并请求html代码中的资源

三次握手

第三次握手是为了防止失效的连接请求到达服务器,让服务器错误打开连接。客户端发送的连接请求如果在网络中滞留,那么就会隔很长一段时间才能收到服务器端发回的连接确认。客户端等待一个超时重传时间之后,就会重新请求连接。但是这个滞留的连接请求最后还是会到达服务器,如果不进行三次握手,那么服务器就会打开两个连接。如果有第三次握手,客户端会忽略服务器之后发送的对滞留连接请求的连接确认,不进行第三次握手,因此就不会再次打开连接。