0%

我不是推广,我也是个学习者,推荐一个慕课网学习,我还专门写了一个笔记博客

iOS面试题大全—全方面剖析面试

  • UIView与CALayer
  • 事件传递与视图响应链
  • 图像显示原理
  • UI卡顿掉帧原因
  • 滑动优化方案
  • UI绘制原理
  • 离屏渲染

UIView与CALayer

<单一职责原则>
UIView为CALayer提供内容,以及负责处理触摸等事件,参与响应链
CALayer负责显示内容contents

事件传递与视图响应链

1
2
3
- (UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event;

- (BOOL)pointInside:(CGPoint)point withEvent:(UIEvent *)event;

如果事件一直传递到UIAppliction还是没处理,那就会忽略掉

图像显示原理

  1. CPU:输出位图
  2. GPU :图层渲染,纹理合成
  3. 把结果放到帧缓冲区(frame buffer)中
  4. 再由视频控制器根据vsync信号在指定时间之前去提取帧缓冲区的屏幕显示内容
  5. 显示到屏幕上

CPU工作

  1. Layout: UI布局,文本计算
  2. Display: 绘制
  3. Prepare: 图片解码
  4. Commit:提交位图

GPU渲染管线(OpenGL)

  • 顶点着色,图元装配,光栅化,片段着色,片段处理

UI卡顿掉帧原因

iOS设备的硬件时钟会发出Vsync(垂直同步信号),然后App的CPU会去计算屏幕要显示的内容,之后将计算好的内容提交到GPU去渲染。随后,GPU将渲染结果提交到帧缓冲区,等到下一个VSync到来时将缓冲区的帧显示到屏幕上。也就是说,一帧的显示是由CPU和GPU共同决定的。
一般来说,页面滑动流畅是60fps,也就是1s有60帧更新,即每隔16.7ms就要产生一帧画面,而如果CPU和GPU加起来的处理时间超过了16.7ms,就会造成掉帧甚至卡顿。

滑动优化方案

CPU:把以下操作放在子线程中

  1. 对象创建、调整、销毁
  2. 预排版(布局计算、文本计算、缓存高度等等)
  3. 预渲染(文本等异步绘制,图片解码等)
    GPU:
  • 纹理渲染,视图混合
  • 一般遇到性能问题时,考虑以下问题:
    • 是否受到CPU或者GPU的限制?
    • 是否有不必要的CPU渲染?
    • 是否有太多的离屏渲染操作?
    • 是否有太多的图层混合操作?
    • 是否有奇怪的图片格式或者尺寸?
    • 是否涉及到昂贵的view或者效果?
    • view的层次结构是否合理?

UI绘制原理

异步绘制:

  • [self.layer.delegate displayLayer: ]
  • 代理负责生成对应的bitmap
  • 设置该bitmap作为该layer.contents属性的值

离屏渲染

On-Screen Rendering:当前屏幕渲染,指的是GPU的渲染操作是在当前用于显示的屏幕缓冲区中进行
Off-Screen Rendering:离屏渲染,分为CPU离屏渲染和GPU离屏渲染两种形式。GPU离屏渲染指的是GPU在当前屏幕缓冲区外新开辟一个缓冲区进行渲染操作
应当尽量避免的则是GPU离屏渲染
GPU离屏渲染何时会触发呢?
圆角(当和maskToBounds一起使用时)、图层蒙版、阴影,设置

layer.shouldRasterize = YES

为什么要避免GPU离屏渲染?

  • GPU需要做额外的渲染操作。通常GPU在做渲染的时候是很快的,但是涉及到offscreen-render的时候情况就可能有些不同,因为需要额外开辟一个新的缓冲区进行渲染,然后绘制到当前屏幕的过程需要做onscreen跟offscreen上下文之间的切换,这个过程的消耗会比较昂贵,涉及到OpenGL的pipeline跟barrier,而且offscreen-render在每一帧都会涉及到,因此处理不当肯定会对性能产生一定的影响。另外由于离屏渲染会增加GPU的工作量,可能会导致CPU+GPU的处理时间超出16.7ms,导致掉帧卡顿。所以可以的话应尽量减少offscreen-render的图层

Runtime/RunLoop 面试请看我相关具体博客内容,不再累赘

暂停,不定期更新,看心情。。。。。

关于断点 objc_exception_throw 问题分析

通过bt打印出堆栈信息
例如:

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
thread #1, queue = 'com.apple.main-thread', stop reason = breakpoint 2.3
frame #0: 0x00000002232679c0 libobjc.A.dylib`objc_exception_throw
frame #1: 0x0000000223fabe10 CoreFoundation`-[NSException raise] + 12
frame #2: 0x0000000224a778fc Foundation`-[NSObject(NSKeyValueCoding) valueForUndefinedKey:] + 248
frame #3: 0x00000002249ebe1c Foundation`-[NSObject(NSKeyValueCoding) valueForKey:] + 260
frame #4: 0x0000000224a091a4 Foundation`-[NSObject(NSKeyValueCoding) setValue:forKeyPath:] + 256
frame #5: 0x00000002507d45ac UIKitCore`-[UINibKeyValuePair apply] + 72
frame #6: 0x0000000223f7be88 CoreFoundation`-[NSArray makeObjectsPerformSelector:] + 280
frame #7: 0x00000002507d3fec UIKitCore`-[UINib instantiateWithOwner:options:] + 1628
frame #8: 0x0000000250d1ef84 UIKitCore`-[UITableView _dequeueReusableViewOfType:withIdentifier:] + 592
frame #9: 0x000000010351f838 Gundam`-[UITableView(TestinUITableView) testin_DequeueReusableCellWithIdentifier:] + 112
frame #10: 0x0000000250d1f42c UIKitCore`-[UITableView _dequeueReusableCellWithIdentifier:forIndexPath:usingPresentationValues:] + 164
frame #11: 0x0000000250d1f358 UIKitCore`-[UITableView dequeueReusableCellWithIdentifier:forIndexPath:] + 96
frame #12: 0x000000010351f9b8 Gundam`-[UITableView(TestinUITableView) testin_DequeueReusableCellWithIdentifier:forIndexPath:] + 292
* frame #13: 0x000000010267cfe8 Gundam`-[DKLandlordEntrustViewController tableView:cellForRowAtIndexPath:](self=0x0000000155b0aa00, _cmd="tableView:cellForRowAtIndexPath:", tableView=0x000000015495fa00, indexPath=0xb59300120e671f5c) at DKLandlordEntrustViewController.m:220:33
frame #14: 0x0000000250d38618 UIKitCore`-[UITableView _createPreparedCellForGlobalRow:withIndexPath:willDisplay:] + 680
frame #15: 0x0000000250d38b18 UIKitCore`-[UITableView _createPreparedCellForGlobalRow:willDisplay:] + 80
frame #16: 0x0000000250d05320 UIKitCore`-[UITableView _updateVisibleCellsNow:isRecursive:] + 2260
frame #17: 0x0000000250d22640 UIKitCore`-[UITableView layoutSubviews] + 140
frame #18: 0x0000000250fb1170 UIKitCore`-[UIView(CALayerDelegate) layoutSublayersOfLayer:] + 1292
frame #19: 0x00000002285cbc60 QuartzCore`-[CALayer layoutSublayers] + 184
frame #20: 0x00000002285d0c08 QuartzCore`CA::Layer::layout_if_needed(CA::Transaction*) + 332
frame #21: 0x00000002285333e4 QuartzCore`CA::Context::commit_transaction(CA::Transaction*) + 348
frame #22: 0x0000000228561620 QuartzCore`CA::Transaction::commit() + 640
frame #23: 0x000000022856215c QuartzCore`CA::Transaction::observer_callback(__CFRunLoopObserver*, unsigned long, void*) + 92
frame #24: 0x000000022401fd08 CoreFoundation`__CFRUNLOOP_IS_CALLING_OUT_TO_AN_OBSERVER_CALLBACK_FUNCTION__ + 32
frame #25: 0x000000022401aa30 CoreFoundation`__CFRunLoopDoObservers + 412
frame #26: 0x000000022401afac CoreFoundation`__CFRunLoopRun + 1228
frame #27: 0x000000022401a7c0 CoreFoundation`CFRunLoopRunSpecific + 436
frame #28: 0x000000022621b79c GraphicsServices`GSEventRunModal + 104
frame #29: 0x0000000250b19c38 UIKitCore`UIApplicationMain + 212
frame #30: 0x00000001024cd140 Gundam`main(argc=1, argv=0x000000016dbc37f0) at main.m:15:16
frame #31: 0x0000000223ade8e0 libdyld.dylib`start + 4

然后通过单步调试,在汇编里面看到生成exception (creat)等等,最后定位到位置。

HttpHttps 的区别?为什么更加安全?

区别

  • 1.HTTPS 需要向机构申请 CA 证书,极少免费。
  • 2.HTTP 属于明文传输,HTTPS基于 SSL 进行加密传输。
  • 3.HTTP 端口号为 80HTTPS 端口号为 443
  • 4.HTTPS 是加密传输,有身份验证的环节,更加安全。

安全

SSL(安全套接层)
TLS(传输层安全)

以上两者在传输层之上,对网络连接进行加密处理,保障数据的完整性,更加的安全。

TCP/IP 五层模型的协议?

  • 应用层
  • 传输层
  • 网路层
  • 数据链路层
  • 物理层

OSI 七层模型的协议?

  • 应用层 文件传输,电子邮件,文件服务,虚拟终端 TFTP,HTTP,SNMP,FTP,SMTP,DNS,Telnet
  • 表示层 数据格式化,代码转换,数据加密 没有协议
  • 会话层 解除或建立与别的接点的联系 没有协议
  • 传输层 提供端对端的接口 TCP,UDP
  • 网络层 为数据包选择路由 IP,ICMP,RIP,OSPF,BGP,IGMP
  • 数据链路层 传输有地址的帧以及错误检测功能 SLIP,CSLIP,PPP,ARP,RARP,MTU
  • 物理层 以二进制数据形式在物理媒体上传输数据 ISO2110,IEEE802,IEEE802.2

断点续传 功能该怎么实现?

  • 1.利用 http 的头部字段 ,设置 httpRange 属性
  • 2.检测下载进度,并且判断是否需要重新创建下载文件

DNS 的解析过程?网络的 DNS 优化。

DNS 是域名到 IP 地址的映射,DNS 解析使用 UDP 数据报,端口号53,并且采用明文传输的方式

客户端在向服务端发送请求时,会先将 域名DNS 服务器映射出 IP 地址,然后再访问。

DNS 解析的两种方式

递归查询

不断地自下而上遍历解析,“我去给你问一下”的方式

迭代查询

迭代查询 是 “我告诉你谁可能知道”的方式

DNS 解析存在的问题

DNS 劫持

被钓鱼网站劫持,有可能返回错误的 IP,浏览的不是目标浏览器

DNS 解析转发

晓得运营商可能将 DNS 解析请求转发,解析的比较慢,效率低

DNS 劫持解决办法

httpDNS

使用 http 协议向 DNS 服务器 80 端口进行请求

长连接

找一个中间的长连 server ,在内网专线进行 Http 请求。客户端和这个长连 server通信即可。

Post请求体有哪些格式?

application/x-www-form-urlencoded

这应该是最常见的 POST 提交数据的方式了。浏览器的原生 form 表单,如果不设置 enctype 属性,那么最终就会以 application/x-www-form-urlencoded 方式提交数据

1
2
3
4
5
6
7
POST  HTTP/1.1
Host: www.demo.com
Cache-Control: no-cache
Postman-Token: 81d7b315-d4be-8ee8-1237-04f3976de032
Content-Type: application/x-www-form-urlencoded

key=value&testKey=testValue

multipart/form-data

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
POST  HTTP/1.1
Host: www.demo.com
Cache-Control: no-cache
Postman-Token: 679d816d-8757-14fd-57f2-fbc2518dddd9
Content-Type: multipart/form-data; boundary=----WebKitFormBoundary7MA4YWxkTrZu0gW

------WebKitFormBoundary7MA4YWxkTrZu0gW
Content-Disposition: form-data; name="key"

value
------WebKitFormBoundary7MA4YWxkTrZu0gW
Content-Disposition: form-data; name="testKey"

testValue
------WebKitFormBoundary7MA4YWxkTrZu0gW
Content-Disposition: form-data; name="imgFile"; filename="no-file"
Content-Type: application/octet-stream


<data in here>
------WebKitFormBoundary7MA4YWxkTrZu0gW--

application/json

text/xml

什么是 Mimetype ?

在浏览器中显示的内容有 HTML、有 XML、有 GIF、还有 Flash ……那么,浏览器是如何区分它们,决定什么内容用什么形式来显示呢?答案是 MIME Type,也就是该资源的媒体类型。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
//向该文件发送请求,根据请求头拿到该文件的MIMEType
-(NSString *)getMIMETypeURLRequestAtPath:(NSString*)path
{
//1.确定请求路径
NSURL *url = [NSURL fileURLWithPath:path];

//2.创建可变的请求对象
NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:url];

//3.发送请求
NSHTTPURLResponse *response = nil;
[NSURLConnection sendSynchronousRequest:request returningResponse:&response error:nil];

NSString *mimeType = response.MIMEType;
return mimeType;
}

网络请求的状态码都大致代表什么意思?

1.1xx

1xx 代表临时响应,需要请求者继续执行操作的状态代码。

2.2xx

2xx 代表的多是操作成功。

3.3xx

3xx 代表重定向,表示要完成请求,需要进一步操作

4.4xx

4xx 代表请求错误,表示请求可能出错,妨碍了服务器的处理。

5.5xx

5xx 代表服务器错误,表示服务器在尝试处理请求时发生内部错误。 这些错误可能是服务器本身的错误,而不是请求出错

如何判断一个请求是否结束?

  • 1.查看 Content-Length 是否达到 1024 字节。
  • 2.通过 Post 发送请求时,消息一般都是一段一段返回的,看最后的一个 chunked 是否为空?

SSL 传输协议?说一下 SSL 验证过程?

  • 1.配备了 身份证书,防止被冒充。
  • 2.有 校验机制,一旦被篡改,双方都会知道。
  • 3.通过 加密 进行传播。

连接过程如下:

  • ①:客户端向服务端发消息,包括客户端支持的 加密算法 以及 随机数1TLS 版本号。
  • ②:服务端向客户端返回信息,包括双方已经匹配好的 加密算法 以及 随机数2Server证书
  • ③:服务端向客户端返回 验证证书
  • ④:客户端进行证书验证,首先要评估信任证书
    • 1.服务端向客户端返回证书的 数字摘要 与服务端证书返回客户端,解密后的是否一致,防止被篡改。
    • 2.验证证书链:逐级验证服务端证书是否在可信任证书列表。
    • 3.验证通过后:
      • ①:将 随机数1随机数2、服务器端返回 证书公钥 加密过后的 预主秘钥 三者组合成 会话密钥
      • ②:服务端拿到 会话秘钥 后,会用服务端证书 私钥进行解密,得到 预主秘钥
      • ③:服务端将 随机数1随机数2、服务器端证书私钥解密得到的 预主秘钥 三者组合成 会话密钥。此时客户端的 会话秘钥 和 服务端的 会话秘钥 应该是一致的。
      • ④:接下来会经行验证:
        - 1.客户端通过 `会话秘钥`,加密信息发给服务端,看服务端能否成功接收。
        - 2.服务端通过 `会话秘钥`,加密信息返给客户端,看客户端能否成功解析。
        
    • 4.以上步骤都通过了,此时 SSL 验证连接。

解释一下 三次握手四次挥手

三次握手

  • 1.由客户端向服务端发送 SYN 同步报文。
  • 2.当服务端收到 SYN 同步报文之后,会返回给客户端 SYN 同步报文和 ACK 确认报文。
  • 3.客户端会向服务端发送 ACK 确认报文,此时客户端和服务端的连接正式建立。

建立连接

  • 1.这个时候客户端就可以通过 Http 请求报文,向服务端发送请求
  • 2.服务端接收到客户端的请求之后,向客户端回复 Http 响应报文。

四次挥手

当客户端和服务端的连接想要断开的时候,要经历四次挥手的过程,步骤如下:

  • 1.先由客户端向服务端发送 FIN 结束报文。
  • 2.服务端会返回给客户端 ACK 确认报文 。此时,由客户端发起的断开连接已经完成。
  • 3.服务端会发送给客户端 FIN 结束报文 和 ACK 确认报文。
  • 4.客户端会返回 ACK 确认报文到服务端,至此,由服务端方向的断开连接已经完成。

解释一下为什么是”三次握手”

客户端在与服务端建立连接的过程中,客户端向服务端发送连接请求,如果因为网络的原因,导致它超时触发了重传机制,那么客户端会重新向服务端发送一次连接请求,那有可能第二次发送的连接请求,服务端收到之后,直接返回给客户端确认连接的报文。

如果只需要两次就可以建立连接,假如说第一次建立连接的请求终于被服务端接收到了,那服务端收到之后,会以为客户端想新建另外一个链接,这样的话就会建立两个连接。

而采取三次握手,在客户端收到服务端的确认报文之后,客户端再向服务端发送一个确认报文,这时才正式建立连接。那么第一次因为超时而迟迟没有收到的建立连接的请求,这时在收到之后,服务端依然会向客户端返回一个确认报文,但是服务端现在已经与客户端建立的连接,所以客户端就不会再向服务端发送确认报文,服务端迟迟没有收到确认报文,那么也就不会真正的建立再一次连接。

又为什么是”四次挥手”?

建立的时候是双方面的建立,而释放的时候,也必然是客户端和服务端双方面的断开连接。

客户端与服务端建立的连接是双通的,这也就意味着客户端可以向服务端发送请求,服务端向客户端返回响应,同样的也可以从服务端向客户端发送,然后由客户端向服务端返回,如果你只关闭了客户端,向服务端通道,这只叫半关闭状态,并没有完全的切断客户端与服务端的关联。

因为 Http 无状态的特性,如果需要判断是哪个用户,这时就需要 CookieSession

Cookie 存储在客户端,用来记录用户状态,区分用户。一般都是服务端把生成的 Cookie 通过响应返回给客户端,客户端保存。

Session 存储在网络端,需要依赖 Cookie 机制。服务端生成了 Session 后,返回给客户端,客户端 setCookie:sessionID,所以下次请求的时候,客户端把 Cookie 发送给服务端,服务端解析出 SessionID,在服务端根据 SessionID 判断当前的用户。

  • 相同 Key 值得新的 Cookie 会覆盖旧的 Cookie.

  • 覆盖规则是 name path 和 domain 等需要与原有的一致

  • 相同 Key 值得新的 Cookie 会覆盖旧的 Cookie.

  • 覆盖规则是 name path 和 domain 等需要与原有的一致

  • 设置 Cookieexpires 为过去的一个时间点,或者 maxAge = 0

  • Cookie 进行加密处理
  • 只在 Https 上携带 Cookie
  • 设置 CookiehttpOnly,防止跨站脚本攻击。

Hybrid

为什么需要这种设计,或者技术方案,我们就不多说了,但是我还是要强调几点个人的见解

  • 最好不要大面积使用此方案,以免影响用户体验,最好别超过项目的30%。
  • 在某些页面变化比较大,而又不太需要性能的页面,采取此设计非常合适
  • 还有一个非常重要的,一定要注意,此交互方案在app内是异步线程,注意要回到主线程处理UI。安卓和iOS都是如此!!!

在安卓上面

我们有三种方案,分别是

  1. Native提供方法,js调用Native方法
  2. Native在本地注入js方法并且调用
  3. Webview拦截Ajax请求,并做业务处理

Native提供方法,js调用Native方法

  • native注入对象(有一系列供js使用的方法)以及提供给js调用的名称。
    1
    2
    webView.addJavascriptInterface(new HybridJsInterface(),"HybridJSInterface");

  • native提供可以供js调用的方法。
    1
    2
    3
    4
    5
    public class HybridJsInterface {
    @JavascriptInterface
    final public void hello(String text)
    Log.i("method hello","text="+text);
    }
  • js调用native的方法。
    1
    2
    3
    function hybrid(){
   
    window.HybridJSInterface.hello("hello hybrid");

    }

Native在本地注入js方法并且调用

1
2
3
4
5
6
7
8
9
10
11
//    function helloJs(){
// alert("hello js");
// }
String script = "function helloJs(){ alert('hello js');}";
//script是js方法对应的字符串
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
mWebContent.evaluateJavascript(script, null);
} else {
mWebContent.loadUrl("javascript:" + script);
}
}
1
2
3
4
5
6
String handle = "helloJs()";
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
mWebContent.evaluateJavascript(handle, null);
} else {
mWebContent.loadUrl("javascript:" + handle);
}

Webview拦截Ajax请求,并做业务处理

  1. native拦截h5发过来的请求协议(自定义协议)。
    webview位置拦截的WebViewClient对象。

    1
    webView.setWebViewClient(new HybridWebViewClient());

    HybridWebViewClient重写shouldOverrideUrlLoading方法

    1
    2
    3
    4
    5
    public boolean shouldOverrideUrlLoading(WebView view, String url) {Uri parse = Uri.parse(url);
    String path = parse.getPath();witch (path) {
    //TODO
    }
    }
  2. js那边发起请求协议。

方案分析

这三种方案单独使用都比较有局限性。

  • 方案一的思路是native提前给h5实现好h5需要使用的方法,方法调用完全由h5控制,缺陷是h5调用的功能实现都由native实现,当h5页面中需要新功能的时候需要修改native才能支持。

  • 方案二的思路是js提前给native提供方法,方法调用完全由native控制,缺陷是当js提供了新的方法那么需要修改native主动调用才能使用新功能。

  • 方案三的思路是native拦截h5发过了的请求进行分发控制,js需要使用的功能是native已经提前准备好的,使用不同的url进行分发来使用,缺陷是h5使用新的功能那么需要修改native提供新功能。

单独使用这三种方案的共同缺点就是js和native都是一个单向的调用,相互太过依赖。要是三种方案都用上是不是可以解决问题呢,其实根本问题不在这里,而在于这些方案都没有办法动态扩展,也就是说native一旦完成之后那么单纯靠js是很难完成功能扩展的。我们需要一种可以适应一定程度动态扩展的方案,那就让h5作为项目主导,native提供服务。

hybrid设计

总体设计思路是h5控制整个业务流程以及交互流程。h5那边负责发命令并且回调,native负责响应命令。我是采用方案三来实现,客户端需要做的就是预先处理好这些命令(url)。既然是响应命令那么首先就需要把和业务无关的命令给整理出来,比较通用的包括下面内容(不一定全):

  • header控制。

heade的样式可以参考新闻类app的详情页(这里不截图),包括内容:左边按钮(多个),右边按钮(多个),主标题,副标题。

需要做的控制是左、右按钮是否显示、显示的文本及图标以及点击按钮的回调,主、副标题是否显示及显示内容。

  • 页面刷新。

页面刷新用于内容改变之后h5主动通知native进行刷新。

  • 页面跳转。

页面跳转分成两种一种是页面跳转到一个新的native页面,另一种是在webview内部做跳转。native的跳转包括内容:跳转动画、跳转目标页、目标页需要的参数。

  • loadingview/progressbar。

通常情况建议直接使用h5的进度显示。loadingview的控制包括:loadingview是否显示,loadingview的显示样式(通常只有一种样式)。

  • 传感器数据。

传感器这部分不一定每个应用都需要。比如某些h5页面需要做活动,那么里面可能会用的摇一摇这样的功能。传感器的控制包括:地理位置、方向、震动、运动量。

  • h5离线包更新。

离线包的更新对于h5来说是一条更新命令。不过在native实现上面需要包括:离线包更新检查(版本比较)、离线包下载、离线包解压保存。

  • 离线包开关。

是否使用离线数据。native需要做的是开启离线包命令之后需要把请求的url映射到本地文件缓存。

  • 数据请求。

数据请求是指h5需要请求数据不通过直接网络访问,而是通过native自己的网络服务获取数据,尤其是在跨域的情况下很方便。

iOS上面

同样的,iOS也具备上述三个方案,分别是

  • JavaScriptCore
  • WebviewLoad
  • Url Schema

JavaScriptCore

在ios7后,Apple新增了一个JavaScriptCore让Native可以与H5更好的交互(Android早就有了),我们这里主要讨论js如何与Native通信,这里举一个简单的例子:
① 首先定义一个js方法,这里注意其中调用了一个没有声明的方法:

1
2
3
4
function printHello() {
//未声明方法
print("Hello, World!");
}

然后,上述未声明方法事实上是Native注入给window对象的:

1
2
3
4
5
6
7
8
9
10
11
12
NSString * scriptPath = [[NSBundle mainBundle] pathForResource:@"hello" ofType:@"js"];
NSString * scriptString = [NSString stringWithContentsOfFile:scriptPath encoding:NSUTF8StringEncoding error:nil];

JSContext *context = [[JSContext alloc] init];
[context evaluateScript:scriptString];

self.context[@"print"] = ^(NSString *text) {
NSLog(@"%@", text");
};

JSValue *function = self.context[@"printHello"];
[function callWithArguments:@[]];

这个样子,JavaScript就可以调用Native的方法了,这里Native需要注意方法注入的时机,一般是一旦载入页面便需要载入变量

  • 此方案需要特别注意的就是注入时机,要保证JS能调用到注入的方法

WebviewLoad

此方案和安卓一样

1
[self.wkWebView loadHTMLString:@"javascriptStr" baseURL:@"urlstr"];

Url Schema

H5与Native交互的桥梁为Webview,而“联系”的方式是以url schema的方式做的,在用户安装app后,app可以自定义url schema,并且把自定义的url注册在调度中心, 例如

  • dongfangdi://open 打开懂房帝App
  • weixin:// 打开微信

事实上Native能捕捉webview发出的一切请求,所以就算这里不是这种协议,Native也能捕捉,这个协议的意义在于可以在浏览器中直接打开APP,
我们在H5获取Native方法时一般是会构造一个这样的请求,使用iframe发出(设置location会有多次请求覆盖的问题):

1
2
3
4
5
6
7
8
9
10
11
requestHybrid({
//创建一个新的webview对话框窗口
tagname: 'dongfangdiApi',
//请求参数,会被Native使用
param: {},
//Native处理成功后回调前端的方法
callback: function (data) {
}
});
//=====>
dongfangdi://dongfangdi?callback=dongfangdiAction&param=%7B%22data1%22%3A1%2C%22data2%22%3A2%7D

总结

由于一开始我就强调过,不要完全依赖Hybrid来做app主要框架,它只是为我们提供更多的页面热更新,页面获取Native信息的一种技术。

  • 在iOS8.0以后,WebKit框架又给我们提供了一套方案
  • 在WKUserContentController类中,我们可以看到这个方法
    1
    2
    3
    4
    5
    6
    7
    8
    /*! @abstract Adds a script message handler.
    @param scriptMessageHandler The message handler to add.
    @param name The name of the message handler.
    @discussion Adding a scriptMessageHandler adds a function
    window.webkit.messageHandlers.<name>.postMessage(<messageBody>) for all
    frames.
    */
    - (void)addScriptMessageHandler:(id <WKScriptMessageHandler>)scriptMessageHandler name:(NSString *)name;
    然后我们可以在下面这个方法中处理对应的name
    1
    - (void)userContentController:(WKUserContentController *)userContentController didReceiveScriptMessage:(WKScriptMessage *)message{}
    强烈推荐此方法!!!

相关demo代码已放在github上

  • https://github.com/Jack-Ving/Hybrid_Interaction 这个是用Vue写的对应的js代码
  • https://github.com/Jack-Ving/Hybrid 这个是采用拦截的方式写的OC代码

由于仓促,如果遇到什么问题,尽管联系我.

QQ:727881945 微信:Q727881945

简介

Linux内核官网www.kernel.org

  • 内核版本2.6.18 3.16等等
  • 发行版本 redhat==conterOS ubuntu debian等等
  • 使用的自由
    • 绝大多数开源软件免费
  • 研究的自由
    • 可以获得软件源代码
  • 散步及改良的自由
    • 可以自由传播、改良甚至销售

应用

  • 基于Linux服务器
  • 嵌入式应用 手机,接顶盒等等

Linux和Windows的不同

  • Linux严格区分大小写
  • Linux中所有内容以文件形式保存,包括硬件
  • 以前内容皆文件
  • Linux不靠扩展名区分文件类型 (->权限)
    • 压缩包 .gz .bz2 .tar.bz2 .tgz登
    • 二进制软件包:.rpm
    • 网页文件: *.html .php
    • 脚本文件: *.sh
    • 配置文件: *.conf

字符界面

  • 占用系统资源更少
  • 减少出错、被攻击的可能性越少

挂载 给每个分区分配挂载点

安装

  • 服务器最小化安装

安装日志

  • /root/install.log: 储存了安装在系统中的软件包及其版本信息
  • /root/install.log.syslog: 储存了安装过程中留下的事件记录
  • /root/anaconda-ks.cfg: 以Kickstart配置文件的格式记录安装过程中设置的选项信息

常用的目录的作用

  • /根目录
  • /bin 命令保存目录(普通用户就可以读取的命令)
  • /sbin 超级权限才能读取的命令
  • /boot 启动目录,启动相关文件
  • /dev 设备文件保存目录
  • /etc 配置文件
  • /home 普通用户家目录
  • /lib 系统库保存目录
  • /mnt 系统挂载目录
  • /media 挂载目录
  • /proc和/sys 目录不能直接操作,保存的是内存的过载点
  • /usr 系统软件资源目录
    • /usr/bin/ 系统命令(普通用户)
    • /usr/sbin/ 系统命令(超级用户)
  • /var 系统相关文档内容

常用命令(文件处理命令)

命令的基本格式

  • ~代表当前所在位置 家目录,初始目录
  • #代表超级用户
  • $代表普通用户
  • 命令 [选项] [参数] -a -all简化

文件格式

  • rw- r– r– 分为三组,三组的权限
  • -文件类型(- 文件, d 目录, | 软连接文件)
  • rw- r– r–
  • 所有者 g所属着 o其他人
  • r读 w写 x执行
  • .文件为隐藏文件

pwd 显示当前所在路径 print working dictionary

mkdir -p [目录名]

  • -p 递归创建 /s/p 这样的

cd 进入目录

  • ~进入当前用户目录,家目录
  • ..上一级目录
  • .当前目录

rmdir [目录] remove empty dir

  • 只能删除空白目录

rm -rf 不询问是否删除

  • -r 删除目录
  • -f 强制

cp [选项] [源文件] [目标目录]

  • -r 复制目录
  • -p 连带文件属性复制
  • -d 若源文件是链接文件,则复制链接属性
  • -a 相当于 -pdr

mv 剪切或者改名命令

  • 源文件或者目录和目标文件目录 在一起,就是改名

链接命令

  • ln -s [源文件] [目标文件]
  • 命令英文愿意: link
  • 功能描述:生成链接文件
    • 选项: -s 创建软链接
  • 硬链接特征 (i 文件索引点)
    1. 拥有相同的i节点和储存block块,可以看作是同一个文件
    2. 可通过i节点识别
    3. 不能跨分区
    4. 不能准对目录使用
    5. 引用计数加1,同一个文件
  • 软链接特征
    • 类似于windows快捷方式
    • 拥有自己的i节点和block块,但是数据块中只保存源文件的文件名和i节点号,并没有实际的文件数据
    • Irwxrwxrwx I 软连接
      • 软连接文件的权限都为rwxrwxrwx
    • 修改任意文件,另一个都改变
    • 删除源文件,软连接不能使用

文件搜索命令

  • 文件搜索命令locate
  • 命令搜索命令whereis与which
  • 文件搜索命令find
  • 字符串搜索命令grep
  • find命令与grep命令的区别

locate (速度相对较快)

  • locate 文件名。 在后台数据库中按照文件名搜索,搜索速度更快
  • /var/lib/mlocate #locate命令所搜索的后台数据库
  • updatedb 更新数据库, 可能是一天更新一次,开机更新一次

whereis与which 搜索命令的命令

  • where 只能搜索系统的命令
    • -b 只查看命令所在位置
    • -m 只差看帮助文档
  • which 只能搜索系统的命令
    • 看命令所在位置,还能查到看别名

find命令

  • find [搜索范围] [搜索条件]

  • find / -name install.log

    • 避免大范围搜索,会非常耗费系统资源
    • find是在系统当中搜索符合条件的文件名。如果需要匹配,通常使用通配符匹配,通配符是完全匹配
  • * 匹配任意字符

  • ? 匹配任意一个字符

  • [] 匹配任意一个中括号内的字符 find -name ab[cd]

  • find /root -inname install.log 就不区分大小写了

  • find /root -user root 按照所有者搜索

  • find /root -nouser 查找没有所有者的文件

  • find /var/log/ -mtime +10 查找10天前修改的文件

    • -10 10天内修改的文件
    • 10 10天当天修改的文件
    • +10 10天前修改的文件
    • atime 文件访问时间
    • ctime 改变文件属性
    • mtime 修改文件内容
  • find . -size 25k 查找文件大小是25k的文件

    • -25k 小于25KB的文件 注意是小写的k,兆字节用大写的M
    • 25k 等于25KB的文件
    • +25k 大于25KB的文件
    • find . -size 25 不加单位就是扇区搜索
  • find /root -inum 262422 查找i节点是262422的文件

  • find /etc -size +20k -a -size -50k

    • 查找/etc/目录下,大于20kb小于50kb的文件
    • -a and 逻辑与,两个条件都满足
    • -0 or 逻辑或,两个条件满足一个即可
  • find /etc -size +20k -a -size -50k -exec ls -lh {} ;

    • 查找/etc/目录下,大于20KB并且小于50KB的文件,并显示详细信息
    • -exec/-ok 命令{} \; 对搜索结果执行操作,"{} \;"为标准格式,只要有-exec就要写它

grep命令

  • grep [选项] 字符串 文件名
    • 在文件档中匹配符合条件的字符串
    • -i 忽略大小写
    • -v 排除指定字符串

find 与 grep区别

  • find命令,在当前系统中搜索符合条件的文件名,如果需要匹配。使用通配符匹配,通配符是完全匹配
  • grep命令:在文件当中搜索符合条件的字符串,如果需要匹配,使用正则表达式进行匹配,正则表达式是包含匹配

帮助命令

  • man帮助等级

    1. 查看命令的帮助
    2. 查看可被内核调用的函数的帮助
    3. 查看函数和函数库的帮助
    4. 查看特殊文件的帮助(主要是/dev目录下的文件)
    5. 查看配置文件的帮助
    6. 查看游戏的帮助
    7. 查看其他杂项的帮助
    8. 查看系统管理员可用命令的帮助
    9. 查看和内核相关文件的帮助
  • man 命令 -> 获取指定命令的帮助 man ls

  • man -f 相当于 whatis 命令

  • man -k 相当于 apropos命令 eg: apropos passwd

  • shell内部命令帮助

    • help 只能获取shell内部命令的帮助 eg: whereis cd. help cd
    • 是安装的软件,就有路径,比如 /bin/ls ,如果 没有比如cd 就是shell内部命令。『whereis cd』
  • info 命令

    • 回车 进入子帮助页面 (带有*号标记)
    • u 上层
    • n 下一帮助小节
    • p 上一帮助小节
    • q 退出

压缩与解压缩命令

.zip .7z .rar .gz .bz2 .tar.gz .tar.bz2等等

.zip格式

  • zip 压缩文件名 源文件名 eg: zip xxx.zip xxx
  • zip -r 压缩目录 源文件
  • unzip 解压缩文件

.gz格式

  • gzip 源文件 压缩为.gz格式的压缩文件,源文件会消失
  • gzip -c 源文件 > 压缩文件
    • 压缩为.gz 格式,源文件保留
    • eg: gzip -c cangls > cangls.gz
  • gzip -r 目录
    • 压缩目录下所有的子文件,但是不能压缩目录
  • gzip -d 解压缩
  • gunzip 解压缩

.bz2格式

  • bzip2 源文件 不保留源文件
  • bzip2 -k 源文件
    • 压缩之后保留源文件

      注意:bzip2命令不能压缩目录

  • bzip2 -d 解压缩 -k保留源文件
  • bunzip2 -k保留源文件

.tar.gz .tar.bz2

先压缩成.tar 再压缩成.gz / .bz2

tar -cvf 打包文件名 源文件

  • -c 打包

  • -v 显示过程

  • -f 指定打包后的文件名

  • -x 解压缩 eg -xvf

  • -t 只查看

  • .tar.gz (z)

    • -z 压缩为.tar.gz格式 tar -zcvf xx.tar.gz 源文件
    • -x 解压缩为.tar.gz格式 tar -zxvf xx.tar.gz
  • .tar.bz2 (j)

    • -j 压缩为.tar.bz2格式 tar -jcvf xx.tar.bz2 源文件
    • -x 解压缩为.tar.bz2格式 tar -jxvf xx.tar.bz2
  • -C 位置

关机和重启命令

shutdown [选项] 时间

  • -c 取消前一个关机命令
  • -h 关机
  • -r 重启

其他关机命令 (非安全,建议shutdown)

  • halt (非安全,建议shutdown)
  • poweroff (非安全,建议shutdown)
  • init 0 (非安全,建议shutdown)

其他重启命令

  • reboot 相对安全
  • init 6 非安全

系统运行级别

查看系统运行级别
cat /etc/inittab 默认级别
runlevel

  • 0 关机
  • 1 单用户
  • 2 不完全多用户,不包含NFS服务
  • 3 完全多用户
  • 4 未分配
  • 5 图形界面
  • 6 重启

其他命令

挂载命令

用户登录查看和用户交互命令

  • mount 查询系统中已经挂载的设备
  • mount -a 依据配置文件/etc/fstab的内容,自动挂载

挂载命令格式

mount [-t 文件系统] [-o 特殊选项] 设备文件名 挂载点

  • -t 文件系统:加入文件系统类型来指定挂载的类型,可以ext3、ext4、iso9660等文件系统
  • -0 特殊选项:可以指定挂载的额外选项
  • #!/bin/bash 最好必须写
  • 默认是可执行 exec.
  • mount -0 remount, noexec /home 重新挂载/boot分区, 并使用noexec权限
  • remount 重新挂载已经挂载的文件系统,一般用于指定修改特殊权限

挂载光盘

  • mkdir /mnt/cdrom/ 建立挂载点
  • mount -t iso9660 /dev/cdrom /mnt/cdrom 挂载光盘
  • mount /dev/sr0 /mnt/cdrom
  • umount /mnt/cdrom 必须卸载

挂载U盘

  • fdisk -i 查看U盘设备文件
  • mount -t vfat /dev/sdb1 /mnt/usb/
  • 注意:Linux默认不支持NTFS文件系统

用户登录查看命令

  • w 用户名 命令输出如下
    • USER: 登录的用户名
    • TTY: 登录终端
    • FROM: 从哪个IP地址登录
    • LOGIN@: 登录时间
    • IDLE: 用户闲置时间
    • JCPU: 指的是和该终端廉洁的所有进程占用的时间。这个时间里并不包括过去的后台作业时间,但却包括当前正在运行的后台作业所占用的时间
    • PCUP:是指当前进程所占用的时间
    • WHAT: 当前正在运行的命令
  • who 用户名
  • last 默认是读取 /var/log/wtmp文件数据,输出如下
    • 用户名
    • 登录终端
    • 登录IP
    • 登录时间
    • 退出时间(在线时间)
  • lastlog 默认是读取/var/log/lastlog文件内容
    • 用户名
    • 登录终端
    • 登录ip
    • 最后一次登录时间

Shell部分

Shell概况

  • 命令行解释器,计算机可不认识命令,只认识0101. 为用户提供了一个向Linux内核发送请求以便运行程序的界面系统级程序,用户可以用Shell来启东、挂起、挺直甚至是编写一个程序
  • SHell还是一个功能相当强大的编译语言,易变写,易调试,灵活性较强。Shell是解释执行的脚本语言,在Sjell中可以直接调用Linux系统命令

Shell的分类

  • Bourne Shell: 从1979起Unix就开始使用Bourne Shell, 主文件名为sh。
  • C Shell: 主要在BSD版的Unix系统中使用,其语言和C语言像相似而得名
  • Shell的两种主要语法类型有Bourne和C,这两种语法彼此不兼容。Bourne家族主要有sh、ksh、Bash、psh、zsh; C家族主要包括csh、tcsh

脚本执行方式

  • echo
  • vi hello.sh (#!/bin/bash) 可以调用系统命令
  • 赋予执行权限,直接运行
    • chmod 755 hello.sh
    • ./hello.sh
  • 通过Bash调用执行脚本
    • bash hello.sh

别名与快捷键

  • alias cp = ‘cp -i’, mv = ‘mv -i’, rm = ‘rm -i’等等,提示,是否xxx?
  • unalias 删除别名
  • vi ~/.bashrc 写入环境变量配置文件
  • source ~/.bashrc 可以让配置重新生效

命令生效顺序

  1. 用绝对路径或相对路径执行的命令
  2. 执行别名
  3. 执行Bash的内部命令
  4. 执行按照$PATH环境变量定义的目录查找顺序找到的第一个命令

快捷键

  • ctrl+c 强制终止当前命令
  • ctrl+l 清屏
  • ctrl+a 光标移动到命令行首
  • ctrl+e 光标移动到命令行尾
  • ctrl+u 从光标所在位置删除到行首
  • ctrl+z 把命令放入后台
  • ctrl+r 在历史命令中搜索

历史命令

  • history [选项] [历史命令保存文件]
  • -c 清空历史命令
  • -w 把缓存中的历史命令写入历史命令保存文件 ~/.bash_history
  • vim /etc/profile
  • 上下剪头调用以前的历史命令
  • !n 重复执行第n条历史命令
  • !! 重复执行上一条命令
  • !字符串 重复执行最后一条以该字符串开头的命令

Bash命令补全

  • tab键

输出重定向

把命令输出保存到文件, > 代表覆盖的形式写入, >>代表追加的方式写入

  • ‘>’覆盖,>> 追加
  • 命令 > 文件 2>&1
  • 命令 >> 文件 2>&1
  • 命令 &> 文件
  • 命令 &>> 文件
  • 命令>>文件1 2>>文件2 把正确的输出追加到文件1,把错误的输出追加到文件2中
  • ls &>/dev/null 相当于既不显示到屏幕上,也不保存

输入重定向

wc [选项] [文件名]

  • -c 统计字节数
  • -w 统计单词数
  • -l 统计行数

管道符

多命令顺序执行

  • ; 命令1:命令2 多个命令顺序执行,命令之间没有任何联系
  • && cmd1 && cmd2 只有当cmd1正确执行 命令cmd2才会执行
  • || cmd1 || cmd2 当命令cmd1不正确的时候,命令2才会执行,正确则不执行

管道符

命令1 | 命令2,命令1的正确输出作为命令2的操作对象

  • ll -a /etc/ | more
  • netstat -an | grep xxx

通配符

  • ? 匹配一个任意字符
  • * 匹配0个或任意多个任意字符,也就是可以匹配任何内容
  • [] 匹配中括号中任意一个字符,例如:[abc]代表一定匹配一个字符串,或者是a,或者b,或者c。
  • [-] 匹配中括号中任意一个字符, -代表一个范围。例如[a-z]代表匹配一个小写字母
  • [^] 逻辑非,代表匹配不是中括号内的一个字符。例如:[^0-9]代表匹配不是一个数字的字符

Bash中其他特殊字符

符号 作用
‘’ 单引号。在单引号中所有的特殊符号,如”$”和”`“(反引号)都 没有特殊含义
“” 双引号。在双引号中特殊字符都没有特殊含义,但是”$”、”`”、和”"是例外,拥有”调用变量的值”、”引用命令”、”转义符”的特殊含义。
`` 反引号。反引号括起来的内容是系统命令,在Bash中会先执行它。和$()作用一样,补货推荐使用$(),因为反引号非常容易看错
$() 和反引号作用一样,用来引用系统命令
# 在Shell脚本中,#开头的行代表注释。
$ 用于调用变量的值,如需要调用变量name的值得时候,需要用$name的方式得到变量的值
\ 转义符,跟在\之后的特殊字符将失去特殊含义,变为普通字符。如$ 将输出”$”符号

动画

AirBnbLottie

Layar的认识

layer 层是涂层绘制、渲染、以及动画的完成者,它无法直接的处理触摸事件(也可以捕捉事件)
layer 包含的方面非常多,常见的属性有 FrameBoundsPositionAnchorPointContents 等等。
想详细了解 CALayer 以及动画的 ,可以看看这本书 - Core-Animation

Block的几种形式

分为全局Block(_NSConcreteGlobalBlock)、栈Block(_NSConcreteStackBlock)、堆Block
(_NSConcreteMallocBlock)三种形式
其中栈Block存储在栈(stack)区,堆Block存储在堆(heap)区,全局Block存储在已初始化数据(.data)区

1、不使用外部变量的block是全局block

比如:

1
2
3
NSLog(@"%@",[^{
NSLog(@"globalBlock");
} class]);

输出:

1
__NSGlobalBlock__

2、使用外部变量并且未进行copy操作的block是栈block

比如:

1
2
3
4
NSInteger num = 10;
NSLog(@"%@",[^{
NSLog(@"stackBlock:%zd",num);
} class]);

输出:

1
__NSStackBlock__

日常开发常用于这种情况:

1
2
3
4
5
6
7
8
9
[self testWithBlock:^{
NSLog(@"%@",self);
}];

- (void)testWithBlock:(dispatch_block_t)block {
block();

NSLog(@"%@",[block class]);
}

3、对栈block进行copy操作,就是堆block,而对全局block进行copy,仍是全局block

  • 比如堆1中的全局进行copy操作,即赋值:
1
2
3
4
5
void (^globalBlock)(void) = ^{
NSLog(@"globalBlock");
};

NSLog(@"%@",[globalBlock class]);

输出:

1
__NSGlobalBlock__

仍是全局block

  • 而对2中的栈block进行赋值操作:
1
2
3
4
5
6
7
8
NSInteger num = 10;

void (^mallocBlock)(void) = ^{

NSLog(@"stackBlock:%zd",num);
};

NSLog(@"%@",[mallocBlock class]);

输出:

1
__NSMallocBlock__

对栈blockcopy之后,并不代表着栈block就消失了,左边的mallock是堆block,右边被copy的仍是栈block
比如:

1
2
3
4
5
6
7
8
9
10
11
12
13
[self testWithBlock:^{

NSLog(@"%@",self);
}];

- (void)testWithBlock:(dispatch_block_t)block
{
block();

dispatch_block_t tempBlock = block;

NSLog(@"%@,%@",[block class],[tempBlock class]);
}

输出:

1
__NSStackBlock__,__NSMallocBlock__
  • 即如果对栈Block进行copy,将会copy到堆区,对堆Block进行copy,将会增加引用计数,对全局Block进行copy,因为是已经初始化的,所以什么也不做。

另外,__block变量在copy时,由于__forwarding的存在,栈上的__forwarding指针会指向堆上的__forwarding变量,而堆上的__forwarding指针指向其自身,所以,如果对__block的修改,实际上是在修改堆上的__block变量。

即__forwarding指针存在的意义就是,无论在任何内存位置,都可以顺利地访问同一个__block变量

  • 另外由于block捕获的__block修饰的变量会去持有变量,那么如果用__block修饰self,且self持有block,并且block内部使用到__block修饰的self时,就会造成多循环引用,即self持有block,block 持有__block变量,而__block变量持有self,造成内存泄漏。
    比如:
1
2
3
4
5
6
7
8
__block typeof(self) blockSelf = self;

_testBlock = ^{

NSLog(@"%@",blockSelf);
};

_testBlock();

如果要解决这种循环引用,可以主动断开__block变量对self的持有,即在block内部使用完blockSelf后,将其置为nil,但这种方式有个问题,如果block一直不被调用,那么循环引用将一直存在。
所以,我们最好还是用__weak来修饰self

回溯算法 问题

类型 题目链接
子集、组合 子集、子集 II、组合、组合总和、组合总和 II
全排列 全排列、全排列 II、字符串的全排列、字母大小写全排列
搜索 解数独、单词搜索、N皇后、分割回文串、二进制手表

参考力扣解法
C++
java

解题思路

①递归树
②找结束条件
③找准选择列表
④判断是否需要剪枝
⑤做出选择
⑥撤销选择

总结:可以发现“排列”类型问题和“子集、组合”问题不同在于:“排列”问题使用used数组来标识选择列表,而“子集、组合”问题则使用start参数

1、不用中间变量,用两种方法交换A和B的值

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
// 1.中间变量
void swap(int a, int b) {
int temp = a;
a = b;
b = temp;
}

// 2.加法
void swap(int a, int b) {
a = a + b;
b = a - b;
a = a - b;
}

// 3.异或(相同为0,不同为1. 可以理解为不进位加法)
void swap(int a, int b) {
a = a ^ b;
b = a ^ b;
a = a ^ b;
}

2、求最大公约数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
/** 1.直接遍历法 */
int maxCommonDivisor(int a, int b) {
int max = 0;
for (int i = 1; i <=b; i++) {
if (a % i == 0 && b % i == 0) {
max = i;
}
}
return max;
}
/** 2.辗转相除法 */
int maxCommonDivisor(int a, int b) {
int r;
while(a % b > 0) {
r = a % b;
a = b;
b = r;
}
return b;
}

// 扩展:最小公倍数 = (a * b)/最大公约数

3、模拟栈操作

  • 栈是一种数据结构,特点:先进后出 -
  • 练习:使用全局变量模拟栈的操作

#include <stdio.h>
#include <stdbool.h>
#include <assert.h>

//保护全局变量:在全局变量前加static后,这个全局变量就只能在本文件中使用
static int data[1024];//栈最多能保存1024个数据
static int count = 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
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
//数据入栈 push
void push(int x){
assert(!full());//防止数组越界
data[count++] = x;
}
//数据出栈 pop
int pop(){
assert(!empty());
return data[--count];
}
//查看栈顶元素 top
int top(){
assert(!empty());
return data[count-1];
}

//查询栈满 full
bool full() {
if(count >= 1024) {
return 1;
}
return 0;
}

//查询栈空 empty
bool empty() {
if(count <= 0) {
return 1;
}
return 0;
}

int main(){
//入栈
for (int i = 1; i <= 10; i++) {
push(i);
}

//出栈
while(!empty()){
printf("%d ", top()); //栈顶元素
pop(); //出栈
}
printf("\n");

return 0;
}

4、排序算法

选择排序、冒泡排序、插入排序三种排序算法可以总结为如下:
都将数组分为已排序部分和未排序部分。

1.选择排序将已排序部分定义在左端,然后选择未排序部分的最小元素和未排序部分的第一个元素交换。

2.冒泡排序将已排序部分定义在右端,在遍历未排序部分的过程执行交换,将最大元素交换到最右端。

3.插入排序将已排序部分定义在左端,将未排序部分元的第一个元素插入到已排序部分合适的位置。

4.1、选择排序

  • 【选择排序】:最值出现在起始端
  • 第1趟:在n个数中找到最小(大)数与第一个数交换位置
  • 第2趟:在剩下n-1个数中找到最小(大)数与第二个数交换位置
  • 重复这样的操作…依次与第三个、第四个…数交换位置
  • 第n-1趟,最终可实现数据的升序(降序)排列。
1
2
3
4
5
6
7
8
9
10
11
void selectSort(int *arr, int length) {
for (int i = 0; i < length - 1; i++) { //趟数
for (int j = i + 1; j < length; j++) { //比较次数
if (arr[i] > arr[j]) {
int temp = arr[i];
arr[i] = arr[j];
arr[j] = temp;
}
}
}
}

4.2、冒泡排序

  • 【冒泡排序】:相邻元素两两比较,比较完一趟,最值出现在末尾
  • 第1趟:依次比较相邻的两个数,不断交换(小数放前,大数放后)逐个推进,最值最后出现在第n个元素位置
  • 第2趟:依次比较相邻的两个数,不断交换(小数放前,大数放后)逐个推进,最值最后出现在第n-1个元素位置
  • …… ……
  • 第n-1趟:依次比较相邻的两个数,不断交换(小数放前,大数放后)逐个推进,最值最后出现在第2个元素位置
1
2
3
4
5
6
7
8
9
10
11
void bublleSort(int *arr, int length) {
for(int i = 0; i < length - 1; i++) { //趟数
for(int j = 0; j < length - i - 1; j++) { //比较次数
if(arr[j] > arr[j+1]) {
int temp = arr[j];
arr[j] = arr[j+1];
arr[j+1] = temp;
}
}
}
}

#### 5、折半查找(二分查找)

折半查找:优化查找时间(不用遍历全部数据)
折半查找的原理:

  • 1> 数组必须是有序的
  • 2> 必须已知min和max(知道范围)
  • 3> 动态计算mid的值,取出mid对应的值进行比较
  • 4> 如果mid对应的值大于要查找的值,那么max要变小为mid-1
  • 5> 如果mid对应的值小于要查找的值,那么min要变大为mid+1

// 已知一个有序数组, 和一个key, 要求从数组中找到key对应的索引位置

1
2
3
4
5
6
7
8
9
10
11
12
13
14
int findKey(int *arr, int length, int key) {
int min = 0, max = length - 1, mid;
while (min <= max) {
mid = (min + max) / 2; //计算中间值
if (key > arr[mid]) {
min = mid + 1;
} else if (key < arr[mid]) {
max = mid - 1;
} else {
return mid;
}
}
return -1;
}

6、集合结构 线性结构 树形结构 图形结构

  • 1.1、集合结构 说白了就是一个集合,就是一个圆圈中有很多个元素,元素与元素之间没有任何关系 这个很简单
  • 1.2、线性结构 说白了就是一个条线上站着很多个人。 这条线不一定是直的。也可以是弯的。也可以是值的 相当于一条线被分成了好几段的样子 (发挥你的想象力)。 线性结构是一对一的关系
  • 1.3、树形结构 说白了 做开发的肯定或多或少的知道xml 解析 树形结构跟他非常类似。也可以想象成一个金字塔。树形结构是一对多的关系
  • 1.4、图形结构 这个就比较复杂了。他呢 无穷。无边 无向(没有方向)图形机构 你可以理解为多对多 类似于我们人的交集关系

7、数据结构的存储

数据结构的存储一般常用的有两种 顺序存储结构 和 链式存储结构

  • 2.1 顺序存储结构

发挥想象力啊。 举个列子。数组。1-2-3-4-5-6-7-8-9-10。这个就是一个顺序存储结构 ,存储是按顺序的 举例说明啊。 栈。做开发的都熟悉。栈是先进后出 ,后进先出的形式 对不对 ?!他的你可以这样理解

hello world 在栈里面从栈底到栈顶的逻辑依次为 h-e-l-l-o-w-o-r-l-d 这就是顺序存储 再比如 队列 ,队列是先进先出的对吧,从头到尾 h-e-l-l-o-w-o-r-l-d 就是这样排对的

  • 2.2 链式存储结构

再次发挥想象力 这个稍微复杂一点 这个图片我一直弄好 ,回头找美工问问,再贴上 例如 还是一个数组

1-2-3-4-5-6-7-8-9-10 链式存储就不一样了 1(地址)-2(地址)-7(地址)-4(地址)-5(地址)-9(地址)-8(地址)-3(地址)-6(地址)-10(地址)。每个数字后面跟着一个地址 而且存储形式不再是顺序 ,也就说顺序乱了,1(地址) 1后面跟着的这个地址指向的是2,2后面的地址指向的是3,3后面的地址指向是谁你应该清楚了吧。他执行的时候是 1(地址)-2(地址)-3(地址)-4(地址)-5(地址)-6(地址)-7(地址)-8(地址)-9(地址)-10(地址),但是存储的时候就是完全随机的。明白了?!

8、单向链表\双向链表\循环链表

还是举例子。理解最重要。不要去死记硬背 哪些什么。定义啊。逻辑啊。理解才是最重要滴

  • 3.1 单向链表

A->B->C->D->E->F->G->H. 这就是单向链表 H 是头 A 是尾 像一个只有一个头的火车一样 只能一个头拉着跑

  • 3.2 双向链表

数组和链表区别:
数组:数组元素在内存上连续存放,可以通过下标查找元素;插入、删除需要移动大量元素,比较适用于元素很少变化的情况
链表:链表中的元素在内存中不是顺序存储的,查找慢,插入、删除只需要对元素指针重新赋值,效率高

  • 3.3 循环链表

循环链表是与单向链表一样,是一种链式的存储结构,所不同的是,循环链表的最后一个结点的指针是指向该循环链表的第一个结点或者表头结点,从而构成一个环形的链。发挥想象力 A->B->C->D->E->F->G->H->A. 绕成一个圈。就像蛇吃自己的这就是循环 不需要去死记硬背哪些理论知识。

9、二叉树/平衡二叉树

  • 4.1 什么是二叉树

树形结构下,两个节点以内 都称之为二叉树 不存在大于2 的节点 分为左子树 右子树 有顺序 不能颠倒 ,懵逼了吧,你肯定会想这是什么玩意,什么左子树右子树 ,都什么跟什么鬼? 现在我以普通话再讲一遍,你把二叉树看成一个人 ,人的头呢就是树的根 ,左子树就是左手,右子树就是右手,左右手可以都没有(残疾嘛,声明一下,绝非歧视残疾朋友,勿怪,勿怪就是举个例子,i am very sorry) , 左右手呢可以有一个,就是不能颠倒。这样讲应该明白了吧

二叉树有五种表现形式

1.空的树(没有节点)可以理解为什么都没 像空气一样
2.只有根节点。 (理解一个人只有一个头 其他的什么都没,说的有点恐怖)
3.只有左子树 (一个头 一个左手 感觉越来越写不下去了)
4.只有右子树
5.左右子树都有

二叉树可以转换成森林 树也可以转换成二叉树。这里就不介绍了 你做项目绝对用不到
数据结构大致介绍这么多吧。理解为主, 别死记,死记没什么用

Clang ARC的文档

autoReleasePool 什么时候释放?

App启动后,苹果在主线程 RunLoop 里注册了两个 Observer,其回调都是 _wrapRunLoopWithAutoreleasePoolHandler()

第一个 Observer 监视的事件是 Entry(即将进入Loop),其回调内会调用 _objc_autoreleasePoolPush() 创建自动释放池。其 order-2147483647,优先级最高,保证创建释放池发生在其他所有回调之前。

第二个 Observer 监视了两个事件: BeforeWaiting(准备进入休眠) 时调用_objc_autoreleasePoolPop()_objc_autoreleasePoolPush() 释放旧的池并创建新池;Exit(即将退出Loop) 时调用 _objc_autoreleasePoolPop() 来释放自动释放池。这个 Observerorder2147483647,优先级最低,保证其释放池子发生在其他所有回调之后。

在主线程执行的代码,通常是写在诸如事件回调、Timer回调内的。这些回调会被 RunLoop 创建好的 AutoreleasePool 环绕着,所以不会出现内存泄漏,开发者也不必显示创建 Pool 了。

Obj-C 中,如何检测内存泄漏?你知道哪些方式?

目前我知道的方式有以下几种

  • Memory Leaks
  • Alloctions
  • Analyse
  • Debug Memory Graph
  • MLeaksFinder

泄露的内存主要有以下两种:

  • Laek Memory 这种是忘记 Release 操作所泄露的内存。
  • Abandon Memory 这种是循环引用,无法释放掉的内存。

上面所说的五种方式,其实前四种都比较麻烦,需要不断地调试运行,第五种是腾讯阅读团队出品,效果好一些,感兴趣的可以看一下这两篇文章:

  • MLeaksFinder:精准 iOS 内存泄露检测工具
  • MLeaksFinder 新特性

事件响应

个人语言简述如下

  • 事件(例如点击屏幕)产生后,系统会把事件传递给当前的application,然后是window依次传递给viewcontroller,最后是手指所接触的view,
  • 这个过程会被阻断,比如 userInteractionEnabled = NO,再比如隐藏 hidden = YES.透明度<0.01,再比如指定了hitTest:withEvent:方法等。
  • 然后,开始处理事件,view(UIResponder)来响应,view不处理,则上一级接受到继续,一直到application,这个过程重要的方法如下
1
2
3
4
5
6
7
8
9
10
11
// 上一个响应者可能是父控件
- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event{
// 默认会把事件传递给上一个响应者,上一个响应者是父控件,交给父控件处理

//再比如自己处理完还想让父响应者也处理,就在这写一些东西
//do something here

[super touchesBegan:touches withEvent:event];
// 注意不是调用父控件的touches方法,而是调用父类的touches方法
// super是父类 superview是父控件
}

属性关键字

1.读写权限:readonly,readwrite(默认)
2.原子性: atomic(默认),nonatomic。atomic读写线程安全,但效率低,而且不是绝对的安全,比如如果修饰的是数组,那么对数组的读写是安全的,但如果是操作数组进行添加移除其中对象的还,就不保证安全了。
3.引用计数:

  • retain/strong
  • assign:修饰基本数据类型,修饰对象类型时,不改变其引用计数,会产生悬垂指针,修饰的对象在被释放后,assign指针仍然指向原对象内存地址,如果使用assign指针继续访问原对象的话,就可能会导致内存泄漏或程序异常
  • weak:不改变被修饰对象的引用计数,所指对象在被释放后,weak指针会自动置为nil
  • copy:分为深拷贝和浅拷贝
    浅拷贝:对内存地址的复制,让目标对象指针和原对象指向同一片内存空间会增加引用计数
    深拷贝:对对象内容的复制,开辟新的内存空间

可变对象的copy和mutableCopy都是深拷贝
不可变对象的copy是浅拷贝,mutableCopy是深拷贝
copy方法返回的都是不可变对象

  • @property (nonatomic, copy) NSMutableArray * array;这样写有什么影响?
    因为copy方法返回的都是不可变对象,所以array对象实际上是不可变的,如果对其进行可变操作如添加移除对象,则会造成程序crash

KVO (Key-value observing)

KVO是观察者模式的另一实现。
使用了isa混写(isa-swizzling)来实现KVO

使用setter方法改变值KVO会生效,使用setValue:forKey即KVC改变值KVO也会生效,因为KVC会去调用setter方法

1
2
3
4
5
6
7
8
- (void)setValue:(id)value
{
[self willChangeValueForKey:@"key"];

[super setValue:value];

[self didChangeValueForKey:@"key"];
}
  • 那么通过直接赋值成员变量会触发KVO吗?
    不会,因为不会调用setter方法,需要加上
    willChangeValueForKey和didChangeValueForKey方法来手动触发才行