2019亲身面试所得
8.21
iOS-NSThread、NSOperation和GCD区别
NSThread:
优点:NSThread 比其他两个轻量级,使用简单
缺点:需要自己管理线程的生命周期、线程同步、加锁、睡眠以及唤醒等。线程同步对数据的加锁会有一定的系统开销NSOperation:
不需要关心线程管理,数据同步的事情,可以把精力放在自己需要执行的操作上
NSOperation是面向对象的GCD:
Grand Central Dispatch是由苹果开发的一个多核编程的解决方案。iOS4.0+才能使用,是替代NSThread, NSOperation的高效和强大的技术
iOS性能优化
卡顿优化:
- 尽可能减少CPU、GPU资源消耗
- 按照60FPS的刷帧率,每隔16ms就会有一次VSync信号
- 尽量用轻量级的对象,比如用不到事件处理的地方,可以考虑使用CALayer取代UIView
- 不要频繁地调用UIView的相关属性,比如frame、bounds、transform等属性,尽量减少不必要的修改
- 尽量提前计算好布局,在有需要时一次性调整对应的属性,不要多次修改属性
- Autolayout会比直接设置frame消耗更多的CPU资源
- 图片的size最好刚好跟UIImageView的size保持一致
- 控制一下线程的最大并发数量
- 尽量把耗时的操作放到子线程
- 文本处理(尺寸计算、绘制)
- 图片处理(解码、绘制)
- 尽量避免短时间内大量图片的显示,尽可能将多张图片合成一张进行显示
- GPU能处理的最大纹理尺寸是4096x4096,一旦超过这个尺寸,就会占用CPU资源进行处理,所以纹理尽量不要超过这个尺寸
- 尽量减少视图数量和层次
- 减少透明的视图(alpha<1),不透明的就设置opaque为YES
- 尽量避免出现离屏渲染
iOS - 内存管理(ARC)
引用计数:
引用计数(Reference Count)是一个简单而有效的管理对象生命周期的方式。当我们创建一个新对象的时候,它的引用计数为 1,当有一个新的指针指向这个对象时,我们将其引用计数加 1,当某个指针不再指向这个对象是,我们将其引用计数减 1,当对象的引用计数变为 0 时,说明这个对象不再被任何指针指向了,这个时候我们就可以将对象销毁,回收内存。ARC的内存管理
ARC 能够解决 iOS 开发中 90% 的内存管理问题,但是另外还有 10% 内存管理,是需要开发者自己处理的,这主要就是与底层 Core Foundation 对象交互的那部分,底层的 Core Foundation 对象由于不在 ARC 的管理下,所以需要自己维护这些对象的引用计数。
内存管理问题
1.循环引用(block,代理)
2.Core Foundation 对象需要手动管理计数器
iOS强引用与弱引用
- 强引用
ARC中修饰符是strong,比如 strong NSObject *obj; - 弱引用
在ARC中修饰符是weak,比如 weak NSObject *obj; - 两者区别
简单点讲的话,强引用持有对象,而弱引用不持有对象。 - 应用
平时一般都是用strong,也就是默认不添加,在会照成循环引用时才使用weak。
当两个不同的对象各有一个强引用指向对方时就造成循环引用,会导致对象无法释放.这时我们得用weak(MRC的话是用assign),代理(delegate)和block很容易造成循环引用。
WebView的使用
主要重点还是webView与JS交互:
1.加载webview
2.webview的代理方法:
// 当点击页面进行加载数据的时候调用- (BOOL)webView:(UIWebView )webView shouldStartLoadWithRequest:(NSURLRequest )request navigationType:(UIWebViewNavigationType)navigationType;// 当页面开始加载的时候调用- (void)webViewDidStartLoad:(UIWebView )webView;// 当页面加载完成的时候调用- (void)webViewDidFinishLoad:(UIWebView )webView;// 页面加载失败的时候调用- (void)webView:(UIWebView )webView didFailLoadWithError:(NSError )error;
3.交互:1、拦截url ;2、注册OC与js方法 ; 3、桥接机制
iOS 如何高效的使用多线程以及对多线程的理解
一、如何高效的使用多线程
- 减少队列切换
- 控制线程数量
- 线程优先级权衡
- 主线程任务的优化:内存复用、懒加载任务、任务拆分排队执行、主线程空闲时执行任务
二、多线程的理解
1、多线程:
- 一个进程中可以开启多条线程,每个线程可以并发(同时)执行不同的任务;
- 多线程可以提高任务的执行效率
2、多线程的原理:
- 同一时间,CPU只能处理一条线程,只有一条线程在执行(单核)
- 多线程并发(同时)执行,其实是CPU快速的在多条线程之间调度(切换)
- 如果CPU调度线程的时间足够快,就造成了多线程并发执行的假象
- 如果线程非常多,会导致CPU在很多的线程之间调度,消耗大量的CPU资源,每条线程被调度执行的频次会降低(线程的执行效率降低)
3、多线程的优缺点
优点:
1、能适当提高程序的执行效率
2、能适当提高资源利用率(CPU,内存利用率)缺点:
1、创建线程是有开销的,iOS下主要成本包括:内核数据结构(大约1kb),栈空间(子线程512kb,主线程1mb,也可以使用-setStackSize:设置,但必须是4k的倍数,而且最小是16k),创建线程大约需要90毫秒的创建时间
2、如果开启大量的线程,会降低程序的性能
3、线程越多,CPU在调度线程上的开销就越大
4、程序设计更加复杂,比如线程之间的通信,多线程的数据共享多线程在iOS开发中的应用
1、主线程:一个iOS程序运行后,默认会开启一条线程,称为主线程或UI线程
2、主线程的主要作用:1.显示/刷新UI界面 2.处理UI事件(比如点击事件,滚动事件,拖拽事件等)
3、主线程的使用注意:1.不要将比较耗时的操作放到主线程中 2.耗时操作会卡住主线程,严重影响UI的流畅度,给用户一种卡的坏体验
4、耗时操作的执行:将耗时操作放在子线程(后台线程,非主线程)
5、获得主线程:NSThread main=[NSThread mainThread];
6、获得当前线程:NSThread current=[NSThread currentThread];
7、判断是否是主线程:1.number==1? 2.类方法 :BOOL isMain= [NSThread isMainThread]; 3.对象方法:BOOL ifMain=[current isMainThread];
ios网络请求时,如何保证网络安全
1.https:
2.token
3.post请求
4.加密
base64,
MD5 登录注册使用 加盐+再次MD5加密
a.对称加密 同一个秘钥
b.非对称加密 公钥私钥
RSA 支付宝支付使用
c.钥匙串存储
keychain
iOS常见的几种加密方法
- base64加密
- POST加密
- Token值(登录令牌)
- MD5加密–(信息-摘要算法) 哈希算法之一
- 时间戳密码
- 钥匙串访问
- 指纹识别
iOS网络请求安全
- Base64编码
- MD5散列
- HTTPS请求
iOS界面布局的几种方式
- 这是最简单的布局方式,在UI控件初始化时通过- (void)initWithFrame进行设置,或者在init之后在进行设置
- 使用AutoLayout进行页面布局
- Masonry:布局库轻量、简便、功能强大
- autoresizing
对称加密和非对称加密的区别?分别有哪些算法的实现?
对称加密与非对称加密:
对称加密又称公开密钥加密,加密和解密都会用到同一个密钥,如果密钥被攻击者获得,此时加密就失去了意义。
常见的对称加密算法有DES、3DES、AES、Blowfish、IDEA、RC5、RC6。
非对称加密又称共享密钥加密,使用一对非对称的密钥,一把叫做私有密钥,另一把叫做公有密钥;公钥加密只能用私钥来解密,私钥加密只能用公钥来解密。
常见的公钥加密算法有:RSA、ElGamal、背包算法、Rabin(RSA的特例)、迪菲-赫尔曼密钥交换协议中的公钥加密算法、椭圆曲线加密算法)。
http和https的区别
HTTPS是HTTP的安全版,在HTTP的基础上加入SSL层(Secure Socket Layer 安全套接字),对数据传输(客户端发送数据和服务器响应数据)进行加密和身份验证,广泛用于网络通讯,如交易和支付。
HTTPS和HTTP的区别:
- https协议需要到ca申请证书,一般免费证书很少,需要交费。
- http是超文本传输协议,信息是明文传输,https 则是具有安全性的ssl加密传输协议。
- http和https使用的是完全不同的连接方式,用的端口也不一样,前者是80,后者是443。
- http的连接很简单,是无状态的。
- HTTPS协议是由SSL+HTTP协议构建的可进行加密传输、身份认证的网络协议要比http协议安全。
8.22
ios 线程间通信
- 什么叫做线程间通信
- 在1个进程中,线程往往不是孤立存在的,多个线程之间需要经常进行通信
- 线程间通信的体现
- 1个线程传递数据给另1个线程,1个线程传递数据给另1个线程
- 线程间通信常用方法
- -(void)performSelectorOnMainThread:(SEL)aSelector withObject:(id)arg waitUntilDone:(BOOL)wait;
- -void)performSelector:(SEL)aSelector onThread:(NSThread *)thr withObject:(id)arg waitUntilDone:(BOOL)wait;
iOS 中几种常用的锁总结
多线程编程中,应该尽量避免资源在线程之间共享,以减少线程间的相互作用。 但是总是有多个线程相互干扰的情况(如多个线程访问一个资源)。在线程必须交互的情况下,就需要一些同步工具,来确保当它们交互的时候是安全的。
锁是线程编程同步工具的基础。iOS开发中常用的锁有如下几种:
- @synchronized
- NSLock 对象锁
- NSRecursiveLock 递归锁
- NSConditionLock 条件锁
- pthread_mutex 互斥锁(C语言)
- dispatch_semaphore 信号量实现加锁(GCD)
- OSSpinLock (暂不建议使用,原因参见这里)
@synchronized 关键字加锁 互斥锁,性能较差不推荐使用
`@synchronized(这里添加一个OC对象,一般使用self) {这里写要加锁的代码
}`
注意点:- 加锁的代码尽量少
- 添加的OC对象必须在多个线程中都是同一对象
- 优点是不需要显式的创建锁对象,便可以实现锁的机制。
- @synchronized块会隐式的添加一个异常处理例程来保护代码,该处理例程会在异常抛出的时候自动的释放互斥锁。所以如果不想让隐式的异常处理例程带来额外的开销,你可以考虑使用锁对象。
NSLock 互斥锁 不能多次调用 lock方法,会造成死锁
在Cocoa程序中NSLock中实现了一个简单的互斥锁。所有锁(包括NSLock)的接口实际上都是通过NSLocking协议定义的,它定义了lock和unlock方法。你使用这些方法来获取和释放该锁。
NSLock类还增加了tryLock和lockBeforeDate:方法。
tryLock试图获取一个锁,但是如果锁不可用的时候,它不会阻塞线程,相反,它只是返回NO。
lockBefore lockBeforeDate:方法试图获取一个锁,但是如果锁没有在规定的时间内被获得,它会让线程从阻塞状态变为非阻塞状态(或者返回NO)。
NSRecursiveLock 递归锁
使用锁最容易犯的一个错误就是在递归或循环中造成死锁。
NSRecursiveLock类定义的锁可以在同一线程多次lock,而不会造成死锁。
递归锁会跟踪它被多少次lock。每次成功的lock都必须平衡调用unlock操作。
只有所有的锁住和解锁操作都平衡的时候,锁才真正被释放给其他线程获得。
NSConditionLock 条件锁
在线程1中的加锁使用了lock,是不需要条件的,所以顺利的就锁住了。
unlockWithCondition:在开锁的同时设置了一个整型的条件 2 。
线程2则需要一把被标识为2的钥匙,所以当线程1循环到 i = 2 时,线程2的任务才执行。
NSConditionLock也跟其它的锁一样,是需要lock与unlock对应的,只是lock,lockWhenCondition:与unlock,unlockWithCondition:是可以随意组合的,当然这是与你的需求相关的。
pthread_mutex 互斥锁
dispatch_semaphore 信号量实现加锁
GCD中也已经提供了一种信号机制,使用它我们也可以来构建一把”锁”。
OSSpinLock
OSSpinLock 显示的效率最高(暂不建议使用)
SVN与Git的区别
- 最核心的区别Git是分布式的,而Svn不是分布的。Git更倾向于分布式开发,因为每一个开发人员的电脑上都有一个Local Repository,所以即使没有网络也一样可以Commit,查看历史版本记录,创建项目分支等操作,等网络再次连接上Push到Server端。
- Git把内容按元数据方式存储,而SVN是按文件:因为,.git目录是处于你的机器上的一个克隆版的版本库,它拥有中心版本库上所有的东西,例如标签,分支,版本记录等。.git目录的体积大小跟.svn比较,你会发现它们差距很大。
- Git没有一个全局版本号,而SVN有:目前为止这是跟SVN相比Git缺少的最大的一个特征。
- Git的内容的完整性要优于SVN: GIT的内容存储使用的是SHA-1哈希算法。这能确保代码内容的完整性,确保在遇到磁盘故障和网络问题时降低对版本库的破坏。
- Git下载下来后,在OffLine状态下可以看到所有的Log,SVN不可以。
- SVN必须先Update才能Commit,忘记了合并时就会出现一些错误,git还是比较少的出现这种情况。
- 克隆一份全新的目录以同样拥有五个分支来说,SVN是同时复制5个版本的文件,也就是说重复五次同样的动作。而Git只是获取文件的每个版本的 元素,然后只载入主要的分支(master)在我的经验,克隆一个拥有将近一万个提交(commit),五个分支,每个分支有大约1500个文件的 SVN,耗了将近一个小时!而Git只用了区区的1分钟!
- 版本库(repository):SVN只能有一个指定中央版本库。当这个中央版本库有问题时,所有工作成员都一起瘫痪直到版本库维修完毕或者新的版本库设立完成。而 Git可以有无限个版本库。或者,更正确的说法,每一个Git都是一个版本库,区别是它们是否拥有活跃目录(Git Working Tree)。如果主要版本库(例如:置於GitHub的版本库)发生了什麼事,工作成员仍然可以在自己的本地版本库(local repository)提交,等待主要版本库恢复即可。工作成员也可以提交到其他的版本库!
- 分支(Branch):在SVN,分支是一个完整的目录。且这个目录拥有完整的实际文件。如果工作成员想要开啟新的分支,那将会影响“全世界”!每个人都会拥有和你一样的分支。如果你的分支是用来进行破坏工作(安检测试),那将会像传染病一样,你改一个分支,还得让其他人重新切分支重新下载,十分狗血。而 Git,每个工作成员可以任意在自己的本地版本库开启无限个分支。举例:当我想尝试破坏自己的程序(安检测试),并且想保留这些被修改的文件供日后使用, 我可以开一个分支,做我喜欢的事。完全不需担心妨碍其他工作成员。只要我不合并及提交到主要版本库,没有一个工作成员会被影响。等到我不需要这个分支时, 我只要把它从我的本地版本库删除即可。Git的分支名是可以使用不同名字的。例如:我的本地分支名为OK,而在主要版本库的名字其实是master。最值得一提,我可以在Git的任意一个提交点(commit point)开启分支!(其中一个方法是使用gitk –all 可观察整个提交记录,然后在任意点开啟分支。)
提交(Commit):在SVN,当你提交你的完成品时,它将直接记录到中央版本库。当你发现你的完成品存在严重问题时,你已经无法阻止事情的发生了。如果网路中断,你根本没办法提交!而Git的提交完全属於本地版本库的活动。而你只需“推”(git push)到主要版本库即可。Git的“推”其实是在执行“同步”(Sync)。
总结:
- SVN的特点是简单,只是需要一个放代码的地方时用是OK的。
- Git的特点版本控制可以不依赖网络做任何事情,对分支和合并有更好的支持(当然这是开发者最关心的地方),不过想各位能更好使用它,需要花点时间尝试下。
iOS开发之使用Git的基本使用
一、GitHub准备工作
跳转我的GitHUb,并且创建一个仓库名字
二、项目准备工作
- 在你的代码目录下(此处以桌面为例)建一个新的目录(文件夹,此处为testGit)
- 打开终端,切换到上面的文件夹,使用 git init命令初始化
- 用Xcode创建一个iOS项目,该项目就放在刚刚新建的文件夹下
- 在Xcode里面选择菜单Source Control下的commit
三、关联GitHub和项目代码
1、上传项目至git仓库
- 打开网站 https://help.github.com ,这个是GitHub的帮助网站,搜索ssh,选择红框的选项:Generating a new SSH key and adding it to the ssh-agent
- 打开之后里面有教大家如何产生SSH key,主要用到一个命令:
ssh-keygen -t rsa -b 4096 -C "your_email@example.com"
,写入终端获取密钥 - 新建一个终端,然后执行上面的命令,一直回车,直到看到如下的界面说明生成SSH key成功
- 此时在用户文件夹下会产生一个ssh的隐藏文件夹,cd切换到该ssh目录下,可以通过ls -a可以看到该文件下有两个重要文件id_rsa和id_rsa.pub,命令:
- cd ~
- cd .ssh/
- ls -a
- 在终端直接使用pbcopy < id_rsa.pub命令复制一下密钥,此时密钥已经在剪贴板
- 重新打开一个GitHub网页,点击个人头像,打开Settings,选择左边的SSH and PGP keys,然后点击右侧的New SSH key,最后将密钥粘贴过去,添加SSH key
- 找到GitHub准备工作中让大家复制的指令,打开终端切换到项目文件夹,执行该命令:
git remote add orgin https://github.com/mlj311/Test
- 打开Xcode选择菜单Source Control,点击的push按钮,然后输入自己的GitHub帐号和密码,等待上传完成
1.1、假如已经有github了,那就简单了:
1.1.1、创建一个新仓库”newProject“
1.1.2、终端cd文件夹,输入指令:git init
1.1.3、git add .
1.1.4、git commit -m “XXX”;
1.1.5、关联仓库:git remote add origin https://github.com/XXX/newProject.git
1.1.6、git push -u origin master
2、对于复杂的系统,我们可能要开好几个分支来开发,那么怎样使用git合并分支呢?
合并步骤:
2.1、进入要合并的分支(如开发分支合并到master,则进入master目录)
git checkout master
git pull
2.2、查看所有分支是否都pull下来了
git branch -a
2.3、使用merge合并开发分支
git merge 分支名
2.4、查看合并之后的状态
git status
2.5、有冲突的话,解决冲突;
2.6、解决冲突之后,将冲突文件提交暂存区
git add 冲突文件
2.7、提交merge之后的结果
git commit
如果不是使用git commit -m “备注” ,那么git会自动将合并的结果作为备注,提交本地仓库;
2.8、本地仓库代码提交远程仓库
git push
git将分支合并到分支,将master合并到分支的操作步骤是一样的。