目录
- 1.main函数的autoreleasepool有什么作用?
- 2.HTTPS的原理是什么,验证原理又是什么?
- 3.iOS触摸时间传递及响应原理
- 4.
load、initialize
详解与区别 - 5.
imageWithContentsOfFile:
和imageNamed
- 6.FMDB怎么保证线程安全的
- 7.MLeaksFinder原理
- 8.FMDB数据库实现多读单写
- 9.HTTP的TCP协议和Socket的TCP协议有何不同?
1、main函数的autoreleasepool有什么作用?
autoreleasepool是自动释放池,加入自动释放池的对象,初始化时调用autorelease方法,那么在autoreleasepool语句块结束时,对象会被释放,从而防止占用过多内存,在ARC机制下,autorelease方法由系统自动调用。main函数中的autoreleasepool的作用,就是当主线程运行循环结束时,释放所有对象。
2、HTTPS的原理是什么,验证原理又是什么?
- HTTP的缺点:通信使用明文、不验证通信方的身份,无法验证报文的完整性
- HTTPS的实质:SSL+ HTTP
- HTTPS加密原理:非对称加密(交换密钥)+ 对称加密(数据通信)
-
HTTPS的验证流程如下:
具体参考:https://www.jianshu.com/p/f088fe60977e
3、iOS触摸时间传递及响应原理
iOS系统触摸事件响应链如下:
- 事件触发:主屏幕触摸事件 --> 系统进程 --> App进程接收系统触摸事件,触发source1回调 --> App进程触发source0回调 --> source0回调将触摸事件添加到Application事件队列
- 寻找最佳响应者:首先通过
hitTest:withEvent:
寻找最佳响应者,顺序为:UIApplication --> UIWindow --> UIViewController --> UIViewController.view --> view - 响应事件:再者通过touchesBegan:withEvent:来响应和传递这个触摸事件。默认顺序为:最佳响应者 --> 最佳响应者的父View --> UIViewController.view --> UIViewController --> UIWindow --> UIApplication --> UIApplicationDelegate
- 触摸事件优先级:UIGestureRecognizer > UIControl > 父view的UIGestureRecognizer > UIResponder
4、load、initialize
详解与区别
load实现原理
- App启动,类第一次加载进内存的时候,会调用 + load 方法,无需导入,无需使用
- 每个类、分类的 + load 在程序运行过程中只会执行一次
-
+ load
走的不是消息发送的objc_msgSend
调用,而是找到+ load
函数的地址,直接调用
调用顺序
- 先调用宿主类的
+ load
函数- 按照编译先后顺序调用(先编译,先调用)
- 调用子类的
+load
之前会先调用父类的+load
- 再调用分类的
+ load
函数- 按照编译先后的顺序调用(先编译,先调用)
Initialize实现原理
- 类第一次接收到消息的时候,会调用该方法,需导入,并使用
-
+ Initialize
走的是消息发送的objc_msgSend
调用
initialize 与 load 的区别
-
load
是类第一次加载的时候调用,initialize
是类第一次接收到消息的时候调用,每个类只会initialize
一次(当子类没有实现initialize
方法时,会调用父类的initialize
)方法 - load 和 initialize,加载or调用的时候,都会先调用父类对应的 load or initialize 方法,再调用自己本身的;
- load 和 initialize 都是系统自动调用的话,都只会调用一次
- 调用方式也不一样,load 是根据函数地址直接调用,initialize 是通过objc_msgSend
- 调用时刻,load是runtime加载类、分类的时候调用(只会调用一次)
- 调用顺序:
-
load
:- 先调用类的load
1)先编译的类,优先调用load
2)调用子类的load之前,会先调用父类的load(父类的load已经调用过了就不会再调用了) - 再调用分类的load
- 先调用类的load
-
initialize
:- 先初始化父类
- 再初始化子类(可能最终调用的是父类的初始化方法)
-
5、imageWithContentsOfFile:
和imageNamed
-
imageWithContentsOfFile:
图片创建是通过读取文件数据得到的,读取一次文件数据就会产生一次NSData以及产生一个UIImage, 当图片创建好后销毁对应的NSData, 当UIImage的引用计数器变为0的时候自动销毁UIImage.这样的话就可以保证图片不会长期地存在在内存中.
由于是从沙盒中加载,加载速度偏慢
-
当同时加载一张图片的时候,占用的内存将会更大
当我们需要图片的时候就会去沙盒中读取这个图片文件, 转换成UIImage对象来使用. 现在假设一种场景:
[email protected] 图片占用 5kb 的内存
[email protected] 在多个界面都用到, 且有7处会同时显示这个图片
通过代码分析就可以知道 Resource 这个方式在这个情景下会占用 5kb/个 X 7个 = 35kb 内存. 然而, 在 ImageAssets 方式下, 全部取自字典缓存中的UIImage, 无论有几处显示图片, 都只会占用 5kb/个 X 1个 = 5kb 内存. 此时 Resource 占用内存将会更大.
-
imageNamed:
-
imageNamed:
也是从图片文件中读取图片数据转为 UIImage, 只不过这些图片数据都打包在 ImageAssets 中. 还有一个最大的区别就是图片缓存. 相当于有一个字典, key 是图片名, value是图片对象. 调用imageNamed:方法时候先从这个字典里取, 如果取到就直接返回, 如果取不到再去文件中创建, 然后保存到这个字典后再返回. 由于字典的key和value都是强引用, 所以一旦创建后的图片永不销毁. - 当图片从字典(内存)中取时,图片加载速度会更快。
-
6.FMDB怎么保证线程安全的
FMDB源码主要由一下几个部分组成:
- FMDatabase:表示一个单独的SQLite数据库操作实例,通过它可以对数据库进行增删改查等操作。
- FMResultSet:查询结果,表示FMDatabase执行查询之后的结果集。
- FMDatabaseQueue:支持在串行队列里访问FMDatabase,保证线程安全。
- FMDatabaseAdditions:扩展FMDatabaseAdditions类,新增对查询结果只返回单个值的方法进行简化,对表、列是否存在,版本号,校验SQL等功能。
- FMDatabasePool: 使用任务池的形式,对多线程的操作提供支持。
直接通过FMDatabase操作数据库,在多线程环境下是容易出现线程安全问题的。
而通过FMDatabaseQueue访问FMDatabase再操作数据库是线程安全的,因为FMDatabaseQueue里面封装了一个串行队列,在串行队列里访问可以始终保持在一个线程下访问。FMDatabaseQueue初始化串行队列源码如下:
- (instancetype)initWithPath:(NSString*)aPath flags:(int)openFlags vfs:(NSString *)vfsName {
self = [super init];
if (self != nil) {
_db = [[[self class] databaseClass] databaseWithPath:aPath];
FMDBRetain(_db);
#if SQLITE_VERSION_NUMBER >= 3005000
BOOL success = [_db openWithFlags:openFlags vfs:vfsName];
#else
BOOL success = [_db open];
#endif
if (!success) {
NSLog(@"Could not create database queue for path %@", aPath);
FMDBRelease(self);
return 0x00;
}
_path = FMDBReturnRetained(aPath);
// 初始化一个串行队列,以后访问数据库就在串行队列里访问
_queue = dispatch_queue_create([[NSString stringWithFormat:@"fmdb.%@", self] UTF8String], NULL);
dispatch_queue_set_specific(_queue, kDispatchQueueSpecificKey, (__bridge void *)self, NULL);
_openFlags = openFlags;
_vfsName = [vfsName copy];
}
return self;
}
通过串行队列访问FMDatabase源码如下:
- (void)inDatabase:(__attribute__((noescape)) void (^)(FMDatabase *db))block {
#ifndef NDEBUG
/* Get the currently executing queue (which should probably be nil, but in theory could be another DB queue
* and then check it against self to make sure we're not about to deadlock. */
/*
使用dispatch_get_specific来查看当前queue是否是之前设定的那个_queue,
如果是的话,那么使用kDispatchQueueSpecificKey作为参数传给dispatch_get_specific的话,
返回的值不为空,而且返回值应该就是上面initWithPath:函数中绑定的那个FMDatabaseQueue对象。
有人说除了当前queue还有可能有其他什么queue?
这就是FMDatabaseQueue的用途,你可以创建多个FMDatabaseQueue对象来并发执行不同的SQL语句。
另外为啥要判断是不是当前执行的这个queue?是为了防止死锁!
*/
FMDatabaseQueue *currentSyncQueue = (__bridge id)dispatch_get_specific(kDispatchQueueSpecificKey);
assert(currentSyncQueue != self && "inDatabase: was called reentrantly on the same queue, which would lead to a deadlock");
#endif
FMDBRetain(self);
// 利用GCD同步函数派发串行队列,在串行队列里访问FMDatabase
dispatch_sync(_queue, ^() {
FMDatabase *db = [self database];
block(db);
if ([db hasOpenResultSets]) {
NSLog(@"Warning: there is at least one open result set around after performing [FMDatabaseQueue inDatabase:]");
#if defined(DEBUG) && DEBUG
NSSet *openSetCopy = FMDBReturnAutoreleased([[db valueForKey:@"_openResultSets"] copy]);
for (NSValue *rsInWrappedInATastyValueMeal in openSetCopy) {
FMResultSet *rs = (FMResultSet *)[rsInWrappedInATastyValueMeal pointerValue];
NSLog(@"query: '%@'", [rs query]);
}
#endif
}
});
FMDBRelease(self);
}
注意:
- 通过源码可知,FMDatabaseQueue提供了同步访问FMDatabase的方法,如果是在主线程操作FMDatabaseQueue话容易阻塞主线程,实际开发中,我们可以二度封装FMDatabaseQueue,提供异步访问FMDatabase的方法,只要将dispatch_sync改为dispatch_async就可以了。
- FMDatabaseQueue利用了dispatch_queue_set_specific和dispatch_get_specific判断是不是当前queue,是因为防止创建了多个FMDatabaseQueue多线程同时访问数据库导致死锁
7.@synchronized
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
@synchronized(self) {
sleep(2);
NSLog(@"线程1");
}
sleep(1);
NSLog(@"线程1解锁成功");
});
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
@synchronized(self) {
NSLog(@"线程2");
}
});
// 线程1
// 线程2
// 线程1解锁成功
-
@synchronized(object)
指令使用的object
为该锁的唯一标识,只有当标识相同时,才满足互斥,所以如果线程2
中的@synchronized(self)
改为@synchronized(self.view)
,则线程2
就不会被阻塞 -
@synchronized
指令实现锁的优点就是我们不需要在代码中显示的创建锁对象,便可以实现锁的机制,但作为一种预防措施,@synchronized
块会隐式的添加一个异常处理例程来保护代码,该处理例程会在异常抛出的时候自动的释放互斥锁。 - 如果在
@sychronized(object){}
内部object
被释放或被设为nil
,从测试的结果来看,的确没有问题,但如果object
被释放或被设为nil
,从测试的结果来看,的确没有问题,但如果object
一开始就是nil
,则失去了锁的功能。但@synchronized([NSNull null])
是完全可以的。
7.MLeaksFinder原理
利用isa-swizzling
技术,将viewController的viewDidDisappear
和viewWillAppear
以及dismissViewControllerAnimated:completion:
进行交换,在viewDidDisappear
会调用自定义的willDealloc
方法,核心代码如下:
__weak id weakSelf = self;
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
__strong id strongSelf = weakSelf;
[strongSelf assertNotDealloc];
});
通过延后两秒,来判定viewController是否存在,存在则说明内存泄露,不存在则说明内存未泄露
8.FMDB数据库实现多读单写
FMDB数据库实现多读单写,只要在读取的时候将写的方法加锁即可。多读可以并发队列结合使用dispatch_semaphore
信号量大于1控制并发数量来实现,加锁也可以使用dispatch_semaphore
信号量从1
降为0
,解锁再升为1
来实现。
9.HTTP的TCP协议和Socket的TCP协议有何不同?
HTTP的TCP协议是默认是短连接,TCP连接上,完成数据发送后即断开。Socket的TCP协议则是长连接,连接上后,只要不手动断开,则一直保持连接状态。