关于一些 iOS 面试问题的解答

1. 什么是 ARC? (ARC 是为了解决什么问题而诞生的?)

ARC 是 Automatic Reference Counting 的缩写, 即自动引用计数. 这是苹果在 iOS5 中引入的内存管理机制. Objective-C 和 Swift 使用 ARC 追踪和管理应用的内存使用. 这一机制使得开发者无需键入 retain 和 release , 这不仅能够降低程序崩溃和内存泄露的风险, 而且可以减少开发者的工作量, 能够大幅度提升程序的 流畅性 和 可预测性 . 但是 ARC 不适用于 Core Foundation 框架中, 仍然需要手动管理内存.

2. 以下 keywords 有什么区别: assign vs weak , __block vs __weak

assign 和 weak 是用于在声明属性时, 为属性指定内存管理的语义.

assign 用于简单的赋值, 不改变属性的引用计数, 用于 Objective-C 中的 NSInteger , CGFloat 以及 C 语言中 int , float , double 等数据类型.

weak 用于对象类型, 由于 weak 同样不改变对象的引用计数且不持有对象实例, 当该对象废弃时, 该弱引用自动失效并且被赋值为 nil , 所以它可以用于避免两个强引用产生的 循环引用 导致内存无法释放的问题.

__block 和 __weak 之间的却是确实极大的, 不过它们都用于修饰变量.

前者用于指明当前声明的变量在被 block 捕获之后, 可以在 block 中改变变量的值. 因为在 block 声明的同时会截获该 block 所使用的全部自动变量的值, 而这些值只在 block 中 只具有"使用权"而不具有"修改权" . 而 __block 说明符就为 block 提供了变量的修改权.

后者是 所有权修饰符 , 什么是所有权修饰符? 这里涉及到另一个问题, 因为在 ARC 有效时, id 类型和对象类型同 C 语言中的其他类型不同, 必须附加所有权修饰符. 所有权修饰符一种有 4 种:

__strong

__weak

__unsafe_unretained

__autorelease

__weak 与 weak 的区别只在于, 前者用于变量的声明, 而后者用于属性的声明.

3. __block 在 ARC 和非 ARC 下含义一样吗?

__block 在 ARC 下捕获的变量会被 block retain , 这样可能导致循环引用, 所以必须要使用弱引用才能解决该问题. 而在非 ARC 下, 可以直接使用 __block 说明符修饰变量, 因为在非 ARC 下, block 不会 retain 捕获的变量.

4. 使用 nonatomic 一定是线程安全的吗?

nonatomic 的内存管理语义是 非原子 的, 非原子的操作本来就是线程不安全的, 而 atomic 的操作是原子的, 但是 并不意味着它是线程安全的 , 它会增加正确的几率, 能够更好的避免线程的错误, 但是它仍然是线程不安全的.

当使用 nonatomic 的时候, 属性的 setter 和 getter 操作是非原子的, 所以当多个线程同时对某一属性进行读和写的操作, 属性的最终结果是不能预测的.

当使用 atomic 时, 虽然对属性的读和写是原子的, 但是仍然可能出现线程错误: 当线程 A 进行写操作, 这时其他线程的读或写操作会因为该操作的进行而等待. 当 A 线程的写操作结束后, B 线程进行写操作, 然后当 A 线程进行读操作时, 却获得了在 B 线程中的值, 这就破坏了线程安全, 如果有线程 C 在 A 线程读操作前 release 了该属性, 那么还会导致程序崩溃. 所以仅仅使用 atomic 并不会使得线程安全, 我们还需要为线程添加 lock 来确保线程的安全.

atomic 都不是一定线程安全的, nonatomic 就更不必多说了.

5. 描述一个你遇到过的 retain cycle 例子.

6. + (void)load; 和 + (void)initialize; 有什么用处?

当类对象被引入项目时, runtime 会向每一个类对象发送 load 消息. load 方法还是非常的神奇的, 因为它会在 每一个类甚至分类 被引入时仅调用一次, 调用的顺序是父类优先于子类, 子类优先于分类. 而且 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 .

What makes Objective-C truly powerful is its runtime.

所有的消息都会在运行时才会确定, [obj message] 在运行时会被转化为 objc_msgSend(id self, SEL cmd, ...) 来执行, 它会在运行时从 选择子表中寻找对应的选择子 并将选择子与实现进行绑定. 而如果没有找到对应的实现, 就会进入类似黑魔法的消息转发流程. 调用 + (BOOL)resolveInstanceMethod:(SEL)aSelector 方法, 我们可以在这个方法中 为类动态地生成方法 .

我们几乎可以使用 runtime 魔改 Objective-C 中的一切: class property object ivar method protocol , 而下面就是它的主要应用:

内省

为分类动态的添加属性

使用方法调剂修改原有的方法实现

...

8. 什么是 Method Swizzling?

method swizzling 实际上就是一种在运行时动态修改原有方法的技术, 它实际上是基于 ObjC runtime 的特性, 而 method swizzling 的核心方法就是 method_exchangeImplementations(SEL origin, SEL swizzle) . 使用这个方法就可以在运行时动态地改变原有的方法实现, 在 DKNigtVersion (为 iOS 应用添加夜间模式) 中能够看到大量 method swizzling 的使用, 方法的调用时机就是在上面提到的 load 方法中, 不在 initialize 方法中改变方法实现的原因是 initialize 可能会被子类所继承并重新执行最终导致错误 , 而 load 并不会被继承并重新执行.

9. UIView 和 CALayer 有什么关系?

看到这个问题不禁想到大一在网易面试时的经历, 当时的两位面试官就问了我这么一个问题, UIView 和 CALayer 是什么关系, 为什么要这么设计? 我已经忘记了当时是怎么回答的. 隐约记得当时说每一个 UIView 都会对应一个 CALayer 至于为什么这样, 当时的我实在是太弱无法回答出来了.

每一个 UIView 的身后对应一个 Core Animation 框架中的 CALayer .

Many of the methods you call on UIView simply delegate to the layer

在 iOS 上 当你处理一个一个有一个的 UIView 时实际上是在操作 CALayer . 尽管有的时候你并不知道 (直接操作 CALayer 并不会在对效率有着显著的提升).

UIView 实际上就是对 CALayer 的轻量级的封装. UIView 继承自 UIResponder 处理来自用户的事件; CALayer 继承自 NSObject 主要用于图层的渲染和动画. 这么设计有以下几个原因:

你可以通过操作 UIView 在一个更高的层级上处理与用户的交互, 触摸, 点击, 拖拽等事件, 这些都是在 UIKit 这个层级上完成的.

UIView 和 NSView(AppKit) 的实现极其不同, 而使用 Core Animation 可以实现底层代码地重用, 因为在 Mac 和 iOS 平台上都使用着近乎相同的 Core Animation 代码, 这样我们可以对这个层级进行抽象在两种平台上产生 UIKit 和 AppKit 用于不同平台的框架.

使用 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];

在这里使用了贝塞尔曲线"切割"个这个图片, 给 UIImageView 添加了的圆角.

11. 使用 drawRect: 有什么影响?

这个问题对于我来说确实有些难以回答, 我记得我在我人生的第一个 iOS 项目 SportJoin 中曾经使用这个方法来绘制图形, 但是具体怎么做的, 我已经忘记了.

这个方法的主要作用是根据传入的 rect 来绘制图像 参见文档 . 这个方法的默认实现没有做任何事情, 我们 可以 在这个方法中使用 Core Graphics 和 UIKit 来绘制视图的内容.

这个方法的调用机制也是非常特别. 当你调用 setNeedsDisplay 方法时, UIKit 将会把当前图层标记为 dirty, 但还是会显示原来的内容, 直到下一次的视图渲染周期, 才会重新建立 Core Graphics 上下文, 然后将内存中的数据恢复出来, 使用 CGContextRef 进行绘制.

12. ASIHttpRequest 或者 SDWebImage 里面给 UIImageView 加载图片的逻辑是什么样的?

我曾经阅读过 SDWebImage 的源代码, 就在这里对如何给 UIImageView 加载图片做一个总结吧, SDWebImage 中为 UIView 提供了一个分类叫做 WebCache, 这个分类中有一个最常用的接口, sd_setImageWithURL:placeholderImage: , 这个分类同时提供了很多类似的方法, 这些方法最终会调用一个同时具有 option progressBlock completionBlock 的方法, 而在这个类最终被调用的方法首先会检查是否传入了 placeholderImage 以及对应的参数, 并设置 placeholderImage .

然后会获取 SDWebImageManager 中的单例调用一个 downloadImageWithURL:... 的方法来获取图片, 而这个 manager 获取图片的过程有大体上分为两部分, 它首先会在 SDWebImageCache 中寻找图片是否有对应的缓存, 它会以 url 作为数据的索引先在内存中寻找是否有对应的缓存, 如果缓存未命中就会在磁盘中利用 MD5 处理过的 key 来继续查询对应的数据, 如果找到了, 就会把磁盘中的缓存备份到内存中.

然而, 假设我们在内存和磁盘缓存中都没有命中, 那么 manager 就会调用它持有的一个 SDWebImageDownloader 对象的方法 downloadImageWithURL:... 来下载图片, 这个方法会在执行的过程中调用另一个方法 addProgressCallback:andCompletedBlock:fotURL:createCallback: 来存储下载过程中和下载完成的回调, 当回调块是第一次添加的时候, 方法会实例化一个 NSMutableURLRequest 和 SDWebImageDownloaderOperation , 并将后者加入 downloader 持有的下载队列开始图片的异步下载.

而在图片下载完成之后, 就会在主线程设置 image, 完成整个图像的异步下载和配置.

13. 设计一个简单的图片内存缓存器 (包含移除策略)

待我阅读完 path 开源的 FastImageCache 的源代码就来回答.

14. 讲讲你用Instrument优化动画性能的经历

15. loadView 的作用是什么?

This method loads or creates a view and assigns it to the view property.

loadView 是 UIViewController 的实例方法, 我们永远不要直接调用这个方法 [self loadView] . 这在苹果的 官方文档 中已经明确的写出了. loadView 会在获取视图控制器的 view 但是却得到 nil 时被调用.

loadView 的具体实现会做下面两件事情中的一件:

如果你的视图控制器关联了一个 storyboard, 那么它就会加载 storyboard 中的视图.

如果试图控制器没有关联的 storyboard, 那么就会创建一个空的视图, 并分配给 view 属性

如果你需要覆写 loadView 方法:

你需要创建一个根视图.

创建并初始化 view 的子视图, 调用 addSubview: 方法将它们添加到父视图上.

如果你使用了自动布局, 提供足够的约束来保证视图的位置.

将根视图分配给 view 属性.

永远不要在这个方法中调用 [super loadView] .

16. viewWillLayoutSubviews 的作用是什么?

viewWillLayoutSubviews 方法会在视图的 bounds 改变时, 视图会调整子视图的位置, 我们可以在视图控制器中覆写这个方法在视图放置子视图前做出改变, 当屏幕的方向改变时, 这个方法会被调用.

17. GCD 里面有哪几种 Queue? 背后的线程模型是什么样的?

GCD 中 Queue 的种类还要看我们怎么进行分类, 如果根据同一时间内处理的操作数分类的话, GCD 中的 Queue 分为两类

Serial Dispatch Queue

Concurrent Dispatch Queue

一类是串行派发队列, 它只使用一个线程, 会等待当前执行的操作结束后才会执行下一个操作, 它按照追加的顺序进行处理. 另一类是并行派发队列, 它同时使用多个线程, 如果当前的线程数足够, 那么就不会等待正在执行的操作, 使用多个线程同时执行多个处理.

另外的一种分类方式如下:

Main Dispatch Queue

Global Dispatch Queue

Custom Dispatch Queue

主线程只有一个, 它是一个串行的进程. 所有追加到 Main Dispatch Queue 中的处理都会在 RunLoop 在执行. Global Dispatch Queue 是所有应用程序都能使用的并行派发队列, 它有 4 个执行优先级 High, Default, Low, Background. 当然我们也可以使用 dispatch_queue_create 创建派发队列.

18. Core Data 或者 sqlite 的读写是分线程的吗? 死锁如何解决?

Core Data 和 sqlite 这两个我还真没深入用过, 我只在小的玩具应用上使用过 Core Data, 但是发现这货实在是太难用了, 我就果断放弃了, sqlite 我也用过, 每次输入 SQL 语句的时候我多想吐槽, 写一些简单的还好, 复杂的就直接 Orz 了. 所以我一般会使用 levelDB 对进行数据的持久存储.

数据库读取操作一般都是多线程的, 在对数据进行读取的时候, 我们要确保当前的状态不会被修改, 所以加锁, 防止由于线程竞争而出现的错误. 在 Core Data 中使用并行的最重要的规则是: 每一个 NSManagedObjectContext 必须只从创建它的进程中访问 .

19. http 的 POST 和 GET 有什么区别?

根据 HTTP 协议的定义 GET 类型的请求是幂等的, 而 POST 请求是有副作用的, 也就是说 GET 用于获取一些资源, 而 POST 用于改变一些资源, 这可能会创建新的资源或更新已有的资源.

POST 请求比 GET 请求更加的安全, 因为你不会把信息添加到 URL 上的查询字符串上. 所以使用 GET 来收集密码或者一些敏感信息并不是什么好主意.

最后, POST 请求比 GET 请求也可以传输更多的信息.

20. 什么是 Binary search tree, 它的时间复杂度是多少?

二叉搜索树是一棵以二叉树来组织的, 它搜索的时间复杂度 $O(h)$ 与树的高度成正比, 最坏的运行时间是 $\\Theta(\\lg n)$.



1.什么是arc?(arc是为了解决什么问题诞生的?)

首先解释ARC: automatic reference counting自动引用计数。

ARC几个要点:

在对象被创建时 retain count +1,在对象被release时 retain count -1.当retain count 为0 时,销毁对象。

程序中加入autoreleasepool的对象会由系统自动加上autorelease方法,如果该对象引用计数为0,则销毁。

那么ARC是为了解决什么问题诞生的呢?这个得追溯到MRC手动内存管理时代说起。

MRC下内存管理的缺点:

1.当我们要释放一个堆内存时,首先要确定指向这个堆空间的指针都被release了。(避免提前释放)

2.释放指针指向的堆空间,首先要确定哪些指针指向同一个堆,这些指针只能释放一次。(MRC下即谁创建,谁释放,避免重复释放)

3.模块化操作时,对象可能被多个模块创建和使用,不能确定最后由谁去释放。

4.多线程操作时,不确定哪个线程最后使用完毕

2.请解释以下keywords的区别: assign vs weak, __block vs __weak

assign适用于基本数据类型,weak是适用于NSObject对象,并且是一个弱引用。

assign其实也可以用来修饰对象,那么我们为什么不用它呢?因为被assign修饰的对象在释放之后,指针的地址还是存在的,也就是说指针并没有被置为nil。如果在后续的内存分配中,刚好分到了这块地址,程序就会崩溃掉。

而weak修饰的对象在释放之后,指针地址会被置为nil。所以现在一般弱引用就是用weak。

首先__block是用来修饰一个变量,这个变量就可以在block中被修改(参考block实现原理)

__block:使用__block修饰的变量在block代码快中会被retain(ARC下,MRC下不会retain)

__weak:使用__weak修饰的变量不会在block代码块中被retain

同时,在ARC下,要避免block出现循环引用 __weak typedof(self)weakSelf = self;

3.__block在arc和非arc下含义一样吗?

是不一样的。

在MRC中__block variable在block中使用是不會retain的

但是ARC中__block則是會Retain的。

取而代之的是用__weak或是__unsafe_unretained來更精確的描述weak reference的目的

其中前者只能在iOS5之後可以使用,但是比較好 (該物件release之後,此pointer會自動設成nil)

而後者是ARC的環境下為了相容4.x的解決方案。

所以上面的範例中

__block MyClass* temp = …;    // MRC環境下使用

__weak MyClass* temp = …;    // ARC但只支援iOS5.0以上的版本

__unsafe_retained MyClass* temp = …;  //ARC且可以相容4.x以後的版本

4.使用nonatomic一定是线程安全的吗?()

不是的。

atomic原子操作,系统会为setter方法加锁。 具体使用 @synchronized(self){//code }

nonatomic不会为setter方法加锁。

atomic:线程安全,需要消耗大量系统资源来为属性加锁

nonatomic:非线程安全,适合内存较小的移动设备

5.描述一个你遇到过的retain cycle例子。

block中的循环引用:一个viewController

@property (nonatomic,strong)HttpRequestHandler * handler;

@property (nonatomic,strong)NSData          *data;

_handler = [httpRequestHandler sharedManager];

[ downloadData:^(id responseData){

_data = responseData;

}];

self 拥有_handler, _handler 拥有block, block拥有self(因为使用了self的_data属性,block会copy 一份self)

解决方法:

__weak typedof(self)weakSelf = self

[ downloadData:^(id responseData){

weakSelf.data = responseData;

}];



6.+(void)load; +(void)initialize;有什么用处?

在Objective-C中,runtime会自动调用每个类的两个方法。+load会在类初始加载时调用,+initialize会在第一次调用类的类方法或实例方法之前被调用。这两个方法是可选的,且只有在实现了它们时才会被调用。

共同点:两个方法都只会被调用一次。

7.为什么其他语言里叫函数调用, objective c里则是给对象发消息(或者谈下对runtime的理解)

先来看看怎么理解发送消息的含义:

曾经觉得Objc特别方便上手,面对着 Cocoa 中大量 API,只知道简单的查文档和调用。还记得初学 Objective-C 时把[receiver message]当成简单的方法调用,而无视了“发送消息”这句话的深刻含义。于是[receiver message]会被编译器转化为:

objc_msgSend(receiver, selector)

如果消息含有参数,则为:

objc_msgSend(receiver, selector, arg1, arg2, ...)

如果消息的接收者能够找到对应的selector,那么就相当于直接执行了接收者这个对象的特定方法;否则,消息要么被转发,或是临时向接收者动态添加这个selector对应的实现内容,要么就干脆玩完崩溃掉。

现在可以看出[receiver message]真的不是一个简简单单的方法调用。因为这只是在编译阶段确定了要向接收者发送message这条消息,而receive将要如何响应这条消息,那就要看运行时发生的情况来决定了。

Objective-C 的 Runtime 铸就了它动态语言的特性,这些深层次的知识虽然平时写代码用的少一些,但是却是每个 Objc 程序员需要了解的。

Objc Runtime使得C具有了面向对象能力,在程序运行时创建,检查,修改类、对象和它们的方法。可以使用runtime的一系列方法实现。

顺便附上OC中一个类的数据结构 /usr/include/objc/runtime.h

struct objc_class {

Class isa OBJC_ISA_AVAILABILITY; //isa指针指向Meta Class,因为Objc的类的本身也是一个Object,为了处理这个关系,r      untime就创造了Meta Class,当给类发送[NSObject alloc]这样消息时,实际上是把这个消息发给了Class Object

#if !__OBJC2__

Class super_class OBJC2_UNAVAILABLE; // 父类

const char *name OBJC2_UNAVAILABLE; // 类名

long version OBJC2_UNAVAILABLE; // 类的版本信息,默认为0

long info OBJC2_UNAVAILABLE; // 类信息,供运行期使用的一些位标识

long instance_size OBJC2_UNAVAILABLE; // 该类的实例变量大小

struct objc_ivar_list *ivars OBJC2_UNAVAILABLE; // 该类的成员变量链表

struct objc_method_list **methodLists OBJC2_UNAVAILABLE; // 方法定义的链表

struct objc_cache *cache OBJC2_UNAVAILABLE; // 方法缓存,对象接到一个消息会根据isa指针查找消息对象,这时会在method      Lists中遍历,如果cache了,常用的方法调用时就能够提高调用的效率。

struct objc_protocol_list *protocols OBJC2_UNAVAILABLE; // 协议链表

#endif

} OBJC2_UNAVAILABLE;

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

OC中一个类的对象实例的数据结构(/usr/include/objc/objc.h):

typedef struct objc_class *Class;

/// Represents an instance of a class.

struct objc_object {

Class isa  OBJC_ISA_AVAILABILITY;

};

/// A pointer to an instance of a class.

typedef struct objc_object *id;

1

2

3

4

5

6

7

8

9

10

11

12

13

向object发送消息时,Runtime库会根据object的isa指针找到这个实例object所属于的类,然后在类的方法列表以及父类方法列表寻找对应的方法运行。id是一个objc_object结构类型的指针,这个类型的对象能够转换成任何一种对象。

然后再来看看消息发送的函数:objc_msgSend函数

在引言中已经对objc_msgSend进行了一点介绍,看起来像是objc_msgSend返回了数据,其实objc_msgSend从不返回数据而是你的方法被调用后返回了数据。下面详细叙述下消息发送步骤:

检测这个 selector 是不是要忽略的。比如 Mac OS X 开发,有了垃圾回收就不理会 retain,release 这些函数了。

检测这个 target 是不是 nil 对象。ObjC 的特性是允许对一个 nil 对象执行任何一个方法不会 Crash,因为会被忽略掉。

如果上面两个都过了,那就开始查找这个类的 IMP,先从 cache 里面找,完了找得到就跳到对应的函数去执行。

如果 cache 找不到就找一下方法分发表。

如果分发表找不到就到超类的分发表去找,一直找,直到找到NSObject类为止。

如果还找不到就要开始进入动态方法解析了,后面会提到。

后面还有:

动态方法解析resolveThisMethodDynamically

消息转发forwardingTargetForSelector

详情可参考 http://www.jianshu.com/p/620022378e97

8.什么是method swizzling?

Method Swizzling 原理(方法搅拌?)

在Objective-C中调用一个方法,其实是向一个对象发送消息,查找消息的唯一依据是selector的名字。利用Objective-C的动态特性,可以实现在运行时偷换selector对应的方法实现,达到给方法挂钩的目的。

每个类都有一个方法列表,存放着selector的名字和方法实现的映射关系。IMP有点类似函数指针,指向具体的Method实现。

方法指向

我们可以利用 method_exchangeImplementations 来交换2个方法中的IMP,

我们可以利用 class_replaceMethod 来修改类,

我们可以利用 method_setImplementation 来直接设置某个方法的IMP,

……

归根结底,都是偷换了selector的IMP,如下图所示:

方法交换

详情:http://blog.csdn.net/yiyaaixuexi/article/details/9374411

9.UIView和CALayer是啥关系?

1.UIView是iOS系统中界面元素的基础,所有的界面元素都继承自它。它本身完全是由CoreAnimation来实现的 (Mac下似乎不是这样)。它真正的绘图部分,是由一个叫CALayer(Core Animation Layer)的类来管理。 UIView本身,更像是一个CALayer的管理器,访问它的跟绘图和跟坐标有关的属性,例如frame,bounds等 等,实际上内部都是在访问它所包含的CALayer的相关属性。

2.UIView有个layer属性,可以返回它的主CALayer实例,UIView有一个layerClass方法,返回主layer所使用的 类,UIView的子类,可以通过重载这个方法,来让UIView使用不同的CALayer来显示,例如通过

- (class) layerClass {

return ([CAEAGLLayer class]);

}

1

2

3

4

=使某个UIView的子类使用GL来进行绘制。

3.UIView的CALayer类似UIView的子View树形结构,也可以向它的layer上添加子layer,来完成某些特殊的表 示。例如下面的代码

grayCover = [[CALayer alloc] init];

grayCover.backgroundColor = [[[UIColor blackColor] colorWithAlphaComponent:0.2] CGColor];

[self.layer addSubLayer: grayCover];

1

2

3

4

5

会在目标View上敷上一层黑色的透明薄膜。

4.UIView的layer树形在系统内部,被系统维护着三份copy(这段理解有点吃不准)。

逻辑树,就是代码里可以操纵的,例如更改layer的属性等等就在这一份。

动画树,这是一个中间层,系统正在这一层上更改属性,进行各种渲染操作。

显示树,这棵树的内容是当前正被显示在屏幕上的内容。

这三棵树的逻辑结构都是一样的,区别只有各自的属性。

10. 如何高性能的给UIImageView加个圆角?(不准说layer.cornerRadius!)

我觉得应该是使用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

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

11. 使用drawRect有什么影响?(这个可深可浅,你至少得用过。。)

drawRect方法依赖Core Graphics框架来进行自定义的绘制,但这种方法主要的缺点就是它处理touch事件的方式:每次按钮被点击后,都会用setNeddsDisplay进行强制重绘;而且不止一次,每次单点事件触发两次执行。这样的话从性能的角度来说,对CPU和内存来说都是欠佳的。特别是如果在我们的界面上有多个这样的UIButton实例。

12. ASIHttpRequest或者SDWebImage里面给UIImageView加载图片的逻辑是什么样的?

详见SDWebImage的实现流程 http://www.cnblogs.com/6duxz/p/4159572.html

13. 麻烦你设计个简单的图片内存缓存器(移除策略是一定要说的)

图片的内存缓存,可以考虑将图片数据保存到一个数据模型中。所以在程序运行时这个模型都存在内存中。

移除策略:释放数据模型对象。

14. 讲讲你用Instrument优化动画性能的经历吧(别问我什么是Instrument)

可以参考iOS App性能优化

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,那么就是浪费了一些资源而已

参考:http://www.cnblogs.com/dyllove98/archive/2013/06/06/3123005.html

16. viewWillLayoutSubView你总是知道的。

横竖屏切换的时候,系统会响应一些函数,其中 viewWillLayoutSubviews 和 viewDidLayoutSubviews。

//

- (void)viewWillLayoutSubviews

{

[self _shouldRotateToOrientation:(UIDeviceOrientation)[UIApplication sharedApplication].statusBarOrientation];

}

-(void)_shouldRotateToOrientation:(UIDeviceOrientation)orientation {

if (orientation == UIDeviceOrientationPortrait ||orientation ==

UIDeviceOrientationPortraitUpsideDown) {

// 竖屏

}

else {

// 横屏

}

}

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

通过上述一个函数就知道横竖屏切换的接口了。

注意: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吗?读写是分线程的吗?遇到过死锁没?咋解决的?

参考:CoreData与SQLite的线程安全

19. http的post和get啥区别?(区别挺多的,麻烦多说点)

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。

POST把提交的数据则放置在是HTTP包的包体中。

2.”GET方式提交的数据最多只能是1024字节,理论上POST没有限制,可传较大量的数据,IIS4中最大为80KB,IIS5中为100KB”??!

以上这句是我从其他文章转过来的,其实这样说是错误的,不准确的:

(1).首先是”GET方式提交的数据最多只能是1024字节”,因为GET是通过URL提交数据,那么GET可提交的数据量就跟URL的长度有直接关系了。而实际上,URL不存在参数上限的问题,HTTP协议规范没有对URL长度进行限制。这个限制是特定的浏览器及服务器对它的限制。IE对URL长度的限制是2083字节(2K+35)。对于其他浏览器,如Netscape、FireFox等,理论上没有长度限制,其限制取决于操作系统的支持。

注意这是限制是整个URL长度,而不仅仅是你的参数值数据长度。[见参考资料5]

(2).理论上讲,POST是没有大小限制的,HTTP协议规范也没有进行大小限制,说“POST数据量存在80K/100K的大小限制”是不准确的,POST数据是没有限制的,起限制作用的是服务器的处理程序的处理能力。

3.在ASP中,服务端获取GET请求参数用Request.QueryString,获取POST请求参数用Request.Form。在JSP中,用request.getParameter(\”XXXX\”)来获取,虽然jsp中也有request.getQueryString()方法,但使用起来比较麻烦,比如:传一个test.jsp?name=hyddd&password=hyddd,用request.getQueryString()得到的是:name=hyddd&password=hyddd。在PHP中,可以用GET和_POST分别获取GET和POST中的数据,而REQUEST则可以获取GET和POST两种请求中的数据。值得注意的是,JSP中使用request和PHP中使用_REQUEST都会有隐患,这个下次再写个文章总结。

4.POST的安全性要比GET的安全性高。注意:这里所说的安全性和上面GET提到的“安全”不是同个概念。上面“安全”的含义仅仅是不作数据修改,而这里安全的含义是真正的Security的含义,比如:通过GET提交数据,用户名和密码将明文出现在URL上,因为(1)登录页面有可能被浏览器缓存,(2)其他人查看浏览器的历史纪录,那么别人就可以拿到你的账号和密码了,除此之外,使用GET提交数据还可能会造成Cross-site request forgery攻击。

总结一下,Get是向服务器发索取数据的一种请求,而Post是向服务器提交数据的一种请求,在FORM(表单)中,Method默认为”GET”,实质上,GET和POST只是发送机制不同,并不是一个取一个发!

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、伸展树等。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 211,948评论 6 492
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,371评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 157,490评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,521评论 1 284
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,627评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,842评论 1 290
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,997评论 3 408
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,741评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,203评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,534评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,673评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,339评论 4 330
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,955评论 3 313
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,770评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,000评论 1 266
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,394评论 2 360
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,562评论 2 349

推荐阅读更多精彩内容

  • 《招聘一个靠谱的 iOS》—参考答案(下) 说明:面试题来源是微博@我就叫Sunny怎么了的这篇博文:《招聘一个靠...
    韩发发吖阅读 1,557评论 0 8
  • *面试心声:其实这些题本人都没怎么背,但是在上海 两周半 面了大约10家 收到差不多3个offer,总结起来就是把...
    Dove_iOS阅读 27,131评论 30 470
  • 雨,滴在身上,是一种自然的实在;而滴在心上便是一种油然的心态。听雨,也就成了自我的一种诉求,说下自身的落寞,随雨水...
    阿土127阅读 169评论 0 1
  • 我很崇拜李笑来老师,他能把他觉得应该卖两千美元的经验标价两百元人民币出售,一卖就卖个三千万,然后把收入全部捐赠,摆...
    吕燕阅读 1,106评论 9 12
  • 风将起 将士登城发冲冠 燮鼓响,着寒甲 三军出城刀剑鸣 风起时 将军不畏虎狼恶 首当冲,长枪染血 战袍猎猎向前声 ...
    梵夜阅读 450评论 0 1