1、FMDB与多线程
SQLITE默认的线程模式是串行模式, 是线程安全的
FMDatabase多线程不安全, 单个FMDatabaseQueue是多线程安全的;
为什么FMDatabaseQueue能实现多线程安全?
- 其多线程是在不同子线程把任务追加_queue中
- 真正操作数据库的任务还是由_queue来分配
- _queue是一个串行队列, 且是同步执行, 所以所有任务是一个接一个执行, 并不会造成资源抢夺
2、iOS的系统架构
iOS基于UNIX系统,iOS的系统架构分为四层,由上到下一次为:可触摸层(Cocoa Touch layer)、媒体层(Media layer)、核心服务层(Core Services layer)、核心操作系统层(Core OS layer),如下图:
- 触摸层:为应用程序开发提供了各种常用的框架并且大部分框架与界面有关,本质上来说它负责用户在iOS设备上的触摸交互操作。如NotificationCenter的本地通知和远程推送服务,iAd广告框架,GameKit游戏工具框架,消息UI框架,图片UI框架,地图框架,连接手表框架,自动适配等等
- 媒体层:提供应用中视听方面的技术,如图形图像相关的CoreGraphics,CoreImage,GLKit,OpenGL ES,CoreText,ImageIO等等。声音技术相关的CoreAudio,OpenAL,AVFoundation,视频相关的CoreMedia,Media Player框架,音视频传输的AirPlay框架等等。
- 核心服务层:提供给应用所需要的基础的系统服务。如Accounts账户框架,广告框架,数据存储框架,网络连接框架,地理位置框架,运动框架等等。这些服务中的最核心的是CoreFoundation和Foundation框架,定义了所有应用使用的数据类型。CoreFoundation是基于C的一组接口,Foundation是对CoreFoundation的OC封装。
- 核心操作系统层包括:包含大多数低级别接近硬件的功能,它所包含的框架常常被其它框架所使用。Accelerate框架包含数字信号,线性代数,图像处理的接口。针对所有的iOS设备硬件之间的差异做优化,保证写一次代码在所有iOS设备上高效运行。CoreBluetooth框架利用蓝牙和外设交互,包括扫描连接蓝牙设备,保存连接状态,断开连接,获取外设的数据或者给外设传输数据等等。Security框架提供管理证书,公钥和私钥信任策略,keychain,hash认证数字签名等等与安全相关的解决方案。
3、load和initialize的区别
-
+load
1、只要程序启动就会将所有类的代码加载到内存中(在main函数执行之前), 放到代码区(无论该类有没有被使用到都会被调用)
2、+load方法会在当前类被加载到内存的时候调用, 有且仅会调用一次
3、当父类和子类都实现+load方法时, 会先调用父类的+load方法, 再调用子类的+load方法
4、先加载原始类,再加载分类的+load方法
5、当子类未实现+load方法时,不会调用父类的+load方法
6、多个类都实现+load方法,+load方法的调用顺序,与Compile Sources中出现的顺序一致 -
+initialize
1、当类第一次被使用的时候就会调用(创建类对象的时候)
2、initialize方法在整个程序的运行过程中只会被调用一次, 无论你使用多少次这个类都只会调用一次
3、initialize用于对某一个类进行一次性的初始化
4、先调用父类的initialize再调用子类的initialize
5、当子类未实现initialize方法时,会把父类的实现继承过来调用一遍,再此之前父类的initialize方法会被优先调用一次
6、当有多个Category都实现了initialize方法,会覆盖类中的方法,只执行一个(会执行Compile Sources 列表中最后一个Category 的initialize方法)
4、APNS的推送机制
- 由App向iOS设备发送一个注册通知,用户需要同意系统发送推送。
- iOS应用向APNS远程推送服务器发送App的Bundle Id和设备的UDID。
- APNS根据设备的UDID和App的Bundle Id生成deviceToken再发回给App。
- App再将deviceToken发送给远程推送服务器(自己的服务器), 由服务器保存在数据库中。
- 当自己的服务器想发送推送时, 在远程推送服务器中输入要发送的消息并选择发给哪些用户的deviceToken,由远程推送服务器发送给APNS。
- APNS根据deviceToken发送给对应的用户。
5、nil、Nil、NULL和NSNull区别
- nil和C语言的NULL相同,在objc/objc.h中定义。nil表示Objective-C对象的值为空。在C语言中,指针的控制用NULL表示。在Objective-C中,nil对象调用任何方法表示什么也不执行,也不会崩溃。
- Nil:那么对于我们Objective-C开发来说,Nil也就代表((void *)0)。但是它是用于代表空类的。比如:Class myClass = Nil;
- NULL:在C语言中,NULL是无类型的,只是一个宏,它代表空。这就是在C/C++中的空指针。对于我们Objective-C开发来说,NULL就表示((void *)0)。
- NSNull:NSNull是继承于NSObject的类型。它是很特殊的类,它表示是空,什么也不存储,但是它却是对象,只是一个占位对象。使用场景就不一样了,比如说服务器端口中让我们在值为空时,传空。NSDictionary *parameters = @{@"arg1":@"value1",@"arg2":arg2.isEmpty ? [NSNull null]:arg2};
- NULL、nil、Nil这三者对于Objective-C中值是一样的,都是((void *)0),那么为什么要区分呢?又与NSNull之间有什么区别:
NULL是宏,是对于C语言指针而使用的,表示空指针
nil是宏,是对于Objective-C中的对象而使用的,表示对象为空
Nil是宏,是对于Objective-C中的类而使用的,表示类指向空
NSNull是类类型,是用于表示空的占位对象,与JS或者服务器的null类似的含义
6、如何高效的切圆角?
切圆角共有以下三种方案:
- cornerRadius + masksToBounds:适用于单个视图或视图不在列表上且量级较小的情况,会导致离屏渲染。
- CAShapeLayer+UIBezierPath:会导致离屏渲染,性能消耗严重,不推荐使用。
- Core Graphics:不会导致离屏渲染,推荐使用。
7、MRC与ARC的区别
1、MRC手动内存管理
引用计数器:在MRC时代,系统判定一个对象是否销毁是根据这个对象的引用计数器来判断的。
- 每个对象被创建时引用计数都为1
- 每当对象被其他指针引用时,需要手动使用[obj retain];让该对象引用计数+1。
- 当指针变量不在使用这个对象的时候,需要手动释放release这个对象。 让其的引用计数-1.
- 当一个对象的引用计数为0的时候,系统就会销毁这个对象。
在MRC模式下必须遵循谁创建,谁释放,谁引用,谁管理
。
在MRC下使用ARC
在Build Phases的Compile Sources中选择需要使用MRC方式的.m文件,然后双击该文件在弹出的会话框中输入 -fobjc-arc
2、ARC自动内存管理
WWDC2011和iOS5所引入自动管理机制——自动引用计数(ARC),它不是垃圾回收机制而是编译器的一种特性。ARC管理机制与MRC手动机制差不多,只是不再需要手动调用retain、release、autorelease;当你使用ARC时,编译器会在在适当位置插入release和autorelease;ARC时代引入了strong强引用来带代替retain,引入了weak弱引用。
在ARC下使用MRC方法
在ARC工程中如果要使用MRC的需要在工程的Build Phases的Compile Sources中选择需要使用MRC方式的.m文件,然后双击该文件在弹出的会话框中输入 -fno-objc-arc
在非MRC文件中无法使用retain release retainCount 方法,无法再dealloc方法中调用[super dealloc]方法。
8、关于内存释放的本质
当一块内存释放的时候,本质上只是给这部分字节打了标签。并没有把字节里的二进制数据全部清成0或者1。所以,当一块内存说是释放了,但如果没有其他的二进制数据去填充它,那么它的内部数据是一直存在的。
内存释放,数据仍然存在,就会出现一种僵尸对象的问题。什么是僵尸对象?堆空间已经被标记清空,能被其他数据使用。但此时此刻,新的二进制数据还没有进来。我们此时用一个指针指向已经标记释放了的堆空间。这个就叫僵尸对象和野指针。
9、MLeaksFinder原理
MLeaksFinder 一开始从 UIViewController 入手。我们知道,当一个 UIViewController 被 pop 或 dismiss 后,该 UIViewController 包括它的 view,view 的 subviews 等等将很快被释放(除非你把它设计成单例,或者持有它的强引用,但一般很少这样做)。于是,我们只需在一个 ViewController 被 pop 或 dismiss 一小段时间后,看看该 UIViewController,它的 view,view 的 subviews 等等是否还存在。
具体的方法是,为基类 NSObject 添加一个方法 -willDealloc 方法,该方法的作用是,先用一个弱指针指向 self,并在一小段时间(2秒)后,通过这个弱指针调用 -assertNotDealloc,而 -assertNotDealloc 主要作用是直接中断言。这样,当我们认为某个对象应该要被释放了,在释放前调用这个方法,如果2秒后它被释放成功,weakSelf 就指向 nil,不会调用到 -assertNotDealloc 方法,也就不会中断言,如果它没被释放(泄露了),-assertNotDealloc 就会被调用中断言。这样,当一个 UIViewController 被 pop 或 dismiss 时(我们认为它应该要被释放了),我们遍历该 UIViewController 上的所有 view,依次调 -willDealloc,若2秒后没被释放,就会中断言。
总结起来一句话就是,当一个对象2秒之后还没释放,那么指向它的 weak 指针还是存在的,所以可以调用其 runtime 绑定的方法 willDealloc 从而提示内存泄漏。
10、UIView和CALayer的区别
- UIView可以响应事件,CALayer不可以,因为他们继承不同,UIView继承UIResponder,CALayer继承NSObject。
- 每个 UIView 内部都有一个 CALayer 在背后提供内容的绘制和显示,并且 UIView 的尺寸样式都由内部的 Layer 所提供。两者都有树状层级结构,layer 内部有 SubLayers,View 内部有 SubViews.但是 Layer 比 View 多了个AnchorPoint
- 在 View显示的时候,UIView 做为 Layer 的 CALayerDelegate,View 的显示内容由内部的 CALayer 的 display
- CALayer 是默认修改属性支持隐式动画的,在给 UIView 的 Layer 做动画的时候,View 作为 Layer 的代理,Layer 通过 actionForLayer:forKey:向 View请求相应的 action(动画行为)
- layer 内部维护着三分 layer tree,分别是 presentLayer Tree(动画树),modeLayer Tree(模型树), Render Tree (渲染树),在做 iOS动画的时候,我们修改动画的属性,在动画的其实是 Layer 的 presentLayer的属性值,而最终展示在界面上的其实是提供 View的modelLayer
- 两者最明显的区别是 View可以接受并处理事件,而 Layer 不可以.
11、函数指针和 Block区别
相同点:
- 二者都可以看成是一个代码片段。
- 函数指针类型和 Block 类型都可以作为变量和函数参数的类型
不同点:
- 函数指针只能指向预先定义好的函数代码块,函数地址是在编译链接时就已经确定好的。从内存的角度看,函数指针只不过是指向代码区的一段可执行代码
- block 本质是 OC对象,是 NSObject的子类,是程序运行过程中在栈内存动态创建的对象,可以向其发送copy消息将block对象拷贝到堆内存,以延长其生命周期。
12、什么是死锁?死锁的4个必要条件?如何避免死锁?
线程死锁
是指由于两个或者多个线程互相持有对方所需要的资源,导致这些线程处于等待状态,无法前往执行。
- 互斥条件:进程对于所分配到的资源具有排它性,即资源不能被共享,只能由一个进程使用(一个资源只能被一个进程占用,直到被该方法进程释放 。)
- 请求和保持条件:一个进程因请求被占用资源而发生阻塞时,对已获得的资源保持不放。
- 非剥夺条件:任何一个资源在没被该进程释放之前,任何其进程都无法对他剥夺占用。
- 循环等待条件:当发生死锁时,所等待的进程必定会形成一个环路(类似于死循环),造成永久阻塞。
以上这四个条件是死锁的必要条件,只要系统发生死锁,这些条件必然成立,而只要上述条件之一不满足,就不会发生死锁。线程死锁和进程死锁都一样。
- 打破互斥条件:改造独占性资源为虚拟资源,大部分资源已无法改造。
- 打破不可抢占条件:当一进程占有一独占性资源后又申请一独占性资源而无法满足,则退出原占有的资源。
- 打破占有且申请条件:采用资源预先分配策略,即进程运行前申请全部资源,满足则运行,不然就等待,这样就不会占有且申请。
- 打破循环等待条件:实现资源有序分配策略,对所有设备实现分类编号,所有进程只能采用按序号递增的形式申请资源。
死锁的处理:鸵鸟策略、预防策略、避免策略、检测与解除死锁
13、索引的作用和优缺点
数据是存储在磁盘上的,操作系统读取磁盘的最小单位是块,如果没有索引,会加载所有的数据到内存,依次进行检索,加载的总数据会很多,磁盘IO多。
如果有了索引,可以大大减少检索数据的范围、减少磁盘IO,使查询速度很快,因为磁盘IO是很慢的,是由它的硬件结构决定的。
索引主要分为四种:普通索引、主键、唯一索引、复合索引
建立索引的优点:
- 索引能够提高数据检索的效率,降低数据库的IO成本。
- 通过创建唯一性索引,可以保证数据库表中每一行数据的唯一性,创建唯一索引
- 在使用分组和排序子句进行数据检索时,同样可以显著减少查询中分组和排序的时间
- 加速两个表之间的连接,一般是在外键上创建索引
建立索引的缺点:
- 需要占用物理空间,建立的索引越多需要的空间越大
- 创建索引和维护索引要耗费时间,这种时间随着数据量的增加而增加
一般需要建立索引的字段
- 经常用在where语句之后的字段
- 主键或者外键
- 字段具有唯一性的时候建立唯一性索引
- 在经常需要根据范围进行搜索的列上创建索引,因为索引已经排序,其指定的范围是连续的
14、Block和delegate的区别
block的特点:
- 写法更简练,不需要写protocol、函数等等.
- block注重结果的传输:比如对于一个事件,只想知道成功或者失败,并不需要知道进行了多少或者额外的一些信息.
- block需要注意防止循环引用
delegate的特点:
优点:
- 减少代码的耦合性,使事件监听和事件处理相分离。
- 清晰的语法定义,减少维护成本,较强的代码可读性。
- 不需要创建第三方来监听事件和传输数据。
- 一个控制器可以实现多个代理,满足自定义开发需求,可选必选有较大的灵活性。
缺点:
- 实现委托的代码过程比较繁琐.
- 当实现跨层传值监听的时候将加大代码的耦合性,并且程序的层次结构将变的混乱。
- 当对多个对象同时传值响应的时候,委托的易用性将大大降低。
15、串行、并行、同步、异步
串行、并行:
并行和串行指的是任务的执行方式。串行是指多个任务时,各个任务按顺序执行,完成一个之后才能进行下一个。并行指的是多个任务可以同时执行,异步是多个任务并行的前提条件。
同步、异步:
指的是能否开启新的线程。同步不能开启新的线程,异步可以。
- 异步:异步和同步是相对的,同步就是顺序执行,执行完一个再执行下一个,需要等待、协调运行。异步就是彼此独立,在等待某事件的过程中继续做自己的事,不需要等待这一事件完成后再工作。线程就是实现异步的一个方式。异步是让调用方法的主线程不需要同步等待另一线程的完成,从而可以让主线程干其它的事情。
- 异步和多线程并不是一个同等关系,异步是最终目的,多线程只是我们实现异步的一种手段。异步是当一个调用请求发送给被调用者,而调用者不用等待其结果的返回而可以做其它的事情。实现异步可以采用多线程技术或则交给另外的进程来处理。
16、Charles为什么可以抓取HTTPS包
Charles主要做了两件事:
- 截获真实客户端的HTTPS请求,伪装客户端向真实服务端发送请求。
- 接受真实服务端响应,用Charles自己的证书伪装服务端发送数据内容。
因为手机请求的服务器是电脑的Charles,我们只需要信任电脑上CharlesCA。证书校验由系统校验我们只要让电脑安装上信任即可。
17、DNS劫持和DNS污染的区别
什么是DNS劫持
DNS劫持就是通过劫持了DNS服务器,通过某些手段取得某域名的解析记录控制权,进而修改此域名的解析结果,导致对该域名的访问由原IP地址转入到修改后的指定IP,其结果就是对特定的网址不能访问或访问的是假网址,从而实现窃取资料或者破坏原有正常服务的目的。DNS劫持通过篡改DNS服务器上的数据返回给用户一个错误的查询结果来实现的。
DNS劫持症状:在某些地区的用户在成功连接宽带后,首次打开任何页面都指向ISP提供的“电信互联星空”、“网通黄页广告”等内容页面。还有就是曾经出现过用户访问Google域名的时候出现了百度的网站。这些都属于DNS劫持。
什么是DNS污染
DNS污染是一种让一般用户由于得到虚假目标主机IP而不能与其通信的方法,是一种DNS缓存投毒攻击(DNS cache poisoning)。其工作方式是:由于通常的DNS查询没有任何认证机制,而且DNS查询通常基于的UDP是无连接不可靠的协议,因此DNS的查询非常容易被篡改,通过对UDP端口53上的DNS查询进行入侵检测,一经发现与关键词相匹配的请求则立即伪装成目标域名的解析服务器(NS,Name Server)给查询者返回虚假结果。而DNS污染则是发生在用户请求的第一步上,直接从协议上对用户的DNS请求进行干扰。
DNS污染症状:目前一些被禁止访问的网站很多就是通过DNS污染来实现的,例如YouTube、Facebook等网站。
解决方法
对于DNS劫持,可以采用使用国外免费公用的DNS服务器解决。例如OpenDNS(208.67.222.222)或GoogleDNS(8.8.8.8)。
对于DNS污染,可以说,个人用户很难单单靠设置解决,通常可以使用VPN或者域名远程解析的方法解决,但这大多需要购买付费的VPN或SSH等,也可以通过修改Hosts的方法,手动设置域名正确的IP地址。
总结
DNS劫持就是指用户访问一个被标记的地址时,DNS服务器故意将此地址指向一个错误的IP地址的行为。范例,网通、电信、铁通的某些用户有时候会发现自己打算访问一个地址,却被转向了各种推送广告等网站,这就是DNS劫持。
DNS污染,指的是用户访问一个地址,国内的服务器(非DNS)监控到用户访问的已经被标记地址时,服务器伪装成DNS服务器向用户发回错误的地址的行为。范例,访问YouTube、Facebook之类网站等出现的状况。
18、HTTPS IP直连存在的问题
-
证书HOST校验问题;
证书校验用的是域名校验,而不是iP地址,OkHostnameVerifier替换为域名; -
SNI问题;
一个域名对应多个iP地址,证书校验时没有找到自己设置的IP地址,报错,在SSLSocketFactory的实现类中设置自己的域名; -
连接复用问题;
具体判断是否可复用问题在RealConnection#isEligible();
boolean equalsNonHost(Address that) {
return this.dns.equals(that.dns)
&& this.proxyAuthenticator.equals(that.proxyAuthenticator)
&& this.protocols.equals(that.protocols)
&& this.connectionSpecs.equals(that.connectionSpecs)
&& this.proxySelector.equals(that.proxySelector)
&& equal(this.proxy, that.proxy)
&& equal(this.sslSocketFactory, that.sslSocketFactory)
&& equal(this.hostnameVerifier, that.hostnameVerifier)
&& equal(this.certificatePinner, that.certificatePinner)
&& this.url().port() == that.url().port();
}
&& equal(this.sslSocketFactory, that.sslSocketFactory)
&& equal(this.hostnameVerifier, that.hostnameVerifier)
修改equals方法
-
兼容性问题;
-
Session复用问题
19、.a与.framework的区别
-
库:库是共享程序代码的方式,一般分为静态库和动态库
-
静态库与动态库的区别:
- 静态库:链接时完整地拷贝至可执行文件中,被多次使用就有多份冗余拷贝。
- 动态库:链接时不复制,程序运行时由系统动态加载到内存,供程序调用,系统只加载一次,多个程序共用,节省内存。
- iOS静态库形式和动态库形式:
- 静态库:.a和.framework
- 动态库:.dylib和.framework
-
framework静态库和动态库的区分:
系统的.framework是动态库,我们自己建立的.framework是静态库 -
.a和.framwork的区别:
- .a是一个纯二进制文件,.framework中除了有二进制文件外还有资源文件。
- .a文件不能直接使用,至少要有.h文件配合,.framework文件可以直接使用。
- .a + .h + sourceFile = .framework
20、商品cell停留的曝光时间埋点怎么统计
hook这两个函数:tableView.visibleCells
和tableView.indexPathsForVisibleRows
,也是可以得到当前屏幕中的cell,然后取差集。
- VisibleCells:出现在屏幕上的cell,没有下标
- indexPathsForVisibleRows:出现在屏幕上的cell,有下标,可以确定唯一标识符
21、runloop在实际开发中的应用
- 线程保活
- 解决NSTimer在滑动时停止工作的问题
- 子线程中执行NSTimer
- 监控应用卡顿
- 性能优化
- performSelector
- 自动释放池
22、UIScrollView嵌套TableView手势冲突问题
- (BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldRequireFailureOfGestureRecognizer:(UIGestureRecognizer *)otherGestureRecognizer
{
NSLog(@"_____%@______other:%@",gestureRecognizer,otherGestureRecognizer);
return NO;
}