多线程
iOS多线程技术有哪几种方式
pthread、NSThread、GCD、NSOperation
1. NSOperation
NSOperationQueue *queue = [[NSOperationQueue alloc] init];(子线程) [NSOperationQueue mainQueue](主线程)
[queue addOperationWithBlock:^{}]; 添加operation
NSBlockOperation *operation1 = [NSBlockOperation blockOperationWithBlock:^{ }]
[queue addOperation:operation1];
queue.maxConcurrentOperationCount = 3; //最大并发数
[queue cancelAllOperations]; //取消
[operationB addDependency:operationA]; //线程之间的依赖
2. GCD
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
dispatch_async(dispatch_get_main_queue(), ^{
3. GCD ground
- (void)gcdGroup
{
// 使用Dispatch Group追加block到Global Group Queue,这些block如果全部执行完毕,就会执行Main Dispatch Queue中的结束处理的block。
// 创建队列组
dispatch_group_t group = dispatch_group_create();
// 获取全局并发队列
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
//dispatch_async(queue, ^{ });
dispatch_group_async(group, queue, ^{
for(int i = 30030; i > 0 ; i--) {
for(int i = 30030; i > 0 ; i--) {
}
}
NSLog(@"加载图片1");
});
dispatch_group_async(group, queue, ^{NSLog(@"加载图片2");});
dispatch_group_async(group, queue, ^{NSLog(@"加载图片3");});
// 当并发队列组中的任务执行完毕后才会执行这里的代码
dispatch_group_notify(group, dispatch_get_main_queue(), ^{
NSLog(@"合并图片");
});
}
4. 栅栏函数
- (void)gcdBarrier
{
//dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
// 1.创建并发队列 (队列要用这种创建方式才可以)
dispatch_queue_t queue = dispatch_queue_create("myQueue", DISPATCH_QUEUE_CONCURRENT);
// 2.向队列中添加任务
dispatch_async(queue, ^{ // 1.2是并行的
for(int i = 30030; i > 0 ; i--) {
for(int i = 30030; i > 0 ; i--) {
}
}
NSLog(@"任务1, %@",[NSThread currentThread]);
});
dispatch_async(queue, ^{
NSLog(@"任务2, %@",[NSThread currentThread]);
});
dispatch_barrier_async(queue, ^{
NSLog(@"任务 barrier, %@", [NSThread currentThread]);
});
dispatch_async(queue, ^{ // 这两个是同时执行的
NSLog(@"任务3, %@",[NSThread currentThread]);
});
dispatch_async(queue, ^{
NSLog(@"任务4, %@",[NSThread currentThread]);
});
// 输出结果: 任务1 任务2 ——》 任务 barrier ——》任务3 任务4
// 其中的任务1与任务2,任务3与任务4 由于是并行处理先后顺序不定。
}
5. 同步死锁
//使用sync函数在当前串行队列执行任务就会产生死锁
- (void)deadLock
{
NSLog(@"1");
dispatch_sync(dispatch_get_global_queue(0, 0), ^{
NSLog(@"2");
});
NSLog(@"3");
//dispatch_sync:立马在当前线程执行
dispatch_sync(dispatch_get_main_queue(), ^{ //执行到dispatch_sync同步方法,主线程进入等待,不往下执行,死锁
NSLog(@"4");
});
NSLog(@"5");
//但是如果是换一个队列,或者使用并发队列的话就不会死锁
//dispatch_get_main_queue,队列先进先出,执行完当前任务后才能执行新的任务
//dispatch_async,异步,不要求立马在当前线程执行
dispatch_async(dispatch_get_main_queue(), ^{
NSLog(@"2");
});
}
死锁
死锁是指两个或两个以上的进程在执行过程中,由于竞争资源或者由于彼此通信而造成的一种阻塞的现象,若无外力作用,它们都将无法推进下去
四个必要条件:
1)互斥条件:指进程对所分配到的资源进行排它性使用,即在一段时间内某资源只由一个进程占用。如果此时还有其它进程请求资源,则请求者只能等待,直至占有资源的进程用毕释放。
2)请求和保持条件:指进程已经保持至少一个资源,但又提出了新的资源请求,而该资源已被其它进程占有,此时请求进程阻塞,但又对自己已获得的其它资源保持不放。
3)不剥夺条件:指进程已获得的资源,在未使用完之前,不能被剥夺,只能在使用完时由自己释放。
4)环路等待条件:指在发生死锁时,必然存在一个进程——资源的环形链,即进程集合{P0,P1,P2,···,Pn}中的P0正在等待一个P1占用的资源;P1正在等待P2占用的资源,……,Pn正在等待已被P0占用的资源。
预防死锁:
1.互斥条件一般都不会去更改
2.“ 一次性分配”方案,一次性申请所有所需资源;(针对请求和保持条件)
3.申请了A资源后,必须释放A才能申请B(针对请求和保持条件)
4.低优先级线程的资源可以被高优先级线程抢占(针对不剥夺条件)
5.按顺序请求资源,将系统中的所有资源统一编号,所有申请必须按照资源的编号顺序(升序)提出(针对环路等待条件)
runloop
它是运行循环,它内部就是do-while循环,在这个循环内部不断地处理各种任务。
runloop五个相关的类
a.CFRunloopRef
b.CFRunloopModeRef【Runloop的运行模式】
c.CFRunloopSourceRef【Runloop要处理的事件源】
d.CFRunloopTimerRef【Timer事件】
e.CFRunloopObserverRef【Runloop的观察者(监听者)】
CFRunLoopModeRef代表RunLoop的运行模式,一个RunLoop包含若干个Mode,每个Mode又包含若干个Source/Timer/Observer
每次RunLoop启动时,只能指定其中一个Mode,这个Mode被称作CurrentMode
如果需要切换Mode,只能退出Loop ,在重新指定一个Mode进入
这样做主要是为了分割开不同组的Source/Timer/Observer,让其互不影响
runloop模式 (系统默认注册了5个Mode常用的有3个)
kCFRynLoopDefaultMode:App的默认Mode,通常主线程是在这个Mode下运行
UITrackingRunLoopMode:界面跟踪Mode,用于ScrollView追踪触摸滑动,保证界面滑动时不受其他Mode影响 (正常情况下不会执行定时器,拖拽UIScrollview的时候才会执行定时器任务)
kCFRunLoopCommonModes:这是一个占位用的Mode,不是一种真正的Mode (NSRunLoopCommonModes = NSDefaultRunLoopMode && UITrackingRunLoopMode)
UIInitializationRunLoopMode:在刚启动App时进入的第一个Mode,启动完成后不再使用
GSEventReceiveRunLoopMode:接受系统事件的内部Mode,通常用不到
CFRunLoopSourseRef事件源
有两种:
sourse0:非基于port的,自己写的方法、响应
sourse1:基于port的,系统提供的
runloop和线程的关系
主线程默认开启的runloop, 一个线程对应一个runloop,线程也可以没有runloop,runloop保证了线程的持续运行。
Runloop的启动方法
[[NSRunLoop mainRunLoop]run]; //不建议使用,因为这个接口会导致Run Loop永久性的运行在NSDefaultRunLoopMode模式,即使使用CFRunLoopStop(runloopRef);也无法停止Run Loop的运行,要用特殊的方法停止
[[NSRunLoop mainRunLoop]runUntilDate:[NSDate dateWithTimeIntervalSinceNow:10]]; //此方法有停止时间
可参考:https://www.jianshu.com/p/2d3c8e084205
NSTimer
如何让定时器调用一个类方法
定时器只能调用实例方法,但是可以在这个实例方法里面调用静态方法。
使用定时器需要注意
定时器一定要加入RunLoop中,并且选好model才能运行。scheduledTimerWithTimeInterval方法创建一个定时器并加入到RunLoop中所以可以直接使用。
如果定时器的repeats选择YES说明这个定时器会重复执行,一定要在合适的时机调用定时器的invalid。不能在dealloc中调用,因为一旦设置为repeats 为yes,定时器会强持有self,导致dealloc永远不会被调用,这个类就永远无法被释放。比如可以在viewDidDisappear中调用,这样当类需要被回收的时候就可以正常进入dealloc中了。
[NSTimer scheduledTimerWithTimeInterval:1 target:self selector:@selector(timerMethod) userInfo:nil repeats:YES];
-(void)timerMethod
{
//调用类方法
[[self class] staticMethod];
}
-(void)invalid
{
[timer invalid];
timer = nil;
}
NSTimer创建后,会在哪个线程运行。
手动创建的Timer,加入到哪个线程的RunLoop中就运行在哪个线程。
但用scheduledTimerWithTimeInterval创建的,在哪个线程创建就会被加入哪个线程的RunLoop中就运行在哪个线程(因为它被加入当前线程的runloop里)
制作一个定时器,使它在滑动列表时不受影响
解决方法其一是将timer加入到NSRunloopCommonModes中。其二是将timer放到另一个线程中,然后开启另一个线程的runloop,这样可以保证与主线程互不干扰。
// 方法1
[[NSRunLoop currentRunLoop] addTimer:timer forMode:NSRunLoopCommonModes];
// 方法2
dispatch_async(dispatch_get_global_queue(0, 0), ^{
timer = [NSTimer scheduledTimerWithTimeInterval:1 target:self selector:@selector(repeat:) userInfo:nil repeats:true];
[[NSRunLoop currentRunLoop] run];
});
iOS开发中方法延迟执行的几种方式
- performSelector方法
- NSTimer定时器
- NSThread线程的sleep
- GCD (dispatch after 和 设置精准定时器)
//使用GCD制作精准定时器
- (void)gcdTime
{
/** 创建定时器对象
* para1: DISPATCH_SOURCE_TYPE_TIMER 为定时器类型
* para2-3: 中间两个参数对定时器无用
* para4: 最后为在什么调度队列中使用
*/
_gcdTimer = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, 0, dispatch_get_global_queue(0, 0));
/** 设置定时器
* para2: 任务开始时间
* para3: 任务的间隔
* para4: 可接受的误差时间,设置0即不允许出现误差
* Tips: 单位均为纳秒
*/
dispatch_source_set_timer(_gcdTimer, DISPATCH_TIME_NOW, 2.0 * NSEC_PER_SEC, 0.0 * NSEC_PER_SEC);
/** 设置定时器任务
* 可以通过block方式
* 也可以通过C函数方式
*/
dispatch_source_set_event_handler(_gcdTimer, ^{
static int gcdIdx = 0;
NSLog(@"GCD Method: %d", gcdIdx++);
NSLog(@"%@", [NSThread currentThread]);
if(gcdIdx == 5) {
// 终止定时器
dispatch_suspend(_gcdTimer);
}
});
// 启动任务,GCD计时器创建后需要手动启动
dispatch_resume(_gcdTimer);
}
Runtime
Runtime又叫运行时,是一套底层的C语言API,其为iOS内部的核心之一,我们平时编写的OC代码,底层都是基于它来实现的。
Runtime实现的机制是什么
1). 使用时需要导入的头文件 <objc/message.h> <objc/runtime.h>
2). Runtime 运行时机制,它是一套C语言库。
3). 实际上我们编写的所有OC代码,最终都是转成了runtime库的东西。
比如:
类转成了 Runtime 库里面的结构体等数据类型,
方法转成了 Runtime 库里面的C语言函数,
平时调方法都是转成了 objc_msgSend 函数(所以说OC有个消息发送机制)
OC是动态语言,每个方法在运行时会被动态转为消息发送,即:objc_msgSend(receiver, selector)。
[stu show]; 在objc动态编译时,会被转意为:objc_msgSend(stu, @selector(show));
4). 因此,可以说 Runtime 是OC的底层实现,是OC的幕后执行者。
Method Swizzle什么时候用
它是一个在运行时候改变一个已存在的选择器对应的实现的过程
1). 可以用 method_exchangeImplementations 来交换2个方法中的IMP。
2). 可以用 class_replaceMethod 来修改类。
3). 可以用 method_setImplementation 来直接设置某个方法的IMP。
(注:IMP有点类似函数指针,指向具体的方法实现)
** _objc_msgForward**
_objc_msgForward是 IMP 类型,用于消息转发的:当向一个对象发送一条消息,但它并没有实现的时候,_objc_msgForward会尝试做消息转发。
其他
在#import <objc/runtime.h> 下能看到相关的方法,
objc_getClass()和class_copyMethodList()获取过私有API;
Method method1 = class_getInstanceMethod(cls, sel1);
方法和选择器有何不同
selector是一个方法的名字,方法是一个组合体,包含了名字和实现
OC中的反射机制
1). class反射
通过类名的字符串形式实例化对象。
Class class = NSClassFromString(@"student");
Student *stu = [[class alloc] init];
将类名变为字符串。
Class class =[Student class];
NSString *className = NSStringFromClass(class);
2). SEL的反射
通过方法的字符串形式实例化方法。
SEL selector = NSSelectorFromString(@"setName");
[stu performSelector:selector withObject:@"Mike"];
将方法变成字符串。
NSStringFromSelector(@selector*(setName:));
网友评论