iOS面试题(初、中级)

1.请描述copy,retain,assign,weak的作用

copy: 表示赋值特性,setter方法将传入对象复制一份;需要完全一份新的变量时。
retain: 表示持有特性,setter方法将传入参数先保留,再赋值,传入参数的retaincount会+1;
assign: 是赋值特性,setter方法将传入参数赋值给实例变量;仅设置变量时;
weak:弱应用,但最后一个strong型指针不再指向对象,那么对象就会被释放,同时所有的weak型指针都将会被清除

2.frame跟bounds有什么区别,苹果为什么要这么设计?

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

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

3.但处理属性声明的时候,原子(atomic)跟非原子(non-atomic)属性有什么区别?

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

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

4.block有几种类型,分别描述他们在内存中的位置?

3种
_NSConcreteGlobalBlock 全局静态

_NSConcreteStackBlock 保存在栈中,出函数作用域就销毁

_NSConcreteMallocBlock 保存在堆中,retainCount == 0销毁

5.尽可能详细的描述响应链条

如果view的控制器存在,就传递给控制器;如果控制器不存在,则将其传递给它的父视图,在视图层次结构的最顶级视图,
如果也不能处理收到的事件或消息,则其将事件或消息传递给window对象进行处理,如果window对象也不处理,
则其将事件或消息传递给UIApplication对象,如果UIApplication也不能处理该事件或消息,则将其丢弃
First Responser -- > The Window -- >TheApplication -- > App Delegate

6.浅拷贝跟深拷贝的区别?

浅拷贝(影子克隆):只复制对象的基本类型,对象类型,仍属于原来的引用.
深拷贝(深度克隆):不紧复制对象的基本类,同时也复制原对象中的对象.就是说完全是新对象产生的.
浅拷贝和深拷贝之间的区别:浅拷贝是指将对象中的数值类型的字段拷贝到新的对象中,而对象中的引用型字段则指复制它的一个引用到目标对象。
如果改变目标对象 中引用型字段的值他将反映在原是对象中,也就是说原始对象中对应的字段也会发生变化。
深拷贝与浅拷贝不同的是对于引用的处理,深拷贝将会在新对象中创建一 个新的和原是对象中对应字段相同(内容相同)的字段,
也就是说这个引用和原是对象的引用是不同的,我们在改变新对象中的这个字段的时候是不会影响到原始对 象中对应字段的内容。所以对于原型模式也有不同的两种处理方法:
对象的浅拷贝和深拷贝

7._block的作用是什么?_weak的作用是什么?他们对于内存的影响是怎样的?

block下循环引用的问题:
__block本身并不能避免循环引用,避免循环引用需要在block内部把__block修饰的obj置为nil
__weak可以避免循环引用,但是其会导致外部对象释放了之后,block 内部也访问不到这个对象的问题,
我们可以通过在 block 内部声明一个 __strong
的变量来指向 weakObj,使外部对象既能在 block 内部保持住,又能避免循环引用的问题

__block与__weak功能上的区别:
__block会持有该对象,即使超出了该对象的作用域,该对象还是会存在的,直到block对象从堆上销毁;
而__weak仅仅是将该对象赋值给weak对象,当该对象销毁时,weak对象将指向nil;
__block可以让block修改局部变量,而__weak不能。
另外,MRC中__block是不会引起retain;但在ARC中__block则会引起retain。所以ARC中应该使用__weak。

8.线程和进程的区别

(1)一个线程只能属于一个进程,而一个进程可以有多个线程,但至少有一个线程。线程是[操作系统](http://lib.csdn.net/base/operatingsystem)
可识别的最小执行和调度单位。
   (2)资源分配给进程,同一进程的所有线程共享该进程的所有资源。 同一进程中的多个线程共享代码段(代码和常量),数据段(全局变量和静态变量),扩展段(堆存储)。
但是每个线程拥有自己的栈段,栈段又叫运行时段,用来存放所有局部变量和临时变量。
    (3)处理机分给线程,即真正在处理机上运行的是线程。
    (4)线程在执行过程中,需要协作同步。不同进程的线程间要利用消息通信的办法实现同步。
    进程和线程并不是一一对应的,一个程序执行在不同的数据集上就成为不同的进程,
可以用进程控制块来唯一地标识每个进程。而这一点正是程序无法做到的,由于程序没有和数据产生直接的联系,即使是执行不同的数据的程序,
他们的指令的集合依然是一样的,所以无法唯一地标识出这些运行于不同数据集上的程序。一般来说,一个进程肯定有一个与之对应的程序,而且只有一个。
而一个程序有可能没有与之对应的进程(因为它没有执行),也有可能有多个进程与之对应(运行在几个不同的数据集上)

9.描述线程,runloop的关系

RunLoop 的作用就是来管理线程的,当线程的 RunLoop,开启后,线程就会在执行完任务后,处于休眠状态,随时等待接受新的任务,而不是退出。
只有主线程的RunLoop是默认开启的,所以程序在开启后,会一直运行,不会退出。其他线程的RunLoop
如果需要开启,就手动开启。

10.描述自动释放池在什么时候释放?

自动释放池以栈的形式实现:当你创建一个新的自动释放池时,它将被添加到 
栈顶.当一个对象收到autorelease消息的时候,它被添加到当前线程的处于 
栈顶的的自动释放池中,当自动释放池被回收时,他们就从栈中被删除,并且会 
给池子里面的所有对象都会做一次release操作

11.导致界面卡顿的原因,可以从CPU,GPU两方面回答?

CPU
一:对象操作
1)对象创建 
2)对象调整
3)对象销毁
二:排版
1)布局计算
2)Autolayout
3)文本计算
4)太多的layer或者几何图形
三:绘制
1)文本绘制
2)图片的解码
3)图像的绘制
GPU
一:接收提交的纹理(Texture)和顶点描述(三角形)
二:应用变化(transform)、混合并渲染
1)纹理的渲染
2)视图的混合
3)图形的生成
三:输出到屏幕

12.执行如下的代码会发生什么情况?为什么?

Ball *ball = [[[Ball alloc]init]autorelease]autorelease];
会崩溃
对象执行两次autorelease意味着自动释放池销毁的时候 对象会执行两次release操作 会报野指针错误 

13.我们说的objc是动态运行时语言是什么意思?尽可能多的描述你所知道的以及运行时在项目中的应用场景。

动态:
主要是将数据类型的确定由编译时,推迟到了运行时。
这个问题其实浅涉及到两个概念,运行时和多态。 简单来说,运行时机制使我们直到运行时才去决定一个对象的类别,以及调用该类别对象指定方法。
OC的动态特性表现为了三个方面:动态类型、动态绑定、动态加载。之所以叫做动态,是因为必须到运行时(run time)才会做一些事情。
(1)动态类型
动态类型,说简单点就是id类型。动态类型是跟静态类型相对的。像内置的明确的基本类型都属于静态类型(int、NSString等)。
静态类型在 编译的时候就能被识别出来。所以,若程序发生了类型不对应,编译器就会发出警告。
而动态类型就编译器编译的时候是不能被识别的,要等到运行时(run time),即程序运行的时候才会根据语境来识别。所以这里面就有两个概念要分清:编译时跟运行时。

id obj = someInstance;

if ([obj isKindOfClass:someClass]) {    

someClass *classSpecifiedInstance = (someClass *)obj;    

}
(2)动态绑定
动态绑定(dynamic binding)貌似比较难记忆,但事实上很简单,只需记住关键词@selector/SEL即可。
先来看看“函数”,对于其他一些静态语言,比如 c++,一般在编译的时候就已经将将要调用的函数的函数签名都告诉编译器了。
静态的,不能改变。而在OC中,其实是没有函数的概念的,我们叫“消息机 制”,所谓的函数调用就是给对象发送一条消息。
这时,动态绑定的特性就来了。OC可以先跳过编译,到运行的时候才动态地添加函数调用,在运行时才决定要调 用什么方法,需要传什么参数进去。
这就是动态绑定,要实现他就必须用SEL变量绑定一个方法。最终形成的这个SEL变量就代表一个方法的引用。
这里要注意 一点:SEL并不是C里面的函数指针,虽然很像,但真心不是函数指针。SEL变量只是一个整数,他是该方法的ID,@selector()就是取类方法的编号。
以前的函数调用,是根据函数名,也就是 字符串去查找函数体。但现在,我们是根据一个ID整数来查找方法,整数的查找字自然要比字符串的查找快得多!
所以,动态绑定的特定不仅方便,而且效率更 高。

由于OC的动态特性,在OC中其实很少提及“函数”的概念,传统的函数一般在编译时就已经把参数信息和函数实现打包到编译后的源码中了,而在OC中最常使 用的是消息机制。
调用一个实例的方法,所做的是向该实例的指针发送消息,实例在收到消息后,从自身的实现中寻找响应这条消息的方法
(3)动态加载
根据需求加载所需要的资源,这点很容易理解,对于iOS开发来说,基本就是根据不同的机型做适配。最经典的例子就是在Retina设备上加载@2x的图片,而在老一些的普通屏设备上加载原图。

14.数组和链表的区别

(http://www.cnblogs.com/FCWORLD/archive/2010/11/20/1882391.html)

数组是将元素在内存中连续存放,由于每个元素占用内存相同,可以通过下标迅速访问数组中任何元素。
但是如果要在数组中增加一个元素,需要移动大量元素,在内存中空出一个元素的空间,然后将要增加的元素放在其中。
同样的道理,如果想删除一个元素,同样需要移动大量元素去填掉被移动的元素。
如果应用需要快速访问数据,很少或不插入和删除元素,就应该用数组。

链表恰好相反,链表中的元素在内存中不是顺序存储的,而是通过存在元素中的指针联系到一起。
比如:上一个元素有个指针指到下一个元素,以此类推,直到最后一个元素。如果要访问链表中一个元素,需要从第一个元素开始,一直找到需要的元素位置。
但是增加和删除一个元素对于链表数据结构就非常简单了,只要修改元素中的指针就可以了。
如果应用需要经常插入和删除元素你就需要用链表数据结构了。
   C++语言中可以用数组处理一组数据类型相同的数据,但不允许动态定义数组的大小,即在使用数组之前必须确定数组的大小。
而在实际应用中,用户使用数组之前有时无法准确确定数组的大小,
只能将数组定义成足够大小,这样数组中有些空间可能不被使用,从而造成内存空间的浪费。****链表是一种常见的数据组织形式,它采用动态分配内存的形式实现。
需要时可以用new分配内存空间,不需要时用delete将已分配的空间释放,不会造成内存空间的浪费。
(1) 从逻辑结构角度来看    
 a, 数组必须事先定义固定的长度(元素个数),不能适应数据动态地增减的情况。当数据增加时,可能超出原先定义的元素个数;当数据减少时,造成内存浪费。     
b,链表动态地进行存储分配,可以适应数据动态地增减的情况,且可以方便地插入、删除数据项。
(数组中插入、删除数据项时,需要移动其它数据项)  
(2)从内存存储角度来看    
 a,(静态)数组从栈中分配空间, 对于程序员方便快速,但自由度小。    
 b, 链表从堆中分配空间, 自由度大但申请管理比较麻烦.

15.KVC 跟KVO有啥联系

KVC
KVC : 键值编码,是Key Value Coding 的简称,cocoa的标准组成部分,是一种可以直接通过字符串的名字(Key)来访问类属性的机制,
而不是通过调用Setter方法、Getter方法进行访问。
KVC是一个用于间接访问对象属性的机制(只是通过字符串访问,而不是访问器方法去访问一个对象实例变量的机制),
使用该机制不需要调用set或get方法和“->”方法访问成员变量,而是通过setValue:forKey: 和 valueForKey:方法进行成员变量的访问,
将在内部查找名为_key或key的成员变量,如果找不到,就会报错。
KVC的使用环境:无论是property还是普通的全局属性变量,都可以使用KVC;
KVC优点:1.主要的好处就是减少代码量;2.没有property的变量(即:私有变量private)也能通过KVC进行设置。
KVC缺点:如果key只写错,编写的时候不会报错,但是运行的时候会报错;
KVO 
KVO : 键值监听,是Key Value ObserVing 的简称,当指定对象的属性被修改之后,允许对象接收到通知的机制。
KVO:是一个对象能够观察另外一个对象的属性的值,并且能够发现值得变化。
KVO适合一个任意类型的对象对另外的对象进行监听,当被监听的对象一旦发生改变,观察者马上做出反应。
但是也只能对属性作出反应,而不会对方法或动作作出反应。
KVO优点:1.能够提供一种简单的方法实现两个对象的同步;
2、能够对内部对象的状态改变作出响应,而且不需要改变内部对象的实现;
3.能够提供被观察者属性的最新值和之前的值;
4.使用key Path来观察属性,因此可以观察嵌套对象;
5.完成了对观察对象的抽象,因为不需要额外的代码来允许观察者被观察。
KVO缺点:
1.我们观察的属性必须使用strings定义,编译时不会出现警告;
2.对属性重构,将导致观察代码不可用;
3.复杂的 if 语句要求对象正在观察多个值,是因为所有的观察代码通过一个方法来指向;
4.当释放观察者的时候不需要移除观察者。

总结一下
1.KVO KVC 没联系
2.KVO 是监听属性值的改变
3.KVO 底层实现原理是系统给当前类创建子类 , 在子类 setter 方法调用父类的 setter 方法

16.消息转发机制,消息发送为什么会失败?

重定向
在消息转发机制执行前,Runtime 系统会再给我们一次偷梁换柱的机会,即通过重载- (id)forwardingTargetForSelector:(SEL)aSelector
方法替换消息的接受者为其他对象:
1234567
- (id)forwardingTargetForSelector:(SEL)aSelector{ if(aSelector == @selector(mysteriousMethod:)){ return alternateObject; } return [super forwardingTargetForSelector:aSelector];}

毕竟消息转发要耗费更多时间,抓住这次机会将消息重定向给别人是个不错的选择,~~不过千万别返回self
,因为那样会死循环。~~ 如果此方法返回nil或self,则会进入消息转发机制(forwardInvocation:
);否则将向返回的对象重新发送消息。
[](http://yulingtianxia.com/blog/2014/11/05/objective-c-runtime/#转发)转发
当动态方法解析不作处理返回NO
时,消息转发机制会被触发。在这时forwardInvocation:
方法会被执行,我们可以重写这个方法来定义我们的转发逻辑:
12345678

- (void)forwardInvocation:(NSInvocation *)anInvocation{ if ([someOtherObject respondsToSelector: [anInvocation selector]]) [anInvocation invokeWithTarget:someOtherObject]; else [super forwardInvocation:anInvocation];}

该消息的唯一参数是个NSInvocation
类型的对象——该对象封装了原始的消息和消息的参数。我们可以实现forwardInvocation:
方法来对不能处理的消息做一些默认的处理,也可以将消息转发给其他对象来处理,而不抛出错误。
这里需要注意的是参数anInvocation
是从哪的来的呢?其实在forwardInvocation:
消息发送前,Runtime系统会向对象发送methodSignatureForSelector:
消息,并取到返回的方法签名用于生成NSInvocation
对象。所以我们在重写forwardInvocation:
的同时也要重写methodSignatureForSelector:
方法,否则会抛异常。
当一个对象由于没有相应的方法实现而无法响应某消息时,运行时系统将通过forwardInvocation:
消息通知该对象。每个对象都从NSObject
类中继承了forwardInvocation:
方法。然而,NSObject
中的方法实现只是简单地调用了doesNotRecognizeSelector:
。通过实现我们自己的forwardInvocation:
方法,我们可以在该方法实现中将消息转发给其它对象。
forwardInvocation:
方法就像一个不能识别的消息的分发中心,将这些消息转发给不同接收对象。或者它也可以象一个运输站将所有的消息都发送给同一个接收对象。
它可以将一个消息翻译成另外一个消息,或者简单的”吃掉“某些消息,因此没有响应也没有错误。forwardInvocation:
方法也可以对不同的消息提供同样的响应,
这一切都取决于方法的具体实现。该方法所提供是将不同的对象链接到消息链的能力。
注意: forwardInvocation:
方法只有在消息接收对象中无法正常响应消息时才会被调用。 所以,如果我们希望一个对象将negotiate
消息转发给其它对象,则这个对象不能有negotiate
方法。否则,forwardInvocation:
将不可能会被调用。

消息发送和转发流程可以概括为:消息发送(Messaging)是 Runtime 通过 selector 快速查找 IMP 的过程,
有了函数指针就可以执行对应的方法实现;消息转发(Message Forwarding)
是在查找 IMP 失败后执行一系列转发流程的慢速通道,如果不作转发处理,则会打日志和抛出异常。

17.使用NStimer要注意什么?

存在延迟
不管是一次性的还是周期性的timer的实际触发事件的时间,都会与所加入的RunLoop和RunLoop Mode有关,
如果此RunLoop正在执行一个连续性的运算,timer就会被延时出发。重复性的timer遇到这种情况,
如果延迟超过了一个周期,则会在延时结束后立刻执行,并按照之前指定的周期继续执行

同一个timer在重复使用之前必需invalidate
同一个timer在重复使用之前必需invalidate, 否则会造成之前的timer无法停掉,两个timer同时存在。导致的现象就是timer同时更新两次。

不要在dealloc函数中停止并释放NSTimer
如果这样做,会导致对象永远无法调用dealloc函数,也就是会造成内存泄漏。一个比较合理的解释是NSTimer的回调方法具有retain属性,所以不停止它的情况下被引用对象的retainCount无法降为0,导致内存泄漏的死循环。
不用scheduled方式初始化的,需要将timer添加到runloop中
NSTimer *myTimer = [NSTimer timerWithTimeInterval:3.0 target:self selector:@selector(timerFired:) userInfo:nilrepeats:NO];
[[NSRunLoopcurrentRunLoop] addTimer:myTimer forMode:NSDefaultRunLoopMode];
滑动UIScrollView的时候

当RunLoop处于UITrackingRunLoopMode模式的时候(滑动UIScrollView的时候),使用
scheduledTimerWithTimeInterval:(NSTimeInterval)seconds
                    invocation:(NSInvocation *)invocation
                       repeats:(BOOL)repeats

的类方法创建的Timer,是不会收到响应事件。只有RunLoop切换到Default模式时才可以正常响应。如果希望滑动时也可以响应Timer时间,需要把Timer加到RunLoop并指定模式为NSRunLoopCommonModes。

18.获取Wi-Fi信息的框架是什么?

SystemConfiguration.famework

附上框架总览:http://blog.csdn.net/hdfqq188816190/article/details/50725314

19.应用程序的生命周期:appdelegate每个方法的介绍

一。appdelegate每个方法的简单介绍:
 
1、应用程序启动,并进行初始化时候调用该方法:aaaplication:didFimnishLanuchingWithOptions:
 
2、应用进入前台并处于活动状态时候调用:applicationDidBecomeActive:
 
3、应用从活动状态进入到非活动状态:applicationWillResignActive :
 
4、应用进入到后台时候调用的方法:applicationDidEnterBackground:
 
5、应用进入到前台时候调用的方法:appplicationWillEnterForeground:
 
6、applicationWillTeminate:应用被终止的状态:

总结:
1.从这个过程我们就知道,appdelegate的每个方法会对应一个通知,没当调用那个方法的时候,就会发出那个方法对应的通知;
2.下面的几个场景,我就不一一说明,直接贴图了;
3.再贴图之前,先把appdelegate每个方法对应的通知都贴出来;
方法 本地通知

didFimnishLanuchingWithOptions: UIApplicationDidFinishLaunchingNotification
applicationDidBecomeActive  UIApplicationDidBecomeActiveNOtification
applicationWillResignActive     UIApplicationWillResignActiveNotification
applicationDidEnterBackground   UIApplicationDidEnterBackgroundNotification
appplicationWillEnterForeground UIApplicationwillEnterForegroundNotification
applicationWillTeminate UIApplicationWillTeminateNotification

你可能感兴趣的:(iOS面试题(初、中级))