HTTP协议(HyperText Transfer Protocol)的请求和响应
HTTP的特点
无状态: 服务器不会保留客户端状态, 不会记忆上次的状态, 不受前面请求的影响, 客户端每次请求独立, 每次请求需带上自己的状态
持久链接: 每个链接可以处理多个事务, 特殊强调http1.0及其之前版本都是非持久链接(每个链接只能处理一个事务)
持久链接如何结束?
HTTP的请求方式
GET、POST、PUT、DELETE、HEAD、OPTIONS等
GET和和POST的区别:
get请求参数拼接在url后面, post的参数拼接在请求体里面,相对安全(在http抓包情况下依然不安全)
get参数长度有限制2048, post无限制
get获取资源是安全的(不会修改服务器资源), 幂等的(多次执行结果相同), 可缓存的(可以直接有CDN缓存, 减轻服务器负担), post相反
HTTP常用状态码含义 参考文档点击 HTTP状态码总结
1xx 表示消息
2xx 表示成功
3xx 表示重定向
4xx 表示请求错误
5xx 表示服务器错误
TCP和UDP的特点, 参考链接 简单理解TCP/IP协议
TCP协议(Transport Control Protocol,传输控制协议): 是一种面向连接、可靠的、基于字节流的传输层协议,采用了确认机制、超时重传机制,还会对接收到的TCP报文段进行重新排列整理。(TCP报头含20字节定长、选项和填充<选项和填充小于等于40字节>)(TCP是一种面向连接的传输层协议。它可以保证两端通信主机之间的通信可达。TCP能够正确处理传输过程中丢包、传输顺序乱掉等异常情况。)
TCP能保证可靠性、稳定性, 适用于可靠性较高的服务
UDP协议:(User Datagram Protocol,用户数据报协议)是一种不可靠无连接的传输层协议,不考虑流控制、错误控制,没有重传机制,不会对分组进行顺序检查和排序。
UDP控制选项少,无须建立连接,从而使得数据传输过程中的延迟小、数据传输效率高, 适用于实时性要求高的程序
网络七层模型
应用层, 表示层, 会话层, 传输层, 网络层, 数据链路层, 物理层
Socket
客户端: socket()->connect()->write()/read()->close()
服务端: socket()->bind()->listen()->accept()->read()/write()->close()
数据解析:
网络安全
Base64加密, 可逆加密
MD5/SHA1/SHA256等 不可逆加密
对称加密和非对称加密
对称加密: 加解密使用同一个秘钥, 代表: DES, AES等, 有点效率高, 缺点, 秘钥交换时安全不能保障
非对称加密: 加解密双方使用不同的秘钥, 加密使用公钥加密, 解密是用私钥解密, 公钥是公开的 代表: RSA, ECC, DSA(数字签名用)
HTTPS
HTTPS中的S含义
S代表的是SSL/TLS协议, 即: Secure Sockets layer(安全套接层)和Transport Layer Security(安全传输层)
HTTPS的建立流程
HTTPS为了兼顾安全与效率,同时采用了对称加密算法和非对称加密算法, 通信过程主要会设计三个秘钥: 服务器端的公钥和私钥, 用来进行非对称加密, 客户端随机生成的随机秘钥, 用来进行对称加密
NSURLSession
NSURLSession的优势
NSURLsession的使用
NSURLsession 是一个管理类, 可以通过NSURLSessionConfiguration进行配置
URLSessionTask是任务的父类, 包含两个子类URLSessionDataTask和URLSessionDownloadTask, 其中URLSessionDataTask有一个子类URLSessionUploadTask, URLSessionDataTask用来处理一般网络请求
URLSessionDownloadTask处理下载任务, URLSessionUploadTask上传任务
iOS开发常用的多线程方式
NSThread简单介绍
创建线程对象: 显示创建(alloc init)和隐式创建(performSelector…)
线程状态:新建, 就绪(start), 阻塞(sleep), 运行, 死亡(exit)
常用属性: name(当前线程名字), threadPriority(线程优先级), isMainThread等
GCD和NSOperation对比
什么情况下会出现死锁
在主线程中将同步任务添加到主队列会导致死锁
- (void)viewDidLoad {
[super viewDidLoad];
dispatch_sync(dispatch_get_main_queue(), ^{
NSLog(@"deallock");
});
// Do any additional setup after loading the view, typically from a nib.
}
在一个串行队列任务中将 同步任务添加到当前的队列中
dispatch_queue_t serialQueue = dispatch_queue_create("test", DISPATCH_QUEUE_SERIAL);
dispatch_async(serialQueue, ^{
dispatch_sync(serialQueue, ^{
NSLog(@"deadlock");
});
});
GCD中的线程栅栏
dispatch_barrier_async/dispatch_barrier_sync 分别表示同步栅栏和异步栅栏
栅栏的作用是可以将任务分块执行, 条件是任务必须在同一个队列上, 否则无效
dispatch_queue_t concurrentQueue = dispatch_queue_create("test", DISPATCH_QUEUE_CONCURRENT);
for (NSInteger i = 0; i < 10; i++) {
dispatch_async(concurrentQueue, ^{
NSLog(@"%zd",i);
});
}
dispatch_barrier_async(concurrentQueue, ^{
NSLog(@"barrier");
});
NSLog(@"哈哈哈");
for (NSInteger i = 10; i < 20; i++) {
dispatch_async(concurrentQueue, ^{
NSLog(@"%zd",i);
});
}
执行结果:
哈哈哈
0-9 乱序打印
barrier
10-19乱序打印
由于采用的是异步栅栏, 所以栅栏后的任务会"哈哈哈"会提起执行, 如果将栅栏换成同步栅栏则 "哈哈哈"一定是在"barrier"之后执行
通过线程栅栏可以实现多度单写, 即允许多个地方同时读取数据, 但是在写入数据时只允许一个地方写入
- (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];
});
}
GCD线程组的使用
需求多个网络请求完成之后一并刷新UI
- (void)testGCDGroup {
__block NSInteger number = 0;
dispatch_group_t group = dispatch_group_create();
//A耗时操作
dispatch_group_async(group, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
sleep(3);
number += 2222;
NSLog(@"A:%zd", number);
});
dispatch_group_async(group, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
[self sendRequestWithCompletion:^(id response) {
number += [response integerValue];
NSLog(@"B:%zd", number);
}];
});
dispatch_group_async(group, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
[self sendRequestWithCompletion:^(id response) {
number += [response integerValue];
NSLog(@"C:%zd", number);
}];
});
// //B网络请求
// dispatch_group_enter(group);
// [self sendRequestWithCompletion:^(id response) {
// number += [response integerValue];
// NSLog(@"B:%zd", number);
// dispatch_group_leave(group);
//
// }];
//
// //C网络请求
// dispatch_group_enter(group);
// [self sendRequestWithCompletion:^(id response) {
// number += [response integerValue];
// NSLog(@"C:%zd", number);
// dispatch_group_leave(group);
// }];
//
dispatch_group_notify(group, dispatch_get_main_queue(), ^{
NSLog(@"finish: %zd", number);
});
}
- (void)sendRequestWithCompletion:(void (^)(id response))completion {
//模拟一个网络请求
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
dispatch_async(queue, ^{
sleep(2);
dispatch_async(dispatch_get_main_queue(), ^{
if (completion) completion(@1111);
});
});
}
调用结果:
B:1111
C:2222
A:4444
finish: 4444
使用方式如代码所示, 先创建线程组, 然后使用dispatch_group_async, 将任务绑定到线程组, 最后使用dispatch_group_notify接受线程组任务完成的回调. 其中也可以使用dispatch_group_enter(group)这种方式将任务添加到线程组, 不过任务结束之后要配合使用 dispatch_group_leave(group);
Dispatch Semaphore 信号量
Dispatch Semaphore 提供了三个函数
Dispatch Semaphore 在实际开发中主要用于:
保持线程同步,将异步执行任务转换为同步执行任务
- (void)testSemaphore {
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;
NSLog(@"执行异步任务");
dispatch_semaphore_signal(semaphore);
});
dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
NSLog(@"semaphore---end,number = %zd",number);
}
//打印结果如下:
//执行异步任务
//semaphore---end,number = 100
创建信号量初始化总量为0, 由于是异步执行,所以直接执行到dispatch_semaphore_wait, 如果信号量为0则一直等待阻断线程, 当异步任务执行完之后将信号量加1, 此时信号量等待函数终结, 继续执行后面的任务
保证线程安全,为线程加锁
- (void)testSemaphore2 {
_semaphore = dispatch_semaphore_create(1);
for (NSInteger i = 0; i < 100; i++) {
dispatch_async(dispatch_get_global_queue(0, 0), ^{
[self asyncTask];
});
}
}
- (void)asyncTask {
dispatch_semaphore_wait(_semaphore, DISPATCH_TIME_FOREVER);
_count++;
sleep(1);
NSLog(@"执行任务:%zd",_count);
dispatch_semaphore_signal(_semaphore);
}
///执行结果发现, 是顺序从1-100执行
原因: 在子线程执行并发任务时, 由于第一次执行任务将信号量减1, 信号总量变为0, 当第二个任务进来时需要由于信号总量为0所以进入等待状态, 任务一执行完之后将信号量加1, 任务二开始执行, 并立即将信号总量减1变为0, 任务三继续等待, 依次类推, 实现了线程加锁目的
使用dispatch_once实现单例
//手写单例
- (id)sharedInstance {
static id instance = nil;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
instance = [[self alloc] init];
})
return instance;
}
NSOperationQueue/NSOperation简介
优势
可以添加任务依赖,方便控制执行顺序
可以设定操作执行的优先级
可以设置最大并发量
任务执行状态控制:isReady,isExecuting,isFinished,isCancelled
如果只是重写NSOperation的main方法,由底层控制变更任务执行及完成状态,以及任务退出
如果重写了NSOperation的start方法,自行控制任务状态
系统通过KVO的方式移除isFinished==YES的NSOperation
基本操作
NSOperation是抽象类, 主要使用两个子类: NSBlockOperation和NSInvocationOperation
可以直接添加Block任务NSOperation到队列中执行, 默认在当前线程执行, NSBlockOperation添加多任务时会自动开启新线程执行
也可以将NSOperation添加到NSOperationQueue中执行操作, 当maxConcurrentOperationCount = 1时为串行队列, 大于1时为并发队列
示例代码
/**
* 设置 MaxConcurrentOperationCount(最大并发操作数)
*/
- (void)setMaxConcurrentOperationCount {
// 1.创建队列
NSOperationQueue *queue = [[NSOperationQueue alloc] init];
// 2.设置最大并发操作数
queue.maxConcurrentOperationCount = 1; // 串行队列
// queue.maxConcurrentOperationCount = 2; // 并发队列
// queue.maxConcurrentOperationCount = 8; // 并发队列
// 3.添加操作
[queue addOperationWithBlock:^{
for (int i = 0; i < 2; i++) {
[NSThread sleepForTimeInterval:2]; // 模拟耗时操作
NSLog(@"1---%@", [NSThread currentThread]); // 打印当前线程
}
}];
[queue addOperationWithBlock:^{
for (int i = 0; i < 2; i++) {
[NSThread sleepForTimeInterval:2]; // 模拟耗时操作
NSLog(@"2---%@", [NSThread currentThread]); // 打印当前线程
}
}];
[queue addOperationWithBlock:^{
for (int i = 0; i < 2; i++) {
[NSThread sleepForTimeInterval:2]; // 模拟耗时操作
NSLog(@"3---%@", [NSThread currentThread]); // 打印当前线程
}
}];
[queue addOperationWithBlock:^{
for (int i = 0; i < 2; i++) {
[NSThread sleepForTimeInterval:2]; // 模拟耗时操作
NSLog(@"4---%@", [NSThread currentThread]); // 打印当前线程
}
}];
}
执行结果为顺序执行1,1,2,2,3,3,4,4
如果将maxConcurrentOperationCount改为2, 则并并发执行, 这里就不再验证
NSOperation的操作依赖设置
/**
* 操作依赖, 操作2依赖操作1
* 使用方法:addDependency:
*/
- (void)addDependency {
// 1.创建队列
NSOperationQueue *queue = [[NSOperationQueue alloc] init];
// 2.创建操作
NSBlockOperation *op1 = [NSBlockOperation blockOperationWithBlock:^{
for (int i = 0; i < 2; i++) {
[NSThread sleepForTimeInterval:2]; // 模拟耗时操作
NSLog(@"1---%@", [NSThread currentThread]); // 打印当前线程
}
}];
NSBlockOperation *op2 = [NSBlockOperation blockOperationWithBlock:^{
for (int i = 0; i < 2; i++) {
[NSThread sleepForTimeInterval:2]; // 模拟耗时操作
NSLog(@"2---%@", [NSThread currentThread]); // 打印当前线程
}
}];
// 3.添加依赖
[op2 addDependency:op1]; // 让op2 依赖于 op1,则先执行op1,在执行op2
// 4.添加操作到队列中
[queue addOperation:op1];
[queue addOperation:op2];
}
NSOperation 提供了queuePriority
属性, 新建操作的默认优先级是Normal
// 优先级的取值
typedef NS_ENUM(NSInteger, NSOperationQueuePriority) {
NSOperationQueuePriorityVeryLow = -8L,
NSOperationQueuePriorityLow = -4L,
NSOperationQueuePriorityNormal = 0,
NSOperationQueuePriorityHigh = 4,
NSOperationQueuePriorityVeryHigh = 8
};
线程间通信示例
/**
* 线程间通信
*/
- (void)communication {
// 1.创建队列
NSOperationQueue *queue = [[NSOperationQueue alloc]init];
// 2.添加操作
[queue addOperationWithBlock:^{
// 异步进行耗时操作
for (int i = 0; i < 2; i++) {
[NSThread sleepForTimeInterval:2]; // 模拟耗时操作
NSLog(@"1---%@", [NSThread currentThread]); // 打印当前线程
}
// 回到主线程
[[NSOperationQueue mainQueue] addOperationWithBlock:^{
// 进行一些 UI 刷新等操作
for (int i = 0; i < 2; i++) {
[NSThread sleepForTimeInterval:2]; // 模拟耗时操作
NSLog(@"2---%@", [NSThread currentThread]); // 打印当前线程
}
}];
}];
}
RunLoop
什么是RunLoop
RunLoop是通过内部维护的事件循环来对事件进行管理的一个对象
为什么main函数不会退出
UIApplicationMain函数内部默认开启了主线程的RunLoop,并执行了一段无限循环的代码
RunLoop的数据结构
NSRunLoop(Foundation)
是CFRunLoop(CoreFoundation)
的封装,提供了面向对象的API
RunLoop 相关的主要涉及五个类:
CFRunLoop
:RunLoop对象
CFRunLoopMode
:运行模式
kCFRunLoopDefaultMode
:默认模式,主线程是在这个运行模式下运行UITrackingRunLoopMode
:跟踪用户交互事件(用于 ScrollView 追踪触摸滑动,保证界面滑动时不受其他Mode影响)UIInitializationRunLoopMode
:在刚启动App时第进入的第一个 Mode,启动完成后就不再使用GSEventReceiveRunLoopMode
:接受系统内部事件,通常用不到kCFRunLoopCommonModes
:伪模式,不是一种真正的运行模式,是同步Source/Timer/Observer到多个Mode中的一种解决方案苹果对外开放的主要有kCFRunLoopDefaultMode
和kCFRunLoopCommonModes
一个比较常见的问题:滑动tableView时,定时器还会生效吗?
默认情况下RunLoop运行在kCFRunLoopDefaultMode
下,而当滑动tableView时,RunLoop切换到UITrackingRunLoopMode
,而Timer是在kCFRunLoopDefaultMode
下的,就无法接受处理Timer的事件。 怎么去解决这个问题呢?把Timer添加到UITrackingRunLoopMode
上并不能解决问题,因为这样在默认情况下就无法接受定时器事件了。
所以我们需要把Timer同时添加到UITrackingRunLoopMode
和kCFRunLoopDefaultMode
上。
那么如何把timer同时添加到多个mode上呢?就要用到NSRunLoopCommonModes
了
[[NSRunLoop currentRunLoop] addTimer:timer forMode:NSRunLoopCommonModes];
CFRunLoopSource
:输入源/事件源
CFRunLoopTimer
:定时源
CFRunLoopObserver
:观察者
RunLoop常见问题
- (void)test {
NSLog(@"1");
dispatch_async(dispatch_get_global_queue(0, 0), ^{
NSLog(@"2");
[[NSRunLoop currentRunLoop] run];
[self performSelector:@selector(test) withObject:nil afterDelay:10];
NSLog(@"3");
});
NSLog(@"4");
}
- (void)test {
NSLog(@"5");
}
输出顺序? 答案是: 1423, 5不会执行, 原因是runloop开启时应该有任务执行mode中应该有item才行, 否则会退出, 所以应调整成如下代码
dispatch_async(dispatch_get_global_queue(0, 0), ^{
NSLog(@"2");
//先添加任务
[self performSelector:@selector(test) withObject:nil afterDelay:10];
//再开启循环
[[NSRunLoop currentRunLoop] run];
NSLog(@"3");
});
如何创建一个常驻线程
@autoreleasepool {
NSRunLoop *runLoop = [NSRunLoop currentRunLoop];
[[NSRunLoop currentRunLoop] addPort:[NSMachPort port] forMode:NSDefaultRunLoopMode];
[runLoop run];
}
怎样保证子线程数据更新回到主线程更新UI时不影响滑动操作
答: 将更新UI的任务添加到主线程的NSDefaultRunLoopMode 上执行即可, 这样主线程将会在用户停止滑动之后由UITrackingRunLoopMode切换到NSDefaultRunLoopMode之后更新UI
[self performSelectorOnMainThread:@selector(reloadData)
withObject:nil waitUntilDone:NO
modes:@[NSDefaultRunLoopMode]];
自旋锁与互斥锁
自旋锁
当任务被另一个线程锁定是, 尝试执行的线程会进入等待(不会休眠), 等上一个线程解除锁定时, 立即执行下一个线程任务,
优点: 因为自旋锁不会引起调用者睡眠,所以不会进行线程调度,CPU时间片轮转等耗时操作。所有如果能在很短的时间内获得锁,自旋锁的效率远高于互斥锁
缺点: 自旋锁一直占用CPU,他在未获得锁的情况下,一直运行--自旋,所以占用着CPU,如果不能在很短的时 间内获得锁,这无疑会使CPU效率降低。自旋锁不能实现递归调用
常用自旋锁: atomic, dispatch_semaphore_t
互斥锁
当上一个线程锁定时, 下一个尝试执行的线程会进入休眠, 等上一个线程解除锁定, 下一个线程自动唤醒然后执行任务
常用互斥锁: NSLock, NSCondition, @ synchronized
UIView与CALayer的关系
UIView为CALayer提供内容,以及负责处理触摸等事件,参与响应链
CALayer负责显示内容contents
事件传递与响应者链
事件传递过程
触摸事件发生后, 系统会将事件加入到UIApplication管理的事件队列
UIApplication会从事件队列中取出最前面的事件,并将事件分发下去以便处理,通常 先发送事件给应 用的主窗口 keyWindow,主窗口再把事件发送给rootViewController
rootViewController再把事件发送给他的根View,然后会在View的层次结构中找到一个最合适的视图来处 理触摸事件, 找到合适的视图控件后,就会调用视图控件的touches方法来做具体的事件处理, 注意如果
找到合适的视图控件后,就会调用视图控件的touches方法来做具体的事件处理
touchesBegan
touchesMoved
touchesEnded
响应者链, 找到最合适处理事件的view
// hitTest:withEvent:方法的处理流程如下:
// • hitTest:withEvent:会忽略隐藏、不和用户交互的、透明度小于0.01的视图
// • 首先调用当前视图的pointInside:withEvent:方法判断触摸点是否在当前视图内;
// • 若返回NO,则hitTest:withEvent:返回nil;
// • 若返回YES,则向当前视图的所有子视图(subviews)发送hitTest:withEvent:消息,所有
// 子视图的遍历顺序是从top到bottom,即从subviews数组的末尾向前遍历,直到有子视图
// 返回非空对象或者全部子视图遍历完毕;
// • 若第一次有子视图返回非空对象,则hitTest:withEvent:方法返回此对象,处理结束;
// • 如所有子视图都返回非,则hitTest:withEvent:方法返回自身(self)。
- (UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event{
//1 判断当前view,是否可以接收事件
if (!self.userInteractionEnabled || self.alpha <= 0.01 || self.hidden) {
return nil;
}
//2 判断点击的点,是否在当前view中
if (![self pointInside:point withEvent:event]) {
return nil;
}
//3 从后往前遍历当前view的子view,是否是最合适的view,如果不是返回self
NSInteger count = self.subviews.count;
for (NSInteger i =count-1;i>=0 ; i--) {
UIView *childView = self.subviews[i]; //把当前坐标系的点,转换成子view坐标系的点
CGPoint subPoint = [self convertPoint:point toView:childView]; //寻找最合适的view
// //判断点是否在子控件中
if ([self pointInside:subPoint withEvent:event]) {
UIView *fitView = [childView hitTest:subPoint withEvent:event];
if (fitView) {
return fitView;
}
}
}
return self;
}
图像显示原理
CPU工作
GPU工作
顶点着色,图元装配,cell光栅化,片段着色,片段处理
滚动视图优化:
CPU角度
GPU角度
考虑是有有不必要的CPU渲染
是否有太多的离屏渲染
是否有图层的混合操作(透明度尽量都不要使用)
view的层次结构是否合理
cell光栅化处理
// 光栅化
layer.shouldRasterize = true
layer.rasterizationScale = UIScreen.mainScreen().scale
// 异步绘制
layer.drawsAsynchronously = true
什么是离屏渲染(Off-Screen Rendering)
与之对应的就是On-Screen Rendering(在屏渲染), GPU的渲染操作主要用于显示当前屏幕缓冲去进行的操作, 而离屏渲染指的是GPU在当前屏幕缓冲区外新开辟缓冲区进行渲染操作
离屏渲染什么时候会触发?
答案: 圆角, 蒙版, 阴影, 等
分类category
分类作用: 声明私有方法, 给已存在类扩展方法(实例方法, 类方法, 协议, 属性), 属性需要借助运行时的关联对象, 不能直接添加属性
分类特点: 运行时决议
扩展
声明私有属性,声明方法(没什么意义),声明私有成员变量
代理
代理是一种设计模式,以@protocol形式体现,一般是一对一传递。
一般以weak关键词以规避循环引用。
通知
使用观察者模式来实现的用于跨层传递信息的机制。传递方式是一对多的。
KVO
实现原理 :
KVO的实现依赖于Objective-C强大的runtime,KVO的底层实现是监听setter方法。当观察某对象A时,KVO动态机制会动态创建一个A类的子类NSKVONotifying_A,并为这个新的子类重写父类的属性的setter方法, 方法内容如下
[super setAge:age]; `
[self willChangeValueForKey:@"age"];
[self didChangeValueForKey:@"age"];
///其中后面两个方法会调用
-(void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context
作用:
能够监听某个对象属性值的改变
KVC
通过Key值直接访问对象属性,或者给属性赋值的操作, 常用操作valueforkey: / setValue: forKey:
底层执行机制: (属性name举例子)
RunTime是OC语言底层运行原理, OC代码最终都要通过runtime去调用,
常见的runtime应用如下
交换方法
通过交换方法可以实现拦截系统方法, 添加自己的需求
//常用方法
class_getClassMethod 获取类方法
class_getInstanceMethod 获取实例方法
method_exchangeImplementations 方法交换
在load方法中执行狡猾
关联对象(给分类添加属性)
const char* name = "rylsj";
- (void) setNick:(NSString *)nick {
objc_setAssociatedObject(self, &name, nick, OBJC_ASSOCIATION_COPY_NONATOMIC);
}
- (NSString*) nick {
return objc_getAssociatedObject(self, &name);
}
字典转模型
动态添加方法
- (void) rylsj_AddMethod {
//"v@:@": v表示void, @表示id, :表示 SEL
class_addMethod([self.persion class], @selector(run:), (IMP)runMethod, "v@:@");
}
void runMethod(id self, SEL _cmd, NSString* rylsj) {
NSLog(@"%@", rylsj);
}
//调用
if ([self.persion respondsToSelector:@selector(run:)]) {
[self.persion performSelector:@selector(run:) withObject:@"66 rylsj"];
} else {
NSLog(@"方法没有实现!!");
}
OC 消息转发机制 (文中代码有错误, 可以c)
首先根据receiver对象的isa指针获取它对应的class
优先在class的cache查找message方法,如果找不到,再到
methodLists查找
如果没有在class找到,再到super_class查找
一旦找到message这个方法,再依据receiver 中的self 指针找到当前的对象,调用当前对象的具体实现的方法(IMP),然后传递参数,调用实现方法。
当对象收到无法解读的消息后, 就会启动"消息转发(Message forwarding)"机制, 程序员可以通过消息转发告诉对象应该如何处理位置消息, 过程如下:
系统会调用resolveInstanceMethod
方法, 如果是类方法则会调用resolveClassMethod
, 在这个方法中我们可以增加调用方法的实现, 我们可以通过一个Person类来实践一下
@interface Person : NSObject
- (void)run;
@end
@implementation Person
///要添加的方法
void run (id self, SEL _cmd) {
NSLog(@"人跑");
}
//在这个方法中添加自己的方法实现
+ (BOOL)resolveInstanceMethod:(SEL)sel {
if(sel == @selector(run)){
//关于生成签名的类型"v@:"解释一下。每一个方法会默认隐藏两个参数,self、_cmd,self代表方 法调用者,_cmd代表这个方法的SEL,签名类型就是用来描述这个方法的返回值、参数的,v代表 返回值为void,@表示self,:表示_cmd。
class_addMethod(self, sel, (IMP)run, "v@:");
return YES;
}
return [super resolveInstanceMethod:sel];
}
@end
@implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
Person *person = [Person new];
[person run];
}
@end
//调用结果打印: "人跑"
如果未实现以上方法, 则会进入步骤二
forwardingTargetForSelector:
调用
在这个方法中可以返回你需要转发消息的对象, 这里我们可以新建一个对象Car来演示, 在Person中不是现方案一中的resolveInstanceMethod方法 改成实现forwardingTargetForSelector方法 如下:
@interface Car : NSObject
- (void)run;
@end
@implementation Car
- (void)run {
NSLog(@"车跑");
}
@end
@implementation Person
- (id)forwardingTargetForSelector:(SEL)aSelector {
return [Car new];
}
//执行结果: 车跑
通过forwardingTargetForSelector把消息转发给我们认为合适的对象去执行, 如果此步骤未实现, 则会进入下一步
通过forwardInvocation
函数 设置我们自己生成的函数签名和对象
//生成签名
- (NSMethodSignature *)methodSignatureForSelector:(SEL)selector {
NSString *sel = NSStringFromSelector(selector);
if([sel isEqualToString:@"run"]) {
return [NSMethodSignature signatureWithObjCTypes:"v@:"];
}
return [super methodSignatureForSelector:selector];
}
//设置
- (void)forwardInvocation:(NSInvocation *)invocation {
SEL selector = [invocation selector];
//新建需要转发消息的对象
Car *car = [[Car alloc] init];
if([car respondsToSelector:selector]){
[invocation invokeWithTarget:car];
}
}
//执行结果: 车跑
以上是当前类未实现方法的三种不就措施, 调用顺序从前往后, 如果实现了第一种, 后面就不会执行以此类推, 如果都没实现程序就会crash
内存布局, 从代码区到栈区地址一次从低到高
static关键字的作用
用于修饰存储类型使之成为静态存储类型
在函数内定义的静态局部变量,该变量存在内存的静态区,所以即使该函数运行结束,静态变量的值不会被销毁,函数下次运行时能仍用到这个值。
在函数外定义的静态变量——静态全局变量,该变量的作用域只能在定义该变量的文件中,不能被其他文件通过extern引用。
用于修饰链接属性使之成为内部链接属性
静态函数只能在声明它的源文件中使用。
const关键字
声明常变量,使得指定的变量不能被修改。
const int a = 5;/*a的值一直为5,不能被改变*/
const int b; b = 10;/*b的值被赋值为10后,不能被改变*/
const int *ptr; /*ptr为指向整型常量的指针,ptr的值可以修改,但不能修改其所指向的值*/
int *const ptr;/*ptr为指向整型的常量指针,ptr的值不能修改,但可以修改其所指向的值*/
const int *const ptr;/*ptr为指向整型常量的常量指针,ptr及其指向的值都不能修改*/
修饰函数形参,使得形参在函数内不能被修改,表示输入参数
int fun(const int a);或int fun(const char *str);
修饰函数返回值,使得函数的返回值不能被修改。
const char *getstr(void);使用:const *str= getstr();
const int getint(void); 使用:const int a =getint();
iOS 得内存管理机制?
采用的是引用计数的方式进行内存管理, MRC下需要用户手动管理引用计数, ARC下, 系统禁用retain,release,retainCount,autorelease等关键字
自动释放池
在当次runloop将要结束的时候调用objc_autoreleasePoolPop,并push进来一个新的AutoreleasePool, AutoreleasePoolPage是以栈为结点通过双向链表的形式组合而成,是和线程一一对应的。内部属性有parent,child对应前后两个结点,thread对应线程 ,next指针指向栈中下一个可填充的位置。
实现原理:
编译器会将 @autoreleasepool {} 改写为:
void * ctx = objc_autoreleasePoolPush;
{}
objc_autoreleasePoolPop(ctx);
objc_autoreleasePoolPush:
把当前next位置置为nil,即哨兵对象,然后next指针指向下一个可入栈位置,
AutoreleasePool的多层嵌套,即每次objc_autoreleasePoolPush,实际上是不断地向栈中插入哨兵对象。
objc_autoreleasePoolPop:
根据传入的哨兵对象找到对应位置。给上次push操作之后添加的对象依次发送release消息。回退next指针到正确的位置。
循环引用场景
代理(delegate)引起的相互循环引用
解决方案: 声明地阿里是,使用weak关键字修饰
NSTimer引起的循环引用
解决方案: 在合适的时候进行invalidate 并将指针指向nil
Block引起的循环引用
对block中使用的self进行弱化 __weak typeof(self) weakSelf = self;
注意: 并不是所有block都会造成循环引用, 只有被强引用了的block才会产生循环引用
Block的内存问题
Block的三种形式
全局Block(_NSConcreteGlobalBlock), 存储在已初始化的数据区(.data)
不使用外部变量的是全局Block, 如下:
NSLog(@"%@",[^{
NSLog(@"globalBlock");
} class]);
//输出: __NSGlobalBlock__
栈Block(_NSConcreteStackBlock), 存储在栈区(Stack)
使用外部变量但是未进行copy操作的Block是栈Block, 如下:
NSInteger num = 10;
NSLog(@"%@",[^{
NSLog(@"stackBlock:%zd",num);
} class]);
//输出: __NSStackBlock__
堆Block(_NSConcreteMallocBlock), 存储在堆区(Heap)
对栈Block进行copy操作,就是堆block,而对全局Block进行copy,仍是全局Block, 如下:
//对堆Block copy操作, 依然是全局Block
void (^globalBlock)(void) = ^{
NSLog(@"globalBlock");
};
NSLog(@"%@",[globalBlock class]);
//输出: __NSGlobalBlock__
//对栈Block进行copy,是堆Block
NSInteger num = 10;
void (^mallocBlock)(void) = ^{
NSLog(@"stackBlock:%zd",num);
};
NSLog(@"%@",[mallocBlock class]);
//输出: __NSMallocBlock__
注意: 对栈Block copy之后,并不代表着栈Block就消失了,左边的mallock是堆Block,右边被copy的仍是栈Block
- (void)testWithBlock:(dispatch_block_t)block {
block();
dispatch_block_t tempBlock = block;
NSLog(@"%@,%@",[block class],[tempBlock class]);
}
//输出: __NSStackBlock__,__NSMallocBlock__
iOS中常用数据持久化方式
NSUserDefaults, 适用于轻量数据存储
偏好设置会将所有数据保存到同一个文件中。即preference目录下的一个以此应用包名来命名的plist文件。
NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
[defaults setObject:@"jack" forKey:@"firstName"];
[defaults setInteger:10 forKey:@"Age"];
///现在可不手动调用同步方法
[defaults synchronize];
文件写入本地(数组, 字典, 字符串等) 如下:
NSString *filePath = [[self getDocumentPath] stringByAppendingString:@"fileTest.txt"];
NSDictionary *dictionary = [NSDictionary dictionaryWithObjectsAndKeys:@"[email protected]", @"email", @"[email protected]", @"emailDisplay", nil];
[dictionary writeToFile:filePath atomically:YES];
对象归档
需要对象实现NSCoding协议
NSString *filePath = [[self getDocumentPath]
- (void)encodeWithCoder:(NSCoder *)aCoder {
[aCoder encodeObject:_name forKey:kAddressCardName];
}
- (nullable instancetype)initWithCoder:(NSCoder *)aDecoder {
_name = [aDecoder decodeObjectForKey:kAddressCardName];
}
//存储
[NSKeyedArchiver archiveRootObject:obj toFile:filePath];
数据库(SQLite, CoreData)
SQLite 常用函数
sqlite3_open() //创建并打开数据库连接
sqlite3_exec() //执行数据库操作(crud)
SQLite常用语句
DDL(Data Definition Language) 数据库定义语句
--create创表,
create table t_student (id integer, name text, age inetger, score real);
--drop删除表
drop table if exists t_student;
DML(Data Manipulation Language) 数据库操作语句
--insert、
insert into t_student (name, age) values (‘wg’, 10);
--update、
update t_student set name = ‘jack’, age = 20;
--delete
delete from t_student;
DQL(Data Query Language) 数据库查询语句
-- select
-- 查指定字段
select name, age from t_student;
-- 查全部
select * from t_student ;
-- 查数量
select count (age) from t_student ;
常用关键字: where,order by,group by和having等
--where关键字示例
delete from t_student where age <= 10 or age > 30 ;
select * from t_student where age > 10 ;
-- 排序
select * from t_student order by age desc
-- limit条件限制
select * from t_student limit 4, 8 ;
别名
-- select 字段1 别名 , 字段2 别名 , … from 表名 别名 ;
select name myname, age myage from t_student;
音频
音效播放 (AVFoundation), 支持acc , wav 等格式, 特征: 比较短 30秒以内
//创建音效
NSString *path = [[NSBundle mainBundle]pathForResource:"music.acc" ofType:nil];
NSURL *url = [NSURL fileURLWithPath:path];
AudioServicesCreateSystemSoundID((__bridge CFURLRef _Nonnull)(url), &soundID);
//播放音效, 静音无振动
AudioServicesPlaySystemSound(soundID);
//静音有震动
//AudioServicesPlayAlertSound(soundID);
音乐播放 (AVAudioPlayer: 只能播放本地音乐, 不支持网络媒体)
//1.创建一个音乐对象
NSString *path = [[NSBundle mainBundle]pathForResource:@"music.mp3" ofType:nil];
NSURL *url = [NSURL fileURLWithPath:path];
AVAudioPlayer *audioPlayer = [[AVAudioPlayer alloc]initWithContentsOfURL:url error:nil];
//2. 准备, 播放
[audioPlayer prepareToPlay];
//播放
[audioPlayer play];
音频录制(AVAudioRecorder)
//保存路径
NSURL *url = [NSURL fileURLWithPath:[[NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES)lastObject]stringByAppendingPathComponent:@"recoder001.wav"]];
//录音参数设置: AVSampleRateKey采样率 AVNumberOfChannelsKey音频通道数, AVLinearPCMBitDepthKey线性音频深度,
NSMutableDictionary *settings = [NSMutableDictionary dictionary];
// 音频采样率
settings[AVSampleRateKey] = @(8000.0);
//创建音频录音机
AVAudioRecorder *recorder = [[AVAudioRecorder alloc]initWithURL:url settings:settings error:&error];
//录音
[recorder record];
//停止录音
[self.recorder stop];
网络媒体流播放(AVPlayer, 见下文视频播放描述)
视频
几种视频播放
首先在iOS平台使用播放视频,可用的选项一般有这四个,他们各自的作用和功能如下:
使用环境 | 优点 | 缺点 | |
---|---|---|---|
MPMoviePlayerController | MediaPlayer | 简单易用, 自带页面 | 不可定制 |
AVPlayerViewController | AVKit | 简单易用, 自带页面 | 不可定制 |
AVPlayer | AVFoundation | 可定制度高,功能强大 | 不支持流媒体 |
IJKPlayer | IJKMediaFramework | 定制度高,支持流媒体播放 | 使用稍复杂 |
AVPlayer
播放音频
NSURL *playUrl = [NSURL URLWithString:@"http://www.xxx.com/music.mp3"];
self.player = [[AVPlayer alloc] initWithURL:playUrl];
[self.player play];//播放
[self.player pause];//暂停
self.player.rate = 1.5//倍速
播放视频 需要创建显示层AVPlayerLayer
直播
主播端:
获取音视频授权
配置采样参数
采集数据
编码
推流
服务器端
用户端
直播常见问题 请参考文章 iOS开发之移动直播技术秒开、直播优化经验、直播问题解析、直播知识解惑
核心动画(CoreAnimation)
核心动画的分类(CAAnimation类的继承结构)
CAAnimation有三个子类: CAAnimationGroup(组动画), CAPropertyAnimation(属性动画), CATranstion(转场动画), 其中属性动画又分为: CABasicAnimation(基础动画)和CAKeyframeAnimation(关键帧动画)
基础动画