iOS面试题|全栈式导航(2017大全)



近一年内陆续面试了不少人了,从面试者到面试官的转变让我对 iOS 招聘有了更多的感受。经过了前段时间的一大波面试,我们终于找到了志同道合的小伙伴,面试也暂时告一段落了。总结下面试人过程中的感受,你也可以读到我们对简历、算法、性格、iOS 基础、底层知识的看法和一些常问的面试题。

一个靠谱的简历

简历非常能反映一个人的性格和水平,相比于你在学校获得多少奖项,工作经历、项目经历、熟悉的技术等更加关键,如果还有博客和一些 Github 上的项目,好感度++,但记得在去面试前收拾下,我们真的会挨个文件 review 你的开源代码的。我们还喜欢关注一些细节,比如简历里关键字的拼写,看似无关紧要但很能反映出对自己的要求,经常见一个简历中iOS这三个字母的拼写就出现 IOS、iOS、ios 三种的,非常不能忍,再列举几个常见问题:

iPhone -> IPHONE IPhone

Xcode -> XCode xcode

Objective-C -> Object-C

JSON -> Json

HTTP -> Http

还有,注意中英文间用一个半角空格隔开,排版会漂亮很多,简历承载的不仅是内容,还有细节和态度,上面这些点往往都反映着面试者的代码风格、做事的认真程度。当然,简历写的很漂亮但面聊之后发现啥都不会的也有,甚至见过来面试上来就跟我说简历是假的,就想求个面试机会这种 - -

面试

别迟到,别迟到,别迟到,重要的事说三遍。有变动提前通知 HR,碰到过临时有事没来,和谁都不说一声,打电话过去还要求改个时间的,这种直接拜拜。

面试时最好准备纸、笔、简历,可能用不上,但很能体现认真程度。有条件的话带着 Mac 和源码,手机中装好所有在简历中出现的 App。

关于算法

我们是实用主义,iOS 开发中很少需要自己写复杂的算法,所以不在面试考核标准中。

代码规范

这是一个重点考察项,曾经在微博上发过一个风格纠错题:

也曾在面试时让人当场改过,槽点不少,能够有 10 处以上修改的就基本达到标准了(处女座的人在这方面表现都很优秀

一个区分度很大的面试题

考察一个面试者基础咋样,基本上问一个 @property 就够了:

@property 后面可以有哪些修饰符?

什么情况使用 weak 关键字,相比 assign 有什么不同?

怎么用 copy 关键字?

这个写法会出什么问题:@property (copy) NSMutableArray *array;

如何让自己的类用 copy 修饰符?如何重写带 copy 关键字的 setter?

这一套问题区分度比较大,如果上面的问题都能回答正确,可以延伸问更深入点的:

@property 的本质是什么?ivar、getter、setter 是如何生成并添加到这个类中的

@protocol 和 category 中如何使用 @property

runtime 如何实现 weak 属性

每个人擅长的领域不一样,我们一般会从简历上找自己写擅长的技术聊,假如自己并不是很熟,最好别写出来或扯出来,万一面试官刚好非常精通这里就露馅了。

Checklist

总结过些面试题,没坚持下去,后来把这些当 checklist,面试的时候实在没话聊的时候做个提醒,语言、框架、运行机制性质的:

[※]@property中有哪些属性关键字?

[※]weak属性需要在dealloc中置nil么?

[※※]@synthesize和@dynamic分别有什么作用?

[※※※]ARC下,不显式指定任何属性关键字时,默认的关键字都有哪些?

[※※※]用@property声明的NSString(或NSArray,NSDictionary)经常使用copy关键字,为什么?如果改用strong关键字,可能造成什么问题?

[※※※]@synthesize合成实例变量的规则是什么?假如property名为foo,存在一个名为_foo的实例变量,那么还会自动合成新变量么?

[※※※※※]在有了自动合成属性实例变量之后,@synthesize还有哪些使用场景?

[※※]objc中向一个nil对象发送消息将会发生什么?

[※※※]objc中向一个对象发送消息[obj foo]和objc_msgSend()函数之间有什么关系?

[※※※]什么时候会报unrecognized selector的异常?

[※※※※]一个objc对象如何进行内存布局?(考虑有父类的情况)

[※※※※]一个objc对象的isa的指针指向什么?有什么作用?

[※※※※]下面的代码输出什么?

@implementationSon:Father- (id)init{self= [superinit];if(self) {NSLog(@"%@",NSStringFromClass([selfclass]));NSLog(@"%@",NSStringFromClass([superclass]));}returnself;}@end

[※※※※]runtime如何通过selector找到对应的IMP地址?(分别考虑类方法和实例方法)

[※※※※]使用runtime Associate方法关联的对象,需要在主对象dealloc的时候释放么?

[※※※※※]objc中的类方法和实例方法有什么本质区别和联系?

[※※※※※]_objc_msgForward函数是做什么的,直接调用它将会发生什么?

[※※※※※]runtime如何实现weak变量的自动置nil?

[※※※※※]能否向编译后得到的类中增加实例变量?能否向运行时创建的类中添加实例变量?为什么?

[※※※]runloop和线程有什么关系?

[※※※]runloop的mode作用是什么?

[※※※※]以+ scheduledTimerWithTimeInterval...的方式触发的timer,在滑动页面上的列表时,timer会暂定回调,为什么?如何解决?

[※※※※※]猜想runloop内部是如何实现的?

[※]objc使用什么机制管理对象内存?

[※※※※]ARC通过什么方式帮助开发者管理内存?

[※※※※]不手动指定autoreleasepool的前提下,一个autorealese对象在什么时刻释放?(比如在一个vc的viewDidLoad中创建)

[※※※※]BAD_ACCESS在什么情况下出现?

[※※※※※]苹果是如何实现autoreleasepool的?

[※※]使用block时什么情况会发生引用循环,如何解决?

[※※]在block内如何修改block外部变量?

[※※※]使用系统的某些block api(如UIView的block版本写动画时),是否也考虑引用循环问题?

[※※]GCD的队列(dispatch_queue_t)分哪两种类型?

[※※※※]如何用GCD同步若干个异步调用?(如根据若干个url异步加载多张图片,然后在都下载完成后合成一张整图)

[※※※※]dispatch_barrier_async的作用是什么?

[※※※※※]苹果为什么要废弃dispatch_get_current_queue?

[※※※※※]以下代码运行结果如何?

- (void)viewDidLoad{    [superviewDidLoad];NSLog(@"1");dispatch_sync(dispatch_get_main_queue(), ^{NSLog(@"2");    });NSLog(@"3");}

[※※]addObserver:forKeyPath:options:context:各个参数的作用分别是什么,observer中需要实现哪个方法才能获得KVO回调?

[※※※]如何手动触发一个value的KVO

[※※※]若一个类有实例变量NSString *_foo,调用setValue:forKey:时,可以以foo还是_foo作为key?

[※※※※]KVC的keyPath中的集合运算符如何使用?

[※※※※]KVC和KVO的keyPath一定是属性么?

[※※※※※]如何关闭默认的KVO的默认实现,并进入自定义的KVO实现?

[※※※※※]apple用什么方式实现对一个对象的KVO?

[※※]IBOutlet连出来的视图属性为什么可以被设置成weak?

[※※※※※]IB中User Defined Runtime Attributes如何使用?

[※※※]如何调试BAD_ACCESS错误

[※※※]lldb(gdb)常用的调试命令?

这些小题可以做为讨论的入口,根据面试者的回答再继续聊下去。其中一些题比较底层,是留给屌屌的面试者或者试探评级用的,一般情况并不是重点的考察内容。

业务能力

毕竟平常的工作内容不是 runtime、runloop,不怎么会用到底层的黑魔法,80% 的时间都是和搭建页面、写业务逻辑、网络请求打交道。

要求面试者能够熟练构建 UI,我会找一个面试者做过的页面让他分析下页面结构、约束或者 frame 布局的连法和计算方法;有时也会让面试者说说 UITableView 常用的几个 delegate 和 data source 代理方法,动态 Cell 高度计算什么的;接下来,在手机里随便找一个 App 的页面,让面试者当场说说如果是他写应该用哪些 UI 组件和布局方式等。问几个问题后就能大概了解业务能力了,我们这边重度使用 IB 和 AutoLayout,假如面试者依然使用代码码 UI 也到没关系,有“从良”意愿就很好~

程序架构和一些设计模式如果面试者自己觉得还不错的话也会聊聊,但跪求别说 Singleton 了,用的越多对水平就越表示怀疑。对设计模式自信的我一般问一个问题,抽象工厂模式在 Cocoa SDK 中哪些类中体现?

架构上 MVC 还是 MVVM 还是 MVP 神马的到是可以聊聊各自的见解,反正也没有正确答案,只要别搞的太离谱就行,比如有的人说网络请求和数据库的操作最好放到 UIView 的子类里面干。

网络请求、数据库等各家都有成熟的封装,基本知道咋用就行。除此之外,我们还会顺带的问下除了 iOS 开发外,还会什么其他编程语言、或者熟悉哪种脚本语言和 Terminal 操作等,甚至还问问是如何翻墙- -,相信这些技能都是很重要的。

性格

大家都是写程序的,没啥必要用奇怪的、很难的问题难为对方,更关键的还是性格,和 Team 的风格是不是和的来。一个心态良好的面试者需要有个平常心,不傲娇也不跪舔,表达要正常,经常遇到问一个问题后一两分钟一直处于沉思状态,一句话不说,交流像挤牙膏一样,很是憋屈;还有非常屌屌的,明明不懂仍然强行据理力争,镇得住面试官也罢,撞枪口上就别怪不客气了- - 。决定要不要一个人基本上聊 5 分钟就可以确定了,喜欢水到渠成的感觉,看对眼了挡都挡不住。

招聘告一段落,后面将会有更精彩的事情发生。最后,再次感谢大家的支持和对我的信任。

基础篇

1. Object-c的类可以多重继承么?可以实现多个接口么?Category是什么?重写一个类的方式用继承好还是分类好?为什么?

答: Object-c的类不可以多重继承;可以实现多个接口,通过实现多个接口可以完成C++的多重继承;Category是类别,一般情况用分类好,用Category去重写类的方法,仅对本Category有效,不会影响到其他类与原有类的关系。

2. #import 跟#include 又什么区别,@class呢, #import<> 跟 #import””又什么区别?

答:#import是Objective-C导入头文件的关键字,#include是C/C++导入头文件的关键字,使用#import头文件会自动只导入一次,不会重复导入,相当于#include和#pragma once;@class告诉编译器某个类的声明,当执行时,才去查看类的实现文件,可以解决头文件的相互包含;#import<>用来包含系统的头文件,#import””用来包含用户头文件。

3. 属性readwrite,readonly,assign,retain,copy,nonatomic 各是什么作用,在那种情况下用?

答:

1). readwrite 是可读可写特性;需要生成getter方法和setter方法时

2). readonly 是只读特性 只会生成getter方法 不会生成setter方法 ;不希望属性在类外改变

3). assign 是赋值特性,setter方法将传入参数赋值给实例变量;仅设置变量时;

4). retain 表示持有特性,setter方法将传入参数先保留,再赋值,传入参数的retaincount会+1;

5). copy 表示赋值特性,setter方法将传入对象复制一份;需要完全一份新的变量时。

6).nonatomic 非原子操作,决定编译器生成的setter getter是否是原子操作,atomic表示多线程安全,一般使用nonatomic

4.写一个setter方法用于完成@property (nonatomic,retain)NSString *name,写一个setter方法用于完成@property(nonatomic,copy)NSString *name

答:

-?(void)?setName:(NSString*)?str

{

[str?retain];

[name?release];

name?=?str;

}

-?(void)setName:(NSString?*)str

{

id?t?=?[str?copy];

[name?release];

name?=?t;

}

5.对于语句NSString*obj = [[NSData alloc] init]; obj在编译时和运行时分别时什么类型的对象?

答: 编译时是NSString的类型;运行时是NSData类型的对象

6.常见的object-c的数据类型有那些, 和C的基本数据类型有什么区别?如:NSInteger和int

答:object-c的数据类型有NSString,NSNumber,NSArray,NSMutableArray,NSData等等,这些都是class,创建后便是对象,而C语言的基本数据类型int,只是一定字节的内存空间,用于存放数值;NSInteger是基本数据类型,并不是NSNumber的子类,当然也不是NSObject的子类。NSInteger是基本数据类型Int或者Long的别名(NSInteger的定义typedef long NSInteger),它的区别在于,NSInteger会根据系统是32位还是64位来决定是本身是int还是Long。

7.id 声明的对象有什么特性?

答:Id 声明的对象具有运行时的特性,即可以指向任意类型的objcetive-c的对象;

8.Objective-C如何对内存管理的,说说你的看法和解决方法?

答:Objective-C的内存管理主要有三种方式ARC(自动内存计数)、手动内存计数、内存池。

1). (Garbage Collection)自动内存计数:这种方式和java类似,在你的程序的执行过程中。始终有一个高人在背后准确地帮你收拾垃圾,你不用考虑它什么时候开始工作,怎样工作。你只需要明白,我申请了一段内存空间,当我不再使用从而这段内存成为垃圾的时候,我就彻底的把它忘记掉,反正那个高人会帮我收拾垃圾。遗憾的是,那个高人需要消耗一定的资源,在携带设备里面,资源是紧俏商品所以iPhone不支持这个功能。所以“Garbage Collection”不是本入门指南的范围,对“Garbage Collection”内部机制感兴趣的同学可以参考一些其他的资料,不过说老实话“Garbage Collection”不大适合适初学者研究。

解决: 通过alloc – initial方式创建的, 创建后引用计数+1, 此后每retain一次引用计数+1, 那么在程序中做相应次数的release就好了.

2). (Reference Counted)手动内存计数:就是说,从一段内存被申请之后,就存在一个变量用于保存这段内存被使用的次数,我们暂时把它称为计数器,当计数器变为0的时候,那么就是释放这段内存的时候。比如说,当在程序A里面一段内存被成功申请完成之后,那么这个计数器就从0变成1(我们把这个过程叫做alloc),然后程序B也需要使用这个内存,那么计数器就从1变成了2(我们把这个过程叫做retain)。紧接着程序A不再需要这段内存了,那么程序A就把这个计数器减1(我们把这个过程叫做release);程序B也不再需要这段内存的时候,那么也把计数器减1(这个过程还是release)。当系统(也就是Foundation)发现这个计数器变 成员了0,那么就会调用内存回收程序把这段内存回收(我们把这个过程叫做dealloc)。顺便提一句,如果没有Foundation,那么维护计数器,释放内存等等工作需要你手工来完成。

解决:一般是由类的静态方法创建的, 函数名中不会出现alloc或init字样, 如[NSString string]和[NSArray arrayWithObject:], 创建后引用计数+0, 在函数出栈后释放, 即相当于一个栈上的局部变量. 当然也可以通过retain延长对象的生存期.

3). (NSAutoRealeasePool)内存池:可以通过创建和释放内存池控制内存申请和回收的时机.

解决:是由autorelease加入系统内存池, 内存池是可以嵌套的, 每个内存池都需要有一个创建释放对, 就像main函数中写的一样. 使用也很简单, 比如[[[NSString alloc]initialWithFormat:@”Hey you!”] autorelease], 即将一个NSString对象加入到最内层的系统内存池, 当我们释放这个内存池时, 其中的对象都会被释放.

9. 原子(atomic)跟非原子(non-atomic)属性有什么区别?

答:

1). atomic提供多线程安全。是防止在写未完成的时候被另外一个线程读取,造成数据错误

2). non-atomic:在自己管理内存的环境中,解析的访问器保留并自动释放返回的值,如果指定了 nonatomic ,那么访问器只是简单地返回这个值。

10. 看下面的程序,第一个NSLog会输出什么?这时str的retainCount是多少?第二个和第三个呢? 为什么?

11NSMutableArray*?ary?=?[[NSMutableArray?array]?retain];

NSString?*str?=?[NSString?stringWithFormat:@"test"];

[str?retain];

[aryaddObject:str];

NSLog(@”%@%d”,str,[str?retainCount]);

[str?retain];

[str?release];

[str?release];

NSLog(@”%@%d”,str,[str?retainCount]);

[aryremoveAllObjects];

NSLog(@”%@%d”,str,[str?retainCount]);

str的retainCount创建+1,retain+1,加入数组自动+1 3

retain+1,release-1,release-1 2

数组删除所有对象,所有数组内的对象自动-1 1

11. 内存管理的几条原则时什么?按照默认法则.那些关键字生成的对象需要手动释放?在和property结合的时候怎样有效的避免内存泄露?

答:谁申请,谁释放

遵循Cocoa Touch的使用原则;

内存管理主要要避免“过早释放”和“内存泄漏”,对于“过早释放”需要注意@property设置特性时,一定要用对特性关键字,对于“内存泄漏”,一定要申请了要负责释放,要细心。

关键字alloc 或new 生成的对象需要手动释放;

设置正确的property属性,对于retain需要在合适的地方释放,

12.如何对iOS设备进行性能测试?

答: Profile-> Instruments ->Time Profiler

13. Object C中创建线程的方法是什么?如果在主线程中执行代码,方法是什么?如果想延时执行代码、方法又是什么?

答:线程创建有三种方法:使用NSThread创建、使用GCD的dispatch、使用子类化的NSOperation,然后将其加入NSOperationQueue;在主线程执行代码,方法是performSelectorOnMainThread,如果想延时执行代码可以用performSelector:onThread:withObject:waitUntilDone:

14. MVC设计模式是什么? 你还熟悉什么设计模式?

答:

设计模式:并不是一种新技术,而是一种编码经验,使用比如java中的接口,iphone中的协议,继承关系等基本手段,用比较成熟的逻辑去处理某一种类型的事情,总结为所谓设计模式。面向对象编程中,java已经归纳了23种设计模式。

mvc设计模式 :模型,视图,控制器,可以将整个应用程序在思想上分成三大块,对应是的数据的存储或处理,前台的显示,业务逻辑的控制。 Iphone本身的设计思想就是遵循mvc设计模式。其不属于23种设计模式范畴。

代理模式:代理模式给某一个对象提供一个代理对象,并由代理对象控制对源对象的引用.比如一个工厂生产了产品,并不想直接卖给用户,而是搞了很多代理商,用户可以直接找代理商买东西,代理商从工厂进货.常见的如QQ的自动回复就属于代理拦截,代理模式在iphone中得到广泛应用.

单例模式:说白了就是一个类不通过alloc方式创建对象,而是用一个静态方法返回这个类的对象。系统只需要拥有一个的全局对象,这样有利于我们协调系统整体的行为,比如想获得[UIApplication sharedApplication];任何地方调用都可以得到 UIApplication的对象,这个对象是全局唯一的。

观察者模式: 当一个物体发生变化时,会通知所有观察这个物体的观察者让其做出反应。实现起来无非就是把所有观察者的对象给这个物体,当这个物体的发生改变,就会调用遍历所有观察者的对象调用观察者的方法从而达到通知观察者的目的。

工厂模式:

public?class?Factory{

public?static?Sample?creator(int?which){

if?(which==1)

return?new?SampleA();

else?if?(which==2)

return?new?SampleB();

}

}

15 浅复制和深复制的区别?

答:浅层复制:只复制指向对象的指针,而不复制引用对象本身。

深层复制:复制引用对象本身。

意思就是说我有个A对象,复制一份后得到A_copy对象后,对于浅复制来说,A和A_copy指向的是同一个内存资源,复制的只不过是是一个指针,对象本身资源

还是只有一份,那如果我们对A_copy执行了修改操作,那么发现A引用的对象同样被修改,这其实违背了我们复制拷贝的一个思想。深复制就好理解了,内存中存在了

两份独立对象本身。

用网上一哥们通俗的话将就是:

浅复制好比你和你的影子,你完蛋,你的影子也完蛋

深复制好比你和你的克隆人,你完蛋,你的克隆人还活着。

16. 类别的作用?继承和类别在实现中有何区别?

答:category 可以在不获悉,不改变原来代码的情况下往里面添加新的方法,只能添加,不能删除修改,并且如果类别和原来类中的方法产生名称冲突,则类别将覆盖原来的方法,因为类别具有更高的优先级。

类别主要有3个作用:

1).将类的实现分散到多个不同文件或多个不同框架中。

2).创建对私有方法的前向引用。

3).向对象添加非正式协议。

继承可以增加,修改或者删除方法,并且可以增加属性。

17. 类别和类扩展的区别。

答:category和extensions的不同在于 后者可以添加属性。另外后者添加的方法是必须要实现的。

extensions可以认为是一个私有的Category。

18. oc中的协议和java中的接口概念有何不同?

答:OC中的代理有2层含义,官方定义为 formal和informal protocol。前者和Java接口一样。

informal protocol中的方法属于设计模式考虑范畴,不是必须实现的,但是如果有实现,就会改变类的属性。

其实关于正式协议,类别和非正式协议我很早前学习的时候大致看过,也写在了学习教程里

“非正式协议概念其实就是类别的另一种表达方式“这里有一些你可能希望实现的方法,你可以使用他们更好的完成工作”。

这个意思是,这些是可选的。比如我门要一个更好的方法,我们就会申明一个这样的类别去实现。然后你在后期可以直接使用这些更好的方法。

这么看,总觉得类别这玩意儿有点像协议的可选协议。”

现在来看,其实protocal已经开始对两者都统一和规范起来操作,因为资料中说“非正式协议使用interface修饰“,

现在我们看到协议中两个修饰词:“必须实现(@requied)”和“可选实现(@optional)”。

19. 什么是KVO和KVC?

答:KVC:键 – 值编码是一种间接访问对象的属性使用字符串来标识属性,而不是通过调用存取方法,直接或通过实例变量访问的机制。

很多情况下可以简化程序代码。apple文档其实给了一个很好的例子。

KVO:键值观察机制,他提供了观察某一属性变化的方法,极大的简化了代码。

具体用看到嗯哼用到过的一个地方是对于按钮点击变化状态的的监控。

比如我自定义的一个button

[self?addObserver:self?forKeyPath:@"highlighted"?options:0?context:nil];

#pragma?mark?KVO

-?(void)observeValueForKeyPath:(NSString?*)keyPath?ofObject:(id)object?change:(NSDictionary?*)change?context:(void?*)context

{

if?([keyPath?isEqualToString:@"highlighted"]?)?{

[self?setNeedsDisplay];

}

}

对于系统是根据keypath去取的到相应的值发生改变,理论上来说是和kvc机制的道理是一样的。

对于kvc机制如何通过key寻找到value:

“当通过KVC调用对象时,比如:[self valueForKey:@”someKey”]时,程序会自动试图通过几种不同的方式解析这个调用。首先查找对象是否带有 someKey 这个方法,如果没找到,会继续查找对象是否带有someKey这个实例变量(iVar),如果还没有找到,程序会继续试图调用 -(id) valueForUndefinedKey:这个方法。如果这个方法还是没有被实现的话,程序会抛出一个NSUndefinedKeyException异常错误。

(cocoachina.com注:Key-Value Coding查找方法的时候,不仅仅会查找someKey这个方法,还会查找getsomeKey这个方法,前面加一个get,或者_someKey以及_getsomeKey这几种形式。同时,查找实例变量的时候也会不仅仅查找someKey这个变量,也会查找_someKey这个变量是否存在。)

设计valueForUndefinedKey:方法的主要目的是当你使用-(id)valueForKey方法从对象中请求值时,对象能够在错误发生前,有最后的机会响应这个请求。这样做有很多好处,下面的两个例子说明了这样做的好处。“

来至cocoa,这个说法应该挺有道理。

因为我们知道button却是存在一个highlighted实例变量.因此为何上面我们只是add一个相关的keypath就行了,

可以按照kvc查找的逻辑理解,就说的过去了。

20. 代理的作用?

答:代理的目的是改变或传递控制链。允许一个类在某些特定时刻通知到其他类,而不需要获取到那些类的指针。可以减少框架复杂度。

另外一点,代理可以理解为java中的回调监听机制的一种类似。

21. oc中可修改和不可以修改类型。

答:可修改不可修改的集合类。这个我个人简单理解就是可动态添加修改和不可动态添加修改一样。

比如NSArray和NSMutableArray。前者在初始化后的内存控件就是固定不可变的,后者可以添加等,可以动态申请新的内存空间。

22. 我们说的oc是动态运行时语言是什么意思?

答:多态。 主要是将数据类型的确定由编译时,推迟到了运行时。

这个问题其实浅涉及到两个概念,运行时和多态。

简单来说,运行时机制使我们直到运行时才去决定一个对象的类别,以及调用该类别对象指定方法。

多态:不同对象以自己的方式响应相同的消息的能力叫做多态。意思就是假设生物类(life)都用有一个相同的方法-eat;

那人类属于生物,猪也属于生物,都继承了life后,实现各自的eat,但是调用是我们只需调用各自的eat方法。

也就是不同的对象以自己的方式响应了相同的消息(响应了eat这个选择器)。

因此也可以说,运行时机制是多态的基础?~~~

23. 通知和协议的不同之处?

答:协议有控制链(has-a)的关系,通知没有。

首先我一开始也不太明白,什么叫控制链(专业术语了~)。但是简单分析下通知和代理的行为模式,我们大致可以有自己的理解

简单来说,通知的话,它可以一对多,一条消息可以发送给多个消息接受者。

代理按我们的理解,到不是直接说不能一对多,比如我们知道的明星经济代理人,很多时候一个经济人负责好几个明星的事务。

只是对于不同明星间,代理的事物对象都是不一样的,一一对应,不可能说明天要处理A明星要一个发布会,代理人发出处理发布会的消息后,别称B的

发布会了。但是通知就不一样,他只关心发出通知,而不关心多少接收到感兴趣要处理。

因此控制链(has-a从英语单词大致可以看出,单一拥有和可控制的对应关系。

24. 什么是推送消息?

答:推送通知更是一种技术。

简单点就是客户端获取资源的一种手段。

普通情况下,都是客户端主动的pull。

推送则是服务器端主动push。 测试push的实现可以查看该博文。

25. 关于多态性

答:多态,子类指针可以赋值给父类。

这个题目其实可以出到一切面向对象语言中,

因此关于多态,继承和封装基本最好都有个自我意识的理解,也并非一定要把书上资料上写的能背出来

26. 对于单例的理解

答:在objective-c中要实现一个单例类,至少需要做以下四个步骤:

1).为单例对象实现一个静态实例,并初始化,然后设置成nil,

2).实现一个实例构造方法检查上面声明的静态实例是否为nil,如果是则新建并返回一个本类的实例,

3).重写allocWithZone方法,用来保证其他人直接使用alloc和init试图获得一个新实力的时候不产生一个新实例,

4).适当实现allocWitheZone,copyWithZone,release和autorelease。

27. 说说响应链

答: 事件响应链。包括点击事件,画面刷新事件等。在视图栈内从上至下,或者从下之上传播。

可以说点事件的分发,传递以及处理。具体可以去看下touch事件这块。因为问的太抽象化了

严重怀疑题目出到越后面就越笼统。

可以从责任链模式,来讲通过事件响应链处理,其拥有的扩展性

28. frame和bounds有什么不同?

答:frame指的是:该view在父view坐标系统中的位置和大小。(参照点是父亲的坐标系统)

bounds指的是:该view在本身坐标系统中 的位置和大小。(参照点是本身坐标系统)

29. 方法和选择器有何不同?

答:selector是一个方法的名字,method是一个组合体,包含了名字和实现.

详情可以看apple文档。

30. OC的垃圾回收机制?

答: OC2.0有Garbage collection,但是iOS平台不提供。

一般我们了解的objective-c对于内存管理都是手动操作的,但是也有自动释放池。

但是差了大部分资料,貌似不要和arc机制搞混就好了。

31. NSOperation queue?

答:存放NSOperation的集合类。

操作和操作队列,基本可以看成java中的线程和线程池的概念。用于处理ios多线程开发的问题。

网上部分资料提到一点是,虽然是queue,但是却并不是带有队列的概念,放入的操作并非是按照严格的先进现出。

这边又有个疑点是,对于队列来说,先进先出的概念是Afunc添加进队列,Bfunc紧跟着也进入队列,Afunc先执行这个是必然的,

但是Bfunc是等Afunc完全操作完以后,B才开始启动并且执行,因此队列的概念离乱上有点违背了多线程处理这个概念。

但是转念一想其实可以参考银行的取票和叫号系统。

因此对于A比B先排队取票但是B率先执行完操作,我们亦然可以感性认为这还是一个队列。

但是后来看到一票关于这操作队列话题的文章,其中有一句提到

“因为两个操作提交的时间间隔很近,线程池中的线程,谁先启动是不定的。”

瞬间觉得这个queue名字有点忽悠人了,还不如pool~

综合一点,我们知道他可以比较大的用处在于可以帮组多线程编程就好了。

32. 什么是延迟加载?

答:懒汉模式,只在用到的时候才去初始化。

也可以理解成延时加载。

我觉得最好也最简单的一个列子就是tableView中图片的加载显示了。

一个延时载,避免内存过高,一个异步加载,避免线程堵塞。

33. 是否在一个视图控制器中嵌入两个tableview控制器?

答:一个视图控制只提供了一个View视图,理论上一个tableViewController也不能放吧,

只能说可以嵌入一个tableview视图。当然,题目本身也有歧义,如果不是我们定性思维认为的UIViewController,而是宏观的表示视图控制者,那我们倒是可以把其看成一个视图控制者,它可以控制多个视图控制器,比如TabbarController那样的感觉。

34. 一个tableView是否可以关联两个不同的数据源?你会怎么处理?

答:首先我们从代码来看,数据源如何关联上的,其实是在数据源关联的代理方法里实现的。

因此我们并不关心如何去关联他,他怎么关联上,方法只是让我返回根据自己的需要去设置如相关的数据源。

因此,我觉得可以设置多个数据源啊,但是有个问题是,你这是想干嘛呢?想让列表如何显示,不同的数据源分区块显示?

35. 什么时候使用NSMutableArray,什么时候使用NSArray?

答:当数组在程序运行时,需要不断变化的,使用NSMutableArray,当数组在初始化后,便不再改变的,使用NSArray。需要指出的是,使用NSArray只表明的是该数组在运行时不发生改变,即不能往NSAarry的数组里新增和删除元素,但不表明其数组內的元素的内容不能发生改变。NSArray是线程安全的,NSMutableArray不是线程安全的,多线程使用到NSMutableArray需要注意。

36. 给出委托方法的实例,并且说出UITableVIew的Data Source方法

答:CocoaTouch框架中用到了大量委托,其中UITableViewDelegate就是委托机制的典型应用,是一个典型的使用委托来实现适配器模式,其中UITableViewDelegate协议是目标,tableview是适配器,实现UITableViewDelegate协议,并将自身设置为talbeview的delegate的对象,是被适配器,一般情况下该对象是UITableViewController。

UITableVIew的Data Source方法有- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section;

- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath;

37. 在应用中可以创建多少autorelease对象,是否有限制?

答案:无

38. 如果我们不创建内存池,是否有内存池提供给我们?

答:界面线程维护着自己的内存池,用户自己创建的数据线程,则需要创建该线程的内存池

39. 什么时候需要在程序中创建内存池?

答:用户自己创建的数据线程,则需要创建该线程的内存池

40. 类NSObject的那些方法经常被使用?

答:NSObject是Objetive-C的基类,其由NSObject类及一系列协议构成。

其中类方法alloc、class、 description 对象方法init、dealloc、– performSelector:withObject:afterDelay:等经常被使用

41. 什么是简便构造方法?

答:简便构造方法一般由CocoaTouch框架提供,如NSNumber的 + numberWithBool: + numberWithChar: + numberWithDouble: + numberWithFloat: + numberWithInt:

Foundation下大部分类均有简便构造方法,我们可以通过简便构造方法,获得系统给我们创建好的对象,并且不需要手动释放。

42. 如何使用Xcode设计通用应用?

答:使用MVC模式设计应用,其中Model层完成脱离界面,即在Model层,其是可运行在任何设备上,在controller层,根据iPhone与iPad(独有UISplitViewController)的不同特点选择不同的viewController对象。在View层,可根据现实要求,来设计,其中以xib文件设计时,其设置其为universal。

43. UIView的动画效果有那些?

答:有很多,如 UIViewAnimationOptionCurveEaseInOut UIViewAnimationOptionCurveEaseIn UIViewAnimationOptionCurveEaseOut UIViewAnimationOptionTransitionFlipFromLeft UIViewAnimationOptionTransitionFlipFromRight UIViewAnimationOptionTransitionCurlUpUIViewAnimationOptionTransitionCurlDown

44. 在iPhone应用中如何保存数据?

答:有以下几种保存机制:

1).通过web服务,保存在服务器上

2).通过NSCoder固化机制,将对象保存在文件中

3).通过SQlite或CoreData保存在文件数据库中

45. 什么是coredata?

答:coredata是苹果提供一套数据保存框架,其基于SQlite

46. 什么是NSManagedObject模型?

答:NSManagedObject是NSObject的子类 ,也是coredata的重要组成部分,它是一个通用的类,实现了core data 模型层所需的基本功能,用户可通过子类化NSManagedObject,建立自己的数据模型。

47. 什么是NSManagedobjectContext?

答:NSManagedobjectContext对象负责应用和数据库之间的交互。

48. 什么是谓词?

答:谓词是通过NSPredicate,是通过给定的逻辑条件作为约束条件,完成对数据的筛选。

1

2predicate?=?[NSPredicate?predicateWithFormat:@"customerID?==?%d",n];

a?=?[customers?filteredArrayUsingPredicate:predicate];

49. 和coredata一起有哪几种持久化存储机制?

答:存入到文件、 存入到NSUserDefaults(系统plist文件中)、存入到Sqlite文件数据库

50. 谈谈对Block 的理解?并写出一个使用Block执行UIVew动画?

答:Block是可以获取其他函数局部变量的匿名函数,其不但方便开发,并且可以大幅提高应用的执行效率(多核心CPU可直接处理Block指令)

1

2

3

4

5[UIView?transitionWithView:self.view

duration:0.2

options:UIViewAnimationOptionTransitionFlipFromLeft

animations:^{?[[blueViewController?view]?removeFromSuperview];?[[self?view]?insertSubview:yellowViewController.view?atIndex:0];?}

completion:NULL];

51. 写出上面代码的Block的定义。

答:

1

2typedef?void(^animations)?(void);

typedef?void(^completion)?(BOOL?finished);

52. 试着使用+ beginAnimations:context:以及上述Block的定义,写出一个可以完成

1

+?(void)transitionWithView:(UIView?*)view?duration:(NSTimeInterval)duration?options:(UIViewAnimationOptions)options?animations:(void?(^)(void))animations?completion:(void?(^)(BOOL?finished))completion?NS_AVAILABLE_IOS(4_0);

操作的函数执行部分

答案:无

53. 做过的项目是否涉及网络访问功能,使用什么对象完成网络功能?

答:ASIHTTPRequest与NSURLConnection

54. 简单介绍下NSURLConnection类及+ sendSynchronousRequest:returningResponse:error:与– initWithRequest:delegate:两个方法的区别?

答: NSURLConnection主要用于网络访问,其中+ sendSynchronousRequest:returningResponse:error:是同步访问数据,即当前线程会阻塞,并等待request的返回的response,而– initWithRequest:delegate:使用的是异步加载,当其完成网络访问后,会通过delegate回到主线程,并其委托的对象。

55. 多线程是什么

答: 多线程是个复杂的概念,按字面意思是同步完成多项任务,提高了资源的使用效率,从硬件、操作系统、应用软件不同的角度去看,多线程被赋予不同的内涵,对于硬件,现在市面上多数的CPU都是多核的,多核的CPU运算多线程更为出色;从操作系统角度,是多任务,现在用的主流操作系统都是多任务的,可以一边听歌、一边写博客;对于应用来说,多线程可以让应用有更快的回应,可以在网络下载时,同时响应用户的触摸操作。在iOS应用中,对多线程最初的理解,就是并发,它的含义是原来先做烧水,再摘菜,再炒菜的工作,会变成烧水的同时去摘菜,最后去炒菜。

56. iOS 中的多线程

答: iOS中的多线程,是Cocoa框架下的多线程,通过Cocoa的封装,可以让我们更为方便的使用线程,做过C++的同学可能会对线程有更多的理解,比如线程的创立,信号量、共享变量有认识,Cocoa框架下会方便很多,它对线程做了封装,有些封装,可以让我们创建的对象,本身便拥有线程,也就是线程的对象化抽象,从而减少我们的工程,提供程序的健壮性。

GCD是(Grand Central Dispatch)的缩写 ,从系统级别提供的一个易用地多线程类库,具有运行时的特点,能充分利用多核心硬件。GCD的API接口为C语言的函数,函数参数中多数有Block,关于Block的使用参看这里,为我们提供强大的“接口”,对于GCD的使用参见本文

NSOperation与Queue

NSOperation是一个抽象类,它封装了线程的细节实现,我们可以通过子类化该对象,加上NSQueue来同面向对象的思维,管理多线程程序。具体可参看这里:一个基于NSOperation的多线程网络访问的项目。

NSThread

NSThread是一个控制线程执行的对象,它不如NSOperation抽象,通过它我们可以方便的得到一个线程,并控制它。但NSThread的线程之间的并发控制,是需要我们自己来控制的,可以通过NSCondition实现。

参看 iOS多线程编程之NSThread的使用

其他多线程

在Cocoa的框架下,通知、Timer和异步函数等都有使用多线程,(待补充).

57. 在项目什么时候选择使用GCD,什么时候选择NSOperation?

答: 项目中使用NSOperation的优点是NSOperation是对线程的高度抽象,在项目中使用它,会使项目的程序结构更好,子类化NSOperation的设计思路,是具有面向对象的优点(复用、封装),使得实现是多线程支持,而接口简单,建议在复杂项目中使用。

项目中使用GCD的优点是GCD本身非常简单、易用,对于不复杂的多线程操作,会节省代码量,而Block参数的使用,会是代码更为易读,建议在简单项目中使用。

58. 什么是block

答: 对于闭包(block),有很多定义,其中闭包就是能够读取其它函数内部变量的函数,这个定义即接近本质又较好理解。对于刚接触Block的同学,会觉得有些绕,因为我们习惯写这样的程序main(){ funA();} funA(){funB();} funB(){…..}; 就是函数main调用函数A,函数A调用函数B… 函数们依次顺序执行,但现实中不全是这样的,例如项目经理M,手下有3个程序员A、B、C,当他给程序员A安排实现功能F1时,他并不等着A完成之后,再去安排B去实现F2,而是安排给A功能F1,B功能F2,C功能F3,然后可能去写技术文档,而当A遇到问题时,他会来找项目经理M,当B做完时,会通知M,这就是一个异步执行的例子。在这种情形下,Block便可大显身手,因为在项目经理M,给A安排工作时,同时会告诉A若果遇到困难,如何能找到他报告问题(例如打他手机号),这就是项目经理M给A的一个回调接口,要回掉的操作,比如接到电话,百度查询后,返回网页内容给A,这就是一个Block,在M交待工作时,已经定义好,并且取得了F1的任务号(局部变量),却是在当A遇到问题时,才调用执行,跨函数在项目经理M查询百度,获得结果后回调该block。

59. block 实现原理

答: Objective-C是对C语言的扩展,block的实现是基于指针和函数指针。

从计算语言的发展,最早的goto,高级语言的指针,到面向对象语言的block,从机器的思维,一步步接近人的思维,以方便开发人员更为高效、直接的描述出现实的逻辑(需求)。

使用实例

cocoaTouch框架下动画效果的Block的调用

使用typed声明block

1

2typedef?void(^didFinishBlock)?(NSObject?*ob);

这就声明了一个didFinishBlock类型的block,

然后便可用

1

@property?(nonatomic,copy)?didFinishBlock?finishBlock;

声明一个blokc对象,注意对象属性设置为copy,接到block 参数时,便会自动复制一份。

__block是一种特殊类型,

使用该关键字声明的局部变量,可以被block所改变,并且其在原函数中的值会被改变。

60.关于block

答: 面试时,面试官会先问一些,是否了解block,是否使用过block,这些问题相当于开场白,往往是下面一系列问题的开始,所以一定要如实根据自己的情况回答。

1). 使用block和使用delegate完成委托模式有什么优点?

首先要了解什么是委托模式,委托模式在iOS中大量应用,其在设计模式中是适配器模式中的对象适配器,Objective-C中使用id类型指向一切对象,使委托模式更为简洁。了解委托模式的细节:

iOS设计模式—-委托模式

使用block实现委托模式,其优点是回调的block代码块定义在委托对象函数内部,使代码更为紧凑;

适配对象不再需要实现具体某个protocol,代码更为简洁。

2). 多线程与block

GCD与Block

使用 dispatch_async 系列方法,可以以指定的方式执行block

GCD编程实例

dispatch_async的完整定义

1

2

3void?dispatch_async(

dispatch_queue_t?queue,

dispatch_block_t?block);

功能:在指定的队列里提交一个异步执行的block,不阻塞当前线程

通过queue来控制block执行的线程。主线程执行前文定义的 finishBlock对象

1

dispatch_async(dispatch_get_main_queue(),^(void){finishBlock();});

62.谈谈Object-C的内存管理方式及过程?

答: 1).当你使用new,alloc和copy方法创建一个对象时,该对象的保留计数器值为1.当你不再使用该对象时,你要负责向该对象发送一条release或autorelease消息.这样,该对象将在使用寿命结束时被销毁.

2).当你通过任何其他方法获得一个对象时,则假设该对象的保留计数器值为1,而且已经被设置为自动释放,你不需要执行任何操作来确保该对象被清理.如果你打算在一段时间内拥有该对象,则需要保留它并确保在操作完成时释放它.

3).如果你保留了某个对象,你需要(最终)释放或自动释放该对象.必须保持retain方法和release方法的使用次数相等.

63.Object-C有私有方法吗?私有变量呢?

答: objective-c – 类里面的方法只有两种, 静态方法和实例方法. 这似乎就不是完整的面向对象了,按照OO的原则就是一个对象只暴露有用的东西. 如果没有了私有方法的话, 对于一些小范围的代码重用就不那么顺手了. 在类里面声名一个私有方法

@interface?Controller?:?NSObject?{?NSString?*something;?}

+?(void)thisIsAStaticMethod;

–?(void)thisIsAnInstanceMethod;

@end

@interface?Controller?(private)?-

(void)thisIsAPrivateMethod;

@end

@private可以用来修饰私有变量

在Objective‐C中,所有实例变量默认都是私有的,所有实例方法默认都是公有的

64.Object-C有多继承吗?没有的话用什么代替?cocoa 中所有的类都是NSObject 的子类

答: 多继承在这里是用protocol 委托代理 来实现的

你不用去考虑繁琐的多继承 ,虚基类的概念.

ood的多态特性 在 obj-c 中通过委托来实现.

65.内存管理 Autorelease、retain、copy、assign的set方法和含义?

答: 1).你初始化(alloc/init)的对象,你需要释放(release)它。例如:

NSMutableArray aArray = [[NSArray alloc] init]; 后,需要 [aArray release];

2).你retain或copy的,你需要释放它。例如:

[aArray retain] 后,需要 [aArray release];

3).被传递(assign)的对象,你需要斟酌的retain和release。例如:

obj2 = [[obj1 someMethod] autorelease];

对象2接收对象1的一个自动释放的值,或传递一个基本数据类型(NSInteger,NSString)时:你或希望将对象2进行retain,以防止它在被使用之前就被自动释放掉。但是在retain后,一定要在适当的时候进行释放。

关于索引计数(Reference Counting)的问题

retain值 = 索引计数(Reference Counting)

NSArray对象会retain(retain值加一)任何数组中的对象。当NSArray被卸载(dealloc)的时候,所有数组中的对象会 被 执行一次释放(retain值减一)。不仅仅是NSArray,任何收集类(Collection Classes)都执行类似操作。例如 NSDictionary,甚至UINavigationController。

Alloc/init建立的对象,索引计数为1。无需将其再次retain。

[NSArray array]和[NSDate date]等“方法”建立一个索引计数为1的对象,但是也是一个自动释放对象。所以是本地临时对象,那么无所谓了。如果是打算在全Class中使用的变量(iVar),则必须retain它。

缺省的类方法返回值都被执行了“自动释放”方法。(*如上中的NSArray)

在类中的卸载方法“dealloc”中,release所有未被平衡的NS对象。(*所有未被autorelease,而retain值为1的)

66. C和obj-c 如何混用

答: 1).obj-c的编译器处理后缀为m的文件时,可以识别obj-c和c的代码,处理mm文件可以识别obj-c,c,c++代码,但cpp文件必须只能用c/c++代码,而且cpp文件include的头文件中,也不能出现obj-c的代码,因为cpp只是cpp

2).在mm文件中混用cpp直接使用即可,所以obj-c混cpp不是问题

3).在cpp中混用obj-c其实就是使用obj-c编写的模块是我们想要的。

如果模块以类实现,那么要按照cpp class的标准写类的定义,头文件中不能出现obj-c的东西,包括#import cocoa的。实现文件中,即类的实现代码中可以使用obj-c的东西,可以import,只是后缀是mm。

如果模块以函数实现,那么头文件要按c的格式声明函数,实现文件中,c++函数内部可以用obj-c,但后缀还是mm或m。

总结:只要cpp文件和cpp include的文件中不包含obj-c的东西就可以用了,cpp混用obj-c的关键是使用接口,而不能直接使用 实现代 码,实际上cpp混用的是obj-c编译后的o文件,这个东西其实是无差别的,所以可以用。obj-c的编译器支持cpp

67. Objective-C堆和栈的区别?

答: 管理方式:对于栈来讲,是由编译器自动管理,无需我们手工控制;对于堆来说,释放工作由程序员控制,容易产生memory leak。

申请大小:

栈:在Windows下,栈是向低地址扩展的数据结构,是一块连续的内存的区域。这句话的意思是栈顶的地址和栈的最大容量是系统预先规定好的,在 WINDOWS下,栈的大小是2M(也有的说是1M,总之是一个编译时就确定的常数),如果申请的空间超过栈的剩余空间时,将提示overflow。因 此,能从栈获得的空间较小。

堆:堆是向高地址扩展的数据结构,是不连续的内存区域。这是由于系统是用链表来存储的空闲内存地址的,自然是不连续的,而链表的遍历方向是由低地址向高地址。堆的大小受限于计算机系统中有效的虚拟内存。由此可见,堆获得的空间比较灵活,也比较大。

碎片问题:对于堆来讲,频繁的new/delete势必会造成内存空间的不连续,从而造成大量的碎片,使程序效率降低。对于栈来讲,则不会存在这个问题,因为栈是先进后出的队列,他们是如此的一一对应,以至于永远都不可能有一个内存块从栈中间弹出

分配方式:堆都是动态分配的,没有静态分配的堆。栈有2种分配方式:静态分配和动态分配。静态分配是编译器完成的,比如局部变量的分配。动态分配由alloca函数进行分配,但是栈的动态分配和堆是不同的,他的动态分配是由编译器进行释放,无需我们手工实现。

分配效率:栈是机器系统提供的数据结构,计算机会在底层对栈提供支持:分配专门的寄存器存放栈的地址,压栈出栈都有专门的指令执行,这就决定了栈的效率比较高。堆则是C/C++函数库提供的,它的机制是很复杂的。

68. ViewController的didReceiveMemoryWarning怎么被调用:

答:[supper didReceiveMemoryWarning];

69.什么时候用delegate,什么时候用Notification?

答: delegate针对one-to-one关系,用于sender接受到reciever的某个功能反馈值。

notification针对one-to-one/many/none,reciver,用于通知多个object某个事件。

70.用预处理指令#define声明一个常数,用以表明1年中有多少秒(忽略闰年问题)

答:

#define SECONDS_PER_YEAR (60 * 60 * 24 * 365)UL

我在这想看到几件事情:

#define 语法的基本知识(例如:不能以分号结束,括号的使用,等等)

懂得预处理器将为你计算常数表达式的值,因此,直接写出你是如何计算一年中有多少秒而不是计算出实际的值,是更清晰而没有代价的。

意识到这个表达式将使一个16位机的整型数溢出-因此要用到长整型符号L,告诉编译器这个常数是的长整型数。

如果你在你的表达式中用到UL(表示无符号长整型),那么你有了一个好的起点。记住,第一印象很重要。

71.写一个”标准"宏MIN ,这个宏输入两个参数并返回较小的一个。

答:

1

#define?MIN(A,B)?((A)?<=?(B)???(A)?:?(B))

这个测试是为下面的目的而设的:

标识#define在宏中应用的基本知识。这是很重要的,因为直到嵌入(inline)操作符变为标准C的一部分,宏是方便产生嵌入代码的唯一方

法,

对于嵌入式系统来说,为了能达到要求的性能,嵌入代码经常是必须的方法。

三重条件操作符的知识。这个操作符存在C语言中的原因是它使得编译器能产生比 if-then-else 更优化的代码,了解这个用法是很重要的。

懂得在宏中小心地把参数用括号括起来

我也用这个问题开始讨论宏的副作用,例如:当你写下面的代码时会发生什么事?

1

least?=?MIN(*p++,?b);

结果是:

1

((*p++)?<=?(b)???(*p++)?:?(*p++))

这个表达式会产生副作用,指针p会作三次++自增操作。

72.关键字const有什么含意?修饰类呢?static的作用,用于类呢?还有extern c的作用

答:

const 意味着"只读",下面的声明都是什么意思?

const?int?a;

int?const?a;

const?int?*a;

int?*?const?a;

int?const?*?a?const;

前两个的作用是一样,a是一个常整型数。

第三个意味着a是一个指向常整型数的指针(也就是,整型数是不可修改的,但指针可以)。

第四个意思a是一个指向整型数的常指针(也就是说,指针指向的整型数是可以修改的,但指针是不可修改的)。

最后一个意味着a是一个指向常整型数的常指针(也就是说,指针指向的整型数是不可修改的,同时指针也是不可修改的)。

结论:

关键字const的作用是为给读你代码的人传达非常有用的信息,实际上,声明一个参数为常量是为了告诉了用户这个参数的应用目的。

如果你曾花很多时间清理其它人留下的垃圾,你就会很快学会感谢这点多余的信息。(当然,懂得用const的程序员很少会留下的垃圾让别人来清理的) ?通过给优化器一些附加的信息,使用关键字const也许能产生更紧凑的代码。合理地使用关键字const可以使编译器很自然地保护那些不希望被改变的参数,防止其被无意的代码修改。简而言之,这样可以减少bug的出现。

1).欲阻止一个变量被改变,可以使用 const 关键字。在定义该 const 变量时,通常需要对它进行初

始化,因为以后就没有机会再去改变它了;

2).对指针来说,可以指定指针本身为 const,也可以指定指针所指的数据为 const,或二者同时指

定为 const;

3).在一个函数声明中,const 可以修饰形参,表明它是一个输入参数,在函数内部不能改变其值;

4).对于类的成员函数,若指定其为 const 类型,则表明其是一个常函数,不能修改类的成员变量;

5).对于类的成员函数,有时候必须指定其返回值为 const 类型,以使得其返回值不为“左值”。

73. 关键字volatile有什么含意?并给出三个不同的例子。

答:一个定义为 volatile的变量是说这变量可能会被意想不到地改变,这样,编译器就不会去假设这个变量的值了。精确地说就是,优化器在用到这个变量时必须每次都小心地重新读取这个变量的值,而不是使用保存在寄存器里的备份。

下面是volatile变量的几个例子:

并行设备的硬件寄存器(如:状态寄存器)

一个中断服务子程序中会访问到的非自动变量(Non-automatic variables)

多线程应用中被几个任务共享的变量

74. 一个参数既可以是const还可以是volatile吗? 一个指针可以是volatile 吗?解释为什么。

答:1).是的。一个例子是只读的状态寄存器。它是volatile因为它可能被意想不到地改变。它是const因为程序不应该试图去修改它。

2).是的。尽管这并不很常见。一个例子是当一个中服务子程序修该一个指向一个buffer的指针时。

75 . static 关键字的作用:

答:

1).函数体内 static 变量的作用范围为该函数体,不同于 auto 变量,该变量的内存只被分配一次,

因此其值在下次调用时仍维持上次的值;

2).在模块内的 static 全局变量可以被模块内所用函数访问,但不能被模块外其它函数访问;

3).在模块内的 static 函数只可被这一模块内的其它函数调用,这个函数的使用范围被限制在声明

它的模块内;

4).在类中的 static 成员变量属于整个类所拥有,对类的所有对象只有一份拷贝;

5).在类中的 static 成员函数属于整个类所拥有,这个函数不接收 this 指针,因而只能访问类的static 成员变量。

76. 线程与进程的区别和联系?

答:

1). 进程和线程都是由操作系统所体会的程序运行的基本单元,系统利用该基本单元实现系统对应用的并发性

2). 进程和线程的主要差别在于它们是不同的操作系统资源管理方式。

3). 进程有独立的地址空间,一个进程崩溃后,在保护模式下不会对其它进程产生影响,而线程只是一个进程中的不同执行路径。

4.)线程有自己的堆栈和局部变量,但线程之间没有单独的地址空间,一个线程死掉就等于整个进程死掉。所以多进程的程序要比多线程的程序健壮,但在进程切换时,耗费资源较大,效率要差一些。

5). 但对于一些要求同时进行并且又要共享某些变量的并发操作,只能用线程,不能用进程。

77. 列举几种进程的同步机制,并比较其优缺点。

答: 原子操作 ?信号量机制 ? ?自旋锁 ? ?管程,会合,分布式系统

78. 进程之间通信的途径

答:共享存储系统消息传递系统管道:以文件系统为基础

79. 进程死锁的原因

答:资源竞争及进程推进顺序非法

80. 死锁的4个必要条件

答:互斥、请求保持、不可剥夺、环路

81. 死锁的处理

答:鸵鸟策略、预防策略、避免策略、检测与解除死锁

82. cocoa touch框架

答:iPhone OS 应用程序的基础 Cocoa Touch 框架重用了许多 Mac 系统的成熟模式,但是它更多地专注于触摸的接口和优化。

UIKit 为您提供了在 iPhone OS 上实现图形,事件驱动程序的基本工具,其建立在和 Mac OS X 中一样的 Foundation 框架上,包括文件处理,网络,字符串操作等。

Cocoa Touch 具有和 iPhone 用户接口一致的特殊设计。有了 UIKit,您可以使用 iPhone OS 上的独特的图形接口控件,按钮,以及全屏视图的功能,您还可以使用加速仪和多点触摸手势来控制您的应用。

各色俱全的框架 除了UIKit 外,Cocoa Touch 包含了创建世界一流 iPhone 应用程序需要的所有框架,从三维图形,到专业音效,甚至提供设备访问 API 以控制摄像头,或通过 GPS 获知当前位置。

Cocoa Touch 既包含只需要几行代码就可以完成全部任务的强大的 Objective-C 框架,也在需要时提供基础的 C 语言 API 来直接访问系统。这些框架包括:

Core Animation:通过 Core Animation,您就可以通过一个基于组合独立图层的简单的编程模型来创建丰富的用户体验。

Core Audio:Core Audio 是播放,处理和录制音频的专业技术,能够轻松为您的应用程序添加强大的音频功能。

Core Data:提供了一个面向对象的数据管理解决方案,它易于使用和理解,甚至可处理任何应用或大或小的数据模型。

功能列表:框架分类

下面是 Cocoa Touch 中一小部分可用的框架:

音频和视频:Core Audio ,OpenAL ,Media Library ,AV Foundation

数据管理 :Core Data ,SQLite

图形和动画 :Core Animation ,OpenGL ES ,Quartz 2D

网络:Bonjour ,WebKit ,BSD Sockets

用户应用:Address Book ,Core Location ,Map Kit ,Store Kit

83. 自动释放池是什么,如何工作

答:当您向一个对象发送一个autorelease消息时,Cocoa就会将该对象的一个引用放入到最新的自动释放.它仍然是个正当的对象,因此自动释放池定义的作用域内的其它对象可以向它发送消息。当程序执行到作用域结束的位置时,自动释放池就会被释放,池中的所有对象也就被释放。

84. Objective-C的优缺点。

答:objc优点:

1). ?Cateogies

2). ?Posing

3). 动态识别

4).指标计算

5).弹性讯息传递

6).不是一个过度复杂的 C 衍生语言

7).Objective-C 与 C++ 可混合编程

objc缺点:

1).不支援命名空间

2).不支持运算符重载

3).不支持多重继承

4).使用动态运行时类型,所有的方法都是函数调用,所以很多编译时优化方法都用不到。(如内联函数等),性能低劣。

85. sprintf,strcpy,memcpy使用上有什么要注意的地方。

答:

1). sprintf是格式化函数。将一段数据通过特定的格式,格式化到一个字符串缓冲区中去。sprintf格式化的函数的长度不可控,有可能格式化后的字符串会超出缓冲区的大小,造成溢出。

2).strcpy是一个字符串拷贝的函数,它的函数原型为strcpy(char *dst, const char *src

将src开始的一段字符串拷贝到dst开始的内存中去,结束的标志符号为 ‘\0',由于拷贝的长度不是由我们自己控制的,所以这个字符串拷贝很容易出错。

3). memcpy是具备字符串拷贝功能的函数,这是一个内存拷贝函数,它的函数原型为memcpy(char *dst, const char* src, unsigned int len);将长度为len的一段内存,从src拷贝到dst中去,这个函数的长度可控。但是会有内存叠加的问题。

86. readwrite,readonly,assign,retain,copy,nonatomic 属性的作用

答:@property是一个属性访问声明,扩号内支持以下几个属性:

1).getter=getterName,setter=setterName,设置setter与 getter的方法名

2).readwrite,readonly,设置可供访问级别

2).assign,setter方法直接赋值,不进行任何retain操作,为了解决原类型与环循引用问题

3).retain,setter方法对参数进行release旧值再retain新值,所有实现都是这个顺序(CC上有相关资料)

4).copy,setter方法进行Copy操作,与retain处理流程一样,先旧值release,再 Copy出新的对象,retainCount为1。这是为了减少对上下文的依赖而引入的机制。

5).nonatomic,非原子性访问,不加同步,多线程并发访问会提高性能。注意,如果不加此属性,则默认是两个访问方法都为原子型事务访问。锁被加到所属对象实例级。

87. http和scoket通信的区别。

答: http是客户端用http协议进行请求,发送请求时候需要封装http请求头,并绑定请求的数据,服务器一般有web服务器配合(当然也非绝对)。 http请求方式为客户端主动发起请求,服务器才能给响应,一次请求完毕后则断开连接,以节省资源。服务器不能主动给客户端响应(除非采取http长连接 技术)。iphone主要使用类是NSUrlConnection。

scoket是客户端跟服务器直接使用socket“套接字”进行连接,并没有规定连接后断开,所以客户端和服务器可以保持连接通道,双方 都可以主动发送数据。一般在游戏开发或股票开发这种要求即时性很强并且保持发送数据量比较大的场合使用。主要使用类是CFSocketRef。

88. TCP和UDP的区别

答: TCP全称是Transmission Control Protocol,中文名为传输控制协议,它可以提供可靠的、面向连接的网络数据传递服务。传输控制协议主要包含下列任务和功能:

* 确保IP数据报的成功传递。

* 对程序发送的大块数据进行分段和重组。

* 确保正确排序及按顺序传递分段的数据。

* 通过计算校验和,进行传输数据的完整性检查。

TCP提供的是面向连接的、可靠的数据流传输,而UDP提供的是非面向连接的、不可靠的数据流传输。

简单的说,TCP注重数据安全,而UDP数据传输快点,但安全性一般

89. 你了解svn,cvs等版本控制工具么?

答: 版本控制 svn,cvs 是两种版控制的器,需要配套相关的svn,cvs服务器。

scm是xcode里配置版本控制的地方。版本控制的原理就是a和b同时开发一个项目,a写完当天的代码之后把代码提交给服务器,b要做的时候先从服务器得到最新版本,就可以接着做。 如果a和b都要提交给服务器,并且同时修改了同一个方法,就会产生代码冲突,如果a先提交,那么b提交时,服务器可以提示冲突的代码,b可以清晰的看到,并做出相应的修改或融合后再提交到服务器。

90. 什么是push。

答: 客户端程序留下后门端口,客户端总是监听针对这个后门的请求,于是 服务器可以主动像这个端口推送消息。

91. 静态链接库

答:此为.a文件,相当于java里的jar包,把一些类编译到一个包中,在不同的工程中如果导入此文件就可以使用里面的类,具体使用依然是#import “ xx.h”。

92. fmmpeg框架

答: 音视频编解码框架,内部使用UDP协议针对流媒体开发,内部开辟了六个端口来接受流媒体数据,完成快速接受之目的。

93. fmdb框架

答:数据库框架,对sqllite的数据操作进行了封装,使用着可把精力都放在sql语句上面。

94. 320框架

答: ui框架,导入320工程作为框架包如同添加一个普通框架一样。cover(open) ?flower框架 (2d 仿射技术),内部核心类是CATransform3D.

94. 什么是沙盒模型?哪些操作是属于私有api范畴?

答:某个iphone工程进行文件操作有此工程对应的指定的位置,不能逾越。

iphone沙箱模型的有四个文件夹documents,tmp,app,Library,永久数据存储一般放documents文件夹,得到模拟器的路径的可使用NSHomeDirectory()方法。Nsuserdefaults保存的文件在tmp文件夹里。

95. 在一个对象的方法里面:self.name= “object”;和 name =”object” 有什么不同吗?

答:self.name =”object”:会调用对象的setName()方法;

name = “object”:会直接把object赋值给当前对象的name属性。

96. 请简要说明viewDidLoad和viewDidUnload何时调用

答:viewDidLoad在view从nib文件初始化时调用,loadView在controller的view为nil时调用。此方法在编程实现view时调用,view控制器默认会注册memory warning notification,当view controller的任何view没有用的时候,viewDidUnload会被调用,在这里实现将retain的view release,如果是retain的IBOutlet view 属性则不要在这里release,IBOutlet会负责release 。

97. 简述内存分区情况

答:

1).代码区:存放函数二进制代码

2).数据区:系统运行时申请内存并初始化,系统退出时由系统释放。存放全局变量、静态变量、常量

3).堆区:通过malloc等函数或new等操作符动态申请得到,需程序员手动申请和释放

4).栈区:函数模块内申请,函数结束时由系统自动释放。存放局部变量、函数参数

98. 队列和栈有什么区别:

答:队列和栈是两种不同的数据容器。从”数据结构”的角度看,它们都是线性结构,即数据元素之间的关系相同。

队列是一种先进先出的数据结构,它在两端进行操作,一端进行入队列操作,一端进行出列队操作。

栈是一种先进后出的数据结构,它只能在栈顶进行操作,入栈和出栈都在栈顶操作。

99. HTTP协议中,POST和GET的区别是什么?

答:

1).GET 方法

GET 方法提交数据不安全,数据置于请求行,客户端地址栏可见;

GET 方法提交的数据大小有限

GET 方法不可以设置书签

2).POST 方法

POST 方法提交数据安全,数据置于消息主体内,客户端不可见

POST 方法提交的数据大小没有限制

POST 方法可以设置书签

100. ?iOS的系统架构

答: iOS的系统架构分为( 核心操作系统层 theCore OS layer )、( 核心服务层theCore Services layer )、( 媒体层 theMedia layer )和( Cocoa 界面服务层 the Cocoa Touch layer )四个层次。

101. ?控件主要响应3种事件

答:1). 基于触摸的事件 ; ?2). 基于值的事件 ; ?3).基于编辑的事件。

102. ?xib文件的构成分为哪3个图标?都具有什么功能。

答: File’s Owner 是所有 nib 文件中的每个图标,它表示从磁盘加载 nib 文件的对象;

First Responder 就是用户当前正在与之交互的对象;

View 显示用户界面;完成用户交互;是 UIView 类或其子类。

103. ?简述视图控件器的生命周期。

答: loadView 尽管不直接调用该方法,如多手动创建自己的视图,那么应该覆盖这个方法并将它们赋值给试图控制器的 view 属性。

viewDidLoad 只有在视图控制器将其视图载入到内存之后才调用该方法,这是执行任何其他初始化操作的入口。

viewDidUnload 当试图控制器从内存释放自己的方法的时候调用,用于清楚那些可能已经在试图控制器中创建的对象。

viewVillAppear 当试图将要添加到窗口中并且还不可见的时候或者上层视图移出图层后本视图变成顶级视图时调用该方法,用于执行诸如改变视图方向等的操作。实现该方法时确保调用 [super viewWillAppear:

viewDidAppear 当视图添加到窗口中以后或者上层视图移出图层后本视图变成顶级视图时调用,用于放置那些需要在视图显示后执行的代码。确保调用 [super viewDidAppear:] 。

104. ?动画有基本类型有哪几种;表视图有哪几种基本样式。

答:动画有两种基本类型:隐式动画和显式动画。

105. ?实现简单的表格显示需要设置UITableView的什么属性、实现什么协议?

答:实现简单的表格显示需要设置 UITableView 的 dataSource 和 delegate 属性,实现UITableViewDataSource 和 UITableViewDelegate 协议。

106. ?Cocoa Touch提供了哪几种Core Animation过渡类型?

答: Cocoa Touch 提供了 4 种 Core Animation 过渡类型,分别为:交叉淡化、推挤、显示和覆盖。

107. ?UIView与CLayer有什么区别?

答:

1).UIView 是 iOS 系统中界面元素的基础,所有的界面元素都是继承自它。它本身完全是由 CoreAnimation 来实现的。它真正的绘图部分,是由一个 CALayer 类来管理。 UIView 本身更像是一个 CALayer 的管理器,访问它的跟绘图和跟坐标有关的属性。

2).UIView 有个重要属性 layer ,可以返回它的主 CALayer 实例。

3).UIView 的 CALayer 类似 UIView 的子 View 树形结构,也可以向它的 layer 上添加子layer ,来完成某些特殊的表示。即 CALayer 层是可以嵌套的。

4).UIView 的 layer 树形在系统内部,被维护着三份 copy 。分别是逻辑树,这里是代码可以操纵的;动画树,是一个中间层,系统就在这一层上更改属性,进行各种渲染操作;显示树,其内容就是当前正被显示在屏幕上得内容。

5).动画的运作:对 UIView 的 subLayer (非主 Layer )属性进行更改,系统将自动进行动画生成,动画持续时间的缺省值似乎是 0.5 秒。

6).坐标系统: CALayer 的坐标系统比 UIView 多了一个 anchorPoint 属性,使用CGPoint 结构表示,值域是 0~1 ,是个比例值。这个点是各种图形变换的坐标原点,同时会更改 layer 的 position 的位置,它的缺省值是 {0.5,0.5} ,即在 layer 的中央。

7).渲染:当更新层,改变不能立即显示在屏幕上。当所有的层都准备好时,可以调用setNeedsDisplay 方法来重绘显示。

8).变换:要在一个层中添加一个 3D 或仿射变换,可以分别设置层的 transform 或affineTransform 属性。

9).变形: Quartz Core 的渲染能力,使二维图像可以被自由操纵,就好像是三维的。图像可以在一个三维坐标系中以任意角度被旋转,缩放和倾斜。 CATransform3D 的一套方法提供了一些魔术般的变换效果。

108. Quatrz 2D的绘图功能的三个核心概念是什么并简述其作用。

答:上下文:主要用于描述图形写入哪里;

路径:是在图层上绘制的内容;

状态:用于保存配置变换的值、填充和轮廓, alpha 值等。

109. ?iPhone OS主要提供了几种播放音频的方法?

答: SystemSound Services

AVAudioPlayer 类

Audio Queue Services

OpenAL

110. ?使用AVAudioPlayer类调用哪个框架、使用步骤?

答: AVFoundation.framework

步骤:配置 AVAudioPlayer 对象;

实现 AVAudioPlayer 类的委托方法;

控制 AVAudioPlayer 类的对象;

监控音量水平;

回放进度和拖拽播放。

111. ?有哪几种手势通知方法、写清楚方法名?

答:

-(void)touchesBegan:(NSSet*)touchedwithEvent:(UIEvent*)event;

-(void)touchesMoved:(NSSet*)touched withEvent:(UIEvent*)event;

-(void)touchesEnded:(NSSet*)touchedwithEvent:(UIEvent*)event;

-(void)touchesCanceled:(NSSet*)touchedwithEvent:(UIEvent*)event;

112. ?CFSocket使用有哪几个步骤。

答:创建 Socket 的上下文;创建 Socket ;配置要访问的服务器信息;封装服务器信息;连接服务器;

113. ?Core Foundation中提供了哪几种操作Socket的方法?

答: CFNetwork 、 CFSocket 和 BSD Socket 。

114. ?解析XML文件有哪几种方式?

答:以 DOM 方式解析 XML 文件;以 SAX 方式解析 XML 文件;

115. ios 平台怎么做数据的持久化?coredata 和sqlite有无必然联系?coredata是一个关系型数据库吗?

答:iOS 中可以有四种持久化数据的方式:属性列表(plist)、对象归档、 SQLite3 和 Core Data; core data 可以使你以图形界面的方式快速的定义 app 的数据模型,同时在你的代码中容易获取到它。 coredata 提供了基础结构去处理常用的功能,例如保存,恢复,撤销和重做,允许你在 app 中继续创建新的任务。在使用 core data 的时候,你不用安装额外的数据库系统,因为 core data 使用内置的 sqlite 数据库。 core data 将你 app 的模型层放入到一组定义在内存中的数据对象。 coredata 会追踪这些对象的改变,同时可以根据需要做相反的改变,例如用户执行撤销命令。当 core data 在对你 app 数据的改变进行保存的时候, core data 会把这些数据归档,并永久性保存。 mac os x 中sqlite 库,它是一个轻量级功能强大的关系数据引擎,也很容易嵌入到应用程序。可以在多个平台使用, sqlite 是一个轻量级的嵌入式 sql 数据库编程。与 core data 框架不同的是, sqlite 是使用程序式的, sql 的主要的 API 来直接操作数据表。 Core Data 不是一个关系型数据库,也不是关系型数据库管理系统 (RDBMS) 。虽然 Core Dta 支持SQLite 作为一种存储类型,但它不能使用任意的 SQLite 数据库。 Core Data 在使用的过程种自己创建这个数据库。 Core Data 支持对一、对多的关系。

116. ?tableView 的重用机制?

答:UITableView 通过重用单元格来达到节省内存的目的: 通过为每个单元格指定一个重用标识符(reuseIdentifier),即指定了单元格的种类,以及当单元格滚出屏幕时,允许恢复单元格以便重用.对于不同种类的单元格使用不同的ID,对于简单的表格,一个标识符就够了.






进阶篇(加强

设计模式是什么? 你知道哪些设计模式,并简要叙述?

设计模式是一种编码经验,就是用比较成熟的逻辑去处理某一种类型的事情。

1). MVC模式:Model View Control,把模型 视图 控制器 层进行解耦合编写。

2). MVVM模式:Model View ViewModel 把模型 视图 业务逻辑 层进行解耦和编写。

3). 单例模式:通过static关键词,声明全局变量。在整个进程运行期间只会被赋值一次。

4). 观察者模式:KVO是典型的通知模式,观察某个属性的状态,状态发生变化时通知观察者。

5). 委托模式:代理+协议的组合。实现1对1的反向传值操作。

6). 工厂模式:通过一个类方法,批量的根据已有模板生产对象。

MVC 和 MVVM 的区别

1). MVVM是对胖模型进行的拆分,其本质是给控制器减负,将一些弱业务逻辑放到VM中去处理。

2). MVC是一切设计的基础,所有新的设计模式都是基于MVC进行的改进。

#import跟 #include 有什么区别,@class呢,#import<> 跟 #import””有什么区别?

答:

1). #import是Objective-C导入头文件的关键字,#include是C/C++导入头文件的关键字,使用#import头文件会自动只导入一次,不会重复导入。

2). @class告诉编译器某个类的声明,当执行时,才去查看类的实现文件,可以解决头文件的相互包含。

3). #import<>用来包含系统的头文件,#import””用来包含用户头文件。

frame 和 bounds 有什么不同?

frame指的是:该view在父view坐标系统中的位置和大小。(参照点是父view的坐标系统)

bounds指的是:该view在本身坐标系统中的位置和大小。(参照点是本身坐标系统)

Objective-C的类可以多重继承么?可以实现多个接口么?Category是什么?重写一个类的方式用继承好还是分类好?为什么?

答:Objective-C的类不可以多重继承;可以实现多个接口(协议);Category是类别;一般情况用分类好,用Category去重写类的方法,仅对本Category有效,不会影响到其他类与原有类的关系。

@property 的本质是什么?ivar、getter、setter 是如何生成并添加到这个类中的

@property 的本质是什么?

@property = ivar + getter + setter;

“属性” (property)有两大概念:ivar(实例变量)、getter+setter(存取方法)

“属性” (property)作为 Objective-C 的一项特性,主要的作用就在于封装对象中的数据。 Objective-C 对象通常会把其所需要的数据保存为各种实例变量。实例变量一般通过“存取方法”(access method)来访问。其中,“获取方法” (getter)用于读取变量值,而“设置方法” (setter)用于写入变量值。

@property中有哪些属性关键字?/ @property 后面可以有哪些修饰符?

属性可以拥有的特质分为四类:

1.原子性--- nonatomic 特质

2.读/写权限---readwrite(读写)、readonly (只读)

3.内存管理语义---assign、strong、 weak、unsafe_unretained、copy

4.方法名---getter= 、setter=

5.不常用的:nonnull,null_resettable,nullable

属性关键字 readwrite,readonly,assign,retain,copy,nonatomic 各是什么作用,在那种情况下用?

答:

1). readwrite 是可读可写特性。需要生成getter方法和setter方法。

2). readonly 是只读特性。只会生成getter方法,不会生成setter方法,不希望属性在类外改变。

3). assign 是赋值特性。setter方法将传入参数赋值给实例变量;仅设置变量时,assign用于基本数据类型。

4). retain(MRC)/strong(ARC) 表示持有特性。setter方法将传入参数先保留,再赋值,传入参数的retaincount会+1。

5). copy 表示拷贝特性。setter方法将传入对象复制一份,需要完全一份新的变量时。

6). nonatomic 非原子操作。决定编译器生成的setter和getter方法是否是原子操作,atomic表示多线程安全,一般使用nonatomic,效率高。

什么情况使用 weak 关键字,相比 assign 有什么不同?

1.在 ARC 中,在有可能出现循环引用的时候,往往要通过让其中一端使用 weak 来解决,比如: delegate 代理属性。

2.自身已经对它进行一次强引用,没有必要再强引用一次,此时也会使用 weak,自定义 IBOutlet 控件属性一般也使用 weak;当然,也可以使用strong。

IBOutlet连出来的视图属性为什么可以被设置成weak?

因为父控件的subViews数组已经对它有一个强引用。

不同点:

assign 可以用非 OC 对象,而 weak 必须用于 OC 对象。

weak 表明该属性定义了一种“非拥有关系”。在属性所指的对象销毁时,属性值会自动清空(nil)。

怎么用 copy 关键字?

用途:

1. NSString、NSArray、NSDictionary 等等经常使用copy关键字,是因为他们有对应的可变类型:NSMutableString、NSMutableArray、NSMutableDictionary;

2. block 也经常使用 copy 关键字。

说明:

block 使用 copy 是从 MRC 遗留下来的“传统”,在 MRC 中,方法内部的 block 是在栈区的,使用 copy 可以把它放到堆区.在 ARC 中写不写都行:对于 block 使用 copy 还是 strong 效果是一样的,但写上 copy 也无伤大雅,还能时刻提醒我们:编译器自动对 block 进行了 copy 操作。如果不写 copy ,该类的调用者有可能会忘记或者根本不知道“编译器会自动对 block 进行了 copy 操作”,他们有可能会在调用之前自行拷贝属性值。这种操作多余而低效。

用@property声明的 NSString / NSArray / NSDictionary 经常使用 copy 关键字,为什么?如果改用strong关键字,可能造成什么问题?

答:用 @property 声明 NSString、NSArray、NSDictionary 经常使用 copy 关键字,是因为他们有对应的可变类型:NSMutableString、NSMutableArray、NSMutableDictionary,他们之间可能进行赋值操作(就是把可变的赋值给不可变的),为确保对象中的字符串值不会无意间变动,应该在设置新属性值时拷贝一份。

1. 因为父类指针可以指向子类对象,使用 copy 的目的是为了让本对象的属性不受外界影响,使用 copy 无论给我传入是一个可变对象还是不可对象,我本身持有的就是一个不可变的副本。

2. 如果我们使用是 strong ,那么这个属性就有可能指向一个可变对象,如果这个可变对象在外部被修改了,那么会影响该属性。

//总结:使用copy的目的是,防止把可变类型的对象赋值给不可变类型的对象时,可变类型对象的值发送变化会无意间篡改不可变类型对象原来的值。

浅拷贝和深拷贝的区别?

答:

浅拷贝:只复制指向对象的指针,而不复制引用对象本身。

深拷贝:复制引用对象本身。内存中存在了两份独立对象本身,当修改A时,A_copy不变。

系统对象的 copy 与 mutableCopy 方法

不管是集合类对象(NSArray、NSDictionary、NSSet ... 之类的对象),还是非集合类对象(NSString, NSNumber ... 之类的对象),接收到copy和mutableCopy消息时,都遵循以下准则:

1. copy 返回的是不可变对象(immutableObject);如果用copy返回值调用mutable对象的方法就会crash。

2. mutableCopy 返回的是可变对象(mutableObject)。

一、非集合类对象的copy与mutableCopy

在非集合类对象中,对不可变对象进行copy操作,是指针复制,mutableCopy操作是内容复制;

对可变对象进行copy和mutableCopy都是内容复制。用代码简单表示如下:

NSString *str = @"hello word!";

NSString *strCopy = [str copy] // 指针复制,strCopy与str的地址一样

NSMutableString *strMCopy = [str mutableCopy] // 内容复制,strMCopy与str的地址不一样

NSMutableString *mutableStr = [NSMutableString stringWithString: @"hello word!"];

NSString *strCopy = [mutableStr copy] // 内容复制

NSMutableString *strMCopy = [mutableStr mutableCopy] // 内容复制

二、集合类对象的copy与mutableCopy (同上)

在集合类对象中,对不可变对象进行copy操作,是指针复制,mutableCopy操作是内容复制;

对可变对象进行copy和mutableCopy都是内容复制。但是:集合对象的内容复制仅限于对象本身,对集合内的对象元素仍然是指针复制。(即单层内容复制)

NSArray *arr = @[@[@"a", @"b"], @[@"c", @"d"];

NSArray *copyArr = [arr copy]; // 指针复制

NSMutableArray *mCopyArr = [arr mutableCopy]; //单层内容复制

NSMutableArray *array = [NSMutableArray arrayWithObjects:[NSMutableString stringWithString:@"a"],@"b",@"c",nil];

NSArray *copyArr = [mutableArr copy]; // 单层内容复制

NSMutableArray *mCopyArr = [mutableArr mutableCopy]; // 单层内容复制

【总结一句话】:

只有对不可变对象进行copy操作是指针复制(浅复制),其它情况都是内容复制(深复制)!

这个写法会出什么问题:@property (nonatomic, copy) NSMutableArray *arr;

问题:添加,删除,修改数组内的元素的时候,程序会因为找不到对应的方法而崩溃。

//如:-[__NSArrayI removeObjectAtIndex:]: unrecognized selector sent to instance 0x7fcd1bc30460

// copy后返回的是不可变对象(即 arr 是 NSArray 类型,NSArray 类型对象不能调用 NSMutableArray 类型对象的方法)

原因:是因为 copy 就是复制一个不可变 NSArray 的对象,不能对 NSArray 对象进行添加/修改。

如何让自己的类用 copy 修饰符?如何重写带 copy 关键字的 setter?

若想令自己所写的对象具有拷贝功能,则需实现 NSCopying 协议。如果自定义的对象分为可变版本与不可变版本,那么就要同时实现 NSCopying 与 NSMutableCopying 协议。

具体步骤:

1. 需声明该类遵从 NSCopying 协议

2. 实现 NSCopying 协议的方法。

// 该协议只有一个方法:

- (id)copyWithZone:(NSZone *)zone;

// 注意:使用 copy 修饰符,调用的是copy方法,其实真正需要实现的是 “copyWithZone” 方法。

写一个 setter 方法用于完成 @property (nonatomic, retain) NSString*name,写一个 setter 方法用于完成 @property (nonatomic, copy) NSString *name

答:

// retain

- (void)setName:(NSString *)str {

[str retain];

[_name release];

_name = str;

}

// copy

- (void)setName:(NSString *)str {

id t = [str copy];

[_name release];

_name = t;

}

@synthesize 和 @dynamic 分别有什么作用?

@property有两个对应的词,一个是@synthesize(合成实例变量),一个是@dynamic。

如果@synthesize和@dynamic都没有写,那么默认的就是 @synthesize var = _var;

// 在类的实现代码里通过 @synthesize 语法可以来指定实例变量的名字。(@synthesize var = _newVar;)

1. @synthesize 的语义是如果你没有手动实现setter方法和getter方法,那么编译器会自动为你加上这两个方法。

2. @dynamic 告诉编译器,属性的setter与getter方法由用户自己实现,不自动生成(如,@dynamic var)。

常见的 Objective-C 的数据类型有那些,和C的基本数据类型有什么区别?如:NSInteger和int

答:

Objective-C的数据类型有NSString,NSNumber,NSArray,NSMutableArray,NSData等等,这些都是class,创建后便是对象,而C语言的基本数据类型int,只是一定字节的内存空间,用于存放数值;NSInteger是基本数据类型,并不是NSNumber的子类,当然也不是NSObject的子类。NSInteger是基本数据类型Int或者Long的别名(NSInteger的定义typedef long NSInteger),它的区别在于,NSInteger会根据系统是32位还是64位来决定是本身是int还是long。

id 声明的对象有什么特性?

答:id 声明的对象具有运行时的特性,即可以指向任意类型的Objcetive-C的对象。

Objective-C 如何对内存管理的,说说你的看法和解决方法?

答:Objective-C的内存管理主要有三种方式ARC(自动内存计数)、手动内存计数、内存池。

1). 自动内存计数ARC:由Xcode自动在App编译阶段,在代码中添加内存管理代码。

2). 手动内存计数MRC:遵循内存谁申请、谁释放;谁添加,谁释放的原则。

3). 内存释放池Release Pool:把需要释放的内存统一放在一个池子中,当池子被抽干后(drain),池子中所有的内存空间也被自动释放掉。内存池的释放操作分为自动和手动。自动释放受runloop机制影响。

Objective-C 中创建线程的方法是什么?如果在主线程中执行代码,方法是什么?如果想延时执行代码、方法又是什么?

答:线程创建有三种方法:使用NSThread创建、使用GCD的dispatch、使用子类化的NSOperation,然后将其加入NSOperationQueue;在主线程执行代码,方法是performSelectorOnMainThread,如果想延时执行代码可以用performSelector:onThread:withObject:waitUntilDone:

Category(类别)、 Extension(扩展)和继承的区别

区别:

1. 分类有名字,类扩展没有分类名字,是一种特殊的分类。

2. 分类只能扩展方法(属性仅仅是声明,并没真正实现),类扩展可以扩展属性、成员变量和方法。

3. 继承可以增加,修改或者删除方法,并且可以增加属性。

我们说的OC是动态运行时语言是什么意思?

答:主要是将数据类型的确定由编译时,推迟到了运行时。简单来说, 运行时机制使我们直到运行时才去决定一个对象的类别,以及调用该类别对象指定方法。

为什么我们常见的delegate属性都用是week而不是retain/strong?

答:是为了防止delegate两端产生不必要的循环引用。

@property (nonatomic, weak) id delegate;

什么时候用delete,什么时候用Notification?

Delegate(委托模式):1对1的反向消息通知功能。

Notification(通知模式):只想要把消息发送出去,告知某些状态的变化。但是并不关心谁想要知道这个。

什么是 KVO 和 KVC?

1). KVC(Key-Value-Coding):键值编码 是一种通过字符串间接访问对象的方式(即给属性赋值)

举例说明:

stu.name = @"张三" // 点语法给属性赋值

[stu setValue:@"张三" forKey:@"name"]; // 通过字符串使用KVC方式给属性赋值

stu1.nameLabel.text = @"张三";

[stu1 setValue:@"张三" forKey:@"nameLabel.text"]; // 跨层赋值

2). KVO(key-Value-Observing):键值观察机制 他提供了观察某一属性变化的方法,极大的简化了代码。

KVO只能被KVC触发,包括使用setValue:forKey:方法和点语法。

// 通过下方方法为属性添加KVO观察

- (void)addObserver:(NSObject *)observer

forKeyPath:(NSString *)keyPath

options:(NSKeyValueObservingOptions)options

context:(nullable void *)context;

// 当被观察的属性发送变化时,会自动触发下方方法

- (void)observeValueForKeyPath:(NSString *)keyPath

ofObject:(id)object

change:(NSDictionary *)change

context:(void *)context{}

KVC 和 KVO 的 keyPath 可以是属性、实例变量、成员变量。

KVC的底层实现?

当一个对象调用setValue方法时,方法内部会做以下操作:

1). 检查是否存在相应的key的set方法,如果存在,就调用set方法。

2). 如果set方法不存在,就会查找与key相同名称并且带下划线的成员变量,如果有,则直接给成员变量属性赋值。

3). 如果没有找到_key,就会查找相同名称的属性key,如果有就直接赋值。

4). 如果还没有找到,则调用valueForUndefinedKey:和setValue:forUndefinedKey:方法。

这些方法的默认实现都是抛出异常,我们可以根据需要重写它们。

KVO的底层实现?

KVO基于runtime机制实现。

ViewController生命周期

按照执行顺序排列:

1. initWithCoder:通过nib文件初始化时触发。

2. awakeFromNib:nib文件被加载的时候,会发生一个awakeFromNib的消息到nib文件中的每个对象。

3. loadView:开始加载视图控制器自带的view。

4. viewDidLoad:视图控制器的view被加载完成。

5. viewWillAppear:视图控制器的view将要显示在window上。

6. updateViewConstraints:视图控制器的view开始更新AutoLayout约束。

7. viewWillLayoutSubviews:视图控制器的view将要更新内容视图的位置。

8. viewDidLayoutSubviews:视图控制器的view已经更新视图的位置。

9. viewDidAppear:视图控制器的view已经展示到window上。

10. viewWillDisappear:视图控制器的view将要从window上消失。

11. viewDidDisappear:视图控制器的view已经从window上消失。

方法和选择器有何不同?

selector是一个方法的名字,方法是一个组合体,包含了名字和实现。

你是否接触过OC中的反射机制?简单聊一下概念和使用

1). class反射

通过类名的字符串形式实例化对象。

Class class = NSClassFromString(@"student");

Student *stu = [[class alloc] init];

将类名变为字符串。

Class class =[Student class];

NSString *className = NSStringFromClass(class);

2). SEL的反射

通过方法的字符串形式实例化方法。

SEL selector = NSSelectorFromString(@"setName");

[stu performSelector:selector withObject:@"Mike"];

将方法变成字符串。

NSStringFromSelector(@selector*(setName:));

调用方法有两种方式:

1). 直接通过方法名来调用。[person show];

2). 间接的通过SEL数据来调用 SEL aaa = @selector(show); [person performSelector:aaa];

如何对iOS设备进行性能测试?

答: Profile-> Instruments ->Time Profiler

开发项目时你是怎么检查内存泄露?

1). 静态分析 analyze。

2). instruments工具里面有个leak可以动态分析。

什么是懒加载?

答:懒加载就是只在用到的时候才去初始化。也可以理解成延时加载。

我觉得最好也最简单的一个例子就是tableView中图片的加载显示了, 一个延时加载, 避免内存过高,一个异步加载,避免线程堵塞提高用户体验。

类变量的 @public,@protected,@private,@package 声明各有什么含义?

@public 任何地方都能访问;

@protected 该类和子类中访问,是默认的;

@private 只能在本类中访问;

@package 本包内使用,跨包不可以。

什么是谓词?

谓词就是通过NSPredicate给定的逻辑条件作为约束条件,完成对数据的筛选。

//定义谓词对象,谓词对象中包含了过滤条件(过滤条件比较多)

NSPredicate *predicate = [NSPredicate predicateWithFormat:@"age<%d",30];

//使用谓词条件过滤数组中的元素,过滤之后返回查询的结果

NSArray *array = [persons filteredArrayUsingPredicate:predicate];

isa指针问题

isa:是一个Class 类型的指针. 每个实例对象有个isa的指针,他指向对象的类,而Class里也有个isa的指针, 指向meteClass(元类)。元类保存了类方法的列表。当类方法被调 用时,先会从本身查找类方法的实现,如果没有,元类会向他父类查找该方法。同时注意的是:元类(meteClass)也是类,它也是对象。元类也有isa指针,它的isa指针最终指向的是一个根元类(root meteClass)。根元类的isa指针指向本身,这样形成了一个封闭的内循环。

如何访问并修改一个类的私有属性?

1). 一种是通过KVC获取。

2). 通过runtime访问并修改私有属性。

一个objc对象的isa的指针指向什么?有什么作用?

答:指向他的类对象,从而可以找到对象上的方法。

下面的代码输出什么?

@implementation Son : Father

- (id)init {

if (self = [super init]) {

NSLog(@"%@", NSStringFromClass([self class])); // Son

NSLog(@"%@", NSStringFromClass([super class])); // Son

}

return self;

}

@end

// 解析:

self 是类的隐藏参数,指向当前调用方法的这个类的实例。

super是一个Magic Keyword,它本质是一个编译器标示符,和self是指向的同一个消息接收者。

不同的是:super会告诉编译器,调用class这个方法时,要去父类的方法,而不是本类里的。

上面的例子不管调用[self class]还是[super class],接受消息的对象都是当前 Son *obj 这个对象。

写一个完整的代理,包括声明、实现

// 创建

@protocol MyDelagate

@required

-(void)eat:(NSString *)foodName;

@optional

-(void)run;

@end

//  声明 .h

@interface person: NSObject

@end

//  实现 .m

@implementation person

- (void)eat:(NSString *)foodName {

NSLog(@"吃:%@!", foodName);

}

- (void)run {

NSLog(@"run!");

}

@end

isKindOfClass、isMemberOfClass、selector作用分别是什么

isKindOfClass:作用是某个对象属于某个类型或者继承自某类型。

isMemberOfClass:某个对象确切属于某个类型。

selector:通过方法名,获取在内存中的函数的入口地址。

delegate 和 notification 的区别

1). 二者都用于传递消息,不同之处主要在于一个是一对一的,另一个是一对多的。

2). notification通过维护一个array,实现一对多消息的转发。

3). delegate需要两者之间必须建立联系,不然没法调用代理的方法;notification不需要两者之间有联系。

什么是block?

闭包(block):闭包就是获取其它函数局部变量的匿名函数。

block反向传值

在控制器间传值可以使用代理或者block,使用block相对来说简洁。

在前一个控制器的touchesBegan:方法内实现如下代码。

// OneViewController.m

TwoViewController *twoVC = [[TwoViewController alloc] init];

twoVC.valueBlcok = ^(NSString *str) {

NSLog(@"OneViewController拿到值:%@", str);

};

[self presentViewController:twoVC animated:YES completion:nil];

// TwoViewController.h  (在.h文件中声明一个block属性)

@property (nonatomic ,strong) void(^valueBlcok)(NSString *str);

// TwoViewController.m  (在.m文件中实现方法)

- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {

// 传值:调用block

if (_valueBlcok) {

_valueBlcok(@"123456");

}

}

block的注意点

1). 在block内部使用外部指针且会造成循环引用情况下,需要用__week修饰外部指针:

__weak typeof(self) weakSelf = self;

2). 在block内部如果调用了延时函数还使用弱指针会取不到该指针,因为已经被销毁了,需要在block内部再将弱指针重新强引用一下。

__strong typeof(self) strongSelf = weakSelf;

3). 如果需要在block内部改变外部栈区变量的话,需要在用__block修饰外部变量。

BAD_ACCESS在什么情况下出现?

答:这种问题在开发时经常遇到。原因是访问了野指针,比如访问已经释放对象的成员变量或者发消息、死循环等。

lldb(gdb)常用的控制台调试命令?

1). p 输出基本类型。是打印命令,需要指定类型。是print的简写

p (int)[[[self view] subviews] count]

2). po 打印对象,会调用对象description方法。是print-object的简写

po [self view]

3). expr 可以在调试时动态执行指定表达式,并将结果打印出来。常用于在调试过程中修改变量的值。

4). bt:打印调用堆栈,是thread backtrace的简写,加all可打印所有thread的堆栈

5). br l:是breakpoint list的简写

你一般是怎么用Instruments的?

Instruments里面工具很多,常用:

1). Time Profiler: 性能分析

2). Zombies:检查是否访问了僵尸对象,但是这个工具只能从上往下检查,不智能。

3). Allocations:用来检查内存,写算法的那批人也用这个来检查。

4). Leaks:检查内存,看是否有内存泄露。

iOS中常用的数据存储方式有哪些?

数据存储有四种方案:NSUserDefault、KeyChain、file、DB。

其中File有三种方式:plist、Archive(归档)

DB包括:SQLite、FMDB、CoreData

iOS的沙盒目录结构是怎样的?

沙盒结构:

1). Application:存放程序源文件,上架前经过数字签名,上架后不可修改。

2). Documents:常用目录,iCloud备份目录,存放数据。(这里不能存缓存文件,否则上架不被通过)

3). Library:

Caches:存放体积大又不需要备份的数据。(常用的缓存路径)

Preference:设置目录,iCloud会备份设置信息。

4). tmp:存放临时文件,不会被备份,而且这个文件下的数据有可能随时被清除的可能。

iOS多线程技术有哪几种方式?

答:pthread、NSThread、GCD、NSOperation

GCD 与 NSOperation 的区别:

GCD 和 NSOperation 都是用于实现多线程:

GCD 基于C语言的底层API,GCD主要与block结合使用,代码简洁高效。

NSOperation 属于Objective-C类,是基于GCD更高一层的封装。复杂任务一般用NSOperation实现。

写出使用GCD方式从子线程回到主线程的方法代码

答:dispatch_sync(dispatch_get_main_queue(), ^{ });

如何用GCD同步若干个异步调用?(如根据若干个url异步加载多张图片,然后在都下载完成后合成一张整图)

// 使用Dispatch Group追加block到Global Group Queue,这些block如果全部执行完毕,就会执行Main Dispatch Queue中的结束处理的block。

// 创建队列组

dispatch_group_t group = dispatch_group_create();

// 获取全局并发队列

dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);

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(), ^{

// 合并图片

});

dispatch_barrier_async(栅栏函数)的作用是什么?

函数定义:dispatch_barrier_async(dispatch_queue_t queue, dispatch_block_t block);

作用:

1.在它前面的任务执行结束后它才执行,它后面的任务要等它执行完成后才会开始执行。

2.避免数据竞争

// 1.创建并发队列

dispatch_queue_t queue = dispatch_queue_create("myQueue", DISPATCH_QUEUE_CONCURRENT);

// 2.向队列中添加任务

dispatch_async(queue, ^{  // 1.2是并行的

NSLog(@"任务1, %@",[NSThread currentThread]);

});

dispatch_async(queue, ^{

NSLog(@"任务2, %@",[NSThread currentThread]);

});

dispatch_barrier_async(queue, ^{

NSLog(@"任务 barrier, %@", [NSThread currentThread]);

});

dispatch_async(queue, ^{  // 这两个是同时执行的

NSLog(@"任务3, %@",[NSThread currentThread]);

});

dispatch_async(queue, ^{

NSLog(@"任务4, %@",[NSThread currentThread]);

});

// 输出结果: 任务1 任务2 ——》 任务 barrier ——》任务3 任务4

// 其中的任务1与任务2,任务3与任务4 由于是并行处理先后顺序不定。

以下代码运行结果如何?

- (void)viewDidLoad {

[super viewDidLoad];

NSLog(@"1");

dispatch_sync(dispatch_get_main_queue(), ^{

NSLog(@"2");

});

NSLog(@"3");

}

// 只输出:1。(主线程死锁)

什么是 RunLoop

从字面上讲就是运行循环,它内部就是do-while循环,在这个循环内部不断地处理各种任务。

一个线程对应一个RunLoop,基本作用就是保持程序的持续运行,处理app中的各种事件。通过runloop,有事运行,没事就休息,可以节省cpu资源,提高程序性能。

主线程的run loop默认是启动的。iOS的应用程序里面,程序启动后会有一个如下的main()函数

int main(int argc, char * argv[]) {

@autoreleasepool {

return UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class]));

}

}

什么是 Runtime

Runtime又叫运行时,是一套底层的C语言API,其为iOS内部的核心之一,我们平时编写的OC代码,底层都是基于它来实现的。

Runtime实现的机制是什么,怎么用,一般用于干嘛?

1). 使用时需要导入的头文件

2). Runtime 运行时机制,它是一套C语言库。

3). 实际上我们编写的所有OC代码,最终都是转成了runtime库的东西。

比如:

类转成了 Runtime 库里面的结构体等数据类型,

方法转成了 Runtime 库里面的C语言函数,

平时调方法都是转成了 objc_msgSend 函数(所以说OC有个消息发送机制)

// OC是动态语言,每个方法在运行时会被动态转为消息发送,即:objc_msgSend(receiver, selector)。

// [stu show];  在objc动态编译时,会被转意为:objc_msgSend(stu, @selector(show));

4). 因此,可以说 Runtime 是OC的底层实现,是OC的幕后执行者。

有了Runtime库,能做什么事情呢?

Runtime库里面包含了跟类、成员变量、方法相关的API。

比如:

(1)获取类里面的所有成员变量。

(2)为类动态添加成员变量。

(3)动态改变类的方法实现。

(4)为类动态添加新的方法等。

因此,有了Runtime,想怎么改就怎么改。

什么是 Method Swizzle(黑魔法),什么情况下会使用?

1). 在没有一个类的实现源码的情况下,想改变其中一个方法的实现,除了继承它重写、和借助类别重名方法暴力抢先之外,还有更加灵活的方法 Method Swizzle。

2). Method Swizzle 指的是改变一个已存在的选择器对应的实现的过程。OC中方法的调用能够在运行时通过改变,通过改变类的调度表中选择器到最终函数间的映射关系。

3). 在OC中调用一个方法,其实是向一个对象发送消息,查找消息的唯一依据是selector的名字。利用OC的动态特性,可以实现在运行时偷换selector对应的方法实现。

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

5). 我们可以利用 method_exchangeImplementations 来交换2个方法中的IMP。

6). 我们可以利用 class_replaceMethod 来修改类。

7). 我们可以利用 method_setImplementation 来直接设置某个方法的IMP。

8). 归根结底,都是偷换了selector的IMP。

_objc_msgForward 函数是做什么的,直接调用它将会发生什么?

答:_objc_msgForward是 IMP 类型,用于消息转发的:当向一个对象发送一条消息,但它并没有实现的时候,_objc_msgForward会尝试做消息转发。

什么是 TCP / UDP ?

TCP:传输控制协议。

UDP:用户数据协议。

TCP 是面向连接的,建立连接需要经历三次握手,是可靠的传输层协议。

UDP 是面向无连接的,数据传输是不可靠的,它只管发,不管收不收得到。

简单的说,TCP注重数据安全,而UDP数据传输快点,但安全性一般。

通信底层原理(OSI七层模型)

OSI采用了分层的结构化技术,共分七层:

物理层、数据链路层、网络层、传输层、会话层、表示层、应用层。

介绍一下XMPP?

XMPP是一种以XML为基础的开放式实时通信协议。

简单的说,XMPP就是一种协议,一种规定。就是说,在网络上传东西,XMM就是规定你上传大小的格式。

OC中创建线程的方法是什么?如果在主线程中执行代码,方法是什么?

// 创建线程的方法

- [NSThread detachNewThreadSelector:nil toTarget:nil withObject:nil]

- [self performSelectorInBackground:nil withObject:nil];

- [[NSThread alloc] initWithTarget:nil selector:nil object:nil];

- dispatch_async(dispatch_get_global_queue(0, 0), ^{});

- [[NSOperationQueue new] addOperation:nil];

// 主线程中执行代码的方法

- [self performSelectorOnMainThread:nil withObject:nil waitUntilDone:YES];

- dispatch_async(dispatch_get_main_queue(), ^{});

- [[NSOperationQueue mainQueue] addOperation:nil];

tableView的重用机制?

答:UITableView 通过重用单元格来达到节省内存的目的: 通过为每个单元格指定一个重用标识符,即指定了单元格的种类,当屏幕上的单元格滑出屏幕时,系统会把这个单元格添加到重用队列中,等待被重用,当有新单元格从屏幕外滑入屏幕内时,从重用队列中找看有没有可以重用的单元格,如果有,就拿过来用,如果没有就创建一个来使用。

用伪代码写一个线程安全的单例模式

static id _instance;

+ (id)allocWithZone:(struct _NSZone *)zone {

static dispatch_once_t onceToken;

dispatch_once(&onceToken, ^{

_instance = [super allocWithZone:zone];

});

return _instance;

}

+ (instancetype)sharedData {

static dispatch_once_t onceToken;

dispatch_once(&onceToken, ^{

_instance = [[self alloc] init];

});

return _instance;

}

- (id)copyWithZone:(NSZone *)zone {

return _instance;

}

如何实现视图的变形?

答:通过修改view的 transform 属性即可。

在手势对象基础类UIGestureRecognizer的常用子类手势类型中哪两个手势发生后,响应只会执行一次?

答:UITapGestureRecognizer,UISwipeGestureRecognizer是一次性手势,手势发生后,响应只会执行一次。

字符串常用方法:

NSString *str = @"abc*123";

NSArray *arr = [str componentsSeparatedByString:@"*"]; //以目标字符串把原字符串分割成两部分,存到数组中。@[@"abc", @"123"];

如何高性能的给 UIImageView 加个圆角?

不好的解决方案:使用下面的方式会强制Core Animation提前渲染屏幕的离屏绘制, 而离屏绘制就会给性能带来负面影响,会有卡顿的现象出现。

self.view.layer.cornerRadius = 5.0f;

self.view.layer.masksToBounds = YES;

正确的解决方案:使用绘图技术

- (UIImage *)circleImage {

// NO代表透明

UIGraphicsBeginImageContextWithOptions(self.size, NO, 0.0);

// 获得上下文

CGContextRef ctx = UIGraphicsGetCurrentContext();

// 添加一个圆

CGRect rect = CGRectMake(0, 0, self.size.width, self.size.height);

CGContextAddEllipseInRect(ctx, rect);

// 裁剪

CGContextClip(ctx);

// 将图片画上去

[self drawInRect:rect];

UIImage *image = UIGraphicsGetImageFromCurrentImageContext();

// 关闭上下文

UIGraphicsEndImageContext();

return image;

}

还有一种方案:使用了贝塞尔曲线"切割"个这个图片, 给UIImageView 添加了的圆角,其实也是通过绘图技术来实现的。

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

你是怎么封装一个view的

1). 可以通过纯代码或者xib的方式来封装子控件2). 建立一个跟view相关的模型,然后将模型数据传给view,通过模型上的数据给view的子控件赋值/** *  纯代码初始化控件时一定会走这个方法 */- (instancetype)initWithFrame:(CGRect)frame {    if(self = [super initWithFrame:frame]) {        [self setupUI];}

return self;

}

/**

*  通过xib初始化控件时一定会走这个方法

*/

- (id)initWithCoder:(NSCoder *)aDecoder {

if(self = [super initWithCoder:aDecoder]) {

[self setupUI];

}

return self;

}

- (void)setupUI {

// 初始化代码

}

HTTP协议中 POST 方法和 GET 方法有那些区别?

1. GET用于向服务器请求数据,POST用于提交数据

2. GET请求,请求参数拼接形式暴露在地址栏,而POST请求参数则放在请求体里面,因此GET请求不适合用于验证密码等操作

3. GET请求的URL有长度限制,POST请求不会有长度限制

请简单的介绍下APNS发送系统消息的机制

APNS优势:杜绝了类似安卓那种为了接受通知不停在后台唤醒程序保持长连接的行为,由iOS系统和APNS进行长连接替代。

APNS的原理:

1). 应用在通知中心注册,由iOS系统向APNS请求返回设备令牌(device Token)

2). 应用程序接收到设备令牌并发送给自己的后台服务器

3). 服务器把要推送的内容和设备发送给APNS

4). APNS根据设备令牌找到设备,再由iOS根据APPID把推送内容展示

第三方框架

AFNetworking 底层原理分析

AFNetworking主要是对NSURLSession和NSURLConnection(iOS9.0废弃)的封装,其中主要有以下类:

1). AFHTTPRequestOperationManager:内部封装的是 NSURLConnection, 负责发送网络请求, 使用最多的一个类。(3.0废弃)

2). AFHTTPSessionManager:内部封装是 NSURLSession, 负责发送网络请求,使用最多的一个类。

3). AFNetworkReachabilityManager:实时监测网络状态的工具类。当前的网络环境发生改变之后,这个工具类就可以检测到。

4). AFSecurityPolicy:网络安全的工具类, 主要是针对 HTTPS 服务。

5). AFURLRequestSerialization:序列化工具类,基类。上传的数据转换成JSON格式

(AFJSONRequestSerializer).使用不多。

6). AFURLResponseSerialization:反序列化工具类;基类.使用比较多:

7). AFJSONResponseSerializer; JSON解析器,默认的解析器.

8). AFHTTPResponseSerializer; 万能解析器; JSON和XML之外的数据类型,直接返回二进

制数据.对服务器返回的数据不做任何处理.

9). AFXMLParserResponseSerializer; XML解析器;

描述下SDWebImage里面给UIImageView加载图片的逻辑

SDWebImage 中为 UIImageView 提供了一个分类UIImageView+WebCache.h, 这个分类中有一个最常用的接口sd_setImageWithURL:placeholderImage:,会在真实图片出现前会先显示占位图片,当真实图片被加载出来后再替换占位图片。

加载图片的过程大致如下:

1.首先会在 SDWebImageCache 中寻找图片是否有对应的缓存, 它会以url 作为数据的索引先在内存中寻找是否有对应的缓存

2.如果缓存未找到就会利用通过MD5处理过的key来继续在磁盘中查询对应的数据, 如果找到了, 就会把磁盘中的数据加载到内存中,并将图片显示出来

3.如果在内存和磁盘缓存中都没有找到,就会向远程服务器发送请求,开始下载图片

4.下载后的图片会加入缓存中,并写入磁盘中

5.整个获取图片的过程都是在子线程中执行,获取到图片后回到主线程将图片显示出来

SDWebImage原理:

调用类别的方法:

1. 从内存(字典)中找图片(当这个图片在本次使用程序的过程中已经被加载过),找到直接使用。

2. 从沙盒中找(当这个图片在之前使用程序的过程中被加载过),找到使用,缓存到内存中。

3. 从网络上获取,使用,缓存到内存,缓存到沙盒。

友盟统计接口统计的所有功能

APP启动速度,APP停留页面时间等

算法

不用中间变量,用两种方法交换A和B的值

// 1.中间变量

void swap(int a, int b) {

int temp = a;

a = b;

b = temp;

}

// 2.加法

void swap(int a, int b) {

a = a + b;

b = a - b;

a = a - b;

}

// 3.异或(相同为0,不同为1. 可以理解为不进位加法)

void swap(int a, int b) {

a = a ^ b;

b = a ^ b;

a = a ^ b;

}

求最大公约数

/** 1.直接遍历法 */

int maxCommonDivisor(int a, int b) {

int max = 0;

for (int i = 1; i <=b; i++) {

if (a % i == 0 && b % i == 0) {

max = i;

}

}

return max;

}

/** 2.辗转相除法 */

int maxCommonDivisor(int a, int b) {

int r;

while(a % b > 0) {

r = a % b;

a = b;

b = r;

}

return b;

}

// 扩展:最小公倍数 = (a * b)/最大公约数

模拟栈操作

/**

*  栈是一种数据结构,特点:先进后出

*  练习:使用全局变量模拟栈的操作

*/

#include

#include

#include

//保护全局变量:在全局变量前加static后,这个全局变量就只能在本文件中使用

static int data[1024];//栈最多能保存1024个数据

static int count = 0;//目前已经放了多少个数(相当于栈顶位置)

//数据入栈 push

void push(int x){

assert(!full());//防止数组越界

data[count++] = x;

}

//数据出栈 pop

int pop(){

assert(!empty());

return data[--count];

}

//查看栈顶元素 top

int top(){

assert(!empty());

return data[count-1];

}

//查询栈满 full

bool full() {

if(count >= 1024) {

return 1;

}

return 0;

}

//查询栈空 empty

bool empty() {

if(count <= 0) {

return 1;

}

return 0;

}

int main(){

//入栈

for (int i = 1; i <= 10; i++) {

push(i);

}

//出栈

while(!empty()){

printf("%d ", top()); //栈顶元素

pop(); //出栈

}

printf("\n");

return 0;

}

排序算法

选择排序、冒泡排序、插入排序三种排序算法可以总结为如下:

都将数组分为已排序部分和未排序部分。

1. 选择排序将已排序部分定义在左端,然后选择未排序部分的最小元素和未排序部分的第一个元素交换。

2. 冒泡排序将已排序部分定义在右端,在遍历未排序部分的过程执行交换,将最大元素交换到最右端。

3. 插入排序将已排序部分定义在左端,将未排序部分元的第一个元素插入到已排序部分合适的位置。

选择排序

/**

* 【选择排序】:最值出现在起始端

*

* 第1趟:在n个数中找到最小(大)数与第一个数交换位置

* 第2趟:在剩下n-1个数中找到最小(大)数与第二个数交换位置

* 重复这样的操作...依次与第三个、第四个...数交换位置

* 第n-1趟,最终可实现数据的升序(降序)排列。

*

*/

void selectSort(int *arr, int length) {

for (int i = 0; i < length - 1; i++) { //趟数

for (int j = i + 1; j < length; j++) { //比较次数

if (arr[i] > arr[j]) {

int temp = arr[i];

arr[i] = arr[j];

arr[j] = temp;

}

}

}

}

冒泡排序

/**

* 【冒泡排序】:相邻元素两两比较,比较完一趟,最值出现在末尾

* 第1趟:依次比较相邻的两个数,不断交换(小数放前,大数放后)逐个推进,最值最后出现在第n个元素位置

* 第2趟:依次比较相邻的两个数,不断交换(小数放前,大数放后)逐个推进,最值最后出现在第n-1个元素位置

* ……  ……

* 第n-1趟:依次比较相邻的两个数,不断交换(小数放前,大数放后)逐个推进,最值最后出现在第2个元素位置

*/

void bublleSort(int *arr, int length) {

for(int i = 0; i < length - 1; i++) { //趟数

for(int j = 0; j < length - i - 1; j++) { //比较次数

if(arr[j] > arr[j+1]) {

int temp = arr[j];

arr[j] = arr[j+1];

arr[j+1] = temp;

}

}

}

}

折半查找(二分查找)

/**

* 折半查找:优化查找时间(不用遍历全部数据)

*

* 折半查找的原理:

*  1> 数组必须是有序的

*  2> 必须已知min和max(知道范围)

*  3> 动态计算mid的值,取出mid对应的值进行比较

*  4> 如果mid对应的值大于要查找的值,那么max要变小为mid-1

*  5> 如果mid对应的值小于要查找的值,那么min要变大为mid+1

*

*/

// 已知一个有序数组, 和一个key, 要求从数组中找到key对应的索引位置

int findKey(int *arr, int length, int key) {

int min = 0, max = length - 1, mid;

while (min <= max) {

mid = (min + max) / 2; //计算中间值

if (key > arr[mid]) {

min = mid + 1;

} else if (key < arr[mid]) {

max = mid - 1;

} else {

return mid;

}

}

return -1;

}

编码格式(优化细节)

在 Objective-C 中,enum 建议使用NS_ENUM和NS_OPTIONS宏来定义枚举类型。

//定义一个枚举(比较严密)

typedef NS_ENUM(NSInteger, BRUserGender) {

BRUserGenderUnknown, // 未知

BRUserGenderMale, // 男性

BRUserGenderFemale, // 女性

BRUserGenderNeuter // 无性

};

@interface BRUser : NSObject

@property (nonatomic, readonly, copy) NSString *name;

@property (nonatomic, readonly, assign) NSUInteger age;

@property (nonatomic, readonly, assign) BRUserGender gender;

- (instancetype)initWithName:(NSString *)name age:(NSUInteger)age gender:(BRUserGender)gender;

@end

//说明:

//既然该类中已经有一个“初始化方法” ,用于设置 name、age 和 gender 的初始值: 那么在设计对应 @property 时就应该尽量使用不可变的对象:其三个属性都应该设为“只读”。用初始化方法设置好属性值之后,就不能再改变了。

//属性的参数应该按照下面的顺序排列: (原子性,读写,内存管理)

避免使用C语言中的基本数据类型,建议使用 Foundation 数据类型,对应关系如下:

int -> NSInteger

unsigned -> NSUInteger

float -> CGFloat

动画时间 -> NSTimeInterval

其它知识点

HomeKit,是苹果2014年发布的智能家居平台。

什么是 OpenGL、Quartz 2D?

Quatarz 2d 是Apple提供的基本图形工具库。只是适用于2D图形的绘制。

OpenGL,是一个跨平台的图形开发库。适用于2D和3D图形的绘制。

ffmpeg框架:​ffmpeg 是音视频处理工具,既有音视频编码解码功能,又可以作为播放器使用。

谈谈 UITableView 的优化

1). 正确的复用cell。

2). 设计统一规格的Cell

3). 提前计算并缓存好高度(布局),因为heightForRowAtIndexPath:是调用最频繁的方法;

4). 异步绘制,遇到复杂界面,遇到性能瓶颈时,可能就是突破口;

4). 滑动时按需加载,这个在大量图片展示,网络加载的时候很管用!

5). 减少子视图的层级关系

6). 尽量使所有的视图不透明化以及做切圆操作。

7). 不要动态的add 或者 remove 子控件。最好在初始化时就添加完,然后通过hidden来控制是否显示。

8). 使用调试工具分析问题。

如何实行cell的动态的行高

如果希望每条数据显示自身的行高,必须设置两个属性,1.预估行高,2.自定义行高。

设置预估行高 tableView.estimatedRowHeight = 200。

设置定义行高 tableView.estimatedRowHeight = UITableViewAutomaticDimension。

如果要让自定义行高有效,必须让容器视图有一个自下而上的约束。

说说你对 block 的理解

栈上的自动复制到堆上,block 的属性修饰符是 copy,循环引用的原理和解决方案。

说说你对 runtime 的理解

主要是方法调用时如何查找缓存,如何找到方法,找不到方法时怎么转发,对象的内存布局。

什么是野指针、空指针?

野指针:不知道指向了哪里的指针叫野指针。即指针指向不确定,指针存的地址是一个垃圾值,未初始化。

空指针:不指向任何位置的指针叫空指针。即指针没有指向,指针存的地址是一个空地址,NULL。

什么是 OOA / OOD / OOP ?

OOA(Object Oriented Analysis)   --面向对象分析

OOD(Object Oriented Design)     --面向对象设计

OOP(Object Oriented Programming)--面向对象编程


加强篇



1.为什么说Objective-C是一门动态的语言?

1.object-c类的类型和数据变量的类型都是在运行是确定的,而不是在编译时确定。例如:多态特性,我们可以使用父类对象来指向子类对象,并且可以用来调用子类的方法。运行时(runtime)特性,我们可以动态的添加方法,或者替换方法。

2.讲一下MVC和MVVM,MVP?

MVC:简单来说就是,逻辑、试图、数据进行分层,实现解耦。

MVVM:是Model-View-ViewMode模式的简称。由视图(View)、视图模型(ViewModel)、模型(Model)三部分组成.比MVC更加释放控制器臃肿,M、V、VM直接是通过信号来传递消息或者数据,本小编将他们直接的通讯理解为通知,一下为MVVM的特点:

低耦合。View可以独立于Model变化和修改,一个ViewModel可以绑定到不同的View上,当View变化的时候Model可以不变,当Model变化的时候View也可以不变。

可重用性。可以把一些视图的逻辑放在ViewModel里面,让很多View重用这段视图逻辑。

独立开发。开发人员可以专注与业务逻辑和数据的开发(ViewModel)。设计人员可以专注于界面(View)的设计。

可测试性。可以针对ViewModel来对界面(View)进行测试

MVP:本小编没有接触,希望可以得到大家的帮助。可以在下面留言。

3.为什么代理要用weak?代理的delegate和dataSource有什么区别?block和代理的区别?

代理是使用weak来修饰的。1.使用weak是为了避免循环引用。2.当使用weak修饰的属性,当对象释放的时候,系统会对属性赋值nil,object-c有个特性就是对nil对象发送消息也就是调用方法,不会cash。

delegate:表示代理,代理可以让A对象通知B对象,我(A)发生的变化,前提B遵循了A的代理,并且实现了A的代理方法。

dataSource:表示数据源,如果A对象声明了数据源,当我们创建A对象的时候,我们就该实现数据源,来告诉A,他所需要的一些数据。例如:tableView数据源方法,需要告诉它,我要实现几组cell,每组cell多少行cell,实现的cell什么样式,什么内容

同样delegate和dataSource,都是可以使用require和optional来修饰的。

代理和Block的区别

相同点:代理和Block大多是我们都可以用来做倒序传值的。我们都得注意避免循环引用。

不同点:代理使用weak修饰,代理必须先声明方法。当我们调用代理的时候要判断是否已经实现。

block:使用的是copy来修饰,block保存的是一段代码,其实也就是一个函数。当我们调用block的时候要判断是否已经实现。

4.属性的实质是什么?包括哪几个部分?属性默认的关键字都有哪些?@dynamic关键字和@synthesize关键字是用来做什么的?

属性是描述类的特征,也就是具备什么特性。三个部分,带下划线的成员变量,get、setter方法。

默认关键字:readwrite,assign, atomic;

@dynamic :修饰的属性,其getter和setter方法编译器是不会自动帮你生成。必须自己是实现的。

@synthesize:修饰的属性,其getter和setter方法编译器是会自动帮你生成。不必自己实现,可以指定与属性相对应的成员变量。

5.属性的默认关键字是什么?

默认关键字:readwrite,assign, atomic;

6.NSString为什么要用copy关键字,如果用strong会有什么问题?(注意:这里没有说用strong就一定不行。使用copy和strong是看情况而定的

众所周知,我们知道,可变类型(NSMutableArray,NSMutableString等)是不可边类型(NSString,NSArray等)的子类,因为多态的原因,我们可以使用赋值指向子类对象,也就是我们可以使用不可边类型去接受可变类型。

1.当我们使用strong修饰A不可边类型的时候,并且使用B可变类型给A赋值,再去修改可变类型B值的时候,A所指向的值也会发生改变。引文strong只是让创建的对象引用计数器+1,并返回当前对象的内容地址,当我们修改B指向的内容的时候,A指向的内容也同样发生了改变,因为他们指向的内存地址是相同的,是一份内容。

2.当我们使用copy修饰A不可边类型的时候,并且使用B可变类型给A赋值,再去修改可变类型B值的时候,A所指向的值不会发生改变。因为当时用copy的修饰的时候,会拷贝一份内容出来,并且返回指针给A,当我们修改B指向的内容的时候,A指向的内容是没有发生改变的。因为A指向的内存地址和B指向的内存地址是不相同的,是两份内容

3.copy修饰不可边类型(NSString,NSArray等)的时候,表示浅拷贝,只拷贝一份指针,和strong修饰一样,当修饰的是可变类型(NSMutableArray,NSMutableString等)的时候,表示深拷贝,直接拷贝新一份内容,到内存中。表示两份内容。

7.如何令自己所写的对象具有拷贝功能?

必须遵循nscopying协议,如果想实现可变和不可边拷贝时,必须同时遵循nscoping和nsmutablecoping协议。并且实现

- (id)copyWithZone:(NSZone *)zone;

8.可变集合类 和 不可变集合类的 copy 和 mutablecopy有什么区别?如果是集合是内容复制的话,集合里面的元素也是内容复制么?

可变使用copy表示深拷贝,不可变集合类使用copy的时候是浅拷贝。

可变集合类使用mutablecopy表示深拷贝,不可变集合类使用copy的时候是浅拷贝。

关于容器实现copy 或 metableCopy ,容器内元素默认都是 指针拷贝,不是内容复制。

9.为什么IBOutlet修饰的UIView也适用weak关键字?

在xib或者Sb拖控件时,其实控件就加载到了父控件的subviews数组里面,进行了强引用,即使使用了weak,也不造成对象的释放。

10.nonatomic和atomic的区别?atomic是绝对的线程安全么?为什么?如果不是,那应该如何实现?

nonatomic:表示非原子,不安全,但是效率高。

atomic:表示原子行,安全,但是效率定。

atomic:不能绝对保证线程的安全,当多线程同时访问的时候,会造成线程不安全。可以使用线程锁来保证线程的安全。

11.UICollectionView自定义layout如何实现?

实现一个自定义layout的常规做法是继承UICollectionViewLayout类,然后重载下列方法:

-(CGSize)collectionViewContentSize

返回collectionView的内容的尺寸

-(NSArray *)layoutAttributesForElementsInRect:(CGRect)rect

返回rect中的所有的元素的布局属性

返回的是包含UICollectionViewLayoutAttributes的NSArray

UICollectionViewLayoutAttributes可以是cell,追加视图或装饰视    图的信息,通过不同的UICollectionViewLayoutAttributes初始化方法可以得到不同类型的UICollectionViewLayoutAttributes:

layoutAttributesForCellWithIndexPath:

layoutAttributesForSupplementaryViewOfKind:withIndexPath:

layoutAttributesForDecorationViewOfKind:withIndexPath:

-(UICollectionViewLayoutAttributes )layoutAttributesForItemAtIndexPath:(NSIndexPath )indexPath

返回对应于indexPath的位置的cell的布局属性

-(UICollectionViewLayoutAttributes )layoutAttributesForSupplementaryViewOfKind:(NSString )kind atIndexPath:(NSIndexPath *)indexPath

返回对应于indexPath的位置的追加视图的布局属性,如果没有追加视图可不重载

-(UICollectionViewLayoutAttributes * )layoutAttributesForDecorationViewOfKind:(NSString)decorationViewKind atIndexPath:(NSIndexPath )indexPath

返回对应于indexPath的位置的装饰视图的布局属性,如果没有装饰视图可不重载

-(BOOL)shouldInvalidateLayoutForBoundsChange:(CGRect)newBounds

当边界发生改变时,是否应该刷新布局。如果YES则在边界变化(一般是scroll到其他地方)时,将重新计算需要的布局信息。

12.用StoryBoard开发界面有什么弊端?如何避免?

使用简单逻辑页面的跳转是可以使用sb的,开发比较块。

但是SB对于逻辑项目比较复杂的时候,开发起来比较慢。不适合多人合作开发;也不利于版本的梗系和后期的维护。使用sb在项目变异编译的时候,也都会直接加载到内存中,造成内存的浪费。

可以使用xib来代替,编辑复杂逻辑界面时候可以使用纯码编写。

13.进程和线程的区别?同步异步的区别?并行和并发的区别?

进程:是具有一定独立功能的程序关于某个数据集合上的一次运行活动,进程是系统进行资源分配和调度的一个独立单位.

线程:是进程的一个实体,是CPU调度和分派的基本单位,它是比进程更小的能独立运行的基本单位.线程自己基本上不拥有系统资源,只拥有一点在运行中必不可少的资源(如程序计数器,一组寄存器和栈),但是它可与同属一个进程的其他的线程共享进程所拥有的全部资源.

同步:只在同一个线程内部执行任务。

异步:可以开辟线程来执行任务。

并发:当有多个线程在操作时,如果系统只有一个CPU,则它根本不可能真正同时进行一个以上的线程,它只能把CPU运行时间划分成若干个时间段,再将时间 段分配给各个线程执行,在一个时间段的线程代码运行时,其它线程处于挂起状。.这种方式我们称之为并发(Concurrent)。

并行:当系统有一个以上CPU时,则线程的操作有可能非并发。当一个CPU执行一个线程时,另一个CPU可以执行另一个线程,两个线程互不抢占CPU资源,可以同时进行,这种方式我们称之为并行(Parallel)。

区别:并发和并行是即相似又有区别的两个概念,并行是指两个或者多个事件在同一时刻发生;而并发是指两个或多个事件在同一时间间隔内发生。在多道程序环境下,并发性是指在一段时间内宏观上有多个程序在同时运行,但在单处理机系统中,每一时刻却仅能有一道程序执行,故微观上这些程序只能是分时地交替执行。倘若在计算机系统中有多个处理机,则这些可以并发执行的程序便可被分配到多个处理机上,实现并行执行,即利用每个处理机来处理一个可并发执行的程序,这样,多个程序便可以同时执行。

14.线程间通信?

当使用dispath-async函数开辟线程执行任务的完成时,我们需要使用dispatch_async(dispatch_get_main_queue(), ^{  });函数会到主线程内刷新UI。并完成通信

15.GCD的一些常用的函数?(group,barrier,信号量,线程同步)

我们使用队列组来开辟线程时,队列组中的队列任务是并发,当所有的队列组中的所有任务完成时候,才可以调用队列组完成任务。

/**创建自己的队列*/

dispatch_queue_t dispatchQueue = dispatch_queue_create("ted.queue.next", DISPATCH_QUEUE_CONCURRENT);

/**创建一个队列组*/

dispatch_group_t dispatchGroup = dispatch_group_create();

/**将队列任务添加到队列组中*/

dispatch_group_async(dispatchGroup, dispatchQueue, ^(){

NSLog(@"dispatch-1");

});

/**将队列任务添加到队列组中*/

dispatch_group_async(dispatchGroup, dispatchQueue, ^(){

NSLog(@"dspatch-2");

});

/**队列组完成调用函数*/

dispatch_group_notify(dispatchGroup, dispatch_get_main_queue(), ^(){

NSLog(@"end");

})

barrier:表示栅栏,当在并发队列里面使用栅栏时候,栅栏之前的并发任务开始并发执行,执行完毕后,执行栅栏内的任务,等栅栏任务执行完毕后,再并发执行栅栏后的任务。

dispatch_queue_t concurrentQueue = dispatch_queue_create("my.concurrent.queue", DISPATCH_QUEUE_CONCURRENT);

dispatch_async(concurrentQueue, ^(){

NSLog(@"dispatch-1");

});

dispatch_async(concurrentQueue, ^(){

NSLog(@"dispatch-2");

});

dispatch_barrier_async(concurrentQueue, ^(){

NSLog(@"dispatch-barrier");

});

dispatch_async(concurrentQueue, ^(){

NSLog(@"dispatch-3");

});

dispatch_async(concurrentQueue, ^(){

NSLog(@"dispatch-4");

});

信号量:Semaphore是通过‘计数’的方式来标识线程是否是等待或继续执行的。信号量

dispatch_semaphore_create(int) // 创建一个信号,并初始化信号的计数大小

/* 等待信号,并且判断信号量,如果信号量计数大于等于你创建时候的信号量的计数,就可以通过,继续执行,并且将你传入的信号计数减1,

* 如果传入的信号计数小于你创建的计数,就表示等待,等待信号计数的变化

*  如果等待的时间超过你传入的时间,也会继续下面操作

*  第一个参数:semaphore 表示信号量

*  第二个参数:表示等待的时间

*    返回int 如果传入的信号计数大于等于你创建信号的计数时候,返回0.  反之,返回的不等于0

*/

int result = dispatch_semaphore_wait(dispatch_semaphore_t  semaphore,time outTime);// 表示等待,也是阻碍线程

// 表示将信号技术+1

dispatch_semaphore_signl(dispatch_semaphore_t semaphore);

实现线程的同步的方法:串行队列,分组,信号量。也是可以使用并发队列。

//加入队列

dispatch_async(concurrentQueue, ^{

//1.先去网上下载图片

dispatch_sync(concurrentQueue, ^{

});

//2.在主线程展示到界面里

dispatch_sync(dispatch_get_main_queue(), ^{

});

});

16.如何使用队列来避免资源抢夺?

当我们使用多线程来访问同一个数据的时候,就有可能造成数据的不准确性。这个时候我么可以使用线程锁的来来绑定。也是可以使用串行队列来完成。如:fmdb就是使用FMDatabaseQueue,来解决多线程抢夺资源。

17.数据持久化的几个方案(fmdb用没用过)

持久化方案:

plist,存储字典,数组比较好用

preference:偏好设置,实质也是plist

NSKeyedArchiver:归档,可以存储对象

sqlite:数据库,经常使用第三方来操作,也就是fmdb

coreData:也是数据库储存,苹果官方的

18.说一下appdelegate的几个方法?从后台到前台调用了哪些方法?第一次启动调用了哪些方法?从前台到后台调用了哪些方法?

1029210 (1).gif

19.NSCache优于NSDictionary的几点?

1.nscache 是可以自动释放内存的。

2.nscache是线程安全的,我们可以在不同的线程中添加,删除和查询缓存中的对象。

3.一个缓存对象不会拷贝key对象。

20.知不知道Designated Initializer?使用它的时候有什么需要注意的问题?

个人理解:初始化函数,如果你想自定义初始化函数时,也是必须要初始化父类,以来保证可以继承父类的一些方法或者属性。

Designated Initializer

21.实现description方法能取到什么效果?

description是nsobject的一个实例的方法,返回的是一个nsstring。当我们使用nslog打印的时候,打印出来的一般都是对象的内存地址,如果我们实现description方法时,我们就可以使用nslog打印对象的时候,我们可以把它里面的属性值和内存地址一起打印出来.打印什么,就是看你写什么了。

-(NSString *)description{

NSString * string = [NSString stringWithFormat:@"",self,self.name,self.age];

return string;

}

22.objc使用什么机制管理对象内存?

使用内存管理计数器,来管理内存的。当内存管理计数器为0的时候,对象就会被释放。

中级

Block

1.block的实质是什么?一共有几种block?都是什么情况下生成的?

block:本质就是一个object-c对象.

block:存储位置,可能分为3个地方:代码去,堆区、栈区(ARC情况下会自动拷贝到堆区,因此ARC下只能有两个地方:代码去、堆区)

代码区:不访问栈区的变量(如局部变量),且不访问堆区的变量(alloc创建的对象),此时block存放在代码去。

堆区:访问了处于栈区的变量,或者堆区的变量,此时block存放在堆区。–需要注意实际是放在栈区,在ARC情况下会自动拷贝到堆区,如果不是ARC则存放在栈区,所在函数执行完毕就回释放,想再外面调用需要用copy指向它,这样就拷贝到了堆区,strong属性不会拷贝、会造成野指针错区。

2.为什么在默认情况下无法修改被block捕获的变量? __block都做了什么?

默认情况下,block里面的变量,拷贝进去的是变量的值,而不是指向变量的内存的指针。

当使用__block修饰后的变量,拷贝到block里面的就是指向变量的指针,所以我们就可以修改变量的值。

3.模拟一下循环引用的一个情况?block实现界面反向传值如何实现?

Person *p = [[Person alloc]init];

[p setPersonBlock:^(NSString *str) {

p.name = str;

}];

Runtime

1.objc在向一个对象发送消息时,发生了什么?

根据对象的isa指针找到类对象id,在查询类对象里面的methodLists方法函数列表,如果没有在好到,在沿着superClass,寻找父类,再在父类methodLists方法列表里面查询,最终找到SEL,根据id和SEL确认IMP(指针函数),在发送消息;

3.什么时候会报unrecognized selector错误?iOS有哪些机制来避免走到这一步?

当发送消息的时候,我们会根据类里面的methodLists列表去查询我们要动用的SEL,当查询不到的时候,我们会一直沿着父类查询,当最终查询不到的时候我们会报unrecognized selector错误

当系统查询不到方法的时候,会调用+(BOOL)resolveInstanceMethod:(SEL)sel动态解释的方法来给我一次机会来添加,调用不到的方法。或者我们可以再次使用-(id)forwardingTargetForSelector:(SEL)aSelector重定向的方法来告诉系统,该调用什么方法,一来保证不会崩溃。

4.能否向编译后得到的类中增加实例变量?能否向运行时创建的类中添加实例变量?为什么?

1.不能向编译后得到的类增加实例变量

2.能向运行时创建的类中添加实例变量

解释:

1.编译后的类已经注册在runtime中,类结构体中的objc_ivar_list实例变量的链表和instance_size实例变量的内存大小已经确定,runtime会调用class_setvarlayout或class_setWeaklvarLayout来处理strong weak引用.所以不能向存在的类中添加实例变量

2.运行时创建的类是可以添加实例变量,调用class_addIvar函数.但是的在调用objc_allocateClassPair之后,objc_registerClassPair之前,原因同上.

5.runtime如何实现weak变量的自动置nil?

runtime 对注册的类, 会进行布局,对于 weak 对象会放入一个 hash 表中。 用 weak 指向的对象内存地址作为 key,当此对象的引用计数为0的时候会 dealloc,假如 weak 指向的对象内存地址是a,那么就会以a为键, 在这个 weak 表中搜索,找到所有以a为键的 weak 对象,从而设置为 nil。

6.给类添加一个属性后,在类结构体里哪些元素会发生变化?

instance_size :实例的内存大小

objc_ivar_list *ivars:属性列表

RunLoop

1.runloop是来做什么的?runloop和线程有什么关系?主线程默认开启了runloop么?子线程呢?

runloop:字面意思就是跑圈,其实也就是一个循环跑圈,用来处理线程里面的事件和消息。

runloop和线程的关系:每个线程如果想继续运行,不被释放,就必须有一个runloop来不停的跑圈,以来处理线程里面的各个事件和消息。

主线程默认是开启一个runloop。也就是这个runloop才能保证我们程序正常的运行。子线程是默认没有开始runloop的

2.runloop的mode是用来做什么的?有几种mode?

model:是runloop里面的模式,不同的模式下的runloop处理的事件和消息有一定的差别。

系统默认注册了5个Mode:

(1)kCFRunLoopDefaultMode: App的默认 Mode,通常主线程是在这个 Mode 下运行的。

(2)UITrackingRunLoopMode: 界面跟踪 Mode,用于 ScrollView 追踪触摸滑动,保证界面滑动时不受其他 Mode 影响。

(3)UIInitializationRunLoopMode: 在刚启动 App 时第进入的第一个 Mode,启动完成后就不再使用。

(4)GSEventReceiveRunLoopMode: 接受系统事件的内部 Mode,通常用不到。

(5)kCFRunLoopCommonModes: 这是一个占位的 Mode,没有实际作用。

注意iOS 对以上5中model进行了封装

NSDefaultRunLoopMode;

NSRunLoopCommonModes

3.为什么把NSTimer对象以NSDefaultRunLoopMode(kCFRunLoopDefaultMode)添加到主运行循环以后,滑动scrollview的时候NSTimer却不动了?

nstime对象是在NSDefaultRunLoopMode下面调用消息的,但是当我们滑动scrollview的时候,NSDefaultRunLoopMode模式就自动切换到UITrackingRunLoopMode模式下面,却不可以继续响应nstime发送的消息。所以如果想在滑动scrollview的情况下面还调用nstime的消息,我们可以把nsrunloop的模式更改为NSRunLoopCommonModes

4.苹果是如何实现Autorelease Pool的?

Autorelease Pool作用:缓存池,可以避免我们经常写relase的一种方式。其实就是延迟release,将创建的对象,添加到最近的autoreleasePool中,等到autoreleasePool作用域结束的时候,会将里面所有的对象的引用计数器-1.

autorelease

作者:七秒记忆的鱼儿

链接:http://www.jianshu.com/p/f9eb6b315c08

來源:简书

著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

16. objc中向一个nil对象发送消息将会发生什么?

在 Objective-C 中向 nil 发送消息是完全有效的——只是在运行时不会有任何作用:

如果一个方法返回值是一个对象,那么发送给nil的消息将返回0(nil)。例如:

Person * motherInlaw = [[aPerson spouse] mother];

如果 spouse 对象为 nil,那么发送给 nil 的消息 mother 也将返回 nil。

如果方法返回值为指针类型,其指针大小为小于或者等于sizeof(void*),float,double,long double 或者 long long 的整型标量,发送给 nil 的消息将返回0。

如果方法返回值为结构体,发送给 nil 的消息将返回0。结构体中各个字段的值将都是0。

如果方法的返回值不是上述提到的几种情况,那么发送给 nil 的消息的返回值将是未定义的。

具体原因如下:

objc是动态语言,每个方法在运行时会被动态转为消息发送,即:objc_msgSend(receiver, selector)。

那么,为了方便理解这个内容,还是贴一个objc的源代码:

// runtime.h(类在runtime中的定义)

// http://weibo.com/luohanchenyilong/

// https://github.com/ChenYilong

struct objc_class {

Class isa OBJC_ISA_AVAILABILITY; //isa指针指向Meta Class,因为Objc的类的本身也是一个Object,为了处理这个关系,runtime就创造了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;

objc在向一个对象发送消息时,runtime库会根据对象的isa指针找到该对象实际所属的类,然后在该类中的方法列表以及其父类方法列表中寻找方法运行,然后在发送消息的时候,objc_msgSend方法不会返回值,所谓的返回内容都是具体调用时执行的。

那么,回到本题,如果向一个nil对象发送消息,首先在寻找对象的isa指针时就是0地址返回了,所以不会出现任何错误。

17. objc中向一个对象发送消息[obj foo]和objc_msgSend()函数之间有什么关系?

具体原因同上题:该方法编译之后就是objc_msgSend()函数调用.

我们用 clang 分析下,clang 提供一个命令,可以将Objective-C的源码改写成C++语言,借此可以研究下[obj foo]和objc_msgSend()函数之间有什么关系。

以下面的代码为例,由于 clang 后的代码达到了10万多行,为了便于区分,添加了一个叫 iOSinit 方法,

//

//  main.m

//  http://weibo.com/luohanchenyilong/

//  https://github.com/ChenYilong

//  Copyright (c) 2015年 微博@iOS程序犭袁. All rights reserved.

//

#import "CYLTest.h"

int main(int argc, char * argv[]) {

@autoreleasepool {

CYLTest *test = [[CYLTest alloc] init];

[test performSelector:(@selector(iOSinit))];

return 0;

}

}

在终端中输入

clang -rewrite-objc main.m

就可以生成一个main.cpp的文件,在最低端(10万4千行左右)

enter image description here

我们可以看到大概是这样的:

((void ()(id, SEL))(void )objc_msgSend)((id)obj, sel_registerName("foo"));

也就是说:

[obj foo];在objc动态编译时,会被转意为:objc_msgSend(obj, @selector(foo));。

18. 什么时候会报unrecognized selector的异常?

简单来说:

当调用该对象上某个方法,而该对象上没有实现这个方法的时候,

可以通过“消息转发”进行解决。

简单的流程如下,在上一题中也提到过:

objc是动态语言,每个方法在运行时会被动态转为消息发送,即:objc_msgSend(receiver, selector)。

objc在向一个对象发送消息时,runtime库会根据对象的isa指针找到该对象实际所属的类,然后在该类中的方法列表以及其父类方法列表中寻找方法运行,如果,在最顶层的父类中依然找不到相应的方法时,程序在运行时会挂掉并抛出异常unrecognized selector sent to XXX 。但是在这之前,objc的运行时会给出三次拯救程序崩溃的机会:

Method resolution

objc运行时会调用+resolveInstanceMethod:或者+resolveClassMethod:,让你有机会提供一个函数实现。如果你添加了函数,那运行时系统就会重新启动一次消息发送的过程,否则 ,运行时就会移到下一步,消息转发(Message Forwarding)。

Fast forwarding

如果目标对象实现了-forwardingTargetForSelector:,Runtime 这时就会调用这个方法,给你把这个消息转发给其他对象的机会。

只要这个方法返回的不是nil和self,整个消息发送的过程就会被重启,当然发送的对象会变成你返回的那个对象。否则,就会继续Normal Fowarding。

这里叫Fast,只是为了区别下一步的转发机制。因为这一步不会创建任何新的对象,但下一步转发会创建一个NSInvocation对象,所以相对更快点。

Normal forwarding

这一步是Runtime最后一次给你挽救的机会。首先它会发送-methodSignatureForSelector:消息获得函数的参数和返回值类型。如果-methodSignatureForSelector:返回nil,Runtime则会发出-doesNotRecognizeSelector:消息,程序这时也就挂掉了。如果返回了一个函数签名,Runtime就会创建一个NSInvocation对象并发送-forwardInvocation:消息给目标对象。

为了能更清晰地理解这些方法的作用,git仓库里也给出了一个Demo,名称叫“_objc_msgForward_demo”,可运行起来看看。

19. 一个objc对象如何进行内存布局?(考虑有父类的情况)

所有父类的成员变量和自己的成员变量都会存放在该对象所对应的存储空间中.

每一个对象内部都有一个isa指针,指向他的类对象,类对象中存放着本对象的

对象方法列表(对象能够接收的消息列表,保存在它所对应的类对象中)

成员变量的列表,

属性列表,

它内部也有一个isa指针指向元对象(meta class),元对象内部存放的是类方法列表,类对象内部还有一个superclass的指针,指向他的父类对象。

每个 Objective-C 对象都有相同的结构,如下图所示:

enter image description here

翻译过来就是

Objective-C 对象的结构图

ISA指针

根类的实例变量

倒数第二层父类的实例变量

...

父类的实例变量

类的实例变量

根对象就是NSObject,它的superclass指针指向nil

类对象既然称为对象,那它也是一个实例。类对象中也有一个isa指针指向它的元类(meta class),即类对象是元类的实例。元类内部存放的是类方法列表,根元类的isa指针指向自己,superclass指针指向NSObject类。

如图:

enter image description here

20. 一个objc对象的isa的指针指向什么?有什么作用?

指向他的类对象,从而可以找到对象上的方法

21. 下面的代码输出什么?

@implementation Son : Father

- (id)init

{

self = [super init];

if (self) {

NSLog(@"%@", NSStringFromClass([self class]));

NSLog(@"%@", NSStringFromClass([super class]));

}

return self;

}

@end

答案:

都输出 Son

NSStringFromClass([self class]) = Son

NSStringFromClass([super class]) = Son

这个题目主要是考察关于 Objective-C 中对 self 和 super 的理解。

我们都知道:self 是类的隐藏参数,指向当前调用方法的这个类的实例。那 super 呢?

很多人会想当然的认为“ super 和 self 类似,应该是指向父类的指针吧!”。这是很普遍的一个误区。其实 super 是一个 Magic Keyword, 它本质是一个编译器标示符,和 self 是指向的同一个消息接受者!他们两个的不同点在于:super 会告诉编译器,调用 class 这个方法时,要去父类的方法,而不是本类里的。

上面的例子不管调用[self class]还是[super class],接受消息的对象都是当前Son *xxx这个对象。

当使用 self 调用方法时,会从当前类的方法列表中开始找,如果没有,就从父类中再找;而当使用 super 时,则从父类的方法列表中开始找。然后调用父类的这个方法。

这也就是为什么说“不推荐在 init 方法中使用点语法”,如果想访问实例变量 iVar 应该使用下划线(_iVar),而非点语法(self.iVar)。

点语法(self.iVar)的坏处就是子类有可能覆写 setter 。假设 Person 有一个子类叫 ChenPerson,这个子类专门表示那些姓“陈”的人。该子类可能会覆写 lastName 属性所对应的设置方法:

//

//  ChenPerson.m

//

//

//  Created by https://github.com/ChenYilong on 15/8/30.

//  Copyright (c) 2015年 http://weibo.com/luohanchenyilong/ 微博@iOS程序犭袁. All rights reserved.

//

#import "ChenPerson.h"

@implementation ChenPerson

@synthesize lastName = _lastName;

- (instancetype)init

{

self = [super init];

if (self) {

NSLog(@"🔴类名与方法名:%s(在第%d行),描述:%@", __PRETTY_FUNCTION__, __LINE__, NSStringFromClass([self class]));

NSLog(@"🔴类名与方法名:%s(在第%d行),描述:%@", __PRETTY_FUNCTION__, __LINE__, NSStringFromClass([super class]));

}

return self;

}

- (void)setLastName:(NSString*)lastName

{

//设置方法一:如果setter采用是这种方式,就可能引起崩溃

//    if (![lastName isEqualToString:@"陈"])

//    {

//        [NSException raise:NSInvalidArgumentException format:@"姓不是陈"];

//    }

//    _lastName = lastName;

//设置方法二:如果setter采用是这种方式,就可能引起崩溃

_lastName = @"陈";

NSLog(@"🔴类名与方法名:%s(在第%d行),描述:%@", __PRETTY_FUNCTION__, __LINE__, @"会调用这个方法,想一下为什么?");

}

@end

在基类 Person 的默认初始化方法中,可能会将姓氏设为空字符串。此时若使用点语法(self.lastName)也即 setter 设置方法,那么调用将会是子类的设置方法,如果在刚刚的 setter 代码中采用设置方法一,那么就会抛出异常,

为了方便采用打印的方式展示,究竟发生了什么,我们使用设置方法二。

如果基类的代码是这样的:

//

//  Person.m

//  nil对象调用点语法

//

//  Created by https://github.com/ChenYilong on 15/8/29.

//  Copyright (c) 2015年 http://weibo.com/luohanchenyilong/ 微博@iOS程序犭袁. All rights reserved.

//

#import "Person.h"

@implementation Person

- (instancetype)init

{

self = [super init];

if (self) {

self.lastName = @"";

//NSLog(@"🔴类名与方法名:%s(在第%d行),描述:%@", __PRETTY_FUNCTION__, __LINE__, NSStringFromClass([self class]));

//NSLog(@"🔴类名与方法名:%s(在第%d行),描述:%@", __PRETTY_FUNCTION__, __LINE__, self.lastName);

}

return self;

}

- (void)setLastName:(NSString*)lastName

{

NSLog(@"🔴类名与方法名:%s(在第%d行),描述:%@", __PRETTY_FUNCTION__, __LINE__, @"根本不会调用这个方法");

_lastName = @"炎黄";

}

@end

那么打印结果将会是这样的:

🔴类名与方法名:-[ChenPerson setLastName:](在第36行),描述:会调用这个方法,想一下为什么?

🔴类名与方法名:-[ChenPerson init](在第19行),描述:ChenPerson

🔴类名与方法名:-[ChenPerson init](在第20行),描述:ChenPerson

我在仓库里也给出了一个相应的 Demo(名字叫:Demo_21题_下面的代码输出什么)。有兴趣可以跑起来看一下,主要看下他是怎么打印的,思考下为什么这么打印。

接下来让我们利用 runtime 的相关知识来验证一下 super 关键字的本质,使用clang重写命令:

$ clang -rewrite-objc test.m

将这道题目中给出的代码被转化为:

NSLog((NSString *)&__NSConstantStringImpl__var_folders_gm_0jk35cwn1d3326x0061qym280000gn_T_main_a5cecc_mi_0, NSStringFromClass(((Class (*)(id, SEL))(void *)objc_msgSend)((id)self, sel_registerName("class"))));

NSLog((NSString *)&__NSConstantStringImpl__var_folders_gm_0jk35cwn1d3326x0061qym280000gn_T_main_a5cecc_mi_1, NSStringFromClass(((Class (*)(__rw_objc_super *, SEL))(void *)objc_msgSendSuper)((__rw_objc_super){ (id)self, (id)class_getSuperclass(objc_getClass("Son")) }, sel_registerName("class"))));

从上面的代码中,我们可以发现在调用 [self class] 时,会转化成objc_msgSend函数。看下函数定义:

id objc_msgSend(id self, SEL op, ...)

我们把 self 做为第一个参数传递进去。

而在调用 [super class]时,会转化成objc_msgSendSuper函数。看下函数定义:

id objc_msgSendSuper(struct objc_super *super, SEL op, ...)

第一个参数是objc_super这样一个结构体,其定义如下:

struct objc_super {

__unsafe_unretained id receiver;

__unsafe_unretained Class super_class;

};

结构体有两个成员,第一个成员是 receiver, 类似于上面的objc_msgSend函数第一个参数self 。第二个成员是记录当前类的父类是什么。

所以,当调用 [self class] 时,实际先调用的是objc_msgSend函数,第一个参数是 Son当前的这个实例,然后在 Son 这个类里面去找 - (Class)class这个方法,没有,去父类 Father里找,也没有,最后在 NSObject类中发现这个方法。而 - (Class)class的实现就是返回self的类别,故上述输出结果为 Son。

objc Runtime开源代码对- (Class)class方法的实现:

- (Class)class {

return object_getClass(self);

}

而当调用[super class]时,会转换成objc_msgSendSuper函数。第一步先构造objc_super结构体,结构体第一个成员就是self。

第二个成员是(id)class_getSuperclass(objc_getClass(“Son”)), 实际该函数输出结果为 Father。

第二步是去 Father这个类里去找- (Class)class,没有,然后去NSObject类去找,找到了。最后内部是使用objc_msgSend(objc_super->receiver, @selector(class))去调用,

此时已经和[self class]调用相同了,故上述输出结果仍然返回 Son。

参考链接:微博@Chun_iOS的博文刨根问底Objective-C Runtime(1)- Self & Super

22. runtime如何通过selector找到对应的IMP地址?(分别考虑类方法和实例方法)

每一个类对象中都一个方法列表,方法列表中记录着方法的名称,方法实现,以及参数类型,其实selector本质就是方法名称,通过这个方法名称就可以在方法列表中找到对应的方法实现.

23. 使用runtime Associate方法关联的对象,需要在主对象dealloc的时候释放么?

在ARC下不需要。

在MRC中,对于使用retain或copy策略的需要 。

在MRC下也不需要

无论在MRC下还是ARC下均不需要。

2011年版本的Apple API 官方文档 - Associative References一节中有一个MRC环境下的例子:

// 在MRC下,使用runtime Associate方法关联的对象,不需要在主对象dealloc的时候释放

// http://weibo.com/luohanchenyilong/ (微博@iOS程序犭袁)

// https://github.com/ChenYilong

// 摘自2011年版本的Apple API 官方文档 - Associative References

static char overviewKey;

NSArray *array =

[[NSArray alloc] initWithObjects:@"One", @"Two", @"Three", nil];

// For the purposes of illustration, use initWithFormat: to ensure

// the string can be deallocated

NSString *overview =

[[NSString alloc] initWithFormat:@"%@", @"First three numbers"];

objc_setAssociatedObject (

array,

&overviewKey,

overview,

OBJC_ASSOCIATION_RETAIN

);

[overview release];

// (1) overview valid

[array release];

// (2) overview invalid

文档指出

At point 1, the stringoverviewis still valid because theOBJC_ASSOCIATION_RETAINpolicy specifies that the array retains the associated object. When the array is deallocated, however (at point 2),overviewis released and so in this case also deallocated.

我们可以看到,在[array release];之后,overview就会被release释放掉了。

既然会被销毁,那么具体在什么时间点?

根据WWDC 2011, Session 322 (第36分22秒)中发布的内存销毁时间表,被关联的对象在生命周期内要比对象本身释放的晚很多。它们会在被 NSObject -dealloc 调用的 object_dispose() 方法中释放。

对象的内存销毁时间表,分四个步骤:

// 对象的内存销毁时间表

// http://weibo.com/luohanchenyilong/ (微博@iOS程序犭袁)

// https://github.com/ChenYilong

// 根据 WWDC 2011, Session 322 (36分22秒)中发布的内存销毁时间表

1. 调用 -release :引用计数变为零

* 对象正在被销毁,生命周期即将结束.

* 不能再有新的 __weak 弱引用, 否则将指向 nil.

* 调用 [self dealloc]

2. 子类 调用 -dealloc

* 继承关系中最底层的子类 在调用 -dealloc

* 如果是 MRC 代码 则会手动释放实例变量们(iVars)

* 继承关系中每一层的父类 都在调用 -dealloc

3. NSObject 调 -dealloc

* 只做一件事:调用 Objective-C runtime 中的 object_dispose() 方法

4. 调用 object_dispose()

* 为 C++ 的实例变量们(iVars)调用 destructors

* 为 ARC 状态下的 实例变量们(iVars) 调用 -release

* 解除所有使用 runtime Associate方法关联的对象

* 解除所有 __weak 引用

* 调用 free()

对象的内存销毁时间表:参考链接

24. objc中的类方法和实例方法有什么本质区别和联系?

类方法:

类方法是属于类对象的

类方法只能通过类对象调用

类方法中的self是类对象

类方法可以调用其他的类方法

类方法中不能访问成员变量

类方法中不能直接调用对象方法

实例方法:

实例方法是属于实例对象的

实例方法只能通过实例对象调用

实例方法中的self是实例对象

实例方法中可以访问成员变量

实例方法中直接调用实例方法

实例方法中也可以调用类方法(通过类名)

25._objc_msgForward函数是做什么的,直接调用它将会发生什么?

_objc_msgForward是 IMP 类型,用于消息转发的:当向一个对象发送一条消息,但它并没有实现的时候,_objc_msgForward会尝试做消息转发。

我们可以这样创建一个_objc_msgForward对象:

IMP msgForwardIMP = _objc_msgForward;

objc_msgSend在“消息传递”中的作用。在“消息传递”过程中,objc_msgSend的动作比较清晰:首先在 Class 中的缓存查找 IMP (没缓存则初始化缓存),如果没找到,则向父类的 Class 查找。如果一直查找到根类仍旧没有实现,则用_objc_msgForward函数指针代替 IMP 。最后,执行这个 IMP 。

Objective-C运行时是开源的,所以我们可以看到它的实现。打开Apple Open Source 里Mac代码里的obj包下载一个最新版本,找到objc-runtime-new.mm,进入之后搜索_objc_msgForward。

enter image description here

里面有对_objc_msgForward的功能解释:

enter image description here

/***********************************************************************

* lookUpImpOrForward.

* The standard IMP lookup.

* initialize==NO tries to avoid +initialize (but sometimes fails)

* cache==NO skips optimistic unlocked lookup (but uses cache elsewhere)

* Most callers should use initialize==YES and cache==YES.

* inst is an instance of cls or a subclass thereof, or nil if none is known.

*  If cls is an un-initialized metaclass then a non-nil inst is faster.

* May return _objc_msgForward_impcache. IMPs destined for external use

*  must be converted to _objc_msgForward or _objc_msgForward_stret.

*  If you don't want forwarding at all, use lookUpImpOrNil() instead.

**********************************************************************/

对objc-runtime-new.mm文件里与_objc_msgForward有关的三个函数使用伪代码展示下:

//  objc-runtime-new.mm 文件里与 _objc_msgForward 有关的三个函数使用伪代码展示

//  Created by https://github.com/ChenYilong

//  Copyright (c)  微博@iOS程序犭袁(http://weibo.com/luohanchenyilong/). All rights reserved.

//  同时,这也是 obj_msgSend 的实现过程

id objc_msgSend(id self, SEL op, ...) {

if (!self) return nil;

IMP imp = class_getMethodImplementation(self->isa, SEL op);

imp(self, op, ...); //调用这个函数,伪代码...

}

//查找IMP

IMP class_getMethodImplementation(Class cls, SEL sel) {

if (!cls || !sel) return nil;

IMP imp = lookUpImpOrNil(cls, sel);

if (!imp) return _objc_msgForward; //_objc_msgForward 用于消息转发

return imp;

}

IMP lookUpImpOrNil(Class cls, SEL sel) {

if (!cls->initialize()) {

_class_initialize(cls);

}

Class curClass = cls;

IMP imp = nil;

do { //先查缓存,缓存没有时重建,仍旧没有则向父类查询

if (!curClass) break;

if (!curClass->cache) fill_cache(cls, curClass);

imp = cache_getImp(curClass, sel);

if (imp) break;

} while (curClass = curClass->superclass);

return imp;

}

虽然Apple没有公开_objc_msgForward的实现源码,但是我们还是能得出结论:

_objc_msgForward是一个函数指针(和 IMP 的类型一样),是用于消息转发的:当向一个对象发送一条消息,但它并没有实现的时候,_objc_msgForward会尝试做消息转发。

上篇中的《objc中向一个对象发送消息[obj foo]和objc_msgSend()函数之间有什么关系?》曾提到objc_msgSend在“消息传递”中的作用。在“消息传递”过程中,objc_msgSend的动作比较清晰:首先在 Class 中的缓存查找 IMP (没缓存则初始化缓存),如果没找到,则向父类的 Class 查找。如果一直查找到根类仍旧没有实现,则用_objc_msgForward函数指针代替 IMP 。最后,执行这个 IMP 。

为了展示消息转发的具体动作,这里尝试向一个对象发送一条错误的消息,并查看一下_objc_msgForward是如何进行转发的。

首先开启调试模式、打印出所有运行时发送的消息:

可以在代码里执行下面的方法:

(void)instrumentObjcMessageSends(YES);

或者断点暂停程序运行,并在 gdb 中输入下面的命令:

call (void)instrumentObjcMessageSends(YES)

以第二种为例,操作如下所示:

enter image description here

之后,运行时发送的所有消息都会打印到/tmp/msgSend-xxxx文件里了。

终端中输入命令前往:

open /private/tmp

enter image description here

可能看到有多条,找到最新生成的,双击打开

在模拟器上执行执行以下语句(这一套调试方案仅适用于模拟器,真机不可用,关于该调试方案的拓展链接:Can the messages sent to an object in Objective-C be monitored or printed out?),向一个对象发送一条错误的消息:

//

//  main.m

//  CYLObjcMsgForwardTest

//

//  Created by http://weibo.com/luohanchenyilong/.

//  Copyright (c) 2015年 微博@iOS程序犭袁. All rights reserved.

//

#import

#import "AppDelegate.h"

#import "CYLTest.h"

int main(int argc, char * argv[]) {

@autoreleasepool {

CYLTest *test = [[CYLTest alloc] init];

[test performSelector:(@selector(iOS程序犭袁))];

return UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class]));

}

}

enter image description here

你可以在/tmp/msgSend-xxxx(我这一次是/tmp/msgSend-9805)文件里,看到打印出来:

enter image description here

+ CYLTest NSObject initialize

+ CYLTest NSObject alloc

- CYLTest NSObject init

- CYLTest NSObject performSelector:

+ CYLTest NSObject resolveInstanceMethod:

+ CYLTest NSObject resolveInstanceMethod:

- CYLTest NSObject forwardingTargetForSelector:

- CYLTest NSObject forwardingTargetForSelector:

- CYLTest NSObject methodSignatureForSelector:

- CYLTest NSObject methodSignatureForSelector:

- CYLTest NSObject class

- CYLTest NSObject doesNotRecognizeSelector:

- CYLTest NSObject doesNotRecognizeSelector:

- CYLTest NSObject class

结合《NSObject官方文档》,排除掉 NSObject 做的事,剩下的就是_objc_msgForward消息转发做的几件事:

调用resolveInstanceMethod:方法 (或resolveClassMethod:)。允许用户在此时为该 Class 动态添加实现。如果有实现了,则调用并返回YES,那么重新开始objc_msgSend流程。这一次对象会响应这个选择器,一般是因为它已经调用过class_addMethod。如果仍没实现,继续下面的动作。

调用forwardingTargetForSelector:方法,尝试找到一个能响应该消息的对象。如果获取到,则直接把消息转发给它,返回非 nil 对象。否则返回 nil ,继续下面的动作。注意,这里不要返回 self ,否则会形成死循环。

调用methodSignatureForSelector:方法,尝试获得一个方法签名。如果获取不到,则直接调用doesNotRecognizeSelector抛出异常。如果能获取,则返回非nil:创建一个 NSlnvocation 并传给forwardInvocation:。

调用forwardInvocation:方法,将第3步获取到的方法签名包装成 Invocation 传入,如何处理就在这里面了,并返回非ni。

调用doesNotRecognizeSelector:,默认的实现是抛出异常。如果第3步没能获得一个方法签名,执行该步骤。

上面前4个方法均是模板方法,开发者可以override,由 runtime 来调用。最常见的实现消息转发:就是重写方法3和4,吞掉一个消息或者代理给其他对象都是没问题的

也就是说_objc_msgForward在进行消息转发的过程中会涉及以下这几个方法:

resolveInstanceMethod:方法 (或resolveClassMethod:)。

forwardingTargetForSelector:方法

methodSignatureForSelector:方法

forwardInvocation:方法

doesNotRecognizeSelector:方法

为了能更清晰地理解这些方法的作用,git仓库里也给出了一个Demo,名称叫“_objc_msgForward_demo”,可运行起来看看。

下面回答下第二个问题“直接_objc_msgForward调用它将会发生什么?”

直接调用_objc_msgForward是非常危险的事,如果用不好会直接导致程序Crash,但是如果用得好,能做很多非常酷的事。

就好像跑酷,干得好,叫“耍酷”,干不好就叫“作死”。

正如前文所说:

_objc_msgForward是 IMP 类型,用于消息转发的:当向一个对象发送一条消息,但它并没有实现的时候,_objc_msgForward会尝试做消息转发。

如何调用_objc_msgForward?

_objc_msgForward隶属 C 语言,有三个参数 :

--_objc_msgForward参数类型

1.所属对象id类型

2.方法名SEL类型

3.可变参数可变参数类型

首先了解下如何调用 IMP 类型的方法,IMP类型是如下格式:

为了直观,我们可以通过如下方式定义一个 IMP类型 :

typedef void (*voidIMP)(id, SEL, ...)

一旦调用_objc_msgForward,将跳过查找 IMP 的过程,直接触发“消息转发”,

如果调用了_objc_msgForward,即使这个对象确实已经实现了这个方法,你也会告诉objc_msgSend:

“我没有在这个对象里找到这个方法的实现”

想象下objc_msgSend会怎么做?通常情况下,下面这张图就是你正常走objc_msgSend过程,和直接调用_objc_msgForward的前后差别:

enter image description here

有哪些场景需要直接调用_objc_msgForward?最常见的场景是:你想获取某方法所对应的NSInvocation对象。举例说明:

JSPatch (Github 链接)就是直接调用_objc_msgForward来实现其核心功能的:

JSPatch 以小巧的体积做到了让JS调用/替换任意OC方法,让iOS APP具备热更新的能力。

作者的博文《JSPatch实现原理详解》详细记录了实现原理,有兴趣可以看下。

同时RAC(ReactiveCocoa)源码中也用到了该方法。

26. runtime如何实现weak变量的自动置nil?

runtime 对注册的类, 会进行布局,对于 weak 对象会放入一个 hash 表中。 用 weak 指向的对象内存地址作为 key,当此对象的引用计数为0的时候会 dealloc,假如 weak 指向的对象内存地址是a,那么就会以a为键, 在这个 weak 表中搜索,找到所有以a为键的 weak 对象,从而设置为 nil。

上篇中的《runtime 如何实现 weak 属性》有论述。(注:在上篇的《使用runtime Associate方法关联的对象,需要在主对象dealloc的时候释放么?》里给出的“对象的内存销毁时间表”也提到__weak引用的解除时间。)

我们可以设计一个函数(伪代码)来表示上述机制:

objc_storeWeak(&a, b)函数:

objc_storeWeak函数把第二个参数--赋值对象(b)的内存地址作为键值key,将第一个参数--weak修饰的属性变量(a)的内存地址(&a)作为value,注册到 weak 表中。如果第二个参数(b)为0(nil),那么把变量(a)的内存地址(&a)从weak表中删除,

你可以把objc_storeWeak(&a, b)理解为:objc_storeWeak(value, key),并且当key变nil,将value置nil。

在b非nil时,a和b指向同一个内存地址,在b变nil时,a变nil。此时向a发送消息不会崩溃:在Objective-C中向nil发送消息是安全的。

而如果a是由assign修饰的,则:

在b非nil时,a和b指向同一个内存地址,在b变nil时,a还是指向该内存地址,变野指针。此时向a发送消息极易崩溃。

下面我们将基于objc_storeWeak(&a, b)函数,使用伪代码模拟“runtime如何实现weak属性”:

// 使用伪代码模拟:runtime如何实现weak属性

// http://weibo.com/luohanchenyilong/

// https://github.com/ChenYilong

id obj1;

objc_initWeak(&obj1, obj);

/*obj引用计数变为0,变量作用域结束*/

objc_destroyWeak(&obj1);

下面对用到的两个方法objc_initWeak和objc_destroyWeak做下解释:

总体说来,作用是:

通过objc_initWeak函数初始化“附有weak修饰符的变量(obj1)”,在变量作用域结束时通过objc_destoryWeak函数释放该变量(obj1)。

下面分别介绍下方法的内部实现:

objc_initWeak函数的实现是这样的:在将“附有weak修饰符的变量(obj1)”初始化为0(nil)后,会将“赋值对象”(obj)作为参数,调用objc_storeWeak函数。

obj1 = 0;

obj_storeWeak(&obj1, obj);

也就是说:

weak 修饰的指针默认值是 nil (在Objective-C中向nil发送消息是安全的)

然后obj_destroyWeak函数将0(nil)作为参数,调用objc_storeWeak函数。

objc_storeWeak(&obj1, 0);

前面的源代码与下列源代码相同。

// 使用伪代码模拟:runtime如何实现weak属性

// http://weibo.com/luohanchenyilong/

// https://github.com/ChenYilong

id obj1;

obj1 = 0;

objc_storeWeak(&obj1, obj);

/* ... obj的引用计数变为0,被置nil ... */

objc_storeWeak(&obj1, 0);

objc_storeWeak函数把第二个参数--赋值对象(obj)的内存地址作为键值,将第一个参数--weak修饰的属性变量(obj1)的内存地址注册到 weak 表中。如果第二个参数(obj)为0(nil),那么把变量(obj1)的地址从weak表中删除。

27. 能否向编译后得到的类中增加实例变量?能否向运行时创建的类中添加实例变量?为什么?

不能向编译后得到的类中增加实例变量;

能向运行时创建的类中添加实例变量;

解释下:

因为编译后的类已经注册在 runtime 中,类结构体中的objc_ivar_list实例变量的链表 和instance_size实例变量的内存大小已经确定,同时runtime 会调用class_setIvarLayout或class_setWeakIvarLayout来处理 strong weak 引用。所以不能向存在的类中添加实例变量;

运行时创建的类是可以添加实例变量,调用class_addIvar函数。但是得在调用objc_allocateClassPair之后,objc_registerClassPair之前,原因同上。

28. runloop和线程有什么关系?

总的说来,Run loop,正如其名,loop表示某种循环,和run放在一起就表示一直在运行着的循环。实际上,run loop和线程是紧密相连的,可以这样说run loop是为了线程而生,没有线程,它就没有存在的必要。Run loops是线程的基础架构部分, Cocoa 和 CoreFundation 都提供了 run loop 对象方便配置和管理线程的 run loop (以下都以 Cocoa 为例)。每个线程,包括程序的主线程( main thread )都有与之相应的 run loop 对象。

runloop 和线程的关系:

主线程的run loop默认是启动的。

iOS的应用程序里面,程序启动后会有一个如下的main()函数

int main(int argc, char * argv[]) {

@autoreleasepool {

return UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class]));

}

}

重点是UIApplicationMain()函数,这个方法会为main thread设置一个NSRunLoop对象,这就解释了:为什么我们的应用可以在无人操作的时候休息,需要让它干活的时候又能立马响应。

对其它线程来说,run loop默认是没有启动的,如果你需要更多的线程交互则可以手动配置和启动,如果线程只是去执行一个长时间的已确定的任务则不需要。

在任何一个 Cocoa 程序的线程中,都可以通过以下代码来获取到当前线程的 run loop 。

NSRunLoop *runloop = [NSRunLoop currentRunLoop];

参考链接:《Objective-C之run loop详解》

29. runloop的mode作用是什么?

model 主要是用来指定事件在运行循环中的优先级的,分为:

NSDefaultRunLoopMode(kCFRunLoopDefaultMode):默认,空闲状态

UITrackingRunLoopMode:ScrollView滑动时

UIInitializationRunLoopMode:启动时

NSRunLoopCommonModes(kCFRunLoopCommonModes):Mode集合

苹果公开提供的 Mode 有两个:

NSDefaultRunLoopMode(kCFRunLoopDefaultMode)

NSRunLoopCommonModes(kCFRunLoopCommonModes)

30. 以+ scheduledTimerWithTimeInterval...的方式触发的timer,在滑动页面上的列表时,timer会暂定回调,为什么?如何解决?

RunLoop只能运行在一种mode下,如果要换mode,当前的loop也需要停下重启成新的。利用这个机制,ScrollView滚动过程中NSDefaultRunLoopMode(kCFRunLoopDefaultMode)的mode会切换到UITrackingRunLoopMode来保证ScrollView的流畅滑动:只能在NSDefaultRunLoopMode模式下处理的事件会影响ScrollView的滑动。

如果我们把一个NSTimer对象以NSDefaultRunLoopMode(kCFRunLoopDefaultMode)添加到主运行循环中的时候,

ScrollView滚动过程中会因为mode的切换,而导致NSTimer将不再被调度。

同时因为mode还是可定制的,所以:

Timer计时会被scrollView的滑动影响的问题可以通过将timer添加到NSRunLoopCommonModes(kCFRunLoopCommonModes)来解决。代码如下:

//

// http://weibo.com/luohanchenyilong/ (微博@iOS程序犭袁)

// https://github.com/ChenYilong

//将timer添加到NSDefaultRunLoopMode中

[NSTimer scheduledTimerWithTimeInterval:1.0

target:self

selector:@selector(timerTick:)

userInfo:nil

repeats:YES];

//然后再添加到NSRunLoopCommonModes里

NSTimer *timer = [NSTimer timerWithTimeInterval:1.0

target:self

selector:@selector(timerTick:)

userInfo:nil

repeats:YES];

[[NSRunLoop currentRunLoop] addTimer:timer forMode:NSRunLoopCommonModes];

31. 猜想runloop内部是如何实现的?

一般来讲,一个线程一次只能执行一个任务,执行完成后线程就会退出。如果我们需要一个机制,让线程能随时处理事件但并不退出,通常的代码逻辑

是这样的:

function loop() {

initialize();

do {

var message = get_next_message();

process_message(message);

} while (message != quit);

}

或使用伪代码来展示下:

//

// http://weibo.com/luohanchenyilong/ (微博@iOS程序犭袁)

// https://github.com/ChenYilong

int main(int argc, char * argv[]) {

//程序一直运行状态

while (AppIsRunning) {

//睡眠状态,等待唤醒事件

id whoWakesMe = SleepForWakingUp();

//得到唤醒事件

id event = GetEvent(whoWakesMe);

//开始处理事件

HandleEvent(event);

}

return 0;

}

参考链接:

《深入理解RunLoop》

摘自博文CFRunLoop,原作者是微博@我就叫Sunny怎么了

32. objc使用什么机制管理对象内存?

通过 retainCount 的机制来决定对象是否需要释放。

每次 runloop 的时候,都会检查对象的 retainCount,如果retainCount 为 0,说明该对象没有地方需要继续使用了,可以释放掉了。

33. ARC通过什么方式帮助开发者管理内存?

编译时根据代码上下文,插入 retain/release

ARC相对于MRC,不是在编译时添加retain/release/autorelease这么简单。应该是编译期和运行期两部分共同帮助开发者管理内存。

在编译期,ARC用的是更底层的C接口实现的retain/release/autorelease,这样做性能更好,也是为什么不能在ARC环境下手动retain/release/autorelease,同时对同一上下文的同一对象的成对retain/release操作进行优化(即忽略掉不必要的操作);ARC也包含运行期组件,这个地方做的优化比较复杂,但也不能被忽略。【TODO:后续更新会详细描述下】

34. 不手动指定autoreleasepool的前提下,一个autorealese对象在什么时刻释放?(比如在一个vc的viewDidLoad中创建)

分两种情况:手动干预释放时机、系统自动去释放。

手动干预释放时机--指定autoreleasepool

就是所谓的:当前作用域大括号结束时释放。

系统自动去释放--不手动指定autoreleasepool

Autorelease对象出了作用域之后,会被添加到最近一次创建的自动释放池中,并会在当前的 runloop 迭代结束时释放。

释放的时机总结起来,可以用下图来表示:

autoreleasepool与 runloop 的关系图

下面对这张图进行详细的解释:

从程序启动到加载完成是一个完整的运行循环,然后会停下来,等待用户交互,用户的每一次交互都会启动一次运行循环,来处理用户所有的点击事件、触摸事件。

我们都知道:

所有 autorelease 的对象,在出了作用域之后,会被自动添加到最近创建的自动释放池中。

但是如果每次都放进应用程序的main.m中的 autoreleasepool 中,迟早有被撑满的一刻。这个过程中必定有一个释放的动作。何时?

在一次完整的运行循环结束之前,会被销毁。

那什么时间会创建自动释放池?运行循环检测到事件并启动后,就会创建自动释放池。

子线程的 runloop 默认是不工作,无法主动创建,必须手动创建。

自定义的 NSOperation 和 NSThread 需要手动创建自动释放池。比如: 自定义的 NSOperation 类中的 main 方法里就必须添加自动释放池。否则出了作用域后,自动释放对象会因为没有自动释放池去处理它,而造成内存泄露。

但对于 blockOperation 和 invocationOperation 这种默认的Operation ,系统已经帮我们封装好了,不需要手动创建自动释放池。

@autoreleasepool 当自动释放池被销毁或者耗尽时,会向自动释放池中的所有对象发送 release 消息,释放自动释放池中的所有对象。

如果在一个vc的viewDidLoad中创建一个 Autorelease对象,那么该对象会在 viewDidAppear 方法执行前就被销毁了。

参考链接:《黑幕背后的Autorelease》

35. BAD_ACCESS在什么情况下出现?

访问了野指针,比如对一个已经释放的对象执行了release、访问已经释放对象的成员变量或者发消息。

死循环

36. 苹果是如何实现autoreleasepool的?

autoreleasepool 以一个队列数组的形式实现,主要通过下列三个函数完成.

objc_autoreleasepoolPush

objc_autoreleasepoolPop

objc_autorelease

看函数名就可以知道,对 autorelease 分别执行 push,和 pop 操作。销毁对象时执行release操作。

举例说明:我们都知道用类方法创建的对象都是 Autorelease 的,那么一旦 Person 出了作用域,当在 Person 的 dealloc 方法中打上断点,我们就可以看到这样的调用堆栈信息:

enter image description here

37. 使用block时什么情况会发生引用循环,如何解决?

一个对象中强引用了block,在block中又强引用了该对象,就会发射循环引用。

解决方法是将该对象使用weak或者block修饰符修饰之后再在block中使用。

id weak weakSelf = self;

或者 weak __typeof(&*self)weakSelf = self该方法可以设置宏

id __block weakSelf = self;

或者将其中一方强制制空xxx = nil。

检测代码中是否存在循环引用问题,可使用 Facebook 开源的一个检测工具FBRetainCycleDetector

38. 在block内如何修改block外部变量?

默认情况下,在block中访问的外部变量是复制过去的,即:写操作不对原变量生效。但是你可以加上__block来让其写操作生效,示例代码如下:

__block int a = 0;

void (^foo)(void) = ^{

a = 1;

};

foo();

//这里,a的值被修改为1

这是微博@唐巧_boy的《iOS开发进阶》中的第11.2.3章节中的描述。你同样可以在面试中这样回答,但你并没有答到“点子上”。真正的原因,并没有书这本书里写的这么“神奇”,而且这种说法也有点牵强。面试官肯定会追问“为什么写操作就生效了?”真正的原因是这样的:

我们都知道:Block不允许修改外部变量的值,这里所说的外部变量的值,指的是栈中指针的内存地址。__block所起到的作用就是只要观察到该变量被 block 所持有,就将“外部变量”在栈中的内存地址放到了堆中。进而在block内部也可以修改外部变量的值。

Block不允许修改外部变量的值。Apple这样设计,应该是考虑到了block的特殊性,block也属于“函数”的范畴,变量进入block,实际就是已经改变了作用域。在几个作用域之间进行切换时,如果不加上这样的限制,变量的可维护性将大大降低。又比如我想在block内声明了一个与外部同名的变量,此时是允许呢还是不允许呢?只有加上了这样的限制,这样的情景才能实现。于是栈区变成了红灯区,堆区变成了绿灯区。

我们可以打印下内存地址来进行验证:

__block int a = 0;

NSLog(@"定义前:%p", &a);        //栈区

void (^foo)(void) = ^{

a = 1;

NSLog(@"block内部:%p", &a);    //堆区

};

NSLog(@"定义后:%p", &a);        //堆区

foo();

2016-05-17 02:03:33.559 LeanCloudChatKit-iOS[1505:713679] 定义前:0x16fda86f8

2016-05-17 02:03:33.559 LeanCloudChatKit-iOS[1505:713679] 定义后:0x155b22fc8

2016-05-17 02:03:33.559 LeanCloudChatKit-iOS[1505:713679] block内部: 0x155b22fc8

“定义后”和“block内部”两者的内存地址是一样的,我们都知道 block 内部的变量会被 copy 到堆区,“block内部”打印的是堆地址,因而也就可以知道,“定义后”打印的也是堆的地址。

那么如何证明“block内部”打印的是堆地址?

把三个16进制的内存地址转成10进制就是:

定义后前:6171559672

block内部:5732708296

定义后后:5732708296

中间相差438851376个字节,也就是 418.5M 的空间,因为堆地址要小于栈地址,又因为iOS中一个进程的栈区内存只有1M,Mac也只有8M,显然a已经是在堆区了。

这也证实了:a 在定义前是栈区,但只要进入了 block 区域,就变成了堆区。这才是__block关键字的真正作用。

__block关键字修饰后,int类型也从4字节变成了32字节,这是 Foundation 框架 malloc 出来的。这也同样能证实上面的结论。(PS:居然比 NSObject alloc 出来的 16  字节要多一倍)。

理解到这是因为堆栈地址的变更,而非所谓的“写操作生效”,这一点至关重要,要不然你如何解释下面这个现象:

以下代码编译可以通过,并且在block中成功将a的从Tom修改为Jerry。

NSMutableString *a = [NSMutableString stringWithString:@"Tom"];

NSLog(@"\n 定以前:------------------------------------\n\

a指向的堆中地址:%p;a在栈中的指针地址:%p", a, &a);              //a在栈区

void (^foo)(void) = ^{

a.string = @"Jerry";

NSLog(@"\n block内部:------------------------------------\n\

a指向的堆中地址:%p;a在栈中的指针地址:%p", a, &a);              //a在栈区

a = [NSMutableString stringWithString:@"William"];

};

foo();

NSLog(@"\n 定以后:------------------------------------\n\

a指向的堆中地址:%p;a在栈中的指针地址:%p", a, &a);              //a在栈区

enter image description here

这里的a已经由基本数据类型,变成了对象类型。block会对对象类型的指针进行copy,copy到堆中,但并不会改变该指针所指向的堆中的地址,所以在上面的示例代码中,block体内修改的实际是a指向的堆中的内容。

但如果我们尝试像上面图片中的65行那样做,结果会编译不通过,那是因为此时你在修改的就不是堆中的内容,而是栈中的内容。

上文已经说过:Block不允许修改外部变量的值,这里所说的外部变量的值,指的是栈中指针的内存地址。栈区是红灯区,堆区才是绿灯区。

39. 使用系统的某些block api(如UIView的block版本写动画时),是否也考虑引用循环问题?

系统的某些block api中,UIView的block版本写动画时不需要考虑,但也有一些api 需要考虑:

所谓“引用循环”是指双向的强引用,所以那些“单向的强引用”(block 强引用 self )没有问题,比如这些:

[UIView animateWithDuration:duration animations:^{ [self.superview layoutIfNeeded]; }];

[[NSOperationQueue mainQueue] addOperationWithBlock:^{ self.someProperty = xyz; }];

[[NSNotificationCenter defaultCenter] addObserverForName:@"someNotification"

object:nil

queue:[NSOperationQueue mainQueue]

usingBlock:^(NSNotification * notification) {

self.someProperty = xyz; }];

这些情况不需要考虑“引用循环”。

但如果你使用一些参数中可能含有 ivar 的系统 api ,如 GCD 、NSNotificationCenter就要小心一点:比如GCD 内部如果引用了 self,而且 GCD 的其他参数是 ivar,则要考虑到循环引用:

__weak __typeof__(self) weakSelf = self;

dispatch_group_async(_operationsGroup, _operationsQueue, ^

{

__typeof__(self) strongSelf = weakSelf;

[strongSelf doSomething];

[strongSelf doSomethingElse];

} );

类似的:

__weak __typeof__(self) weakSelf = self;

_observer = [[NSNotificationCenter defaultCenter] addObserverForName:@"testKey"

object:nil

queue:nil

usingBlock:^(NSNotification *note) {

__typeof__(self) strongSelf = weakSelf;

[strongSelf dismissModalViewControllerAnimated:YES];

}];

self --> _observer --> block --> self 显然这也是一个循环引用。

检测代码中是否存在循环引用问题,可使用 Facebook 开源的一个检测工具FBRetainCycleDetector

40. GCD的队列(dispatch_queue_t)分哪两种类型?

串行队列Serial Dispatch Queue

并行队列Concurrent Dispatch Queue

41. 如何用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(), ^{

// 合并图片

});

42.dispatch_barrier_async的作用是什么?

在并行队列中,为了保持某些任务的顺序,需要等待一些任务完成后才能继续进行,使用 barrier 来等待之前任务完成,避免数据竞争等问题。

dispatch_barrier_async函数会等待追加到Concurrent Dispatch Queue并行队列中的操作全部执行完之后,然后再执行dispatch_barrier_async函数追加的处理,等dispatch_barrier_async追加的处理执行结束之后,Concurrent Dispatch Queue才恢复之前的动作继续执行。

打个比方:比如你们公司周末跟团旅游,高速休息站上,司机说:大家都去上厕所,速战速决,上完厕所就上高速。超大的公共厕所,大家同时去,程序猿很快就结束了,但程序媛就可能会慢一些,即使你第一个回来,司机也不会出发,司机要等待所有人都回来后,才能出发。dispatch_barrier_async函数追加的内容就如同 “上完厕所就上高速”这个动作。

(注意:使用dispatch_barrier_async,该函数只能搭配自定义并行队列dispatch_queue_t使用。不能使用:dispatch_get_global_queue,否则dispatch_barrier_async的作用会和dispatch_async的作用一模一样。 )

43. 苹果为什么要废弃dispatch_get_current_queue?

dispatch_get_current_queue容易造成死锁

44. 以下代码运行结果如何?

- (void)viewDidLoad

{

[super viewDidLoad];

NSLog(@"1");

dispatch_sync(dispatch_get_main_queue(), ^{

NSLog(@"2");

});

NSLog(@"3");

}

只输出:1 。发生主线程锁死。

45. addObserver:forKeyPath:options:context:各个参数的作用分别是什么,observer中需要实现哪个方法才能获得KVO回调?

// 添加键值观察

/*

1 观察者,负责处理监听事件的对象

2 观察的属性

3 观察的选项

4 上下文

*/

[self.person addObserver:self forKeyPath:@"name" options:NSKeyValueObservingOptionNew | NSKeyValueObservingOptionOld context:@"Person Name"];

observer中需要实现一下方法:

// 所有的 kvo 监听到事件,都会调用此方法

/*

1. 观察的属性

2. 观察的对象

3. change 属性变化字典(新/旧)

4. 上下文,与监听的时候传递的一致

*/

- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context;

46. 如何手动触发一个value的KVO

所谓的“手动触发”是区别于“自动触发”:

自动触发是指类似这种场景:在注册 KVO 之前设置一个初始值,注册之后,设置一个不一样的值,就可以触发了。

想知道如何手动触发,必须知道自动触发 KVO 的原理:

键值观察通知依赖于 NSObject 的两个方法:willChangeValueForKey:和didChangevlueForKey:。在一个被观察属性发生改变之前,willChangeValueForKey:一定会被调用,这就

会记录旧的值。而当改变发生后,observeValueForKey:ofObject:change:context:会被调用,继而didChangeValueForKey:也会被调用。如果可以手动实现这些调用,就可以实现“手动触发”了。

那么“手动触发”的使用场景是什么?一般我们只在希望能控制“回调的调用时机”时才会这么做。

具体做法如下:

如果这个value是  表示时间的self.now,那么代码如下:最后两行代码缺一不可。

相关代码已放在仓库里。

//  .m文件

//  Created by https://github.com/ChenYilong

//  微博@iOS程序犭袁(http://weibo.com/luohanchenyilong/).

//  手动触发 value 的KVO,最后两行代码缺一不可。

//@property (nonatomic, strong) NSDate *now;

- (void)viewDidLoad {

[super viewDidLoad];

_now = [NSDate date];

[self addObserver:self forKeyPath:@"now" options:NSKeyValueObservingOptionNew context:nil];

NSLog(@"1");

[self willChangeValueForKey:@"now"]; // “手动触发self.now的KVO”,必写。

NSLog(@"2");

[self didChangeValueForKey:@"now"]; // “手动触发self.now的KVO”,必写。

NSLog(@"4");

}

但是平时我们一般不会这么干,我们都是等系统去“自动触发”。“自动触发”的实现原理:

比如调用setNow:时,系统还会以某种方式在中间插入wilChangeValueForKey:、didChangeValueForKey:和observeValueForKeyPath:ofObject:change:context:的调用。

大家可能以为这是因为setNow:是合成方法,有时候我们也能看到有人这么写代码:

- (void)setNow:(NSDate *)aDate {

[self willChangeValueForKey:@"now"]; // 没有必要

_now = aDate;

[self didChangeValueForKey:@"now"];// 没有必要

}

这完全没有必要,不要这么做,这样的话,KVO代码会被调用两次。KVO在调用存取方法之前总是调用willChangeValueForKey:,之后总是调用didChangeValueForkey:。怎么做到的呢?答案是通过 isa 混写(isa-swizzling)。下文《apple用什么方式实现对一个对象的KVO?》会有详述。

参考链接:Manual Change Notification---Apple 官方文档

47. 若一个类有实例变量NSString *_foo,调用setValue:forKey:时,可以以foo还是_foo作为key?

都可以。

48. KVC的keyPath中的集合运算符如何使用?

必须用在集合对象上或普通对象的集合属性上

简单集合运算符有@avg, @count , @max , @min ,@sum,

格式 @"@sum.age"或 @"集合属性.@max.age"

49. KVC和KVO的keyPath一定是属性么?

KVC 支持实例变量,KVO 只能手动支持手动设定实例变量的KVO实现监听

50. 如何关闭默认的KVO的默认实现,并进入自定义的KVO实现?

请参考:

《如何自己动手实现 KVO》

KVO for manually implemented properties

51. apple用什么方式实现对一个对象的KVO?

Apple 的文档对 KVO 实现的描述:

Automatic key-value observing is implemented using a technique called isa-swizzling... When an observer is registered for an attribute of an object the isa pointer of the observed object is modified, pointing to an intermediate class rather than at the true class ...

Apple 的文档可以看出:Apple 并不希望过多暴露 KVO 的实现细节。不过,要是借助 runtime 提供的方法去深入挖掘,所有被掩盖的细节都会原形毕露:

当你观察一个对象时,一个新的类会被动态创建。这个类继承自该对象的原本的类,并重写了被观察属性的 setter 方法。重写的 setter 方法会负责在调用原 setter 方法之前和之后,通知所有观察对象:值的更改。最后通过isa 混写(isa-swizzling)把这个对象的 isa 指针 ( isa 指针告诉 Runtime 系统这个对象的类是什么 ) 指向这个新创建的子类,对象就神奇的变成了新创建的子类的实例。我画了一张示意图,如下所示:

enter image description here

KVO 确实有点黑魔法:

Apple 使用了isa 混写(isa-swizzling)来实现 KVO 。

下面做下详细解释:

键值观察通知依赖于 NSObject 的两个方法:willChangeValueForKey:和didChangevlueForKey:。在一个被观察属性发生改变之前,willChangeValueForKey:一定会被调用,这就会记录旧的值。而当改变发生后,observeValueForKey:ofObject:change:context:会被调用,继而didChangeValueForKey:也会被调用。可以手动实现这些调用,但很少有人这么做。一般我们只在希望能控制回调的调用时机时才会这么做。大部分情况下,改变通知会自动调用。

比如调用setNow:时,系统还会以某种方式在中间插入wilChangeValueForKey:、didChangeValueForKey:和observeValueForKeyPath:ofObject:change:context:的调用。大家可能以为这是因为setNow:是合成方法,有时候我们也能看到有人这么写代码:

- (void)setNow:(NSDate *)aDate {

[self willChangeValueForKey:@"now"]; // 没有必要

_now = aDate;

[self didChangeValueForKey:@"now"];// 没有必要

}

这完全没有必要,不要这么做,这样的话,KVO代码会被调用两次。KVO在调用存取方法之前总是调用willChangeValueForKey:,之后总是调用didChangeValueForkey:。怎么做到的呢?答案是通过 isa 混写(isa-swizzling)。第一次对一个对象调用addObserver:forKeyPath:options:context:时,框架会创建这个类的新的 KVO 子类,并将被观察对象转换为新子类的对象。在这个 KVO 特殊子类中, Cocoa 创建观察属性的 setter ,大致工作原理如下:

- (void)setNow:(NSDate *)aDate {

[self willChangeValueForKey:@"now"];

[super setValue:aDate forKey:@"now"];

[self didChangeValueForKey:@"now"];

}

这种继承和方法注入是在运行时而不是编译时实现的。这就是正确命名如此重要的原因。只有在使用KVC命名约定时,KVO才能做到这一点。

KVO 在实现中通过isa 混写(isa-swizzling)把这个对象的 isa 指针 ( isa 指针告诉 Runtime 系统这个对象的类是什么 ) 指向这个新创建的子类,对象就神奇的变成了新创建的子类的实例。这在Apple 的文档可以得到印证:

Automatic key-value observing is implemented using a technique called isa-swizzling... When an observer is registered for an attribute of an object the isa pointer of the observed object is modified, pointing to an intermediate class rather than at the true class ...

然而 KVO 在实现中使用了isa 混写( isa-swizzling),这个的确不是很容易发现:Apple 还重写、覆盖了-class方法并返回原来的类。 企图欺骗我们:这个类没有变,就是原本那个类。。。

但是,假设“被监听的对象”的类对象是MYClass,有时候我们能看到对NSKVONotifying_MYClass的引用而不是对MYClass的引用。借此我们得以知道 Apple 使用了isa 混写(isa-swizzling)。具体探究过程可参考这篇博文

那么wilChangeValueForKey:、didChangeValueForKey:和observeValueForKeyPath:ofObject:change:context:这三个方法的执行顺序是怎样的呢?

wilChangeValueForKey:、didChangeValueForKey:很好理解,observeValueForKeyPath:ofObject:change:context:的执行时机是什么时候呢?

先看一个例子:

代码已放在仓库里。

- (void)viewDidLoad {

[super viewDidLoad];

[self addObserver:self forKeyPath:@"now" options:NSKeyValueObservingOptionNew context:nil];

NSLog(@"1");

[self willChangeValueForKey:@"now"]; // “手动触发self.now的KVO”,必写。

NSLog(@"2");

[self didChangeValueForKey:@"now"]; // “手动触发self.now的KVO”,必写。

NSLog(@"4");

}

- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context {

NSLog(@"3");

}

enter image description here

如果单单从下面这个例子的打印上,

顺序似乎是wilChangeValueForKey:、observeValueForKeyPath:ofObject:change:context:、didChangeValueForKey:。

其实不然,这里有一个observeValueForKeyPath:ofObject:change:context:, 和didChangeValueForKey:到底谁先调用的问题:如果observeValueForKeyPath:ofObject:change:context:是在didChangeValueForKey:内部触发的操作呢? 那么顺序就是:wilChangeValueForKey:、didChangeValueForKey:和observeValueForKeyPath:ofObject:change:context:

不信你把didChangeValueForKey:注视掉,看下observeValueForKeyPath:ofObject:change:context:会不会执行。

了解到这一点很重要,正如46. 如何手动触发一个value的KVO所说的:

“手动触发”的使用场景是什么?一般我们只在希望能控制“回调的调用时机”时才会这么做。

而“回调的调用时机”就是在你调用didChangeValueForKey:方法时。

52. IBOutlet连出来的视图属性为什么可以被设置成weak?

参考链接:Should IBOutlets be strong or weak under ARC?

文章告诉我们:

因为既然有外链那么视图在xib或者storyboard中肯定存在,视图已经对它有一个强引用了。

不过这个回答漏了个重要知识,使用storyboard(xib不行)创建的vc,会有一个叫_topLevelObjectsToKeepAliveFromStoryboard的私有数组强引用所有top level的对象,所以这时即便outlet声明成weak也没关系

53. IB中User Defined Runtime Attributes如何使用?

它能够通过KVC的方式配置一些你在interface builder 中不能配置的属性。当你希望在IB中作尽可能多得事情,这个特性能够帮助你编写更加轻量级的viewcontroller

54. 如何调试BAD_ACCESS错误

重写object的respondsToSelector方法,现实出现EXEC_BAD_ACCESS前访问的最后一个object

通过 Zombie

enter image description here

设置全局断点快速定位问题代码所在行

Xcode 7 已经集成了BAD_ACCESS捕获功能:Address Sanitizer

用法如下:在配置中勾选✅Enable Address Sanitizer

enter image description here

55. lldb(gdb)常用的调试命令?

breakpoint 设置断点定位到某一个函数

n 断点指针下一步

po打印对象

更多 lldb(gdb) 调试命令可查看

The LLDB Debugger

苹果官方文档:iOS Debugging Magic


实战篇

新手必看

下面的文章是笔者推荐大家阅读的:

iOS学习路线

iOS开发必懂

做好面试的准备

在收到通知到xxxx公司面试前,我们应该好好阅读下面的文章,做好充分的准备,不打无准备之仗。 我也面试过不少同学,在与他们的交谈中可以看出准备不足,面对一个个简单的问题,答非所问或者说了半天没有说到点上,于是给面试官一种菜鸟的形象。

本人只是本科毕业,本以为研究生会对所学知识更加巩固,但是我面试过几个研究生,真的让我很失望。当然,对于每一个让我面试过的同学,我都会给他/她建议。

记住一点:我们可以不是高手,但是绝对不能告诉面试官我们是菜鸟,至少要拿出点本事来证明。

第一关:笔试题

iOS基础C/C++笔试题

ObjC基础笔试题集锦一

ObjC基础笔试题集锦二

宝库iOS开发笔试题

要出发公司iOS笔试题

iOS中高级笔试题一

iOS中高级笔试题二

南京JingDong笔试题

YouKu iOS笔试题一

YouKu iOS笔试题二

第二关:面试题

通常第二面是技术面,技术面面试官会先看看面试者的简历和笔试题,然后根据这些信息提出一些问题。

iOS面试一般性问题

iOS中级面试题一

iOS中级面试题二

招聘一个靠谱的iOS(上)

招聘一个靠谱的iOS(下)

大浪淘沙公司iOS面试题

BAT面试题

BAT面试指南试答百度一面

BAT面试指南试答百度二面

BAT面试指南试答百度三面

写在最后

文章中难免有说得不合理的地方,如果您认为说法不正确或者哪里有错误的地方,请在评论中留言,笔者会在第一时间修正!!!



作者:CatherinePlans
链接:http://www.jianshu.com/p/a1111480e4a7
來源:简书
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。


0