面试基础知识
一、内存
1.内存区域
image
栈区:速度快,系统分配,随函数创建和释放。
堆区:ARC MRC
全局区(静态区):全局变量
文字常量区:常量 字符串
程序代码区:程序二进制代码
2.内存管理
管理的对象:继承自NSObject的对象或者C的对象
如果在property后边加上retain,系统就会自动帮我们生成getter/setter方法内存管理的代码,
如果在property后边加上assign,系统就不会帮我们生成set方法内存管理的代码
自动释放池autoreleasepool
@autoreleasepool
{ // 创建一个自动释放池
Person *p = [[Person new] autorelease];
// 将代码写到这里就放入了自动释放池,如果不加autorelease,则没有放入自动释放池
} // 销毁自动释放池(会给池子中所有对象发送一条release消息)
ARC MRC
二、架构 设计模式
架构是设计模式的集合
1.追求:易开发维护
2.原则:Solid原则
simple单一功能原则 open close 开闭可扩展 里氏替换原则 接口隔离原则 最少知识原则
设计模式:代理 观察者 单例 工厂 组件化 消息绑定
MVVM:分层:
系统层:UIKit
通用层:(通用框架 设计模式)ReactiveCocoa等
公司通用业务层:Model ViewModel DataManager
app通用业务层:CommonUI
app功能层:ActivityViewController
三、网络
tcp和udp是传输层协议,tcp面向连接,连接需要三次握手,udp无连接,
http是应用层协议,基于tcp,是短链接
socket软件抽象层,基于tcp是长连接(即时通讯),基于udp(游戏)。
四、性能优化
性能优化从以下几方面入手:架构,内存,UIKit,数据,网络,技术选型。
1.架构:减少动态库依赖,减少类及层级,文件数量,工程大小,使用合理的设计模式如工厂、单例、组件化、重用、懒加载。
2.内存及CPU优化:
a.使用ARC,避免内存泄漏,使用instrument和第三方库优化内存。
b.监听内存警告.
c.使用Autorelease Pool优化循环。
d.避免日期格式转换。
3.UIKit:减少View层级,减少设置透明度,避免离屏渲染,使用reuse,避免使用较大xib文件(加载一个XIB的时候所有内容都被放在了内存里)。
4.数据:
合理使用数据格式及数据处理流程。
合理使用缓存。
5.网络:
使用NSURLSession代替NSURLConnection。
使用https提高安全性。
约定合理的接口交互。
监听网络状态。
使用离散型网络封装。
封装网络错误处理。
五、多线程
知识点:pthread NSThread GCD
任务和队列
主线程同步会死锁
线程间的通信
栅栏方法:dispatch_barrier_async
延时执行方法:dispatch_after
只执行一次:dispatch_once
快速遍历方法:dispatch_apply
队列组:dispatch_group及监听dispatch_group_notify
线程等待dispatch_group_wait
dispatch_group_enter、dispatch_group_leave
信号量:dispatch_semaphore
线程同步
线程安全
1.NSThread封装pthread
NSThread *thread = [[NSThread alloc] initWithTarget:self selector:@selector(run:) object:@"jack"];
[self performSelectorInBackground:@selector(run:) withObject:@"jack"];
[NSThread sleepForTimeInterval:2.0];
2.GCD:Grand Central Dispatch(GCD) 是 Apple 开发的一个多核编程的较新的解决方法。
优点:GCD 可用于多核的并行运算
GCD 会自动利用更多的 CPU 内核(比如双核、四核)
GCD 会自动管理线程的生命周期(创建线程、调度任务、销毁线程)
同步执行(sync)异步执行(async)
// 同步执行任务创建方法
dispatch_sync(queue, ^{
// 这里放同步执行任务代码
});
// 异步执行任务创建方法
dispatch_async(queue, ^{
// 这里放异步执行任务代码
});
不同队列关键字:
主队列 dispatch_get_main_queue
全局并发队列 DISPATCH_QUEUE_CONCURRENT
串行队列 DISPATCH_QUEUE_SERIAL
image.png
/**
* 同步执行 + 主队列
* 特点(主线程调用):互等卡主不执行。
* 特点(其他线程调用):不会开启新线程,执行完一个任务,再执行下一个任务。
*/
- (void)syncMain {
NSLog(@"currentThread---%@",[NSThread currentThread]); // 打印当前线程
NSLog(@"syncMain---begin");
dispatch_queue_t queue = dispatch_get_main_queue();
dispatch_sync(queue, ^{
// 追加任务1
for (int i = 0; i < 2; ++i) {
[NSThread sleepForTimeInterval:2]; // 模拟耗时操作
NSLog(@"1---%@",[NSThread currentThread]); // 打印当前线程
}
});
dispatch_sync(queue, ^{
// 追加任务2
for (int i = 0; i < 2; ++i) {
[NSThread sleepForTimeInterval:2]; // 模拟耗时操作
NSLog(@"2---%@",[NSThread currentThread]); // 打印当前线程
}
});
dispatch_sync(queue, ^{
// 追加任务3
for (int i = 0; i < 2; ++i) {
[NSThread sleepForTimeInterval:2]; // 模拟耗时操作
NSLog(@"3---%@",[NSThread currentThread]); // 打印当前线程
}
});
NSLog(@"syncMain---end");
}
主队列同步执行死锁。原因:这是因为我们在主线程中执行syncMain方法,相当于把syncMain任务放到了主线程的队列中。
而同步执行会等待当前队列中的任务执行完毕,才会接着执行。
那么当我们把任务1追加到主队列中,任务1就在等待主线程处理完syncMain任务。
而syncMain任务需要等待任务1执行完毕,才能接着执行。
异步才会开启新线程,同步不开新线程
线程间通信:
/**
* 线程间通信
*/
- (void)communication {
// 获取全局并发队列
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
// 获取主队列
dispatch_queue_t mainQueue = dispatch_get_main_queue();
dispatch_async(queue, ^{
// 异步追加任务
for (int i = 0; i < 2; ++i) {
[NSThread sleepForTimeInterval:2]; // 模拟耗时操作
NSLog(@"1---%@",[NSThread currentThread]); // 打印当前线程
}
// 回到主线程
dispatch_async(mainQueue, ^{
// 追加在主线程中执行的任务
[NSThread sleepForTimeInterval:2]; // 模拟耗时操作
NSLog(@"2---%@",[NSThread currentThread]); // 打印当前线程
});
});
}
3.GCD 栅栏方法:dispatch_barrier_async
dispatch_barrier_async函数会等待前边追加到并发队列中的任务全部执行完毕之后,再将指定的任务追加到该异步队列中。
4.GCD 延时执行方法:dispatch_after
5.只执行一次dispatch_once
6.GCD 快速遍历方法:dispatch_apply,可以同步或者异步,推荐异步。
同步:
- (void)apply {
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
NSLog(@"apply---begin");
dispatch_apply(6, queue, ^(size_t index) {
NSLog(@"%zd---%@",index, [NSThread currentThread]);
});
NSLog(@"apply---end");
}
打印结果:先执行打印begin ,然后0到6线程不排序,最后打印end
异步:
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT,0);
//异步(推荐)
dispatch_async(queue,^{
//Global Dispatch Queue
//等待dispatch_apply函数中全部处理执行结束
dispatch_apply([array count],queue,^(size_t index){
//并列处理包含在NSArray对象的全部对象
NSLog(@"%zu:%@",index,[array objectAtIndex:index]);
});
//dispatch_apply函数中的处理全部执行结束
//在Main Dispatch Queue中非同步执行
dispatch_async(dispatch_get_main_queue(),^{
//在Main Dispatch Queue中执行处理
//用户界面更新等
NSLog(@"done");
});
});
打印结果和同步类似,只是会开多线程,推荐。
7、GCD 队列组:dispatch_group
/**
* 队列组 dispatch_group_notify
*/
- (void)groupNotify {
NSLog(@"currentThread---%@",[NSThread currentThread]); // 打印当前线程
NSLog(@"group---begin");
dispatch_group_t group = dispatch_group_create();
dispatch_group_async(group, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
// 追加任务1
for (int i = 0; i < 2; ++i) {
[NSThread sleepForTimeInterval:2]; // 模拟耗时操作
NSLog(@"1---%@",[NSThread currentThread]); // 打印当前线程
}
});
dispatch_group_async(group, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
// 追加任务2
for (int i = 0; i < 2; ++i) {
[NSThread sleepForTimeInterval:2]; // 模拟耗时操作
NSLog(@"2---%@",[NSThread currentThread]); // 打印当前线程
}
});
dispatch_group_notify(group, dispatch_get_main_queue(), ^{
// 等前面的异步任务1、任务2都执行完毕后,回到主线程执行下边任务
for (int i = 0; i < 2; ++i) {
[NSThread sleepForTimeInterval:2]; // 模拟耗时操作
NSLog(@"3---%@",[NSThread currentThread]); // 打印当前线程
}
NSLog(@"group---end");
});
}
7.1 GCD 队列组中的任务等待 dispatch_group_wait
dispatch_group_enter 标志着一个任务追加到 group,执行一次,相当于 group 中未执行完毕任务数+1
dispatch_group_leave 标志着一个任务离开了 group,执行一次,相当于 group 中未执行完毕任务数-1。
当 group 中未执行完毕任务数为0的时候,才会使dispatch_group_wait解除阻塞,以及执行追加到dispatch_group_notify中的任务。
7.2 GCD 信号量:dispatch_semaphore线程同步
异步执行耗时任务,并使用异步执行的结果进行一些额外的操作。
- (NSArray *)tasksForKeyPath:(NSString *)keyPath {
__block NSArray *tasks = nil;
dispatch_semaphore_t semaphore = dispatch_semaphore_create(0);
[self.session getTasksWithCompletionHandler:^(NSArray *dataTasks, NSArray *uploadTasks, NSArray *downloadTasks) {
if ([keyPath isEqualToString:NSStringFromSelector(@selector(dataTasks))]) {
tasks = dataTasks;
} else if ([keyPath isEqualToString:NSStringFromSelector(@selector(uploadTasks))]) {
tasks = uploadTasks;
} else if ([keyPath isEqualToString:NSStringFromSelector(@selector(downloadTasks))]) {
tasks = downloadTasks;
} else if ([keyPath isEqualToString:NSStringFromSelector(@selector(tasks))]) {
tasks = [@[dataTasks, uploadTasks, downloadTasks] valueForKeyPath:@"@unionOfArrays.self"];
}
dispatch_semaphore_signal(semaphore);
}];
dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
return tasks;
}
8.线程同步与线程安全
当多个线程改写同一数据时,若不加安全锁可能会数据错误
/**
* 线程安全:使用 semaphore 加锁
* 初始化火车票数量、卖票窗口(线程安全)、并开始卖票
*/
- (void)initTicketStatusSave {
NSLog(@"currentThread---%@",[NSThread currentThread]); // 打印当前线程
NSLog(@"semaphore---begin");
semaphoreLock = dispatch_semaphore_create(1);
self.ticketSurplusCount = 50;
// queue1 代表北京火车票售卖窗口
dispatch_queue_t queue1 = dispatch_queue_create("net.bujige.testQueue1", DISPATCH_QUEUE_SERIAL);
// queue2 代表上海火车票售卖窗口
dispatch_queue_t queue2 = dispatch_queue_create("net.bujige.testQueue2", DISPATCH_QUEUE_SERIAL);
__weak typeof(self) weakSelf = self;
dispatch_async(queue1, ^{
[weakSelf saleTicketSafe];
});
dispatch_async(queue2, ^{
[weakSelf saleTicketSafe];
});
}
/**
* 售卖火车票(线程安全)
*/
- (void)saleTicketSafe {
while (1) {
// 相当于加锁
dispatch_semaphore_wait(semaphoreLock, DISPATCH_TIME_FOREVER);
if (self.ticketSurplusCount > 0) { //如果还有票,继续售卖
self.ticketSurplusCount--;
NSLog(@"%@", [NSString stringWithFormat:@"剩余票数:%d 窗口:%@", self.ticketSurplusCount, [NSThread currentThread]]);
[NSThread sleepForTimeInterval:0.2];
} else { //如果已卖完,关闭售票窗口
NSLog(@"所有火车票均已售完");
// 相当于解锁
dispatch_semaphore_signal(semaphoreLock);
break;
}
// 相当于解锁
dispatch_semaphore_signal(semaphoreLock);
}
}
9.NSOperation、NSOperationQueue是基于GCD封装的
六、block
1.block本质上也是一个OC对象,继承自NSObject,有isa指针,函数指针,捕获变量,copy函数,dispose函数。block是封装了函数调用以及函数调用环境,能自动捕获周围变量,使用copy或者strong修饰
2.类型:有三种类型NSGlobalBlock,NSStackBlock ,NSMallocBlock
在苹果使用ARC管理之前,Block的内存管理需要区分是Global(全局)、Stack(栈)还是Heap(堆)。而在使用了ARC之后,苹果自动会将所有原本应该放在栈中的Block全部放到堆中。
3.基本数据类型变量的捕获
image
只要是局部变量,不管是auto 变量,还是static 变量,block都会捕获.不同的是,对于auto 变量,block是保存值,而static 变量 是保存的指针.
如果是全局变量,根本不需要捕获,直接访问
4.对象类型变量捕获
如果block被拷贝到堆上, copy函数会调用_Block_object_assign, 该函数会根据变量的修饰符(__strong,__weak,unsafe_unretained)来做出相应的操作, 行成强引用或者弱引用.
如果block从堆中移除,dispose函数会调用_Block_object_dispose函数,自动释放引用的变量。
5.循环引用:Block的拥有者在Block作用域内部又引用了自己,因此导致了Block的拥有者永远无法释放内存,就出现了循环引用的内存泄漏。
解决方法,将“自己”弱化,再被block引用,自己被释放时,block肯定需要被释放了。所以不存在自己被提前释放的问题。
七、NSObject
类对象就是一个结构体
八、数据安全
九、缓存
1.NSUserDefaults
2.Plist文件
3.解归档
4.沙盒。
document:同步icloud,存储重要数据,用户数据,定期备份数据。
library:包含caches和preference,caches存放大容量文件,NSUserDefaults文件存放在prefenrence。
tmp:临时文件(系统会不定期删除里面的文件)。
5.sqlite
6.coredata
十、H5
实现方式:cordova 、JavaScriptCore 、javaScriptBridge
十一:category
对象方法,类方法,协议,和属性都可以找到对应的存储方式。没有成员变量。
十二:runtime消息传递 消息转发
1.runtime
Objective-C 扩展了 C 语言,并加入了面向对象特性和 Smalltalk 式的消息传递机制。而这个扩展的核心是一个用 C 和 编译语言 写的 Runtime 库。它是 Objective-C 面向对象和动态机制的基石。
高级编程语言想要成为可执行文件需要先编译为汇编语言再汇编为机器语言,机器语言也是计算机能够识别的唯一语言,但是OC并不能直接编译为汇编语言,而是要先转写为纯C语言再进行编译和汇编的操作,从OC到C语言的过渡就是由runtime来实现的。然而我们使用OC进行面向对象开发,而C语言更多的是面向过程开发,这就需要将面向对象的类转变为面向过程的结构体。
2.消息传递:遍历子类到父类消息列表,经常被调用的函数缓存下来,去提高函数查询的效率
所以如果调用了一个方法,就会进行一次发送消息会在相关的类对象中搜索方法列表,如果找不到则会沿着继承树向上一直搜索知道继承树根部(通常为NSObject),如果还是找不到并且消息转发都失败了就回执行doesNotRecognizeSelector:方法报unrecognized selector错。那么消息转发到底是什么呢?接下来将会逐一介绍最后的三次机会。
首先它会发送-methodSignatureForSelector:消息获得函数的参数和返回值类型。如果-methodSignatureForSelector:返回nil,Runtime则会发出-doesNotRecognizeSelector:消息,程序这时也就挂掉了。如果返回了一个函数签名,Runtime就会创建一个NSInvocation对象并发送-forwardInvocation:消息给目标对象。
lector:(SEL)aSelector;
方案三:
- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector;
- (void)forwardInvocation:(NSInvocation *)anInvocation;
前一套方案实现后一套方法就不会执行。如果这几套方案你都没有做处理,那么程序就会报错crash。
方案一:重写resolveInstanceMethod动态添加方法// 需要导入 #import <objc/runtime.h>
+ (BOOL)resolveInstanceMethod:(SEL)sel{
if (sel == @selector(test)) {
IMP imp = class_getMethodImplementation(self, @selector(run));
class_addMethod([self class], sel, imp, "v@:");
return YES;
}
return [super resolveInstanceMethod:sel];
}
runtime 消息转发参考https://www.jianshu.com/p/45db86af7b60
十三、runloop
运行循环,在程序运行过程中循环做一些事情,如果没有Runloop程序执行完毕就会立即退出,如果有Runloop程序会一直运行,并且时时刻刻在等待用户的输入操作。
每条线程都有唯一的一个与之对应的RunLoop对象,主线程的RunLoop已经自动创建好了,子线程的RunLoop需要主动创建,创建子线程RunLoop时,只需在子线程中获取当前线程的RunLoop对象即可[NSRunLoop currentRunLoop];RunLoop在第一次获取时创建,在线程结束时销毁