一、什么是arc?(arc是为了解决什么问题诞生的?)
Automatic Reference Counting,自动引用计数,即ARC,WWDC2011和iOS5所引入的。ARC是新的LLVM 3.0编译器的一项特性,使用ARC,解决了手动内存管理的麻烦。永远不写retain,release和autorelease三个关键字。
当ARC开启时,编译器将自动在代码合适的地方插入retain,release和autorelease。
关于效率:ARC是Objective-C编译器的特性,而不是运行时特性或者垃圾回收机制,ARC所做的只不过是在代码编译时为你自动在合适的位置插入release或autorelease,就如同之前MRC时你所做的那样。因此,至少在效率上ARC机制是不会比MRC弱的,而因为可以在最合适的地方完成引用计数的维护,以及部分优化,使用ARC甚至能比MRC取得更高的运行效率。
ARC的基本规则:只要某个对象被任一strong指针指向,那么它将不会被销毁。如果对象没有被任何strong指针指向,那么就将被销毁。weak类型的指针也可以指向对象,但是并不会持有该对象。
使用ARC以后,不论是strong还是weak类型的指针,都不再会指向一个dealloced的对象,从根源上解决了意外释放导致的crash。
那么是为了什么而诞生的ARC?
当然是因为MRC手动管理内存的缺陷,那么MRC的缺陷有哪些呢:
1.当我们要释放一个堆内存时,首先要确定指向这个堆空间的指针都被release了。(避免提前释放)
2.释放指针指向的堆空间,首先要确定哪些指针指向同一个堆,这些指针只能释放一次。(MRC下即谁创建,谁释放,避免重复释放)
3.模块化操作时,对象可能被多个模块创建和使用,不能确定最后由谁去释放。
4.多线程操作时,不确定哪个线程最后使用完毕
这就是为什么需要诞生ARC的原因啦。
二、请解释以下keywords的区别:assign vs weak, __block vs __weak
assign vs weak:
(1).weak,表明该属性定义了一种“非拥有关系” (nonowning relationship)。为这种属性设置新值时,设置方法既不保留新值,也不释放旧值。
(2).assign也可以修饰对象,但是用assign修饰的对象在释放后,指针的地址还是存在的,也就是说指针并没有被置为nil,会造成众所周知的野指针异常。然而,assign修饰的基础数据类型(例如NSInteger等)和C数据类型(int,float,double,char)等一般分配在栈空间上,栈空间的内存会由系统自动处理,当分配的栈空间的内存没有被指针指向时就会被销毁,所以不会造成野指针异常。
(3).weak比 assign多了一个功能就是当属性所指向的对象消失的时候(也就是内存引用计数为0)会自动赋值为 nil,这样再向 weak修饰的属性发送消息就不会导致野指针操作crash。
__block vs __weak:
_weak__typeof(&*self)weakSelf =self; 等同于
__weakUIViewController*weakSelf =self;
为什么不用__block 是因为通过引用来访问self的实例变量 ,self被retain,block也是一个强引用,引起循环引用,用__week是弱引用,当self释放时,weakSelf已经等于nil。
三、__block在arc和非arc下含义一样吗?
MRC下__block修饰的变量,并不改变引用计数,同时block内部并不对引入的外部对象,更改引用计数。
ARC下block会被修改为__NSMallocBlock__,同时引用计数增加了
四、使用atomic一定是线程安全的吗?
当使用atomic时,虽然对属性的读和写是原子性的,但是仍然可能出现线程错误:当线程A进行写操作,这时其他线程的读或者写操作会因为该操作而等待。当A线程的写操作结束后,B线程进行写操作,然后当A线程需要读操作时,却获得了在B线程中的值,这就破坏了线程安全,如果有线程C在A线程读操作前release了该属性,那么还会导致程序崩溃。所以仅仅使用atomic并不会使得线程安全,我们还要为线程添加lock来确保线程的安全。
也就是要注意:atomic所说的线程安全只是保证了getter和setter存取方法的线程安全,并不能保证整个对象是线程安全的。如下列所示:
比如:@property(atomic,strong)NSMutableArray *arr;
如果一个线程循环的读数据,一个线程循环写数据,那么肯定会产生内存问题,因为这和setter、getter没有关系。如使用[self.arr objectAtIndex:index]就不是线程安全的。好的解决方案就是加锁。
据说,atomic要比nonatomic慢大约20倍。一般如果条件允许,我们可以让服务器来进行加锁操作。
五、描述一个你遇到过的retain cycle例子。
父类@class 子类,并且声明了子类对象,子类引用了父类,声明了父类对象,在Main函数中,对子类中的父类赋值,并且对父类中子类赋值。就会导致retain cycle 循环引用,详细看如下代码:
这样,引用循环就形成了,造成的结果就是,子对象对父对象的引用无法再取得,导致两个对象被隐式引用,最后两个test实例的引用计数都是1,最后无法释放。
六、+(void)load; +(void)initialize;有什么用处?
+ initialize和+ load是 NSObject 类的两个类方法,它们会在运行时自动调用,我们可以利用其特性做一些初始化操作。
initialize和load的区别在于:load是只要类所在文件被引用就会被调用,而initialize是在类或者其子类的第一个方法被调用前调用。所以如果类没有被引用进项目,就不会有load调用;但即使类文件被引用进来,但是没有使用,那么initialize也不会被调用。详情信息
七、为什么其他语言里叫函数调用,OC里则是给对象发消息(或者谈下对runtime的理解)
在OC中,消息是直到运行时才被绑定在方法实现上的。在编译的过程中,编译器会把一个消息表达式转换成对objc_msgSend方法的调用。objc_msgSend方法会以接收消息的对象(receiver)和消息的方法名(selector)作为两个主要的参数,再加上消息本身带有的参数
[receiver message];
会被转化成
objc_msgSend(receiver, selector);
当程序运行的时候,运行时系统会根据传入objc_msgSend方法的receiver和selector来查找应该被执行的过程。也就是说,具体哪段代码被执行是由receiver和selector这两者共同决定的。
那么怎么根据receiver和selector来查找对应的过程呢?
发送消息的关键就是,编译器给每个类和每个对象都构建了一个结构。每个类的结构都包含了一个指向自己父类的指针,和一个分发表。分发表中存放着这个类的每个selector和这个selector对应的过程的入口。
而当一个继承自NSObject或NSProxy的对象被创建的时候,在对象的变量中,还有一个指向它对应的类结构的指针,这个指针叫做isa。所以,当得知receiver时,objc_msgSend方法会通过receiver的isa指针找到它对应的类的结构,并查找这个类的分发表中是否有需要的selector。如果没有找到,则沿着指向父类的指针,去父类的分发表中查找。
为了加速这个过程,运行时系统会为每个类缓存selector和对应的方法的地址。每个类都有一个这样的cache,其中存放的不仅有它本身的方法,还有它继承的方法。
如果objc_msgSend方法找到了这个过程,它会调用这个过程,并且传递所有的参数以及两个隐藏参数:receiver和selector。正是因为传递了这两个隐藏参数,程序员才能在OC源代码中使用self和_cmd。
唯一一个能绕过晚绑定的方式,就是直接得到某个方法的地址,然后像调用函数一样调用它。NSObject中有一个方法methodForSelector:,它可以直接返回对应方法的函数指针。
注意,在调用这个函数指针的时候,也需要传递receiver和selector的这两个隐藏参数
八、什么是method swizzling?
其实跟字面的意思很相近。方法的调和。可以去修改oc中两个方法的调用。就是把两个实现调换
Method ori_Method = class_getInstanceMethod([MYclass class], @selector(lastObject));
Method my_Method = class_getInstanceMethod([MYclass class], @selector(myLastObject));
method_exchangeImplementations(ori_Method, my_Method);
九、UIView和CALayer是啥关系
1. 首先UIView可以响应事件,Layer不可以.
UIKit使用UIResponder作为响应对象,来响应系统传递过来的事件并进行处理。在 UIResponder中定义了处理各种事件和事件传递的接口。
UIApplication、UIViewController、UIView、和所有从UIView派生出来的UIKit类(包括UIWindow)都直接或间接地继承自UIResponder类。
CALayer直接继承 NSObject,并没有相应的处理事件的接口。
2. UIView是CALayer的delegate
3. UIView主要处理事件,CALayer负责绘制就更好
4. 每个 UIView 内部都有一个 CALayer 在背后提供内容的绘制和显示,并且 UIView 的尺寸样式都由内部的 Layer 所提供。两者都有树状层级结构,layer 内部有 SubLayers,View 内部有 SubViews.但是 Layer 比 View 多了个AnchorPoint
十、如何高性能的给UIImageView加个圆角?
使用Quartz2D直接绘制图片
步骤:
a、创建目标大小(cropWidth,cropHeight)的画布。
b、使用UIImage的drawInRect方法进行绘制的时候,指定rect为(-x,-y,width,height)。
c、从画布中得到裁剪后的图像。
- (UIImage*)cropImageWithRect:(CGRect)cropRect
{
//豆电雨
CGRect drawRect = CGRectMake(-cropRect.origin.x , -cropRect.origin.y, self.size.width * self.scale, self.size.height * self.scale);
UIGraphicsBeginImageContext(cropRect.size);
CGContextRef context = UIGraphicsGetCurrentContext();
CGContextClearRect(context, CGRectMake(0, 0, cropRect.size.width, cropRect.size.height));
[self drawInRect:drawRect];
UIImage *image = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
return image;
}
@end
十一、使用drawRect有什么影响
drawRect方法依赖Core Graphics框架来进行自定义的绘制,但这种方法主要的缺点就是它处理touch事件的方式:每次按钮被点击后,都会用setNeddsDisplay进行强制重绘;而且不止一次,每次单点事件触发两次执行。这样的话从性能的角度来说,对CPU和内存来说都是欠佳的。特别是如果在我们的界面上有多个这样的UIButton实例。
这个方法的主要作用是根据传入的 rect 来绘制图像参见文档. 这个方法的默认实现没有做任何事情, 我们可以在这个方法中使用 Core Graphics 和 UIKit 来绘制视图的内容.
这个方法的调用机制也是非常特别. 当你调用 setNeedsDisplay 方法时, UIKit 将会把当前图层标记为 dirty, 但还是会显示原来的内容, 直到下一次的视图渲染周期, 才会为标记为 dirty 的图层重新建立 Core Graphics 上下文, 然后将内存中的数据恢复出来, 再使用 CGContextRef 进行绘制.
十二、ASIHttpRequest或者SDWebImage里面给UIImageView加载图片的逻辑是什么样的?(把UIImageView放到UITableViewCell里面问更赞)
首先判断url中的图片是否在imagecache中存在,如果存在的话,直接显示在tableviewcell上,如果不存在,就会去寻找沙盒中是否存在,如果沙盒中存在,那么会将沙盒中url信息缓存到imagecache中,接着显示到tableviewcell,如果不存在,显示占位图,然后查看是否url图片信息已经存在operacache中进行下载,如果不是,生成一个operacache下载进程,下载完成之后,移除下载进程,将图片加入imagecache中,显示在tableviewcell中。清晰的思路如图:
十三、麻烦你设计个简单的图片内存缓存器(移除策略是一定要说的)
图片的内存缓存,可以考虑将图片数据保存到一个数据模型中。所以在程序运行时这个模型都存在内存中。
移除策略:释放数据模型对象
十四、讲讲你用Instrument优化动画性能的经历吧(别问我什么是Instrument)
优化的话,基础的的,reuse大家都知道,然后把opaque都设置为yes(有个选项可以显示那些为false的UIView),然后把cornerRadius和shadow(主要是shadow)优化,不要用代码写。然后不要在delegate方法里使用类似 tableview.cellForIndexpath 还是 indexpathForCell 类似的方法 。
要求高一点,就尽可能缓存和预加载。而且不在delegate里减少需要计算的东西,和占内存的东西。在后台进程先把layer的image绘制好,然后缓存起来要显示的时候再加载。
十五、loadView是干嘛用的?
viewController的方法,会在viewDidLoad之前进行调用。
很多人都会疑惑self.view,这个view道理是哪里来的,就是在这里。一般不需要去操作这个。但如果有特殊的需求,要求这个self.view是我们自己自定义的view时候就可以用这个方法,
MyView *myview = [[MyView alloc]init];
self.view = myview;
十六、viewWillLayoutSubView是什么
当viewController的bounds又改变,调用这个方法来实现subview的位置。可重写这个方法来实现父视图变化subview跟着变化
十七、GCD里面有哪几种Queue?你自己建立过串行queue吗?背后的线程模型是什么样的?
参见GCD详解
十八、用过coredata或者sqlite吗?读写是分线程的吗?遇到过死锁没?咋解决的
最好是在同一个线程里面,比价方便。如果要分线程的话要注意:
1、只用一个NSPersistentStoreCoordinator
2、每个线程创建一个NSManagedObjectContext
3、不要传递NSManagedObject,传objectID,通过fetch获得。
4、先存后取,利用NSManagedObjectContext
-mergeChangesFromContextDidSaveNotification:
5、保护思路清晰。链接
死锁解决方法:链接
十九、http的post和get啥区别?(区别挺多的,麻烦多说点)
原理的区别
一般我们在浏览器输入一个网址访问网站都是GET请求;再FORM表单中,可以通过设置Method指定提交方式为GET或者POST提交方式,默认为GET提交方式。
HTTP定义了与服务器交互的不同方法,其中最基本的四种:GET,POST,PUT,DELETE,HEAD,其中GET和HEAD被称为安全方法,因为使用GET和HEAD的HTTP请求不会产生什么动作。不会产生动作意味着GET和HEAD的HTTP请求不会在服务器上产生任何结果。但是安全方法并不是什么动作都不产生,这里的安全方法仅仅指不会修改信息。
根据HTTP规范,POST可能会修改服务器上的资源的请求。比如CSDN的博客,用户提交一篇文章或者一个读者提交评论是通过POST请求来实现的,因为再提交文章或者评论提交后资源(即某个页面)不同了,或者说资源被修改了,这些便是“不安全方法”。
两种请求方式的区别:
1、GET请求,请求的数据会附加在URL之后,以?分割URL和传输数据,多个参数用&连接。URL的编码格式采用的是ASCII编码,而不是uniclde,即是说所有的非ASCII字符都要编码之后再传输。
POST请求:POST请求会把请求的数据放置在HTTP请求包的包体中。上面的item=bandsaw就是实际的传输数据。
因此,GET请求的数据会暴露在地址栏中,而POST请求则不会。
2、传输数据的大小
在HTTP规范中,没有对URL的长度和传输的数据大小进行限制。但是在实际开发过程中,对于GET,特定的浏览器和服务器对URL的长度有限制。因此,在使用GET请求时,传输数据会受到URL长度的限制。
对于POST,由于不是URL传值,理论上是不会受限制的,但是实际上各个服务器会规定对POST提交数据大小进行限制,Apache、IIS都有各自的配置。
3、安全性
POST的安全性比GET的高。这里的安全是指真正的安全,而不同于上面GET提到的安全方法中的安全,上面提到的安全仅仅是不修改服务器的数据。比如,在进行登录操作,通过GET请求,用户名和密码都会暴露再URL上,因为登录页面有可能被浏览器缓存以及其他人查看浏览器的历史记录的原因,此时的用户名和密码就很容易被他人拿到了。除此之外,GET请求提交的数据还可能会造成Cross-site
request frogery攻击
4、HTTP中的GET,POST,SOAP协议都是在HTTP上运行的
二十、什么是Binary search tree? search的时间复杂度是多少