1. 什么是arc?(arc是为了解决什么问题诞生的?)
- 什么是arc:
arc就是自动引用技术,作用是编译器代替程序员为对象添加retain/release。(我觉得了解到这种程度就差不多了!)- arc是为了解决什么问题诞生的?
对象的生命周期包括诞生和死亡。alloc一个对象obj,obj的retain+1,结束使用后调用[obj release],这样对象的retainCount为0,不会造成内存泄漏。MRC——>ARC的过程就是手动添加改为了自动添加的过程。- 那么为什么弃用MRC改用ARC呢?是为了解决retain和release不匹配错误,而导致不匹配的原因有:
- 创建一个对象,要确保该对象使用结束后销毁,避免造成内存泄露。(用完未销毁)
- 解决重复释放.(谁创建谁释放,谁释放,重复释放会导致奔溃)
2.请解释以下keywords的区别: assign vs weak, __block vs __weak
- assign和weak都是用于修饰弱引用,而他们的区别是:
assign:简单赋值,修饰基本数据类型(NSInteger)、c数据类型(int)和delegate(避免循环引用)。
- 那么assign为什么不用来修饰对象呢?
因为assign会导致下面这个问题:
指针a指向一块内存,这时把a赋给了b,如果使用assign属性,a和b就指向同一块内存。如果这时候把a释放了,那么b在使用使用这块内存的时候就会crash.
weak:弱引用,修饰对象类型。
- 为什么基本类型和C数据类型的修饰用assign?
因为基本数据类型不是对象,在内存中创建和使用后,在一个方法体结束后就被删除。基本数据类型不需要引用计数,而其他修饰词需要引用计数,所以使用assign。
2.__block和__weak的区别:
- __block:引用修饰,所以被__block修饰的变量的值是动态变化的,也就是可以被重新赋值。
注意:如果在block内部要修改外部变量而不用__block修饰,会直接报错.- __weak:主要作用是避免循环引用, __weak typeof (self)weakSelf = self;
3. __block在arc和非arc下含义一样吗?
MRC下的__block相当于ARC下的__weak,而ARC下的__block是为了修饰一个变量能在block中被修改。
- ARC循环引用解决:
__weak:弱引用(首选方案)不会产生强引用,指向的对象销毁时,会自动让指针置为nil
__unsafe_unretain:弱引用,不安全 ,指向的对象销毁时,指针存储的地址值不变
__block:缺点就是必须执行block,清空指针- MRC循环引用解决:
不支持__weak的
__unsafe_unretain
__block
4. 使用atomic一定是线程安全的吗?
- atomic的作用是为属性的setter/getter方法加锁,避免多线程情况下访问setter/getter产生数据错乱。所以在访问setter/getter方法时是线程安全的,而对于进行其他操作的多线程则和atomic没有直接关系
- 给属性加上atomic修饰,可以保证属性的setter和getter都是原子性操作,也就是说保证setter和getter内部都是线程同步
它并不能保证使用属性的过程是线程安全的
atomic在ios里基本上不适用,太消耗内存,一般在Mac上用
5. 描述一个你遇到过的retain cycle例子
- 在-(void)dealloc里invalidate一个NSTimer对象。
这么做会retain cycle的原因:
+ (NSTimer *)timerWithTimeInterval:(NSTimeInterval)seconds target:(id)target selector:(SEL)aSelector userInfo:(id)userInfo repeats:(BOOL)repeats
这个方法在官方文档里的说明:The timer maintains a strong reference to this object until it (the timer) is invalidated.解释:timer对象会对object拥有一个强引用,如果object是self,那么self就被timer retain,如果timer一直不invalidated,那么dealloc就一直不会被调用。
解决方法:在viewWillDisappear的时候invalidateNSTimer对象。
- (void)viewWillDisappear:(BOOL)animated{
[super viewWillDisappear:animated];
[timer invalidate];
}
- block中的retain cycle:
block会对用到的对象做一个copy操作,retainCount+1,所以block对用到的对象就有了一个强引用。那么用到的对象是否对block有一个强引用是要看具体条件的。
下面就产生了循环引用
[self doSomething:^(BOOL param) {
self.tage = param;
}];
self.doSomethingWithBlock(YES);
6.+(void)load; +(void)initialize;有什么用处?
- app启动后会自动调用每个类+ (void)load方法。
先调用类的load方法,如果有继承了父类的,先调用load,然后在调用分类的load方法。- 当类第一次接受到消息时候调用initialze。
某个类继承了父类的时候,先调用父类的initialze,在调用自己的initialze,注意,不需要调用super。如果之前已经调用过initialze就不会再initialze进行初始化。有分类的时候,有继承父类的,先调用父类的initialze,继承了父类的子类调用分类的initialze,因为在调用过程中分类的方法移到了子类的前面。
7. 为什么其他语言里叫函数调用, objective c里则是给对象发消息(或者谈下对runtime的理解)
当我们给对象发送消息时[tableView cellForRowAtIndexPath:indexPath],编译器会把这段Object-c代码转换为c的函数调用objc_msgSend(tableVIew,@selector(cellForRowAtIndexPath:),indexPath),其中indexPath是参数。
8. 什么是method swizzling?
黑魔法——偷梁换柱。每个类都有一个方法列表,存放着selector的名字和对应方法实现的映射关系。而IMP就是指向方法实现(类似指针)。method swizzling可以做到运行时更换selector指向对应方法实现的IMP。比如selectorA指向A实现,selectorB指向B实现,更换两个方法的IMP后,selectorA调用的就是B实现了。
9. UIView和CALayer是啥关系?
- 1.在IOS上创建一个UIView就默认创建一个CALaye,可以通过如下方法获得CALayer *layer = self.view.layer;
- 2.Layer提供内容绘制和动画,但它本身不能创建可视界面,需要添加到UIView.
- 3.Layer不能响应事件。
- CALayer和UIView的区别及联系:
UIView是iOS系统中界面元素的基础,所有的元素界面都集成自它,UIView本身完全是由CoreAnimation来实现,真正的绘图部分是由一个CALayer类来管理,UIView更像是一个CALayer的管理类,所以访问它的与绘图和坐标相关的属性,如frame,bounds等,实际上都是在访问其所包含的CALayer的相关属性,因此,,可以在所有UIview的子类上实现动画效果。
UIview是继承自UIResponder,能接受并相应事件,负责显示内容的管理,而CALayer继承自NSObject,不能响应事件,负责显示内容的绘制。- UIView可以响应事件,Layer不可以
UIKit使用UIResponder作为响应对象,来响应系统传递过来的事件并进行处理。UIApplication、UIViewController、UIView、和所有从UIView派生出来的UIKit类(包括UIWindow)都直接或间接地继承自UIResponder类。
在 UIResponder中定义了处理各种事件和事件传递的接口, 而 CALayer直接继承 NSObject,并没有相应的处理事件的接口。- UIView主要是对显示内容的管理而 CALayer 主要侧重显示内容的绘制
UIView主要是对显示内容的管理, 而CALayer主要是显示内容的绘制. UIView是CALayer的CALayerDelegate, 在代理方法内部[UIView(CALayerDelegate) drawLayer:inContext]调用UIView的drawRect方法, 从而绘制出UIView的内容. UIView的显示内容由内部的CALayer:display方法来实现.编程问题都可以抽离出机制和策略部分。机制一旦实现,就会很少更改,但策略会经常得到优化。CALayer也可以看做是一种机制,提供图层绘制,CALayer的头文件基本上是没怎么变过的,而UIView可以看做是策略,变动很多。越是底层越是机制,越是机制就越是稳定。机制与策略分离,可以使得需要修改的代码更少,特别是底层代码,这样可以提高系统的稳定性。UIView遮蔽了大部分的CALayer接口,抽取构造出更易用的frame和动画实现,这样上手更容易。- frame, position, bounds调用
一个CALayer的frame是由其anchorPoint, position, bounds, transform共同决定的, 而一个UIView的的frame只是简单地返回CALayer的frame, 同样UIView的center和bounds也只是简单返回CALayer的Position和Bounds对应属性- 在做 iOS 动画的时候,修改非 RootLayer的属性(譬如位置、背景色等)会默认产生隐式动画,而修改UIView则不会
对于每一个 UIView 都有一个 layer,把这个 layer 且称作RootLayer,而不是 View 的根 Layer的叫做 非 RootLayer。我们对UIView的属性修改时时不会产生默认动画,而对单独 layer属性直接修改会,这个默认动画的时间缺省值是0.25s.
在 Core Animation 编程指南的 “How to Animate Layer-Backed Views” 中,对为什么会这样做出了一个解释:(UIView 默认情况下禁止了 layer 动画,但是在 animation block 中又重新启用了它们)是因为任何可动画的 layer 属性改变时,layer 都会寻找并运行合适的 'action' 来实行这个改变。在 Core Animation 的专业术语中就把这样的动画统称为动作 (action,或者 CAAction)。- 为什么CALayer不能直接使用UIColor,UIImage
首先,CALayer是定义在QuartzCore框架中的,CGImageRef、CGColorRef两种数据类型是定义在CoreGraphics框架中的
,而UIColor和UIImage是定义在UIKit框架中的。
其次,QuartzCore框架和CoreGraphics框架是可以跨平台使用的,在iOS和Mac OS X上都能使用但是UIKit只能在iOS中使用。
所以,为了保证可移植性,QuartzCore不能使用UIImage、UIColor,只能使用CGImageRef、CGColorRef。
10. 如何高性能的给UIImageView加个圆角?(不准说layer.cornerRadius!)
使用CoreGraphics将图片添加到graphics context中,绘制圆角路径然后对图片内容进行裁剪:
- (UIImage *)roundCornersOfImage:(UIImage *)image {
CGRect rect = CGRectMake(0, 0, image.size.width, image.size.height);
UIGraphicsBeginImageContextWithOptions(image.size, false, 0);
CGContextRef ctx = UIGraphicsGetCurrentContext();
CGPathRef path = [UIBezierPath bezierPathWithRoundedRect:rect cornerRadius:20].CGPath;
CGContextAddPath(ctx, path);//将创建的路径添加到当前上下文路径中
CGContextClip(ctx);//裁剪路径
[image drawInRect:rect];
UIImage *finalImage = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
return finalImage;
}
注意:比使用layer.cornerRadius性能高的原因在于对大量图片处理时(比如tableViewCell中的图片)。layer.cornerRadius需要的离屏渲染会占用额外的资源导致掉帧。而使用CoreGraphics不会引起这个问题。(CoreGraphics是耗时操作,最好异步调用)。ps. IOS9后直接cornerRadius设置图片的圆角不会引起离屏渲染问题,但是如果设置了背景等一些属性后再使用cornerRadius,还是会有屏渲染问题的。
11. 使用drawRect有什么影响?
- 可以在这个方法里完成绘制视图的操作。这个方法执行时表明系统已经准备好绘制环境,用户可以进行绘制操作了。除了绘制,其他更改视图的行为最好不要在这个方法里完成,比如修改视图属性的行为,如背景色或者对view.layer的设置。
- 视图在生命周期内触发这个方法的情况有多种,比如添加进视图层级或者重绘等。因此如果一个调用drawRect的视图在一个页面大量使用,可能会有性能问题。(我猜的,如果只是做了视图重绘,我认为一般是不会出问题的)
13. 麻烦你设计个简单的图片内存缓存器(移除策略是一定要说的)(SDWebImage源码学习)
同时使用内存缓存和硬盘缓存实现,其中内存缓存使用NSCache类。
- 存储:先将image对象直接存到cache,然后将image转为data写入到硬盘
- 读取:每次读取图片缓存,先去cache中查找。如果cache中不存在,则去disk中查找图片,然后把结果返回,同时保存到cache中。
- 图片移除:
- 通知注册监听app进程移除和app进入后台两个事件。如果两个事件发生时,1.自动删除存放超过1周的图片;2.如果当前图片容量合计超过总容量,则按时间顺序删除旧的文件,直到容量达标。
- 通知注册监听内存警告事件,当时间发生时,自动清空cache。
14. 讲讲你用Instrument优化动画性能的经历吧(别问我什么是Instrument)
开发中需要优化最多的就是tableView的滑动动画了。core Animation工具可以查看页面的fps。当滑动视图时,fps明显低于60则可能出现性能问题需要优化了。
Debug -> View Debugging -> Rendering之前是在core Animation里,现在单独拿出来了。
现在开发中会严重影响动画FPS的可能原因:
- 离屏渲染问题
图片圆角、阴影等操作会引起离屏渲染,会产生严重掉帧问题。Color offscreen-Rendering Yellow可以查看是哪些视图产生了离屏渲染。- 主线程阻塞
比如使用coreGraphics绘图这种耗时操作,如果放在主线程里去实现就很可能引起tableView滑动卡顿。- 优化:
- cell中减少阴影圆角的使用,必须的话可以使用光栅化缓存,或者coreGraphics绘制
- 耗时操作异步完成
- 尽量别空含有透明度的控件
- 图片尽量使用36bit格式,不要含有alpha通道,大小和视图frame匹配
15.loadView是干嘛用的?
- 当跳转到一个vc时要用到self.view,而self.view==nil时,系统会调用loadView创建一个view赋值给self.view。
- 我们可以重写loadView方法给self.view设置自定义view。这和 我们在viewDidLoad方法里创建视图添加到self.view的区别就是,通过loadView可以直接修改vc的rootView,即self.view的类型,前者的视图层级有两层,后者是一层。
- loadView中初始化的view不需要设置frame,系统默认它等于[UIScreen mainScreen].bounds。
16. viewWillLayoutSubView你总是知道的。。
- 和UIView的layoutSubviews类似。用于布局添加的子视图,只是平时我们都在viewDidLoad完成了。
- 当vc.view的bounds变化时,系统会执行这个方法。比如你在vc旋转时,你可以在这个方法里重新给子视图布局,让他满足横向的界面。layoutSubviews和viewWillLayoutSubView都适合用于重新布局子视图的需求,不要在里面进行初始化操作,毕竟在一个周期里可能会执行多次,引起歧义.
17. GCD里面有哪几种Queue?你自己建立过串行queue吗?背后的线程模型是什么样的?
- 我觉得算4种,主队列、全局队列、串行队列、并行队列。
- 背后的线程模型是什么意思?我是在不懂,按我自己的理解说:
- 任何队列使用dispatch_sync方式执行,不会创建新的线程,都是在当前线程里执行的。
- 使用dispatch_async执行主队列中任务,那么不会创建线程,在主线程中执行
- 使用dispatch_async执行全局队列或者并行队列,那么根据任务数开辟线程数
- 使用dispatch_async执行串行队列,会开辟一个线程,串行执行
18.http的post和get啥区别?(区别挺多的,麻烦多说点)
- get:
获取服务器上的数据
是通过URL传递数据,效率高
请求的数据在URL上 不安全
get请求的结果能够被浏览器缓存- post:
一般是往服务器提交数据,并获取服务器返回的结果
通过请求体传输数据 ,效率低
请求的数据用户看不到,在请求提上 相对安全
请求不能被浏览器缓存
19.UDP和TCP有什么区别:
- 连接方面区别
TCP面向连接(如打电话要先拨号建立连接)。
UDP是无连接的,即发送数据之前不需要建立连接。- 安全方面的区别
TCP提供可靠的服务,通过TCP连接传送的数据,无差错,不丢失,不重复,且按序到达。
UDP尽最大努力交付,即不保证可靠交付。- 传输效率的区别
TCP传输效率相对较低。
UDP传输效率高,适用于对高速传输和实时性有较高的通信或广播通信。- 连接对象数量的区别
TCP连接只能是点到点、一对一的
UDP支持一对一,一对多,多对一和多对多的交互通信。
20.runtime如何实现weak变量的自动置nil?
runtime 对注册的类, 会进行布局,对于 weak 对象会放入一个 hash 表中。 用 weak 指向的对象内存地址作为 key,当此对象的引用计数为0的时候会 dealloc,假如 weak 指向的对象内存地址是a,那么就会以a为键, 在这个 weak 表中搜索,找到所有以a为键的 weak 对象,从而设置为 nil。
21.BAD_ACCESS在什么情况下出现?
访问了野指针,比如对一个已经释放的对象执行了release、访问已经释放对象的成员变量或者发消息。 死循环
22.如何用GCD同步若干个异步调用?(如根据若干个url异步加载多张图片,然后在都下载完成后合成一张整图)
使用Dispatch Group追加block到Global Group Queue,这些block如果全部执行完毕,就会执行Main Dispatch Queue中的结束处理的block。
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
dispatch_group_t group = dispatch_group_create();
dispatch_group_async(group, queue, ^{ /*加载图片1 */ });
dispatch_group_async(group, queue, ^{ /*加载图片2 */ });
dispatch_group_async(group, queue, ^{ /*加载图片3 */ });
dispatch_group_notify(group, dispatch_get_main_queue(), ^{
// 合并图片
});
23.dispatch_barrier_async的作用是什么?
- 在并行队列中,为了保持某些任务的顺序,需要等待一些任务完成后才能继续进行,使用 barrier 来等待之前任务完成,避免数据竞争等问题。 dispatch_barrier_async 函数会等待追加到Concurrent Dispatch Queue并行队列中的操作全部执行完之后,然后再执行 dispatch_barrier_async 函数追加的处理,等 dispatch_barrier_async 追加的处理执行结束之后,Concurrent Dispatch Queue才恢复之前的动作继续执行。
- 打个比方:比如你们公司周末跟团旅游,高速休息站上,司机说:大家都去上厕所,速战速决,上完厕所就上高速。超大的公共厕所,大家同时去,程序猿很快就结束了,但程序媛就可能会慢一些,即使你第一个回来,司机也不会出发,司机要等待所有人都回来后,才能出发。 dispatch_barrier_async 函数追加的内容就如同 “上完厕所就上高速”这个动作。
24.苹果为什么要废弃dispatch_get_current_queue?
dispatch_get_current_queue容易造成死锁
24.NSTimer有什么需注意的以及和RunLoop的关系?
- 不管是重复性的timer还是一次性的timer都会对它的方法的接收者进行retain,这两种timer的区别在于“一次性的timer在完成调用以后会自动将自己invalidate,而重复的timer则将永生,直到你显式的invalidate它为止”。
- timer不是一种实时的机制,会存在延迟,而且延迟的程度跟当前线程的执行情况有关。
- 必须得把timer添加到runloop中,它才会生效。
- 要让timer生效,必须保证该线程的runloop已启动,而且其运行的runloopmode也要匹配。
网友评论