0%

iOS学习笔记

以下笔记是边看视频课程边记录下来的,供以后自己查阅复习使用

Runtime学习笔记1

消息转发

在OC中,调用方法其实是给对象发送消息

1
2
3
[[Person new] sendMessage:@"gelo"];
// 等价于
objc_msgSend([Person new], @selector(sendMessage:), "gelo");

通过对象的isa指针找到class,如果有方法的话,直接调用。没有找到的话,通过继承树查找,进入消息转发机制

动态方法解析,动态添加方法实现

​ resolveInstanceMethod

快速转发,当前的类或者继承树没有该方法的实现,在更加广的范围寻找

​ forwardingTargetForSelector

慢速转发

​ methodSignatureForSelector

​ forwardInvocation

doesNotRecognizeSelector:

方法交换 Method Swizzling

用自己写的方法替换系统方法,通过class_getInstanceMethod方法获取,通过method_exchangeImplementations交换两个函数

字典转模型

遍历字典获取key和value

key作为属性名,value作为属性值

通过objc_msgSend发送set方法

模型转字典

字典的key通过模型的属性列表获取

字典的value通过调用get方法获取

实现KVO

KVO是基于runtime的

A监听B 系统会为B创建一个子类

B的isa指针指向B的子类

在子类中重新set方法

KVO底层实现

KVO的基础使用

观察某一个对象的某个属性

options参数可以观察一下几个值

new 返回变化后的新值

old 返回变化前的旧值

init 注册的时候就会发一次通知,改变后的值的时候也会发送

prior 新值和旧值都会返回

KVO默认是自动模式,每次修改值都会发送通知

手动发送通知的时候,对象调用willchangeValueForKey,改变之后调用didchangeValueForKey

监听属性下面的属性值,只需要在监听path中通过点监听:”dog.age”(属性依赖)

KVO监听的是set方法。比如监听不到数组的add方法

要是需要监听容器方法,需要结合KVC

需要监听对象下多个属性,只需要监听对象本身,并实现keyPat***ForKey。返回需要的真正监听的内部属性的NSSet

内部实现

属性是对成员变量和set、get方法的封装

KVO观察的是set方法(设置成员变量之后,外部通过person->name方法修改,KVO监听不到)通过runtime创建一个观察者的子类(NSKVONotifying_Person)重写set方法。修改指针到子类、在重写的方法里面调用willchangeValueForKey、superSetName、didchangeValueForKey。

在创建的子类中,没有父类的set方法!需要重写set方法

OC的方法中包含SEL(方法编号)、IMP(方法实现)一一对应。调用方法的时候发送的是SEL

OC的方法调用里面有两个默认参数:id self,SEL _cmd。由于sendMsg传递了该参数(调用者以及SEL)

监听容器类(NSArray、NSDic)

通过KVO观察容器属性的变化,利用KVC

通过KVC的mutableArray**ForKey返回一个容器对象,向该对象添加元素可以实现KVO。内部新建子类、重写add方法。

KVO返回的NSDic中,kind类型

观察set方法 返回1

观察插入方法 返回2

观察删除方法 返回3

观察替换方法 返回4

数组中count

使用KVO中监听不到数组中的count、使用KVC同样取不到[array valueForKey:@”count”]

count是集合运算符,KVC需要用@”@count”取值。

count是只读属性

数组(NSMutableArray)

关于数组的容量,容量不够用的时候,会成倍的增加

对象本身是指针,指向该对象的结构体

x/100xb arr 打印arr 100个内存地址

找到count的内存地址,修改内存地址的值,进而可以修改count的值

Runtime学习笔记2

消息机制

OC代码会转化为C语言执行,使用runtime的时候,需要关闭代码的严格检测

调用函数的方法:

[p eat]

[p performSelector: SEL]

objc_msgSend()

使用runtime创建对象:
类名.class即为对象 Person.class == objc_getClass(“Person”)

1
2
3
// 在目录下执行
clang --rewrite-objc main.m
// 手动编译OC代码生成cpp文件

归档/解档

归档和解档对象,需要遵循NSCoding的协议,并且实现协议方法

KVC可以使用id类型为属性赋值

Ivar:成员变量

Method:成员方法

C语言 基本数据类型的指针 函数内部是为了修改外部的值

class_copyIvarList获取所有属性数量

关键字copy、new、creat代表着会在堆区域(malloc)开辟空间

方法执行完毕—>方法调用栈平衡—>内部变量指针出栈—>但是指针指向的堆区的值还在—>内存泄漏

在OC中使用C的代码,要手动释放指针,防止内存溢出

OC方法定位以及替换

OC的方法表:返回值类型+参数类型一样 编号就一样

​ 类

SEL 编号 ————— IMP实现(地址指针)

SEL 编号 ————— IMP实现(地址指针)

SEL 编号 ————— IMP实现(地址指针)

用HOOK!钩住系统方法,在调用之前修改方法的调用

在分类中的load方法(由于预加载,比main更早执行)中交换IMP

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
#import <objc/runtime.h>

+ (void)load {
// 获取替换后的类方法
Method otherMethod = class_getClassMethod([UIImage class], @selector(imageNameNextWith:));
// 获取替换前的类方法
Method method = class_getClassMethod([UIImage class], @selector(imageNamed:));
// 然后交换类方法
method_exchangeImplementations(otherMethod, method);
}

+ (UIImage *)imageNameNextWith:(NSString *)nameString {
UIImage *image = nil;
image = [UIImage imageNameNextWith:[nameString stringByAppendingString:@"tupian.jpg"]];
return image;
}

关于在imageNameNextWith中调用自身,并不会引起循环引用。

交换之前

SEL(系统方法:ImageNamed) —————> IMP(系统方法:ImageNamed的实现地址指针)

SEL(自己的方法:ImageNamedNextWith) —————> IMP(自己的方法:ImageNamedNextWith的实现地址指针)

交换之后

SEL(系统方法:ImageNamed) —————> IMP(自己的方法:ImageNamedNextWith的实现地址指针)

SEL(自己的方法:ImageNamedNextWith) —————> IMP(系统方法:ImageNamed的实现地址指针)

交换之后,每当再次调用imageNameNextWith方法的时候,实际上执行的是系统方法ImageNamed指向的方法实现,所以不会引起循环调用。

OC对象本质上是指针占用8个字节

OC方法调用顺序:消息发送——>SEL——>IMP——>代码——>函数——>汇编

函数响应式编程RAC

RAC的代理

RAC里面内部实现类似于通知

  1. 创建信号 提供外界订阅

创建了一个容量为1的可变数组_subscribers

支持多个订阅者订阅该信号

1
RACSubject *subject = [RACSubject subject];
  1. 订阅信号(注册通知)

创建订阅者对象

将Block放到订阅者对象中

将订阅者对象放入_subscribers里面

1
2
3
[subject subscribeNext:^(id){
// 函数式编程,免除了遵循协议,引用方法的步骤
}];
  1. 发送信号(发起通知)

遍历_subscribers取出中的订阅者对象

执行订阅者对象中的Block

1
[subject sentNext:@"hahha];

RAC中可以使用Selector通过方法名称创建信号,直接订阅

RAC中的KVO

RAC可以直接用Block回调,在观察多个属性的时候,可以避免在回调函数中判断。

RAC同样不能观察到数组的count

RAC监听事件

将按钮的点击事件包装成信号,订阅

RAC中的Timer

使用NSTimer的时候,创建完之后需要添加到NSRunLoop中

在NSTread子线程中需要手动启动NSRunLoop

1
[[NSRunLoop currentRunLoop] run]

RAC中通过信号创建子线程并发Timer,底层使用GCD创建

RAC中的宏定义

当输入框内容发生变化,相应更新到_label上

1
RAC(_label, text) = _textField.rac_textSignal;

只要对象的属性发生改变,就会产生信号

1
RACObserver(self, name) sub....

关于Block中的循环引用,但是在特殊情况下是允许循环应用的出现

NSURLSession中的delegate是强引用,目的是发送请求的时候只需要一个对象。是单例

使用强引用,并不会销毁,导致内存泄漏

Socket探索

IP地址可以在网络上定位到一台终端设备

端口号可以访问到设备上的服务:比如80端口为Apache端口服务

网络的七层协议从上至下:应用层、表示层、会话层、传输层、网络层、数据链路层、物理层

标准帧格式数据包

socket处于传输层、IP/TCP协议在网络层

TCP与UDP协议的区别

UDP(用户数据报协议)短信

​ 只管发送,不确认对方是否收到

​ 将数据及源和目的封装成数据包中,不需要建立连接

​ 每个数据报大小限制在64k

​ 因为无需连接,因此是不可靠协议

​ 不需要连接,速度快

TCP(传输控制协议)电话

​ 建立连接,形成传输数据通道

​ 在连接中进行大数据传输(数据大小不受限制)

​ 通过三次握手连接,是可靠的协议,安全送达

​ 必须建立连接,效率稍低

直播推流、游戏是UDP协议,下载的过程是TCP协议

Socket

类比插座,socket需要两端的IP+端口号建立连接

  1. 创建socket
1
2
3
4
5
6
/*
domain: 协议域 AF_INET = IPV4 IPV6
typeSocket类型 SOCK_STREAM(TCP)/SOCK_DGRAM(UDP)
protocol: IPPROTO_TCP, 传入0, 会自动根据第二个值选择合适的协议
*/
int clientSocket = socket(AF_INET, SOCK_STREAM, 0);
  1. 连接服务器
1
2
3
4
5
6
7
8
9
10
11
12
/*
客户端socket
服务器IP地址结构体指针
结构体长度
*/
struct sockaddr_in serverAddr;
serverAddr.sin_family = AF_INET;//IPV4
serverAddr.sin_port = htons(80);//端口号
serverAddr.sin_addr.s_addr = inet_addr("127.0.0.1");//IP地址

int connectResult = connect();
// 返回0代表连接成功

netcat工具,用于监听本地端口

  1. 发送数据
1
2
3
4
5
6
7
8
9
10
/*
客户端socket
发送内容地址
发送内容长度
发送方式标志,一般为0

返回值
发送成功之后返回发送字节长度,失败返回error
*/
send();
  1. 读取数据
1
2
3
4
5
6
7
8
9
10
11
12
13
14
/*
客户端socket
接受内容缓冲区域地址
接受内容缓冲区长度
接受方式标志,0表示阻塞,必须等待服务器返回数据

返回值
接受成功之后返回发送字节长度,失败返回error
*/

uint8_t buffer[1024];
recv();

通过data数据读取二进制数组,解析返回字节流
  1. 关闭socket
1
close();

HTTP访问

通过向百度的IP地址,发送”GET HTTP/1.1\n Host: www.baidu.com\n\n"可以接收到百度服务器返回的百度首页数据。

手写了一个HTTP协议

Connection被废弃的原因

异步下载,无法回调数据,因为Runloop默认在子线程不开启

线程管理

线程中有任务才有可能不被释放

HTTPS协议

https本身不会对客户端进行验证

加密算法:RSA

RSA:公钥和私钥

明文+公钥 = 密文

密文+私钥 = 明文

第一次请求HTTPS服务器,客户端安装证书(下载公钥并保存),通讯时通过该公钥加密传输

登录校验时随机盐可以提高安全性

加密详解

Base64

base64是编码方式,不属于加密算法

可以将任意的二进制数据进行编码 编码成为65中字符的文本文件

0-9,a-z,A-Z,+ / =

对称加密:

DES

3DES

AES(高级密码标准)

数学算法:

哈希函数MD5

内存释放

free()以及CFRelease()的区别

单元测试

setup 初始化

tearDown 销毁

所有的测试用例必须以test开头

given

when

then

架构模式

MVC解耦

vc代码过于沉重

代码耦合性过高 UI与Model的通讯

MVP

面向协议编程—代理

@synchronized(self)多线程锁

通过代理使UI与Model通讯

MVVM

双向绑定:数据和UI的绑定,即修改一处另一处随之修改
异步的一般处理:代理、通知、匿名函数(Block)

从数据 —–> UI 通过Block进行通讯
从UI ——-> 数据 通过KVO监听通讯

NSMutableArray是线程不安全的,在处理里面的数据的时候需要加锁

多线程

进程

线程

NSThread

1
2
3
- alloc init
+ detacNewThread
- self perform

线程的名称

线程的优先级

多线程的共享资源

线程不安全:获取的数据和预期可能不一样

互斥锁:线程同步,@synchronized 当一个线程在操作数据的时候,其它线程不得操作该数据

原子属性 atomic

原子属性是线程安全的,自旋锁

原子属性的成员变量,在set方法中会添加@synchronized保护线程安全