iOS不想看的一些Question
搜集一些总是不愿意面对的东西,答案自由发挥……
1. objc在向一个对象发送消息时,发生了什么?objc中向一个nil对象发送消息将会发生什么?如果向一个nil对象发消息不会crash的话,那么unrecognized selector sent to instance的错误是怎么回事?
- 根据对象的 isa 指针找到类对象 id,在查询类对象里面的 methodLists 方法函数列表,如果没有在好到,在沿着 superClass ,寻找父类,再从父类 methodLists 方法列表里面查询,最终找到 SEL ,根据 id 和 SEL 确认 IMP(指针函数),再发送消息。
- 如果向一个nil对象发送消息,首先在寻找对象的isa指针时就是0地址返回了,所以不会出现任何错误,也不会崩溃。
- 因为这个对象已经被释放了(引用计数为0了),那么这个时候再去调用方法肯定是会Crash的,这个时候对象就是一个野指针(指向僵尸对象(对象的引用计数为0,指针指向的内存已经不可用)的指针)了,安全的做法是释放后将对象重新置为nil,使它成为一个空指针。
2. 一个objc对象的isa的指针指向什么,有什么作用?
1、对象的isa指向类,类的isa指向元类(meta class),元类isa指向元类的根类。isa帮助一个对象找到它的方法
2、是一个Class 类型的指针. 每个实例对象有个isa的指针,他指向对象的类,而Class里也有个isa的指针, 指向meteClass(元类)。元类保存了类方法的列表。当类方法被调用时,先会从本身查找类方法的实现,如果没有,元类会向他父类查找该方法。同时注意的是:元类(meteClass)也是类,它也是对象。元类也有isa指针,它的isa指针最终指向的是一个根元类(root meteClass).根元类的isa指针指向本身,这样形成了一个封闭的内循环
3. iOS分类中能否添加属性,why?
分类是只可以添加方法的,不能直接添加属性的,因为分类的结构体中没有struct objc_ivar_list * _Nullable ivars这个数组。
如果要给分类添加属性,可以利用runtime,重写getter、setter方法
4. ARC通过什么方式帮助开发者管理内存?
ARC 在代码编译时,自动的向代码中插入retain和release,完成了对象的引用计算加减。
在运行时,通过对weak对象的管理,避免了引用环。runtime维护的weak对象列表。
5. 如何用GCD同步若干个异步调用?(如根据若干个url异步加载多张图片,然后在都下载完成后合成一张整图
6. block中使用self是否一定会造成循环引用?
block容易引起循环引用是因为在VC中声明block,相当于VC拥有block,然后在block中访问self,block又拥有self,所以会造成循环引用。但是如果self没有拥有block,在block中使用self就不会循环引用
7. BAD_ACCESS在什么情况下出现?如何调试BAD_ACCESS错误?
1⃣️访问了野指针,比如对一个已经释放的对象执行了release、访问已经释放对象的成员变量或者发消息,死循环。
3、如下图设置方法 image.png
2⃣️如下方法都可行:
1、重写object的respondsToSelector方法,找到出现EXEC_BAD_ACCESS前访问的最后一个object
2、设置全局断点快速定位问题代码所在行
8. dispatch_barrier_sync 和dispatch_barrier_async的区别
dispatch_barrier_sync 需要等待栅栏执行完才会执行栅栏后面的任务,而dispatch_barrier_async 无需等待栅栏执行完,会继续往下走(保留在队列里)
原因:在同步栅栏时栅栏函数在主线程中执行,而异步栅栏中开辟了子线程栅栏函数在子线程中执行
9. 字符串为什么使用copy?能不能使用Strong?
这是一个安全性问题,比如声明的一个NSString *str变量,然后把一个NSMutableString * mStr变量的赋值给它了,如果要求str跟着mStr变化,那么就用strong;如果str不能跟着mStr一起变化,那就用copy。而对于要把NSString类型的字符串赋值给str,那两都没啥区别。不会影响安全性,内存管理也一样
10. UITableView继承于谁?如何做性能优化?
1、UITableView继承于UIScrollview,因此它默认支持垂直滚动(只支持垂直滚动)
2、性能优化:cell复用、cell高度的计算、渲染、减少视图的数目、减少多余的绘制操作、不要给cell动态添加subView、异步化UI不要阻塞主线程等
11. 在做画板时使用一直在DrawRect方法画会造成内存爆涨?如果是你你会怎么样解决该问题?
1、drawLayer:(CALayer *)layer inContext:(CGContextRef)ctx;
在此方法调用之前,CALayer需要创建一个空寄宿图(有尺寸)和一个CoreGraphics 的CGContextRef(上下文),当绘制结束后,Core Animation打包所有图层和动画属性,然后通过IPC(内部处理通信)发送到渲染服务器进行显示,同时上下文会被不断渲染到屏幕上,直到下次调用setNeedsDisplay。
所以每次重绘都需要抹掉内存重新分配,空寄宿图的产生就消耗了大量内存,这也就是drawRect 内存暴增原因。
2、1⃣️用CGPath来定义想要绘制的图形,CAShapeLayer会自动渲染。它可以完美替代我们的直接使用Core Graphics绘制layer;
2⃣️CAShapeLayer继承自CALayer,可以使用CALayer的所有属性值,CAShapeLayer是一个通过矢量图形而不是bitmap来绘制的图层子类;3⃣️渲染快速,CAShapeLayer 使用了硬件加速,绘制同一图形会比用 Core Graphics 快很多;4⃣️高效使用内存,一个 CAShapeLayer 不需要像普通 CALayer 一样创建一个contents寄宿图形,所以无论有多大,都不会占用太多的内存;5⃣️不会被图层边界剪裁掉;6⃣️不会出现像素化;
总结:
CAShapeLayer属于CoreAnimation框架,其动画渲染直接提交到手机的GPU当中,相较于view的drawRect方法使用CPU渲染而言(实现drawRect消耗性能跟CoreGraphics 这个框架是基于CPU没有关系),GPU图像处理工作更多在硬件层面,效率极高;
一个 CAShapeLayer 不需要像普通 CALayer 一样创建一个寄宿图形,所以无论有多大,都不会占用太多的内存。
所以,关于绘图,我们可以尝试使用CAShapeLayer + UIBezierPath
12. 什么时候使用代理,什么时候使用Block,什么时候使用通知?
通知,代理,block都用于系统回调
代理
优点:
1.代理语法清晰,可读性高,易于维护
2.它减少代码耦合性,使事件监听与事件处理分离
3.一个控制器可以实现多个代理,满足自定义开发需求,灵活性较高
缺点:
1.实现代理的过程较繁琐
2.跨层传值时加大代码的耦合性,并且程序的层次结构也变得混乱
3.当多个对象同时传值时不易区分,导致代理易用性大大降低
Block
优点:
1.语法简洁,代码可读性和维护性较高 ;2. 配合GCD优秀的解决多线程问题
缺点:
1.Block中得代码将自动进行一次retain操作,容易造成内存泄漏
2.Block内默认引用为强引用,容易造成循环应用
通知
优点:
1.使用简单,代码精简
2.支持一对多,解决同时向多个对象监听的问题
3.传值方便快捷,context自带携带相应的内容
缺点:1.通知使用完毕后需要注销,否则会造成意外崩溃
2.key不够安全,编译器不会检测到是否被通知中心处理
3.调试时难以跟踪
4.当使用者向通知中心发送通知的时候,并不能获得任何反馈信息
5.需要一个第三方的对象来做监听与被监听者的中介
注:delegate运行成本低,block成本高
block出栈需要将使用的数据从栈内存拷贝到堆内存,当然对象的话就是加计数,使用完或者block置 nil 后才消除;delegate只是保存了一个对象指针,直接回调,没有额外消耗,相对C的函数指针,只做了一个查表的动作
13. 多个cell展示图片圆形头像时,如何处理优化性能?
问题:
1、我们需要一个圆角的图片,但是使用 cornerRadius 切了个假圆角,实际上是把四周透明了,操作了layer层。
2、有透明就有混合图层。
3、有混合图层,GPU 计算量就会变大。
4、图层固定还好,如果图层一直在移动(cell 滚动),那么就会出现离屏渲染。
所以使用核心绘图 + 贝塞尔圆形切割 产生一张新真正切割过四个角的图图片就好了。渲染最慢的操作之一是混合(blending),混合操作由GPU来执行。提高性能的方法是减少混合操作的次数。优化混合操作的关键点是在平衡CPU和GPU的负载。
拖慢帧率的原因其实都是Off-Screen Rendering(离屏渲染)的原因。离屏渲染是个好东西,但是频繁发生离屏渲染是非常耗时的。
14. 离屏渲染
通常,GPU 的渲染性能要比 CPU 高效很多,同时对系统的负载和消耗也更低一些。
CPU离屏渲染:core graphics是cpu渲染
GPU离屏渲染:需要开辟新的缓冲区渲染,绘制的时候需要上下文切换。
15. coreData和FMDB的区别及用法?
CF9CED8C-4B12-4DAA-909E-5B5D319BD91D.png1⃣️FMDB:用OC封装C语言SQLite3,非苹果官方,第三方;还是需要程序员编写SQL语句
2⃣️CoreData:用OC封装C语言SQLite3,相对FMDB来说较重量级,苹果官方;不需要程序员编写SQL语句
3⃣️CoreData会把一个对象整个存到数据库,而使用FMDB可以做到存储一个对象的某个属性
16. const常量与宏定义区别?
1.一般项目中尽量避免使用大量的宏定义,宏定义不会分配内存,是代码段的替换,而static const 修饰的常量 ,则只会开启一份内存空间,其使用效果和宏定义相同,建议以后项目开发中定义常量时,多用 static const 来代替宏定义
2.const修饰字符串:一般const修改可变与不可变,就看const右边修饰的是什么,如:NSString * const name = @"JACK",则代表指针name不可变,*name可变,若NSString const * name = @"JACK",则 *name不可变,指针name可变
3.const常量有数据类型,而宏常量没有数据类型;编译器可以对前者进行类型安全检查。而对后者只进行字符替换,没有类型安全检查,并且在字符替换可能会产生意料不到的错误(边际效应);宏定义是一个’编译时’概念,const是’运行时’概念
4.宏定义可以通过#undef来使之前的宏定义失效,const常量定义后将在定义域内永久有效
5.宏定义不能作为参数传递给函数,const常量可以在函数的参数列表中出现
17. runloop的运行模式
主线程已经自动创建了Runloop,子线程中需要手动创建之后开启runloop才能保证线程不死,定时器才能正常工作。RunLoop的5个类: CFRunLoopRef、CFRunLoopModeRef、CFRunLoopSourceRef、CFRunLoopTimerRef、CFRunLoopObserverRef。
在RunLoop中有多个运行模式,但RunLoop只能选择一种模式运行
Mode里面至少要有一个Timer或Source
CFRunLoopModeRef代表RunLoop的运行模式
一个RunLoop包含若干个Mode,每个Mode又包含若干个Source/Timer/Observer
每次RunLoop启动时,只能指定其中一个Mode,这个Mode被称作CurrentMode
如果需要切换Mode,只能退出Loop ,在重新指定一个Mode进入
这样做主要是为了分割开不同组的Source/Timer/Observer,让其互不影响
18. NSMutableArray和NSArray用什么修饰好?
[答案链接]https://www.jianshu.com/p/503ed56bbf1f
19. 什么情况下用assign,什么情况下用retain,什么情况下用copy?
这些关键字一般影响的是set方法:
assign:直接赋值,无retain操作;
retain:release旧值,retain新值;
copy:set方法会先release旧值,再copy一个新的对象,reference count 为1。
assign 适用于基本数据类型如int,float,struct等值类型,不适用于引用类型。因为值类型会被放入栈中,遵循先进后出原则,由系统负责管理栈内存。而引用类型会被放入堆中,需要我们自己手动管理内存或通过ARC管理
20. 浅拷贝和深拷贝区别
浅拷贝:浅拷贝并不拷贝对象本身,只是对指向对象的指针进行拷贝
深拷贝:直接拷贝对象到内存中一块区域,然后把新对象的指针指向这块内存
调用copy和mutableCopy方法的区别:
1、非容器类对象:
非容器类不可变对象调用Copy方法其实只是把当前对象的指针指向了原对象的地址,而调用mutableCopy方法则是新分配了一块内存区域并把新对象的指针指向了这块区域。对于可变对象来说调用Copy和MutableCopy方法都会重新分配一块内存。但是copy和mutableCopy的区别在于copy在复制对象的时候其实是返回了一个不可变对象,当调用方法改变对象的时候会崩溃。
2、容器类对象:
可变对象不管调用Copy还是MutableCopy都是新分配一块内存。但是虽然重新分配了一块内存,对象里面的数据依然是指针复制,容器类对象,其元素对象始终是指针复制