1. 什么是arc?(arc是为了解决什么问题诞生的?)
- ARC的全称是Automatic Reference Counting(自动引用计数)
- ARC是自iOS 5之后增加的新特性,不需要程序员管理内容, 编译器会在适当的地方自动给我们添加release/retain等代码(主要针对内存泄露)
相关知识:
1.与ARC相对的是MRC, 手动引用计数, 可以简称MRC (Manual Reference Counting) 所有对象的内容都需要我们手动管理, 需要程序员自己编写release/retain等代码
2.ARC的判断原则:只要没有强指针指向对象,对象就会被释放。
(1)强指针:默认的情况下,所有的指针都是强指针,关键字strong
(2)弱指针:_ _weak关键字修饰的指针
3.ARC的特点总结:
(1)不允许调用release,retain,retainCount
(2)允许重写dealloc,但是不允许调用[super dealloc]
(3)@property的参数:
Strong:相当于原来的retain(适用于OC对象类型),成员变量是强指针
Weak:相当于原来的assign,(适用于oc对象类型),成员变量是弱指针
Assign:适用于非OC对象类型(基础类型)
2. 请解释以下keywords的区别: assign vs weak, __block
vs__weak
- weak和assign都是引用计算不变,两个的差别在于,weak用于指针类型,而assign用于简单的数据类型,如int BOOL 等。
- assign看起来跟weak一样,其实不能混用的,assign的变量在释放后并不设置为nil(和weak不同),当你再去引用时候就会发生错误,崩溃,EXC_BAD_ACCESS.
-
__block
是通过引用来访问self的实例变量,block也是一个强引用,self被retain,引起循环引用,用__weak
是弱引用,当self释放时,weakSelf已经等于nil
相关知识
在垃圾回收机制里面,如果你同时使用__weak
和__block
来标识一个变量,那么该block将不会保证它是一直是有效的。 如果你在实现方法的时候使用了block,对象的内存管理规则更微妙:也是(__weak
与__block
区别:)
(1)如果你通过引用来访问一个实例变量,self会被retain。
(2)如果你通过值来访问一个实例变量,那么变量会被retain
扩展:NSTimer注意避免循环引用的地方,需要找个合适的时机和地方来 invalidate timer
在引用计数的环境里面,默认情况下当你在block里面引用一个Objective-C对象的时候,该对象会被retain。当你简单的引用了一个对象的实例变量时,它同样被retain。但是被__block存储类型修饰符标记的对象变量不会被retain
3. __block在arc和非arc下含义一样吗?
- 对于非ARC下, 为了防止循环引用, 我们使用
__block
来修饰在Block中使用的对象: - 对于ARC下, 为了防止循环引用, 我们使用
__weak
来修饰在Block中使用的对象。原理就是:ARC中,Block中如果引用了__strong
修饰符的自动变量,则相当于Block对该变量的引用计数+1。
<解决循环引用问题>
//非ARC
__block typeof(self) weakSelf = self;
self.myBlock = ^(int paramInt){
//使用weakSelf访问self成员
[weakSelf anotherFunc];
};
4. 使用atomic一定是线程安全的吗?
- 当然不是。
atomic在set方法里加了锁,防止了多线程一直去写这个property,造成难以预计的数值, 但这也只是读写的锁定, 线程安全其实还是差一些。
atomic有个很大的问题是很慢,要比nonatomic慢20倍。
当然最后建议这种数值数值变化可以让服务器来做
5. 描述一个你遇到过的retaincycle例子。
- retain cycle 会造成内存溢出,严重情况会引起崩溃。一般注意点也不会发生,但在网络连接比较多的地方就会不小心出现,vc异步的网络请求,成功后的block调用vc,如果此时,用户已经不用此vc了,vc还是没有释放。
一个下拉刷新,那个view和vc互相强引用,导致了没释放。view回去调用vc的scrollview的contentoffset
6. +(void)load; +(void)initialize;有什么用处?
- 当类对象被引入项目时, runtime 会向每一个类对象发送 load 消息, 并且会在每一个类甚至分类被引入时仅调用一次,(调用的顺序是父类优先于子类, 子类优先于分类) 而且每一个类中的 load 方法都不需要像 viewDidLoad 方法一样调用父类的方法.
举个例子:
由于 load 方法会在类被 import 时调用一次, 而这时往往是改变类的行为的最佳时机. 我在 DKNightVersion 中使用 method swizlling 来修改原有的方法时, 就是在分类 load 中实现的.
- initialize 方法和 load 方法有一些不同, 它虽然也会在整个 runtime 过程中调用一次, 但是它是在该类的第一个方法执行之前调用, 也就是说 initialize 的调用是 <惰性> 的, 它的实现也与我们在平时使用的惰性初始化属性时基本相同.
我在实际的项目中并没有遇到过必须使用这个方法的情况, 在该方法中主要做 静态变量的设置 并用于 确保在实例初始化前某些条件必须满足 .
7. 为什么其他语言里叫函数调用,objective-c语言里则是给对象发消息(或者谈下对runtime的理解)
- 我们在其他语言中比如说: C, Python, Java, C++, Haskell ... 中提到函数调用或者方法调用(面向对象). 函数调用是在编译期就已经决定了会调用哪个函数(方法), 编译器在编译期就能检查出函数的执行是否正确.
**然而 Objective-C(ObjC) 是一门动态的语言, 整个 ObjC 语言都是尽可能的将所有的工作推迟到运行时才决定. 它基于 runtime 来工作, runtime 就是 ObjC 的灵魂, 其核心就是消息发送 objc_msgSend **
- 所有的消息都会在运行时才会确定,
[obj message]
在运行时会被转化为objc_msgSend(id self, SEL cmd, ...)
来执行, 它会在运行时从 选择子表中寻找对应的选择子 并将选择子与实现进行绑定. 而如果没有找到对应的实现, 就会进入类似黑魔法的消息转发流程. 调用+ (BOOL)resolveInstanceMethod:(SEL)aSelector
方法, 我们可以在这个方法中 为类动态地生成方法 .
我们几乎可以使用 runtime 修改 Objective-C 中的一切:
*class property object ivar method protocol *
8. 什么是methodswizzling?
-
method swizzling
实际上就是一种在运行时动态修改原有方法的技术, 它实际上是基于ObjC runtime
的特性, 而method swizzling
的核心方法就是method_exchangeImplementations(SEL origin, SEL swizzle)
使用这个方法就可以在运行时动态地改变原有的方法实现
在DKNigtVersion (为iOS应用添加夜间模式) 中能够看到大量 method swizzling 的使用, 方法的调用时机就是在上面提到的 load 方法中, 不在 initialize 方法中改变方法实现的原因是 initialize 可能会被子类所继承并重新执行最终导致错误 , 而 load 并不会被继承并重新执行.
9. UIView和CALayer是啥关系?
- 每一个 UIView 的身后对应一个 Core Animation 框架中的 CALayer.
在 iOS 上 当你处理一个一个有一个的 UIView 时实际上是在操作 CALayer . 尽管有的时候你并不知道 (直接操作 CALayer 并不会在对效率有着显著的提升).
- UIView 实际上就是对 CALayer 的轻量级的封装. UIView 继承自 UIResponder 处理来自用户的事件; CALayer 继承自 NSObject 主要用于图层的渲染和动画. 设计原因:
1.你可以通过操作 UIView 在一个更高的层级上处理与用户的交互, 触摸, 点击, 拖拽等事件, 这些都是在 UIKit 这个层级上完成的.
2.UIView 和 NSView(AppKit) 的实现极其不同, 而使用 Core Animation 可以实现底层代码地重用, 因为在 Mac 和 iOS 平台上都使用着近乎相同的 Core Animation 代码, 这样我们可以对这个层级进行抽象在两种平台上产生 UIKit 和 AppKit 用于不同平台的框架.
3.使用 CALayer 的唯一原因大概是便于移植到不同的平台, 如果仅仅使用 Core Animation 层级, 处理用户的交互时间需要写更多的代码.
10. 如何高性能的给UIImageView加个圆角?
(不准说layer.cornerRadius!)
- 一般情况下给 UIImageView 或者说 UIKit 的控件添加圆角都是改变
clipsToBounds 和 layer.cornerRadius
, 这样大约两行代码就可以解决. 但是, 这样使用这样的方法会 强制Core Animation
提前渲染屏幕的离屏绘制 , 而离屏绘制就会为性能带来负面影响.
<贝塞尔曲线> 可以完美解决
UIImageView *imageView = [[UIImageView alloc] initWithFrame:CGRectMake(0, 0, 100, 100)];
imageView.center = CGPointMake(200, 300);
UIImage *anotherImage = [UIImage imageNamed:@"image"];
UIGraphicsBeginImageContextWithOptions(imageView.bounds.size, NO, 1.0);
[[UIBezierPath bezierPathWithRoundedRect:imageView.bounds
cornerRadius:50] addClip];
[anotherImage drawInRect:imageView.bounds];
imageView.image = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
[self.view addSubview:imageView];
11. 使用drawRect有什么影响?(这个可深可浅,你至少得用过。。)
这个方法的主要作用是根据传入的 rect 来绘制图像,我们还可以在这个方法中使用 Core Graphics 和 UIKit 来绘制视图的内容.
这个方法的调用机制也是非常特别. 当你调用 setNeedsDisplay 方法时, UIKit 将会把当前图层标记为 dirty, 但还是会显示原来的内容, 直到下一次的视图渲染周期, 才会重新建立 Core Graphics 上下文, 然后将内存中的数据恢复出来, 使用 CGContextRef 进行绘制.
12. ASIHttpRequest或者SDWebImage里面给
UIImageView加载图片的逻辑是什么样的?(把
UIImageView放到UITableViewCell里面问更赞)
SDWebImage 中为 UIView 提供了一个分类叫做 WebCache, 这个分类中有一个最常用的接口,
sd_setImageWithURL:placeholderImage:
, 这个分类同时提供了很多类似的方法, 这些方法最终会调用一个同时具有optionprogressBlockcompletionBlock
的方法, 而在这个类最终被调用的方法首先会检查是否传入了placeholderImage
以及对应的参数,并设置
placeholderImage
-
然后获取SDWebImageManager中的单例调用一个
``downloadImageWithURL:...`` 的方法来获取图片, 而这个manager 获取图片的过程有大体上分为两部分:
首先会在 SDWebImageCache 中寻找图片是否有对应的缓存, 它会以 url 作为数据的索引先在内存中寻找是否有对应的缓存, 如果缓存未命中就会在磁盘中利用 MD5 处理过的 key 来继续查询对应的数据, 如果找到了, 就会把磁盘中的缓存备份到内存中.
然而, 假设我们在内存和磁盘缓存中都没有命中, 那么manager就会调用 SDWebImageDownloader 对象的方法
downloadImageWithURL:...
来下载图片, 过程中调用另一个方法:
addProgressCallback:andCompletedBlock:fotURL:createCallback:
来存储下载过程中和下载完成的回调, 当回调块是第一次添加的时候, 方法会实例化一个 NSMutableURLRequest 和 SDWebImageDownloaderOperation , 并将后者加入 downloader 持有的下载队列开始图片的异步下载.
- 而在图片下载完成之后, 就会在主线程设置 image, 完成整个图像的异步下载和配置.
13. 麻烦你设计个简单的图片内存缓存器(移除策略是一定要说的)
图片的内存缓存,可以考虑将图片数据保存到一个数据模型中。所以在程序运行时这个模型都存在内存中。
移除策略:释放数据模型对象。
14. 讲讲你用Instrument优化动画性能的经历吧(别问我什么是Instrument)
考虑程序的性能,可以借助数据化图形化的输出方式。与其花费时间在优化小细节上不如多点时间找到你改优化的地方.时间事件查看器 ——> Time Profiler打开Instrument的步骤:xcode ——> open developer Tool ——> instrument 选取相应的strumnet的工具,选择相应的strument里面的工具,然后选择模拟器或者真机上面的应用,点击右上角的开始按钮,然后就可以看到相应内容
Instrument详细介绍
15. loadView是干嘛用的?
- 当你访问一个ViewController的view属性时,如果此时view的值是nil,那么,ViewController就会自动调用loadView这个方法。这个方法就会加载或者创建一个view对象,赋值给view属性。
loadView默认做的事情是:如果此ViewController存在一个对应的nib文件,那么就加载这个nib。否则,就创建一个UIView对象。
如果你用Interface Builder来创建界面,那么不应该重载这个方法。
如果你想自己创建view对象,那么可以重载这个方法。此时你需要自己给view属性赋值。你自定义的方法不应该调用super。如果你需要对view做一些其他的定制操作,在viewDidLoad里面去做。
- 根据上面可以知道,有两种情况:
1、如果你用了nib文件,重载这个方法就没有太大意义。因为loadView的作用就是加载nib。如果你重载了这个方法不调用super,那么nib文件就不会被加载。如果调用了super,那么view已经加载完了,你需要做的其他事情在viewDidLoad里面做更合适。
2、如果你没有用nib,这个方法默认就是创建一个空的view对象。如果你想自己控制view对象的创建,例如创建一个特殊尺寸的view,那么可以重载这个方法,自己创建一个UIView对象,然后指定 self.view = myView; 但这种情况也没有必要调用super,因为反正你也不需要在super方法里面创建的view对象。如果调用了super,那么就是浪费了一些资源而已
16. viewWillLayoutSubView你总是知道的。
横竖屏切换的时候,系统会响应一些函数,其中
viewWillLayoutSubviews 和 viewDidLayoutSubviews。
- (void)viewWillLayoutSubviews {
[self _shouldRotateToOrientation:(UIDeviceOrientation)[UIApplication sharedApplication].statusBarOrientation];
}
-(void)_shouldRotateToOrientation:(UIDeviceOrientation)orientation {
if (orientation == UIDeviceOrientationPortrait ||orientation ==UIDeviceOrientationPortraitUpsideDown) {
// 竖屏 }
else {
// 横屏 }
}
- (NSUInteger)supportedInterfaceOrientations{
// 切换横屏
return UIInterfaceOrientationMaskLandscape;
}
通过上述一个函数就知道横竖屏切换的接口了。
注意:viewWillLayoutSubviews只能用在ViewController里面,在view里面没有响应。
17. GCD里面有哪几种Queue?你自己建立过串行queue吗?背后的线程模型是什么样的?
- 1.主队列 dispatch_main_queue(); 串行 ,更新UI
2.全局队列 dispatch_global_queue(); 并行,四个优先级:background,low,default,high
3.自定义队列 dispatch_queue_t queue ; 可以自定义是并行:DISPATCH_QUEUE_CONCURRENT
或者串行DISPATCH_QUEUE_SERIAL
18. 用过coredata或者sqlite吗?读写是分线程的吗?遇到过死锁没?咋解决的?
- 数据库读取操作一般都是多线程访问的。在对数据进行读取时,我们要保证其当前状态不能被修改,即读取时加锁,否则就会出现数据错误混乱。
IOS中常用的两种数据持久化存储方式:CoreData和SQLite,两者都需要设置线程安全
19. http的post和get啥区别?(区别挺多的,麻烦多说点)
GET和POST与数据如何传递没有关系, GET和POST是由HTTP协议定义的。在HTTP协议中,Method和Data(URL, Body, Header)是正交的两个概念,也就是说,使用哪个Method与应用层的数据如何传输是没有相互关系的。HTTP没有要求,如果Method是POST数据就要放在BODY中。也没有要求,如果Method是GET,数据(参数)就一定要放在URL中而不能放在BODY中.
- 1.(1) GET请求的数据会附在URL之后(就是把数据放置在HTTP协议头中),以?分割URL和传输数据,参数之间以&相连,如:login.action?name=hyddd&password=idontknow&verify=%E4%BD%A0%E5%A5%BD。如果数据是英文字母/数字,原样发送,如果是空格,转换为+,如果是中文/其他字符,则直接把字符串用BASE64加密,得出如:%E4%BD%A0%E5%A5%BD,其中%XX中的XX为该符号以16进制表示的ASCII。
(2) POST把提交的数据则放置在是HTTP包的包体中。 - 2 . HTTP协议对GET和POST都没有对长度的限制
(1).首先是”GET方式提交的数据是有字节限制的”,因为GET是通过URL提交数据,那么GET可提交的数据量就跟URL的长度有直接关系了。其限制取决于操作系统的支持。
注意这是限制是整个URL长度,而不仅仅是你的参数值数据长度。
(2).理论上讲,POST是没有大小限制的,HTTP协议规范也没有进行大小限制,POST数据是没有限制的,起限制作用的是服务器的处理程序的处理能力。
- 3.POST的安全性要比GET的安全性高,其实没有丝毫关系。对于GET和POST的理解,是纯粹地来源于HTTP协议。他们只有一点根本区别,简单点儿说,一个用于获取数据,一个用于修改数据
20. 我知道你大学毕业过后就没接触过算法数据结构了,但是请你一定告诉我什么是Binary search tree? search的时间复杂度是多少?我很想知道!
- Binary search tree:二叉搜索树。
主要由四个方法:(用C语言实现或者Python)
1.search:时间复杂度为O(h),h为树的高度
2.traversal:时间复杂度为O(n),n为树的总结点数。
3.insert:时间复杂度为O(h),h为树的高度。
4.delete:最坏情况下,时间复杂度为O(h)+指针的移动开销。
可以看到,二叉搜索树的dictionary operation的时间复杂度与树的高度h相关。所以需要尽可能的降低树的高度,由此引出平衡二叉树Balanced binary tree。它要求左右两个子树的高度差的绝对值不超过1,并且左右两个子树都是一棵平衡二叉树。这样就可以将搜索树的高度尽量减小。常用算法有红黑树、AVL、Treap、伸展树等。