iOS面试题(2)



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


答案:当您向一个对象发送一个autorelease 消息时,Cocoa就会将该对象
的一个引用放入到最新的自动释放池。

它仍然是个正当的对象,因此
自动释放池定义的作用域内的其它对象可以向它发送消息。当 程序
执行到作用域

结束的位置时,自动释放池就会被释放,池中的所有对
象也就被释放。 
ojc-c 是 通过一种"referring counting

"(引用计数)的方式来管理内
存的, 对象在开始分配内存(alloc)的时候引用计数为一,以后每当碰
到有copy,retain

的时候引用计数都会加一, 每当碰到release和
autorelease的时候引用计数就会减一,如果此对象的计数变为了0,

 
就会被系统销毁. 
2. NSAutoreleasePool 就是用来做引用计数的管理工作的,这个东西
一般不用你管的. 


3. autorelease和release没什么区别,只是引用计数减一的时机不
同而已,autorelease会在对象的使用真正结束

的时候才做引用计数
减1.


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

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

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


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


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


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


5,copy,setter方法进行Copy操作,与retain处理流程一样,先旧值
release,再Copy出新的对象,

retainCount为1。这是为了减少对上下文
的依赖而引入的机制。 


6,nonatomic,非原子性访问,不加同步,多线程并发访问会提高性能。
注意,如果不加此属性,

则默认是两个访问方法都为原子型事务访问。锁
被加到所属对象实例级。


ViewController 的 loadView, viewDidLoad, viewDidUnload 
分别是在什么时候调用的?

在自定义ViewController的时候这几个
函数里面应该做什么工作?

答案:
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 。


类工厂方法是什么
 

答案:类工厂方法的实现是为了向客户提供方便,它们将分配和初始化合在一个步骤中,返回
被创建的对象,

并
进行自动释放处理。这些方法的形式是+ (type)className...
(其中 className不包括任何前缀)。
工厂

方法可能不 仅仅为了方便使用。它们不但可以将分配和初始化合在一起,还可以 为
初始化过程提供对
象的分

配信息。
类工厂方法的 另一个目的是使类(比如NSWorkspace)提供单件实例。虽 然init...方法
可以确认一


个类在每次程序运行过程只存在一个实例,但它需要首先分配一个“生的”实
例,然后还必须释放该实例。
工厂

 方法则可以避免为可能没有用的对象盲目分配内存。
 



为什么很多内置类如UITableViewController的delegate属性都是assign而不是
retain的?


答:会引起循环引用。
这里delegate我们只是想得到实现了它delegate方法的对象,然后拿到这个对象的

指针就
可以了,
我们不期望去改变它或者做别的什么操作,所以我们只要用assign拿到它的指针就可以了。


而 用retain的话,计数器加1。我们有可能在别的地方期望释放掉delegate这个对象,
然后通过一些判断

比如说它是否已经被释放,做一些操作。但是 实际上它retainCount还
是1,没有被释放掉,要在UITableViewControlle

r的dealloc里面才被释放掉(这里我只是
举个 例子,一般retain的对象都是在dealloc里被释放)。这里就

会造成一些问题出现。
而如果你确定不会有冲突的问题出现的话,或者你也希望用到delegate的这个对象,

直到
你不用它为止,那么用retain也未尝不可,只是需要最后release一次。


.main() 

 {  

   int a[5]={1,2,3,4,5};  

   int *ptr=(int *)(&a+1);   

   printf("%d,%d",*(a+1),*(ptr-1)); 

} 

答:2,5 

     *(a+1)就是a[1],*(ptr-1)就是a[4],执行结果是2,5 

  &a+1不是首地址+1,系统会认为加一个a数组的偏移,是偏移了一个数组的大小(本例是5个int) 

  int *ptr=(int *)(&a+1); 

  则ptr实际是&(a[5]),也就是a+5 

原因如下: 

  &a是数组指针,其类型为 int (*)[5]; 

  而指针加1要根据指针类型加上一定的值,不同类型的指针+1之后增加的大小不同。 

  a是长度为5的int数组指针,所以要加 5*sizeof(int) 

  所以ptr实际是a[5] 

  但是prt与(&a+1)类型是不一样的(这点很重要) 

  所以prt-1只会减去sizeof(int*) 

  a,&a的地址是一样的,但意思不一样 

    a是数组首地址,也就是a[0]的地址,&a是对象(数组)首地址, 

    a+1是数组下一元素的地址,即a[1],&a+1是下一个对象的地址,即a[5]. 



以下为Windows NT下的32位C++程序,请计算sizeof的值 

void Func ( char str[100] )  

{  

  sizeof( str ) = ?  

}  

void *p = malloc( 100 ); 

sizeof ( p ) = ? 

答:这题很常见了,Func ( char str[100] )函数中数组名作为函数形参时,在函数体内,

数组名失去了本身的内涵,仅仅只是一个指针;在失去其内涵的同时,它还失去了其常量特性,

可以作自增、自减等操作,可以被修改。Windows NT 32位平台下,指针的长度(占用内存的大小)

为4字节,故sizeof( str ) 、sizeof ( p ) 都为4。 


- (void)*getNSString(const NSString * inputString) 

     inputString = @"This is a main test\n"; 

     return ; 

-main(void) 

NSString *a=@"Main"; 

 NSString *aString = [NSString stringWithString:@"%@",getNSString(a)]; 

 NSLog(@"%@\n", aString); 

答:最后问输出的字符串:NULL,output在函数返回后,内存已经被释放。 


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

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

我在这想看到几件事情:  

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

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

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

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


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

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

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

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

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

三重条件操作符的知识。这个操作符存在C语言中的原因是它使得编译器能产生比 if-then-else 更优化的代码,了解这个用法是很重要的。 懂得在宏中小心地把参数用括号括起来  我也用这个问题开始讨论宏的副作用,例如:当你写下面的代码时会发生什么事?  least = MIN(*p++, b);  

结果是: 

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

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


static 关键字的作用: 

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

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

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

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

它的模块内; 

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

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


#import跟#include的区别,@class呢? 

 @class一般用于头文件中需要声明该类的某个实例变量的时候用到,在m文件中还是需要使用#import 

而#import比起#include的好处就是不会引起交叉编译 


MVC模式的理解 

MVC设计模式考虑三种对象:模型对象、视图对象、和控制器对象。模型对象代表特别的知识和专业技能,

它们负责保有应用程序的数据和定义操作数据的逻辑。视图对象知道如何显示应用程序的模型数据,而且

可能允许用户对其进行编辑。控制器对象是应用程序的视图对象和模型对象之间的协调者。 


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

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

进程之间通信的途径 

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

进程死锁的原因 

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

死锁的4个必要条件 

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

死锁的处理 

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



堆和栈的区别 

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

容易产生memory leak。 

申请大小: 

栈:在Windows下,栈是向低地址扩展的数据结构,是一块连续的内存的区域。这句话的意思是栈顶的地址和栈的

最大容量是系统预先规定好的,在WINDOWS下,栈的大小是2M(也有的说是1M,总之是一个编译时就确定的常数),

如果申请的空间超过栈的剩余空间时,将提示overflow。因此,能从栈获得的空间较小。 

堆:堆是向高地址扩展的数据结构,是不连续的内存区域。这是由于系统是用链表来存储的空闲内存地址的,自然是不连续的,

而链表的遍历方向是由低地址向高地址。堆的大小受限于计算机系统中有效的虚拟内存。由此可见,堆获得的空间比较灵活,也比较大。 

碎片问题:对于堆来讲,频繁的new/delete势必会造成内存空间的不连续,从而造成大量的碎片,使程序效率降低。

对于栈来讲,则不会存在这个问题,因为栈是先进后出的队列,他们是如此的一一对应,以至于永远都不可能有一个内存块从栈中间弹出 

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

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


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.



目标-动作机制 

  答案:

目标是动作消息的接收者。一个控件,或者更为常见的是它的单元,以插座变量(参见"插座变量"部分) 

的形式保有其动作消息的目标。 

动作是控件发送给目标的消息,或者从目标的角度看,它是目标为了响应动作而实现的方法。 

程序需要某些机制来进行事件和指令的翻译。这个机制就是目标-动作机制。



objc的内存管理 

答案:  如果您通过分配和初始化(比如[[MyClass alloc] init])的方式来创建对象,您就拥 有这个对象,

需要负责该对象的释放。这个规则在使用NSObject的便利方法new 时也同样适用。 如果您拷贝一个对象,

您也拥有拷贝得到的对象,需要负责该对象的释放。 如果您保持一个对象,您就部分拥有这个对象,需要

在不再使用时释放该对象。 反过来,如果您从其它对象那里接收到一个对象,则您不拥有该对象,也不应

该释放它(这个规则有少数 的例外,在参考文档中有显式的说明)。 


单件实例是什么 

答案:

Foundation 和 Application Kit 框架中的一些类只允许创建单件对象,即这些类在当前进程中的唯一实例。

举例来说,NSFileManager 和NSWorkspace 类在使用时都是基于进程进行单件对象的实例化。当向这些类

请求实例的时候,它们会向您传递单一实例的一个引用,如果该实例还不存在,则首先进行实例的分配和初始化

。单件对象充当控制中心的角色,负责指引或协调类的各种服务。如果类在概念上只有一个实例(比如 NSWorkspace),

就应该产生一个单件实例,而不是多个实例;如果将来某一天可能有多个实例,您可以使用单件实例机制,

而不是工厂方法或函数。 



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

答案:

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

进程和线程的主要差别在于它们是不同的操作系统资源管理方式。进程有独立的地址空间,一个进程崩溃后,

在保护模式下不会对其它进程产生影响,而线程只是一个进程中的不同执行路径。线程有自己的堆栈和局部变量,

但线程之间没有单独的地址空间,一个线程死掉就等于整个进程死掉,所以多进程的程序要比多线程的程序健壮,

但在进程切换时,耗费资源较大,效率要差一些。但对于一些要求同时进行并且又要共享某些变量的并发操作,

只能用线程,不能用进程


定义属性时,什么情况使用copy,assign,和retain?

答案:assign用于简单数据类型,如NSInteger,double,boolretain和copy用于对象copy用于当a指向一个对象,

b也想指向同样的对象的时候,如果用assign,a如果释放,再调用b会crash,如果用copy 的方式,a和b各自有自己的内存,

就可以解决这个问题。retain 会使计数器加1,也可以解决assign的问题。另外:atomic和nonatomic用来决定编译器

生成的getter和setter是否为原子操作。在多线程环境下,原子操作是必要的,否则有可能引起错误的结果。?加了

atomic,setter函数会变成下面这样:

if (property != newValue) 

{

  [property release];

  property = [newValue retain];


对象是在什么时候被release的?

答案:

引用计数为0时。

autorelease实际上只是把对release的调用延迟了,对于每一个Autorelease,系统只是把该Object放入了

当前的Autorelease pool中,当该pool被释放时,该pool中的所有Object会被调用Release。对于每一个Runloop,

 系统会隐式创建一个Autorelease pool,这样所有的release pool会构成一个象CallStack一样的一个栈式结构,

在每一个Runloop结束时,当前栈顶的Autorelease pool会被销毁,这样这个pool里的每个Object(就是autorelease的对象)

会被release。那什么是一个Runloop呢 一个UI事件,Timer call, delegate call, 都会是一个新的Runloop。



IPhone OS有没有垃圾回收?

没有。iPhone开发的时候没有垃圾回收机制


ViewController 的 loadView, viewDidLoad, viewDidUnload 分别是在什么时候调用的?

在自定义ViewController的时候这几个函数里面应该做什么工作?

答案:由init、loadView、viewDidLoad、viewDidUnload、dealloc的关系说起

init方法在init方法中实例化必要的对象(遵从LazyLoad思想)init方法中初始化ViewController本身

loadView方法当view需要被展示而它却是nil时,viewController会调用该方法。不要直接调用该方法。如果手工维护views,必须重载重写该方法如果使用IB维护views,必须不能重载重写该方法loadView和IB构建view你在控制器中实现了loadView方法,那么你可能会在应用运行的某个时候被内存管理控制调用。 如果设备内存不足的时候, view 控制器会收到didReceiveMemoryWarning的消息。 默认的实现是检查当前控制器的view是否在使用。 如果它的view不在当前正在使用的view hierarchy里面,且你的控制器实现了loadView方法,那么这个view将被release, loadView方法将被再次调用来创建一个新的view。

viewDidLoad方法viewDidLoad 此方法只有当view从nib文件初始化的时候才被调用。重载重写该方法以进一步定制view在iPhone OS 3.0及之后的版本中,还应该重载重写viewDidUnload来释放对view的任何索引viewDidLoad后调用数据Model

viewDidUnload方法当系统内存吃紧的时候会调用该方法(注:viewController没有被dealloc)内存吃紧时,在iPhone OS 3.0之前didReceiveMemoryWarning是释放无用内存的唯一方式,但是OS 3.0及以后viewDidUnload方法是更好的方式在该方法中将所有IBOutlet(无论是property还是实例变量)置为nil(系统release view时已经将其release掉了)在该方法中释放其他与view有关的对象、其他在运行时创建(但非系统必须)的对象、在viewDidLoad中被创建的对象、缓存数据等 release对象后,将对象置为nil(IBOutlet只需要将其置为nil,系统release view时已经将其release掉了)

一般认为viewDidUnload是viewDidLoad的镜像,因为当view被重新请求时,viewDidLoad还会重新被执行viewDidUnload中被release的对象必须是很容易被重新创建的对象(比如在viewDidLoad或其他方法中创建的对象),不要release用户数据或其他很难被重新创建的对象

dealloc方法viewDidUnload和dealloc方法没有关联,dealloc还是继续做它该做的事情








  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值