今天博主有一个iOS核心面试题的需求,遇到了一些困难点,在此和大家分享,希望能够共同进步.
1.怎么用 copy 关键字?
一般使用 retain 或者 strong 修饰属性时,是使引用对象的指针指向同一个对象,即为同一块内存地址。只要其中有一个指针变量被修改时所有其他引用该对象的变量都会被改变。
而使用 copy 关键字修饰在赋值时是释放旧对象,拷贝新对象内容。重新分配了内存地址。以后该指针变量被修改时就不会影响旧对象的内容了。
copy 只有实现NSCopying协议的对象类型才有效。
常用于NSString和Block。
2.这个写法会出什么问题: @@property (copy) NSMutableArray *array;
当一个NSMutableArray对象使用 initWithArray: 初始化方法创建时,并将该对象赋值给了array属性。那么之后array执行可变数组的方法,比如: removeObjectAtIndex: 时会出现unrecognized selector sent to instance的崩溃。原因在于array属性在被赋值(setter)的时候默认执行了copy方法后变为了不可变NSArray对象。
3.如何让自己的类用 copy 修饰符?如何重写带 copy 关键字的 setter?
该类必须要实现NSCopying协议。实现 - (id)copyWithZone:(NSZone *)zone; 方法。
重写copy关键字的setter时,需要调用一下传入对象的copy方法。然后赋值给该setter的方法对应的成员变量。
4.@property 的本质是什么?ivar、getter、setter 是如何生成并添加到这个类中的.
property在编译时编译器会自动的为我们生成一个私有的成员变量和setter与getter方法的声明和实现。反编译property大致生成五个东西
OBJC IVAR $类名$属性名称 该属性的偏移量
setter与getter方法对应的实现函数
ivar_list 就是成员变量列表
method_list 方法列表
prop_list 属性列表
也就是说我们每次在增加一个属性,系统都会在ivar_list中添加一个成员变量的描述,在method_list中增加setter与getter方法的描述,在属性列表中增加一个属性的描述,然后计算该属性在对象中的偏移量,然后产生setter与getter方法对应的实现,在setter方法方法中从偏移量的位置开始赋值,在getter方法中从偏移量开始取值,为了能够读取正确字节数,系统对象偏移量的指针类型进行了类型强转。
5.@protocol 和 category 中如何使用 @property
在protocol中使用property只会生成setter和getter方法声明,我们使用属性的目的,是希望遵守我协议的对象的实现该属性
category 使用 @property 也是只会生成setter和getter方法的声明,如果我们真的需要给category增加属性的实现,需要借助于运行Objective-C动态运行机制中的两个函数:
objc_setAssociatedObject
objc_getAssociatedObject
6.@synthesize和@dynamic分别有什么作用?
@property有两个对应的词,一个是@synthesize,一个是@dynamic。如果@synthesize和@dynamic都没写,那么默认的就是@syntheszie var = var,var为property变量。可以手动修改属性var对应的实例变量。例如:@syntheszie var = var1
@synthesize的语义是如果你没有手动实现setter方法和getter方法,那么编译器会自动为你加上这两个方法, 在Xcode4.4之后的版本可以省略不写.
@dynamic告诉编译器不要自动生成成员变量的getter和setter方法,而是开发者自己手工生成或者运行时生成.
7.用@property声明的NSString(或NSArray,NSDictionary)经常使用copy关键字,为什么?如果改用strong关键字,可能造成什么问题?
使用 copy 的目的是为了让本对象的属性不受外界影响,使用copy无论给我传入是一个可变对象还是不可对象,我本身持有的就是一个不可变的副本.
如果使用 strong .这个属性有可能指向一个可变对象,如果这个可变对象被外部意外的修改了,由于可变对象被改变之后起始地址不会发生变化。而附有strong修饰的属性依然指向这这块内存地址,下次读取的时候就会是被改变以后的对象了.也就是说如果使用 strong 可能会被外部意外的修改。
8.objc中向一个对象发送消息[obj foo]和objc_msgSend()函数之间有什么关系?
[obj foo]变量编译之后就是objc_msgSend()函数的调用。该方法编译后的形式大致如下:objc_msgSend(obj, sel_registerName(“foo”));
9.一个objc对象如何进行内存布局?(考虑有父类的情况)
每一个objc对象都是一个类的实例。在Objective-C语言的内部,每一个对象都有一个名为isa的指针,指向该对象的类(类对象)。每一个类描述了一系列它的实例的特点,包括成员变量的列表,成员函数的列表等。每一个对象都可以接受消息,而对象能够接收的消息列表是保存在它所对应的类对象中。如下图:
类对象内部还有一个指向superclass的指针,指向他的父类对象,根类为NSObject类它的superclass指针指向nil,如下图:
类对象既然称为对象那它也是一个实例。类对象中也有一个isa指针指向它的元类(meta class),即类对象是元类的实例。元类内部存放的是类方法列表,根元类的isa指针指向自己,superclass指针指向NSObject类
10.一个objc对象的isa的指针指向什么?有什么作用?
objc对象的isa指针指向类对象,用于寻找对象的方法。
11.objc中的类方法和实例方法有什么本质区别和联系?
类方法
类方法保存在类对象的元类中
类方法只能通过类对象调用
类方法中的self是类对象
类方法中不能访问成员变量
类方法中不能直接调用对象方法
实例方法
保存在类对象的对象模型中
实例方法只能通过实例对象调用
实例方法中的self是实例对象
实例方法中可以访问成员变量
实例方法中可以访问成员变量
实例方法中可以访问成员变量
12.runtime如何实现weak变量的自动置nil?
/* 编译器的模拟源代码 */
id obj1;
objc_initWeak(&obj1, obj);
objc_destroyWeak(&obj1);
通过objc_initWeak函数初始化附有 weak修饰符的变量,在变量作用域结束时通过objc_destoryWeak函数释放该变量。objc_initWeak函数将附有 weak修饰符的变量初始化为0后,将会赋值的对象作为参数调用objc_storeWeak函数。
obj1 = 0;
obj_storeWeak(&obj1, obj);
然后obj_destroyWeak函数将0作为参数的调用objc_storeWeak函数。
obj_destroyWeak(&obj1, 0);
前面的源代码与下列源代码相同。
/ * 编译器的模拟代码 */
id obj1;
obj1 = 0;
objc_storeWeak&obj1, obj);
objc_storeWeak(&obj1, 0);
objc_storeWeak函数的第二参数的赋值对象的地址作为键值,将第一个参数的附有__weak修饰符的变量的地址注册到weak表中。如果第二参数为0,则把变量的地址从weak表中删除。
13._objc_msgForward函数是做什么的,直接调用它将会发生什么?
这个没接触过,也没找到相关资料。但是跟objc动态运行机制中的 forwardingTargetForSelector: 和 forwardInvocation: 方法有点相似。猜测是该方法编译后的源码,都用于消息转发机制
14.以+ scheduledTimerWithTimeInterval…的方式触发的timer,在滑动页面上的列表时,timer会暂定回调,为什么?如何解决?
当前主线程的 RunLoop 正在以 UITrackingRunLoopMode 的模式运行。 这个时候 RunLoop 只会处理与 UITrackingRunLoopMode “绑定”的源, 比如触摸、滚动等事件;而 NSTimer 是默认“绑定”到 NSRunLoopDefaultMode 上的, 所以 Timer 是事情是不会被 RunLoop 处理的,定时器被暂停了!
常见的解决方案是把Timer“绑定”到 NSRunLoopCommonModes 模式上:
[[NSRunLoop currentRunLoop] addTimer:timer forMode:NSRunLoopCommonModes];
>
这样这个Timer就可以和当前组中的两种模式 UITrackingRunLoopMode 和 kCFRunLoopDefaultMode 相关联了。 RunLoop在这两种模式下,Timer都可以正常运行了。
15.BAD_ACCESS在什么情况下出现?
对一个已经释放的对象执行了release
执行了死循环
16.苹果是如何实现autoreleasepool的?
autoreleasepool以一个队列数组的形式实现,主要通过下列三个函数完成.
objc_autoreleasepoolPush
objc_autoreleasepoolPop
objc_aurorelease
看函数名就可以知道,对autorelease分别执行push,和pop操作。销毁对象时执行release操作。
17.如何用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(), ^{
// 合并图片
});
18.dispatch_barrier_async的作用是什么?
dispatch_barrier_async函数会等待追加到Concurrent Dispatch Queue并行队列中的操作全部执行完之后,然后再执行dispatch_barrier_async函数追加的处理,等dispatch_barrier_async追加的处理执行结束之后,Concurrent Dispatch Queue才恢复之前的动作继续执行。
打个比方:比如你在看电视每个台都在播放精彩的内容,突然插进来一则广告。于是你就换台,结果发现每个台都在放这个广告,等这个广告播放完之后,各个台才开始恢复原来播放的内容。dispatch_barrier_async函数追加的内容就如这条广告。
19.KVC的keyPath中的集合运算符如何使用?
必须用在集合对象上或普通对象的集合属性上
简单集合运算符有@avg, @count , @max , @min ,@sum,
格式 @”@sum.age”或 @”集合属性.@max.age”
20.如何调试BAD_ACCESS错误
重写object的respondsToSelector方法,现实出现EXEC_BAD_ACCESS前访问的最后一个object
通过NSZombieEnabled
设置全局断点快速定位问题代码所在行
21.__block 和 __weak 之间的区别.
• 前者用于指明当前声明的变量在被 block 捕获之后, 可以在 block 中改变变量的值. 因为在 block 声明的同时会截获该 block 所使用的全部自动变量的值, 而这些值只在 block 中只具有"使用权"而不具有"修改权". 而 __block 说明符就为 block 提供了变量的修改权.
• 后者是所有权修饰符, 什么是所有权修饰符? 这里涉及到另一个问题, 因为在 ARC 有效时, id 类型和对象类型同 C 语言中的其他类型不同, 必须附加所有权修饰符. 所有权修饰符一种有 4 种:
◦ __strong
◦ __weak
◦ __unsafe_unretained
◦ __autorelease
· __weak 与 weak 的区别只在于, 前者用于变量的声明, 而后者用于属性的声明.
22.什么是method swizzling?
在Objective-C中调用一个方法,其实是向一个对象发送消息,查找消息的唯一依据是selector的名字。利用Objective-C的动态特性,可以实现在运行时偷换selector对应的方法实现,达到给方法挂钩的目的。
每个类都有一个方法列表,存放着selector的名字和方法实现的映射关系。IMP有点类似函数指针,指向具体的Method实现。
23.为什么其他语言里叫函数调用,Object-C里则叫给我对象发消息(或者谈下对runtime的理解)
在java中,类和方法在编译期就绑定在一起
在OC中,方法调用是向类发送消息,如(bady cry)在运行时会转换成objc_msgSend(bady,cry),向对象发送消息时根据isa指针找到类,在根据类的调度表查找方法,没找到方法则在父类中查找直至基类,如果始终没有找到返回nil
Objective-C 的 Runtime 铸就了它动态语言的特性,这些深层次的知识虽然平时写代码用的少一些,但是却是每个 Objc 程序员需要了解的。Objc Runtime使得C具有了面向对象能力,在程序运行时创建,检查,修改类、对象和它们的方法。可以使用runtime的一系列方法实现。
24.TCP和UDP有什么区别?
TCP是面向连接的,建立连接需要经历三次握手,保证数据正确性和数据顺序
UDP是非连接的协议,传送数据受生成速度,传输带宽等限制,可能造成丢包
UDP一台服务端可以同时向多个客户端传输信息
TCP报头体积更大,对系统资源要求更多
25.+(void)load; +(void)initialize;有什么用处?
当类对象被引入项目时, runtime 会向每一个类对象发送 load 消息. load 方法还是非常的神奇的, 因为它会在每一个类甚至分类被引入时仅调用一次, 调用的顺序是父类优先于子类, 子类优先于分类. 而且 load 方法不会被类自动继承, 每一个类中的 load 方法都不需要像 viewDidLoad 方法一样调用父类的方法. 由于 load 方法会在类被 import 时调用一次, 而这时往往是改变类的行为的最佳时机. 我在 DKNightVersion 中使用 method swizlling 来修改原有的方法时, 就是在分类 load 中实现的.
initialize 方法和 load 方法有一些不同, 它虽然也会在整个 runtime 过程中调用一次, 但是它是在该类的第一个方法执行之前调用, 也就是说 initialize 的调用是惰性的, 它的实现也与我们在平时使用的惰性初始化属性时基本相同. 我在实际的项目中并没有遇到过必须使用这个方法的情况, 在该方法中主要做静态变量的设置并用于确保在实例初始化前某些条件必须满足.
在Objective-C中,runtime会自动调用每个类的两个方法。+load会在类初始加载时调用,+initialize会在第一次调用类的类方法或实例方法之前被调用。这两个方法是可选的,且只有在实现了它们时才会被调用。
共同点:两个方法都只会被调用一次。
26.使用drawRect有什么影响?(这个可深可浅,你至少得用过。。)
drawRect方法依赖Core Graphics框架来进行自定义的绘制,但这种方法主要的缺点就是它处理touch事件的方式:每次按钮被点击后,都会用setNeddsDisplay进行强制重绘;而且不止一次,每次单点事件触发两次执行。这样的话从性能的角度来说,对CPU和内存来说都是欠佳的。特别是如果在我们的界面上有多个这样的UIButton实例。
这个方法的主要作用是根据传入的 rect 来绘制图像 参见文档. 这个方法的默认实现没有做任何事情, 我们可以在这个方法中使用 Core Graphics 和 UIKit 来绘制视图的内容.
这个方法的调用机制也是非常特别. 当你调用 setNeedsDisplay 方法时, UIKit 将会把当前图层标记为 dirty, 但还是会显示原来的内容, 直到下一次的视图渲染周期, 才会为标记为 dirty 的图层重新建立 Core Graphics 上下文, 然后将内存中的数据恢复出来, 再使用 CGContextRef 进行绘制.
27.sip是什么?
1> SIP(Session Initiation Protocol),会话发起协议
2> SIP是建立VOIP连接的 IETF 标准,IETF是全球互联网最具权威的技术标准化组织
3> 所谓VOIP,就是网络电话,直接用互联网打电话,不用耗手机话费
28.AFN 与 ASI 有什么区别
1> AFN基于NSURL,ASI基于底层的CFNetwork框架,因此ASI的性能优于AFN
2> AFN采取block的方式处理请求,ASI最初采取delegate的方式处理请求,后面也增加了block的方式
3> AFN只封装了一些常用功能,满足基本需求,直接忽略了很多扩展功能,比如没有封装同步请求;ASI提供的功能较多,预留了各种接口和工具供开发者自行扩展
4> AFN直接解析服务器返回的JSON、XML等数据,而ASI比较原始,返回的是NSData二进制数据
29.在异步线程中下载很多图片,如果失败了,该如何处理?请结合RunLoop来谈谈解决方案.(提示:在异步线程中启动一个RunLoop重新发送网络请求,下载图片)
1> 重新下载图片
2> 下载完毕, 利用RunLoop的输入源回到主线程刷新UIImageVIUew
30.如果后期需要增加数据库中的字段怎么实现,如果不使用CoreData呢?
编写SQL语句来操作原来表中的字段
1> 增加表字段
ALTER TABLE 表名 ADD COLUMN 字段名 字段类型;
2> 删除表字段
ALTER TABLE 表名 DROP COLUMN 字段名;
3> 修改表字段
ALTER TABLE 表名 RENAME COLUMN 旧字段名 TO 新字段名;
31.简单描述下客户端的缓存机制?
1. 缓存可以分为:内存数据缓存、数据库缓存、文件缓存
2. 每次想获取数据的时候
1> 先检测内存中有无缓存
2> 再检测本地有无缓存(数据库文件)
3> 最终发送网络请求
4> 将服务器返回的网络数据进行缓存(内存、数据库、文件), 以便下次读取
32.利用Socket建立网络连接的步骤
建立Socket连接至少需要一对套接字,其中一个运行于客户端,称为ClientSocket ,另一个运行于服务器端,称为ServerSocket 。
套接字之间的连接过程分为三个步骤:服务器监听,客户端请求,连接确认。
1。服务器监听:服务器端套接字并不定位具体的客户端套接字,而是处于等待连接的状态,实时监控网络状态,等待客户端的连接请求。
2。客户端请求:指客户端的套接字提出连接请求,要连接的目标是服务器端的套接字。为此,客户端的套接字必须首先描述它要连接的服务器的套接字,指出服务器端套接字的地址和端口号,然后就向服务器端套接字提出连接请求。
3。连接确认:当服务器端套接字监听到或者说接收到客户端套接字的连接请求时,就响应客户端套接字的请求,建立一个新的线程,把服务器端套接字的描述发给客户端,一旦客户端确认了此描述,双方就正式建立连接。而服务器端套接字继续处于监听状态,继续接收其他客户端套接字的连接请求