一.在查看优化经验文章的时候,经常会看到关于+load
和initialize
两个方法,其中+load
方法,是只要程序启动了,程序就会将所有代码加载到内存的代码区,此时,这个+load
方法就会执行,同时,此方法会在main
函数执行前就调用,而 initialize
是该类初始化时候才调用。所以推荐在做启动优化的时候,要检查系统中的无用类、过期类,合并多余类、中间类,因为每次启动app,这些都会加载一遍。
二.启动时间大体可分为两步(冷启动热启动就算了,不区分了):
第一步:main函数加载之前,系统会加载动态库、引入类的+load
方法等,所以需要:删减不必要的动态库,定期检查系统中无用类,过期类,合并多余中间类(多说一嘴,中小项目没必要用MVVM),除了系统动态库,个人创建的动态库多常见于一些自己封装的UIKit,可以考虑封装成静态库,或者合并。
第二步:main函数加载之后,此时,主要是针对业务模块进行优化,
1.日志、统计、广告业等必须一开始加载的功能,保留在didFinishLaunchingWithOptions
中。
2.推送、项目配置、环境配置、信息初始化等必须在主页面进入前完成的工作,放到广告页的viewDidAppear里进行。
3.其他SDK和配置事件,由于启动时间不是必须的,所以可以放到首页的viewDidAppear里进行。
4.初始化tabbar的时候,除了首页外,延迟其他几个控制器的创建时间,比如“个人中心”,“订单”等模块,用空的viewController占位,第一次点击的时候再初始化加载。
其他更厉害的优化,可以参考抖音团队分享的:基于二进制文件重排的解决方案。http://www.zyiz.net/tech/detail-127196.html
三:KVO是如何实现监听的
KVO底层使用了 isa-swizling的技术.
OC中每个对象/类都有isa指针, isa 表示这个对象是哪个类的对象.
当使用KVO给对象的某个属性注册了一个 observer,系统会创建一个新的中间类(intermediate class)继承原来的class,把该对象的isa指针指向中间类(这样,这个对象的类其实就改变为这个新创建的中间类)。
然后中间类会重写该属性的setter方法,当这个属性的值更改的时候,会在调用setter之前调用willChangeValueForKey, 调用setter之后调用didChangeValueForKey,以此通知所有观察者值发生更改。
之后重写了 -class 方法,企图欺骗我们这个类没有变,就是原本那个类。
四:NSTimer的循环引用和Block循环引用有什么区别?
__weak typeof(self) weakSelf = self;
self.timer = [NSTimer timerWithTimeInterval:1 target:weakSelf selector:@selector(timerAction) userInfo:nil repeats:YES];
同样是用__weak修饰self,为什么NSTimer会导致循环引用,而block不会呢?
因为block中,引用的是weakSelf这个指针,而NSTimer引用的是weakSelf指针指向的内存地址,所以只要NSTimer不释放,就会一直持有weakSelf指向的self,而因为timer被runloop强持有,runloop又是常驻的,所以timer一直没有被释放。
NSTimer解决循环引用的几种方式:
1.离开页面的时候,置空timer
2.ios 10之后,系统帮我们对NSTimer进行了block的封装,防止block循环引用即可
3.做个中间类,帮助控制器持有NSTimer
4.用GCD的定时器
五:GCD什么情况下会开启多线程?
1.同步函数串行队列:
不会开启新线程,在当前线程执行任务
任务串行执行,一个接着一个
会产生死锁
2.同步函数并行队列:
不会开启新线程,在当前线程执行任务
任务依然一个接一个
3.异步函数串行队列:
开启一个新线程,任务一个接一个执行
4.异步函数并行队列:
开启多个线程,任务并发执行
5.创建队列的方式:
- (void)test {
//串行队列
dispatch_queue_t serial = dispatch_queue_create("com", DISPATCH_QUEUE_SERIAL);
//并发队列
dispatch_queue_t concurrent = dispatch_queue_create("com", DISPATCH_QUEUE_CONCURRENT);
//主队列
dispatch_queue_t mainQueue = dispatch_get_main_queue();
//全局队列
dispatch_queue_t globalQueue = dispatch_get_global_queue(0, 0);
}
根据官网介绍,多线程创建需要耗费一定的内核空间,主线程一般是1MB,非主线程一般是16Kb到512KB,创建线程的时间大概是90微秒,切换线程,也会消耗一定的资源时间
六:栅栏函数
1.dispatch_barrier_async
: 当前任务队列中,前面的任务执行完毕才会执行barrier中的逻辑,以及barrier后加入该队列的任务,但是不影响该栅栏函数所在队列后面函数的执行。
2.dispatch_barrier_sync
:作用相同,区别是会堵塞队列后面的函数执行。
举例:
dispatch_queue_t concurrentQueue = dispatch_queue_create("testQueue", DISPATCH_QUEUE_CONCURRENT);
dispatch_async(concurrentQueue, ^{
NSLog(@"1");
});
dispatch_async(concurrentQueue, ^{
NSLog(@"2");
});
dispatch_barrier_async(concurrentQueue, ^{
NSLog(@"3:%@",[NSThread currentThread]);
});
dispatch_async(concurrentQueue, ^{
NSLog(@"4");
});
NSLog(@"5");
}
上面代码打印出来的数据,1,2,5的排序是不固定的,,但是3、4一定是最后两个,3一定在4前面。
3.栅栏函数在全局队列中是不生效的,因为全局队列中,不仅有你的任务,还有其他系统的任务,如果加barrier
,不仅会影响你自己的任务,还会影响系统的任务,对于全局队列来说,barrier
相当于普通的异步函数。
4.栅栏函数可以当成锁来用:
NSMutableArray *array = [NSMutableArray array];
dispatch_queue_t concurrentQueue = dispatch_queue_create("testQueue", DISPATCH_QUEUE_CONCURRENT);
for (int i = 0; i < 1000; i++) {
dispatch_async(concurrentQueue, ^{
[array addObject:@(i)];
});
}
上面代码中,多线程操作一个数组array,在addObject的时候有可能存在同一时间对同一块内存空间写入数据(因为多线程是无序的,i=4的时候和i=100的时候,有可能i=100先运行,那么这时候往数组里插数据,就有可能数据错误,此时就会崩溃报错)。
比如写第3个数据的时候,当前数组中数据是(1,2)
这个时候有2个线程同时写入数据就存在了(1,2,3)
和(1,2,4)
这个时候数据就发生了混乱造成了错误。
将数组添加元素的操作放入dispatch_barrier_async
中:
dispatch_queue_t concurrentQueue = dispatch_queue_create("Hotpot", DISPATCH_QUEUE_CONCURRENT);
for (int i = 0; i < 1000; i++) {
dispatch_async(concurrentQueue, ^{
dispatch_barrier_async(concurrentQueue , ^{
[array addObject:@(i)];
});
});
}
七.信号量
dispatch_semaphore_create(long value);
初始化一个值为value的信号量,value等于几,就支持最多几个线程并发访问。
dispatch_semaphore_wait(dispatch_semaphore_t dsema, dispatch_time_t timeout);
此函数会让信号量-1(通常来讲,我们会将信号量初始化为1),信号量小于0,则线程进入堵塞、等待状态。
dispatch_semaphore_signal(dispatch_semaphore_t dsema);
此函数会释放信号量,让信号量+1,信号量不为1,则该线程继续执行
信号量的作用:
1.可以限制并发队列里最大并发数
2.配合线程组,解决多个网络请求后,统一刷新页面、处理数据的问题
3.保护数据上锁
dispatch_queue_t queue = dispatch_queue_create("并发队列", DISPATCH_QUEUE_CONCURRENT);
NSMutableArray *array = [NSMutableArray array];
dispatch_semaphore_t semaphore = dispatch_semaphore_create(1);
for (int i = 0; i < 10000; i++) {
dispatch_async(queue, ^{
dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);//此时当前并发队列,只有一个线程能访问
NSLog(@"添加的值:%d 当前线程%@",i,[NSThread currentThread]);
[array addObject:@(i)];
dispatch_semaphore_signal(semaphore);
});
}
dispatch_queue_t queue = dispatch_get_global_queue(0, 0);
dispatch_semaphore_t sem = dispatch_semaphore_create(0);
dispatch_async(queue, ^{
dispatch_semaphore_wait(sem, DISPATCH_TIME_FOREVER);
NSLog(@"1 start");
NSLog(@"1 end");
});//由于初始值为0,wait后,信号量-1,小于0,所以后面的打印方法不执行
dispatch_async(queue, ^{
sleep(2);
NSLog(@"2 start");
NSLog(@"2 end");
dispatch_semaphore_signal(sem);
});//此异步任务先执行
打印出来的值:
2 start
2 end
1 start
1 end
八:线程组:
控制线程任务执行顺序,类似于栅栏函数的作用
dispatch_group_t group = dispatch_group_create();
dispatch_queue_t queue = dispatch_get_global_queue(0, 0);
dispatch_queue_t queue1 = dispatch_queue_create("test", DISPATCH_QUEUE_CONCURRENT);
dispatch_group_async(group, queue, ^{
sleep(3);
NSLog(@"1");
});
dispatch_group_async(group, queue1, ^{
sleep(2);
NSLog(@"2");
});
dispatch_group_async(group, queue1, ^{
sleep(1);
NSLog(@"3");
});
dispatch_group_async(group, queue, ^{
NSLog(@"4");
});
dispatch_group_notify(group, dispatch_get_global_queue(0, 0), ^{
NSLog(@"5");
});
任务5永远在任务1、2、3、4之后执行。
网友评论