一、 js 与 原生OC交互方式:
1.JS发起一个假请求,然后用UIwebView的代理方法拦截这起请求,再做相应的处理
2.在iOS 7 之后Apple添加了一个新的库JavaScriptCore,用来做js交互。
首先导入JavaScriptCore 库,然后在OC 中获取上下文对象。在定义好JS需要调用的方法。JSContext对象context[@”share”] 等于一个返回值为空的block。这个block 在子线程中调用,因此执行UI 操作需要回到主线程
二、 OC调用JS
1、webView stringByEvaluatingJavaScriptFromString:jsStr];
2、继续使用JavaScriptCore库 JSContext 对象调用 evaluateScript:方法
以上两个方法都是同步方法,如果JS方法比较耗时会造成界面卡顿。
所以可以使用wkwebview 的 evaluateJavaScript:代替
WKWebView优势:
运行速度更快,占用内存低,大概是UIwebView 的四分之一到三分之一
多进程,在APP的主进程之外执行;为空webView为多进程组件,他会从APP内存中分离内存到单独的进程中。当内存超过了系统分配给为空webView的内存时候,会导致为空webView浏览器崩溃白屏,但是APP不会crash。(APP会收到系统通知,并且尝试去重新加载页面)
相反的UIwebView 和APP同一个进程,内存不够用时就会crash ,从而导致APP crash
wkwebview 使用和手机Safari浏览器一样的nitro JavaScript引擎,相比于UIwebView的javaScript 引擎有非常大的性能提升
wkwebview 是异步处理APP原生代码与JavaScript之间的通信,因此普遍上执行速度会更快
消除了触摸延迟
缺点:
不能截屏
不支持记录webkit 的请求
APP退出会清湖HTML5的本地存储数据
不支持accept cookies 的设置
需要iOS 9 或更高版本
三、线程
线程安全
1、互斥锁 @synchronized(所对象){}
atomic 原子属性 为setter方法加锁 线程安全需要消耗大量的资源
nonatomic 非原子属性
2、NSLock
3、递归锁 NSRecursivelock
4、NSConditionLock条件锁
5、GCD 信号量 dispatch_semaphore
6、自选锁
1、@synchronized 的效率最低,不过它的确用起来最方便,所以如果没什么性能瓶颈的话,可以选择使用 @synchronized。
2、当性能要求较高时候,可以使用 pthread_mutex 或者 dispath_semaphore,由于 OSSpinLock 不能很好的保证线程安全,而在只有在 iOS10 中才有 os_unfair_lock ,所以,前两个是比较好的选择。既可以保证速度,又可以保证线程安全。
3、对于 NSLock 及其子类,速度来说 NSLock < NSCondition < NSRecursiveLock < NSConditionLock 。
线程间通信:
两个线程之间要想互相通信,可以使用:NSMachPort
//1. 创建主线程的port // 子线程通过此端口发送消息给主线程 NSPort *myPort = [NSMachPort port];
//2. 设置port的代理回调对象 myPort.delegate = self;
//3. 把port加入runloop,接收port消息 [[NSRunLoop currentRunLoop] addPort:myPort forMode:NSDefaultRunLoopMode];
NSLog(@"---myport %@", myPort);
//4. 启动次线程,并传入主线程的port MyWorkerClass *work = [[MyWorkerClass alloc] init];
(void)performSelectorOnMainThread:(SEL)aSelector withObject:(id)arg waitUntilDone:(BOOL)wait;
- (void)performSelector:(SEL)aSelector onThread:(NSThread *)thr withObject:(id)arg waitUntilDone:(BOOL)wait;
进程间通信;
共享存储系统消息传递系统管道:以文件系统为基础
URL scheme
Keychain
Uipasteboard
UIDocumentInteractionController
Local socket
Airdrop
UiactivityViewController
APPgroups
线程保活:
循环
runloop
1.获取RunLoop只能使用 [NSRunLoop currentRunLoop] 或 [NSRunLoop mainRunLoop];
2.即使RunLoop开始运行,如果RunLoop 中的 modes 为空,或者要执行的mode里没有item,那么RunLoop会直接在当前loop中返回,并进入睡眠状态。
3.自己创建的Thread中的任务是在kCFRunLoopDefaultMode这个mode中执行的。
4.在子线程创建好后,最好所有的任务都放在AutoreleasePool中。
如果不执行下列语句:
[runLoop addPort:[NSMachPort port] forMode:NSRunLoopCommonModes];
Runloop
就是运行循环。保持程序的持续运行、处理APP中的各种事件(手势定时器等)
节省CPU资源提高程序的性能,在有任务的时候做任务 ,在没任务的时候休息 空转
runloop 和线程是一对一的
系统默认五个mode
NSDefaultRunloopMode
UITrackingRunloopMode 为了保证滑动ScrollView滑动不受影响,在滑动的时候就主动切换到这个mode运行
UITInitializationRunLoopMode 在刚启动APP的时候第一次进入的mode
GSEventReceive 接收系统内部事件
Common 包含default 和UItrackingMode
应用:
1、延迟显示UIimageView 用runloop performSelector:withObject:afterDelay:inmodes:
2、NSTimer 需要加入mode
3、常驻线程 线程保活
4、在创建场主线程的时候加入 @autoreleasepool,在runloop睡眠的时候释放
定时器:
- NSTimer 通过runloop来是实现,一般情况下较为准确,但当当前循环耗时操作较多时会出现延时的问题。同事也受所加入的runloopmode 的影响,commonmode(包含default、UItracking)
- CADisplayLink是基于屏幕刷新的周期,所以其一般很准时,每秒60次。其本质也是通过runloop,所以存在和NSTimer相同的问题
- GCD定时器是dispatch_source_t类型的变量,其可以实现更加精准的定时效果。GCD定时器实际上是使用了dispatch源(dispatch source),dispatch源监听系统内核对象并处理。通过系统级调用更加精准 (dispatch类似生产者消费者模式,通过监听系统内核对象,在生产者生产数据后自动通知相应的dispatch队列执行,后者充当消费者)。GCD通过dispatch_suspend()释放定时器
ViewDidload 什么时候调用:(界面初始话操作,加载数据等)在view创建完毕后,不管是通过xib、Storyboard还是loadview自定义view
- view被加载的时候
- view Controller 用代码创建的时候
- view Controller通过nib解析的时候
在UIViewController 的初始化方法中访问实例变量view,会导致延迟加载机制失效,会受到内存警告
当访问UIViewController中的self.view时,如果view为nil 就会调用loadview
iOS 程序在main 函数之前:
- 首先加载可执行文件
- 然后加载苹果的动态链接器dyld,(dyld是一个专门用来加载动态链接库的库)
- 执行从dyld开始,dyld从可执行的文件开始,递归加载所有的依赖动态链接库
- 动态链接库包括:iOS中用到的所以系统的framework,加载OC runtime方法的libobjec,系统级别的libSystem
- 所有动态链接库和我们APP的静态库.a和所有类文件编译后的.o文件,最终都由dyld 加载到内存中
整个事件由苹果的动态链接器主导,完成运行环境的初始化后,配合imageLoader将二进制文件按格式加载到内存。
OC 方法调用的过程原理:
OC中的所有方法调用,最终都是转换成runtime中的一个C语言消息分发函数:objc_msgSend(消息接收者,方法名 ,参数。。。)
这条消息发送之后,系统会在receiver的类对象的方法了吧中找这个方法,如果没找到,再到receiver的父类的方法列表中找,如此直到根类至找到为止,如果还没有找到会报出错误。(缓存:方法第一次被调用之后,方法会被存入一张缓存表,之后如果再被调用时就直接从缓存表中取出,以提高效率)。
Runtime中对调用过程做了缓存,在抛出错误之前会进行动态决议和消息转发过程。
若对象无法响应某个选择子,则进入消息转发流程:
1、动态方法解析:+bool) resolveInstanceMethod:(SEL)selector
+(bool)resolvelassMethod:(SEL)selector
2、备用接受者:
- (id)forwardingargetForSelector(SEL)slector (把这条消息转发给其他对象处理)
- 获取方法签名进行消息转发
- (NSMethodSignature*)methodSignatureForSelector:
- 完整的消息转发
- (void)forwardingInvocation(NSInvocation*)invocateion
1、通过运行期的动态方法解析功能,我们可以在需要某个方法是在将其加入类中
2、对象可以把其无法解读的某些消息转交给其他对象来处理
3、经过上述两步后,如果还是没有办法处理消息,那就启动完整的消息转发机制
OC类结构
OC类是objc_class结构体
它里面有isa指针、指向父类的super_class 指针、类名、类的版本信息、该类的实例变量大小、类信息供运行期使用的一些标识符、类的成员变量链表、方法定义的链表、方法缓存、协议链表
UIView 和 CALayer
所有的view都是由一个底层的Layer来驱动。Layer侧重于图形的显示,而view相当于layer的管理者。
UIView 继承与UIResponder 而 CALayer 继承于NSObject。所以UIView 可以响应事件,而CALayer 则不能。
每个UIView内部都有一个CALayer在背后提供内容的绘制和显示。两者都有树状层级结构,layer 内部有sublayers,view 内部有subviews
layer 内部维护着三份layer tree ,分别是动画树、模型树、渲染树,在iOS 做动画的时候,我们修改动画的属性,在动画的其实是动画树,而最终展示在界面上的其实是提供view的modelayer
设计模式
- 单例模式: 确保程序运行期某个类只有一个实例对象,用于资源共享控制
- 代理模式:一对一反向传值。当一个类的某些功能需要别的类来实现,但是又不确定具体会是哪个类实现
- 观察者模式:
通知: notification通知中心,注册通知中心,任何位置可以发送消息,注册观察者的对象可以接收。
KVO 键值对改变 通知观察者
4、工厂模式:工厂方式创建类的实例
5、MVC 模式 :model 、view 、control 模型、视图、控制器对应用程序进行数据处理、业务逻辑和视图展示解耦
6、MVVM模式: model、view、viewModel 将MVC中controller的业务逻辑提取到viewModel层,进行解耦合
设计模式:并不是一种新技术,而是一种编码经验。 mvc设计模式 :模型,视图,控制器,可以将整个应用程序在思想上分成三大块,对应是的数据的存储或处理,前台的显示,业务逻辑的控制。 代理模式:代理模式给某一个对象提供一个代理对象,并由代理对象控制对源对象的引用. 单例模式:说白了就是一个类不通过alloc方式创建对象,而是用一个静态方法返回这个类的对象。系统只需要拥有一个的全局对象,这样有利于我们协调系统整体的行为,比如想获得[UIApplication sharedApplication];任何地方调用都可以得到 UIApplication的对象,这个对象是全局唯一的。 观察者模式: 当一个物体发生变化时,会通知所有观察这个物体的观察者让其做出反应。实现起来无非就是把所有观察者的对象给这个物体,当这个物体的发生改变,就会调用遍历所有观察者的对象调用观察者的方法从而达到通知观察者的目的。 工厂模式:可以简单概括为同类型不同型号的产品有各自对应的工厂进行生产。
@autoreleasepool
- 是自动释放池,让我们更自由的管理内存
- 当我们手动创建了一个@autoreleasepool,里面创建了很多临时变量,当@autoreleasepool结束的时候,里面的内存就回回收
- ARC 系统自动管理自己的autoreleasepool,runloop就开始iOS 中的消息循环机制,当一个runloop结束时系统才会一次性清理掉呗autoreleasepool 处理过的对象,其实本质上说是在本次runloop迭代结束时清理掉本次迭代期间被放到autoreleasepool中的对象。至于何时runloop结束并没有固定的duration。
会产生大量临时变量的时候使用@autoreleasepool
循环中创建了很多的临时对象,使用@autoreleasepool
你生成了一个辅助数据线程,使用 @autoreleasepool
长期在后台运行的任务
Static
Static 修饰局部变量 ,只会初始化一次,并且在程序中只有一份内存,可以延长局部变量的生命周期,改变了知道整个项目结束时才会被销毁
Static修饰全局变量时,作用域仅限于当前文件
1).函数体内 static 变量的作用范围为该函数体,不同于 auto 变量,该变量的内存只被分配一次,因此其值在下次调用时仍维持上次的值; 2).在模块内的 static 全局变量可以被模块内所用函数访问,但不能被模块外其它函数访问; 3).在模块内的 static 函数只可被这一模块内的其它函数调用,这个函数的使用范围被限制在声明它的模块内; 4).在类中的 static 成员变量属于整个类所拥有,对类的所有对象只有一份拷贝; 5).在类中的 static 成员函数属于整个类所拥有,这个函数不接收 this 指针,因而只能访问类的static 成员变量。
Http 请求方式:
- opions 返回服务器针对特定资源所支持的HTML请求方法 或 允许客户端查看服务器性能
- get 向特定资源发出请求
- post 向指定资源提交数据(提交表单、上传文件)
- put 向指定资源位置上上传其最新内容
- head 与get请求类似,返回的响应中没有具体内容,用于获取报头
- delete 请求服务器删除request-URl所表示的资源
- trace 回显服务器收到的请求,用于测试和诊断
- connect 能够将连接改为管道方式的代理服务器
在项目什么时候选择使用GCD,什么时候选择NSOperation?
答: 项目中使用NSOperation的优点是NSOperation是对线程的高度抽象,在项目中使用它,会使项目的程序结构更好,子类化NSOperation的设计思路,是具有面向对象的优点(复用、封装),使得实现是多线程支持,而接口简单,建议在复杂项目中使用。
项目中使用GCD的优点是GCD本身非常简单、易用,对于不复杂的多线程操作,会节省代码量,而Block参数的使用,会是代码更为易读,建议在简单项目中使用
线程与进程的区别和联系
1). 进程和线程都是由操作系统所体会的程序运行的基本单元,系统利用该基本单元实现系统对应用的并发性 2). 进程和线程的主要差别在于它们是不同的操作系统资源管理方式。 3). 进程有独立的地址空间,一个进程崩溃后,在保护模式下不会对其它进程产生影响,而线程只是一个进程中的不同执行路径。 4.)线程有自己的堆栈和局部变量,但线程之间没有单独的地址空间,一个线程死掉就等于整个进程死掉。所以多进程的程序要比多线程的程序健壮,但在进程切换时,耗费资源较大,效率要差一些。 5). 但对于一些要求同时进行并且又要共享某些变量的并发操作,只能用线程,不能用进程。
一、TCP 是传输控制协议 transport control protocol ,基于字节流传输,有连接,可以提供可靠地通信传输
TCP充分实现了数据传输时的各种控制功能,可以进行丢包的重发,还可以对次序乱掉的分包进行顺序控制。TCP 作为一种面向连接的传输协议,只有在确认通信端存在时才会发送数据,从而避免数据流量的浪费。TCP 通过检验和、序列号、确认应答 和重发控制等机制实现可靠性传输。TCP 连接只能是点到点
二、UDP 是用户数据协议 user data protocol ,基于数据报,无连接,不可靠
UDP将部分控制转移到应用程序去处理,自己只提供作为传输层协议的最基本功能。对丢包乱序不做处理,UDP 支持 一对一、一对多、多对一、多对多通信
编程步骤: TCP 1、创建一个socket 2、设置socket属性 3、绑定IP地址 端口信息到socket 上bind() 4、设置要连接的对方的IP地址和端口号 5、连接服务器 connect() 6、收发数据 receive () send() 7、关闭网络连接
UDP 1、创建一个socket 2、设置socket属性 3、绑定IP地址 端口信息到socket 上bind() 4、设置要连接的对方的IP地址和端口号 5、发送数据 sendto() 6、关闭网络连接
Viewcontroller 生命周期
当一个视图控制器被创建,并在屏幕上现实的时候
1、alloc 2、init 3、loadview 4、viewdidload 5、viewwillappear 6、viewdidappear
当一个视图被移除屏幕并且销毁的时候的执行顺序
1、viewwilldisappear 2、viewdiddisappear 3、dealloc
关于viewdidunload 在发生内存警告的时候如果本视图不是当前屏幕正在显示的视图的话,viewdidunload将会被执行,本视图的所有子视图将被销毁已释放内存,此时开发者需要手动释放该控制器中创建的对象。
iOS APP性能测试:
instrument
1、leaks 内存泄漏 ,一般查看内存的使用情况,检查泄漏的内存,并提供了所有活动的分配和泄漏模块的类对象分配统计信息以及内存地址历史记录
2、time profile 时间探测: 分析代码的执行时间,找出导致程序变慢的原因
3、allocations 内存分配:检测内存使用、分配情况。跟踪工程的匿名虚拟内存和堆的对象提供类名和可选保留释放历史
事件的分发和传递
- 当iOS 程序中发生触摸事件后,系统会将事件加入到UIApplication管理的一个任务队列中
- UIApplication将处于任务队列最前端的事件向下分发。即UIWindow
- UIWindow将事件向下分发,即UIView
- UIView 首先看自己是否能处理事件,触摸点是否在自己身上。如果能,那么继续寻找子视图
- 遍历子控件,重复以上两步
- 如果没有找到,那么自己就是事件处理者
- 如果自己不能处理,那么不做任何事情
- 该事件被废弃
其中UIView 不接受事件处理的情况主要有一下三种:alph< 0.01 userinteractionEnable = NO hidden = yes
这个从父控件到子控件寻找处理时间最合适的view过程,如果父视图不接受事件处理,则子视图也不能接收事件
响应者链:
响应链是从最合适的view开始传递,处理事件传递给下一个响应者,响应者链的传递方向是事件传递的反方向,如果所有响应者都不处理事件,则事件被丢弃。
数据持久化
1、属性列表plist 用于存储在程序中不经常修改、数据量小的数据,不支持自定义对象存储
2、nsuserdefautls 同样适合于存储轻量级数据,本质上就是一个plist ,也不支持自定义对象存储
3、归档序列化存储
可以直接将对象存储为文件,也可将文件直接解归档为对象,相对于plist文件与nsuserdefault 存储更加多样,支持自定义对象存储,归档后的文件是加密的,也更加安全
4、Core data
5、数据库 FMDB
谓词 NSPredicate
通过设置逻辑条件 对数据进行过滤
volatile 是一个类型修饰符。该变量可能会被意想不到的改变。优化器在使用个这个变量时必须每次都小心的重新读取这个变量的值,而不是使用保存在寄存器里的备份。
- 并行设备的硬件寄存器
- 一个中断服务子程序中会访问到的非自动变量
- 多线程应用中被几个任务共享的变量
一个参数既可以是const 还可以是volatile。一个例子是只读的状态寄存器,他可能会被意想不到的改变,而程序不应该视图去修改他
一个指针可以使volatile。当一个中断服务子程序修改一个指向一个buffer的指针时
什么是push
客户端程序留下后门端口,客户端总是监听针对这个后门的请求,于是 服务器可以主动像这个端口推送消息
常见第三方库:afnetworking 、SDWebImageView、Reactive Cocoa、 React Native
block 本质: 是带有函数执行上下文环境的结构体,其中包含被调函数的指针
离屏渲染:
离屏渲染指的是GPU在当前屏幕缓冲区意外开辟了一个缓冲区进行渲染操作。对性能的影响主要是因为:创建了新的缓冲区以及上下文的频繁切换
导致离屏渲染的原因:
shouldRasterize 光栅化、遮罩masks、shadows 阴影、edgeantialiasing 抗锯齿、不透明、复杂形状设置圆角等、渐变
UIView 的绘制原理
当我们调用[UIView setNeedsDisplay]这个方法时 ,其实并没有立即进行绘制工作,系统会立即调用CALayer的同名方法,并且会在当前layer上打上一个标记,然后会在当前runloop将要结束的时候调用【CALayer display】这个方法 然后进入视图的真正绘制过程
在CALayer display 这个方法的内部实现中会判断这个layer的delegate是否响应displaylayer这个方法,如果不响应这个方法,就回进入到系统会址流程中,如果响应这个方法,那么就会为我们提供异步绘制的入口
异步绘制
1假如我们在某一个时机调用了【view setNeedsDisplay】这个方法,系统会在当前runloop将要结束的时候调用【CALayer display】方法,然后如果我们这个layer的代理实现了【view displayerlayer】这个方法
2、然后会通过子线程的切换,我们在子线程中去做一个位图的绘制,主线程可以去做一些其他的操作
3、 在子线程中创建一个位图的上下文,然后通过CoregraphIC API 可以做当前UI空间的一些绘制工作,最后再通过CGBitmapContextCreateImage()这个函数来根据当前所绘制的上下文来生成一张cgimage图片
4、最后回到主线程来提交这个位图,设置layer的contents属性,这样就完成了一个UI控件的异步绘制
分类的实现原理:
分类在编译过程中,会生成 类方法列表、实例方法列表、属性列表等,但是却没有 实例变量列表,分类所属类是存在实例变量列表的。对比实例方法列表,可以发现 分类的实例方法列表中,并未对分类属性生成getter /setter方法
分类是在运行时进行加载的。
把分类的饿实例方法、属性、协议 添加到类的实例对象中原本存储的实例方法、属性、协议列表的前面
把分类的类方法和协议添加到类的元类上
应用多线程的时候会出现什么问题,应如何避免问题的发生?
多线程容易导致资源争抢,发生死锁现象.死锁通常是一个线程锁定了一个资源A,而又想去锁定资源B;在另一个线程中,锁定了资源B,而又想去锁定资源A以完成自身的操作,两个线程都想得到对方的资源,而不愿释放自己的资源,造成两个线程都在相互等待,造成了无法执行的情况。
避免死锁的一个通用的经验法则是:当几个线程都要访问共享资源A、B、C时,保证使每个线程都按照同样的顺序去访问它们,比如都先访问A,在访问B和C
采用GCD中的栅栏方法,用并行队列去装载事件并异步去执行
构建缓存时选用NSCache而非NSDictionary
当系统资源将要耗尽时,NSCache可以自动删减缓存。如果采用普通的字典,那么就要自己编写挂钩,在系统发出"低内存"通知时手工删减缓存,NSCache会先行删减"最久未使用的"对象。
NSCache并不会"拷贝"键,而是会"保留"它。此行为用NSDictionary也可以实现,但是需要编写比较复杂的代码。NSCache对象不拷贝键的原因在于:很多时候,键都是由不支持拷贝操作的对象来充当的。因此,NSCache对象不会自动拷贝键,所以说,在键不支持拷贝操作的情况下,该类用起来比字典更方便。
NSCache是线程安全的,NSDictionary不是。在开发者自己不编写加锁代码的前提下,多个线程便可以同时访问NSCache。对缓存来说,线程安全通常很重要,因为开发者可能要在某个线程中读取数据,此时如果发现缓存里找不到指定的键,那么就要下载该键所对应的数据了。
Static
1).函数体内 static 变量的作用范围为该函数体,不同于 auto 变量,该变量的内存只被分配一次,因此其值在下次调用时仍维持上次的值; 2).在模块内的 static 全局变量可以被模块内所用函数访问,但不能被模块外其它函数访问; 3).在模块内的 static 函数只可被这一模块内的其它函数调用,这个函数的使用范围被限制在声明它的模块内; 4).在类中的 static 成员变量属于整个类所拥有,对类的所有对象只有一份拷贝; 5).在类中的 static 成员函数属于整个类所拥有,这个函数不接收 this 指针,因而只能访问类的static 成员变量。
AsyncDisplayKit的使用
它是一个UI框架,能在异步线程绘制修改UI,然后统一添加进内存 渲染出来。
造成卡顿的原因:CPU 或 GPU消耗过大,导致再一次同步信号之间没有准备完成,没有内容提交,导致掉帧问题。
优化原理:
- 布局:iOS 自带的autolayout在布局上存在性能瓶颈,并且只能在主线程进行计算。因此asdk弃用了autolayout自己设计了一套布局方案
- 渲染:对于大量文本、图片等的渲染,uikit组件只能在主线程并且可能会造成GPU绘制的资源紧张。ASDK 使用了一些方法,如图层的预混合、并且异步在后台绘制图层,不阻塞主线程的运行
- 系统对象创建与销毁:uikit组件封装了CALayer图层的对象,在创建、调整销毁的时候,都会在主线程消耗资源。ASDK设计了一套NODE机制,也能够调用
ASDK最大特点就是异步。将消耗时间的渲染、布局、图片解码、及其他UI操作全部移出主线程,这样主线程就可以及时响应用户的操作,来达到流畅运行的目的
iOS 应用加密技术防止反编译
- 本地数据加密
- URL编码加密
- 网络传输数据加密
- 方法体、方法名高度混淆
- 程序结构混排加密