iOS面试题

iOS面试题及详解(上篇)

2016-06-05  本文已影响737人  LZM轮回

多线程、特别是NSOperation 和 GCD 的内部原理。运行时机制的原理和运用场景。
SDWebImage的原理。实现机制。如何解决TableView卡的问题。
block和代理的,通知的区别。block的用法需要注意些什么。
strong,weak,retain,assign,copy nomatic 等的区别。
设计模式,mvc,单利,工厂,代理等的应用场景。
单利的写法。在单利中创建数组应该注意些什么。
NSString 的时候用copy和strong的区别。
响应值链。
NSTimer 在子线程中应该手动创建NSRunLoop ,否则不能循环执行。
UIScrollView和NSTimer组合做循环广告图轮播的时候有一个属性可以控制当上下滚动tableview的时候广告轮播图依然正常滚动。
Xcode最新的自动布局。。。这个很多公司都用。尽量自学下。
git ,和svn的用法。。。git的几个命令简单的记下。。。
友盟报错可以查到具体某一行的错误,原理是什么。
Instrument 可以检测 电池的耗电量、和内存的消耗。的用法。
动画CABaseAnimation CAKeyAni…. CATrans….. CAGoup…. 等熟悉。。
ARC的原理。
自己写过什么自定义控件就最好了。。
———————————————回答好上面的足够了-------------------------------------
__block和__weak修饰符的区别其实是挺明显的:
1.__block不管是ARC还是MRC模式下都可以使用,可以修饰对象,还可以修饰基本数据类型。
2.__weak只能在ARC模式下使用,也只能修饰对象(NSString),不能修饰基本数据类型(int)。
3.__block对象可以在block中被重新赋值,__weak不可以。
tableView 滑动卡的问题主要是因为:从缓存中或者是从本地读取图片给UIImage的时候耗费的时间。需要把下面的两句话放到子线程里面:
NSDataimgData=[NSDatadataWithContentsOfURL:[NSURLURLWithString:app.icon]];//得到图像数据
UIImage
image=[UIImageimageWithData:imgData];
把UIImage赋值给图片的时候在主线程。
子线程不能更新UI 所有的UI跟新都是主线程执行了。手指滑动屏幕了。或者屏幕的某个方法执行了。
子线程里面加入NSTimer 的时候需要 手动添加NSRunloop 否则不能循环。
单利里面添加 NSMutableArray 的时候,防止多个地方对它同时便利和修改的话,需要加原子属性。并且用strong,,,并且写一个遍历和修改的方法。加上锁。 Lock UnLock
__weak ViewController* weakSelf = self;
GCD里面用 __weak 防止内存释放不了,循环引用。
二、SDWebImage内部实现过程
入口 setImageWithURL:placeholderImage:options: 会先把 placeholderImage 显示,然后 SDWebImageManager 根据 URL 开始处理图片。
进入 SDWebImageManager-downloadWithURL:delegate:options:userInfo:,交给 SDImageCache 从缓存查找图片是否已经下载 queryDiskCacheForKey:delegate:userInfo:.
先从内存图片缓存查找是否有图片,如果内存中已经有图片缓存,SDImageCacheDelegate 回调 imageCache:didFindImage:forKey:userInfo: 到 SDWebImageManager。
SDWebImageManagerDelegate 回调 webImageManager:didFinishWithImage: 到 UIImageView+WebCache 等前端展示图片。
如果内存缓存中没有,生成 NSInvocationOperation 添加到队列开始从硬盘查找图片是否已经缓存。
根据 URLKey 在硬盘缓存目录下尝试读取图片文件。这一步是在 NSOperation 进行的操作,所以回主线程进行结果回调 notifyDelegate:。
如果上一操作从硬盘读取到了图片,将图片添加到内存缓存中(如果空闲内存过小,会先清空内存缓存)。SDImageCacheDelegate 回调 imageCache:didFindImage:forKey:userInfo:。进而回调展示图片。
如果从硬盘缓存目录读取不到图片,说明所有缓存都不存在该图片,需要下载图片,回调 imageCache:didNotFindImageForKey:userInfo:。
共享或重新生成一个下载器 SDWebImageDownloader 开始下载图片。
图片下载由 NSURLConnection 来做,实现相关 delegate 来判断图片下载中、下载完成和下载失败。
connection:didReceiveData: 中利用 ImageIO 做了按图片下载进度加载效果。
connectionDidFinishLoading: 数据下载完成后交给 SDWebImageDecoder 做图片解码处理。
图片解码处理在一个 NSOperationQueue 完成,不会拖慢主线程 UI。如果有需要对下载的图片进行二次处理,最好也在这里完成,效率会好很多。
在主线程 notifyDelegateOnMainThreadWithInfo: 宣告解码完成,imageDecoder:didFinishDecodingImage:userInfo: 回调给 SDWebImageDownloader。
imageDownloader:didFinishWithImage: 回调给 SDWebImageManager 告知图片下载完成。
通知所有的 downloadDelegates 下载完成,回调给需要的地方展示图片。
将图片保存到 SDImageCache 中,内存缓存和硬盘缓存同时保存。写文件到硬盘也在以单独 NSInvocationOperation 完成,避免拖慢主线程。
SDImageCache 在初始化的时候会注册一些消息通知,在内存警告或退到后台的时候清理内存图片缓存,应用结束的时候清理过期图片。
SDWI 也提供了 UIButton+WebCache 和 MKAnnotationView+WebCache,方便使用。
SDWebImagePrefetcher 可以预先下载图片,方便后续使用。
从上面流程可以看出,当你调用setImageWithURL:方法的时候,他会自动去给你干这么多事,当你需要在某一具体时刻做事情的时候,你可以覆盖这些方法。比如在下载某个图片的过程中要响应一个事件,就覆盖这个方法:
1 2 3 4 5 6 7 8 9 10 11//覆盖方法,指哪打哪,这个方法是下载imagePath2的时候响应 SDWebImageManagermanager=[SDWebImageManagersharedManager];[managerdownloadImageWithURL:imagePath2options:SDWebImageRetryFailedprogress:^(NSIntegerreceivedSize,NSIntegerexpectedSize){ NSLog(@"显示当前进度"); }completed:^(UIImageimage,NSErrorerror,SDImageCacheTypecacheType,BOOLfinished,NSURLimageURL){NSLog(@"下载完成"); }];
对于初级来说,用sd_setImageWithURL:的若干个方法就可以实现很好的图片缓存。
UIButton 的父类是UIControl UIControl的父类是UIView UIView的父类是 UIResponder
http状态吗 :302 是请求重定向。500以上是服务器错误。400以上是请求链接错误或者找不到服务器。200以上是正确。100以上是请求接受成功。
HTTP Keep-Alive详解[转]
HTTP是一个请求<->响应模式的典型范例,即客户端向服务器发送一个请求信息,服务器来响应这个信息。在老的HTTP版本中,每个请求都将被创建一个新的客户端->服务器的连接,在这个连接上发送请求,然后接收请求。这样的模式有一个很大的优点就是,它很简单,很容易理解和编程实现;它也有一个很大的缺点就是,它效率很低,因此Keep-Alive被提出用来解决效率低的问题。
Keep-Alive功能使客户端到服务器端的连接持续有效,当出现对服务器的后继请求时,Keep-Alive功能避免了建立或者重新建立连接。市场上 的大部分Web服务器,包括iPlanet、IIS和Apache,都支持HTTPKeep-Alive。对于提供静态内容的网站来说,这个功能通常很有用。但是,对于负担较重的网站来说,这里存在另外一个问题:虽然为客户保留打开的连 接有一定的好处,但它同样影响了性能,因为在处理暂停期间,本来可以释放的资源仍旧被占用。当Web服务器和应用服务器在同一台机器上运行时,Keep-Alive功能对资源利用的影响尤其突出。 此功能为HTTP 1.1预设的功能,HTTP 1.0加上Keep-Aliveheader也可以提供HTTP的持续作用功能。
Keep-Alive: timeout=5, max=100
timeout:过期时间5秒(对应httpd.conf里的参数是:KeepAliveTimeout),max是最多一百次请求,强制断掉连接
就是在timeout时间内又有新的连接过来,同时max会自动减1,直到为0,强制断掉。见下面的四个图,注意看Date的值(前后时间差都是在5秒之内)!
HTTP/1.0
在HTTP/1.0版本中,并没有官方的标准来规定Keep-Alive如何工作,因此实际上它是被附加到HTTP/1.0协议上,如果客户端浏览器支持Keep-Alive,那么就在HTTP请求头中添加一个字段Connection: Keep-Alive,当服务器收到附带有Connection: Keep-Alive的请求时,它也会在响应头中添加一个同样的字段来使用Keep-Alive。这样一来,客户端和服务器之间的HTTP连接就会被保持,不会断开(超过Keep-Alive规定的时间,意外断电等情况除外),当客户端发送另外一个请求时,就使用这条已经建立的连接
HTTP/1.1
在HTTP/1.1版本中,官方规定的Keep-Alive使用标准和在HTTP/1.0版本中有些不同,默认情况下所在HTTP1.1中所有连接都被保持,除非在请求头或响应头中指明要关闭:Connection: Close ,这也就是为什么Connection: Keep-Alive字段再没有意义的原因。另外,还添加了一个新的字段Keep-Alive:,因为这个字段并没有详细描述用来做什么,可忽略它
Not reliable(不可靠)
HTTP是一个无状态协议,这意味着每个请求都是独立的,Keep-Alive没能改变这个结果。另外,Keep-Alive也不能保证客户端和服务器之间的连接一定是活跃的,在HTTP1.1版本中也如此。唯一能保证的就是当连接被关闭时你能得到一个通知,所以不应该让程序依赖于Keep-Alive的保持连接特性,否则会有意想不到的后果
Keep-Alive和POST
在HTTP1.1细则中规定了在一个POST消息体后面不能有任何字符,还指出了对于某一个特定的浏览器可能并不遵循这个标准(比如在POST消息体的后面放置一个CRLF符)。而据我所知,大部分浏览器在POST消息体后都会自动跟一个CRLF符再发送,如何解决这个问题呢?根据上面的说明在POST请求头中禁止使用Keep-Alive,或者由服务器自动忽略这个CRLF,大部分服务器都会自动忽略,但是在未经测试之前是不可能知道一个服务器是否会这样做。
1、常用的方法dispatch_async
为了避免界面在处理耗时的操作时卡死,比如读取网络数据,IO,数据库读写等,我们会在另外一个线程中处理这些操作,然后通知主线程更新界面。
用GCD实现这个流程的操作比前面介绍的NSThread NSOperation的方法都要简单。代码框架结构如下:
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT,0),^{
//耗时的操作
dispatch_async(dispatch_get_main_queue(),^{
//更新界面
});
}); 如果这样还不清晰的话,那我们还是用上两篇博客中的下载图片为例子,代码如下:
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT,0),^{
NSURLurl=[NSURLURLWithString:@"http://avatar.csdn.net/2/C/D/1_totogo2010.jpg"];
NSData
data=[[NSDataalloc]initWithContentsOfURL:url];
UIImage*image=[[UIImagealloc]initWithData:data];
if(data!=nil){
dispatch_async(dispatch_get_main_queue(),^{
self.imageView.image=image;
});
}
});
运行显示:

![](https://img.haomeiwen.com/i1663804/478804f77004b0fc.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)

是不是代码比NSThread NSOperation简洁很多,而且GCD会自动根据任务在多核处理器上分配资源,优化程序。
系统给每一个应用程序提供了三个concurrent dispatch queues。这三个并发调度队列是全局的,它们只有优先级的不同。因为是全局的,我们不需要去创建。我们只需要通过使用函数dispath_get_global_queue去得到队列,如下:
dispatch_queue_tglobalQ=dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT,0);
这里也用到了系统默认就有一个串行队列main_queue
dispatch_queue_tmainQ=dispatch_get_main_queue();
虽然dispatch queue是引用计数的对象,但是以上两个都是全局的队列,不用retain或release。
2、dispatch_group_async的使用
dispatch_group_async可以实现监听一组任务是否完成,完成后得到通知执行其他的操作。这个方法很有用,比如你执行三个下载任务,当三个任务都下载完成后你才通知界面说完成的了。下面是一段例子代码:
dispatch_queue_tqueue=dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT,0);
dispatch_group_tgroup=dispatch_group_create();
dispatch_group_async(group,queue,^{
[NSThreadsleepForTimeInterval:1];
NSLog(@"group1");
});
dispatch_group_async(group,queue,^{
[NSThreadsleepForTimeInterval:2];
NSLog(@"group2");
});
dispatch_group_async(group,queue,^{
[NSThreadsleepForTimeInterval:3];
NSLog(@"group3");
});
dispatch_group_notify(group,dispatch_get_main_queue(),^{
NSLog(@"updateUi");
});
dispatch_release(group); dispatch_group_async是异步的方法,运行后可以看到打印结果:
2012-09-25 16:04:16.737 gcdTest[43328:11303] group1
2012-09-25 16:04:17.738 gcdTest[43328:12a1b] group2
2012-09-25 16:04:18.738 gcdTest[43328:13003] group3
2012-09-25 16:04:18.739 gcdTest[43328:f803] updateUi
每个一秒打印一个,当第三个任务执行后,upadteUi被打印。
3、dispatch_barrier_async的使用
dispatch_barrier_async是在前面的任务执行结束后它才执行,而且它后面的任务等它执行完成之后才会执行
例子代码如下:
dispatch_queue_tqueue=dispatch_queue_create("gcdtest.rongfzh.yc",DISPATCH_QUEUE_CONCURRENT);
dispatch_async(queue,^{
[NSThreadsleepForTimeInterval:2];
NSLog(@"dispatch_async1");
});
dispatch_async(queue,^{
[NSThreadsleepForTimeInterval:4];
NSLog(@"dispatch_async2");
});
dispatch_barrier_async(queue,^{
NSLog(@"dispatch_barrier_async");
[NSThreadsleepForTimeInterval:4];
});
dispatch_async(queue,^{
[NSThreadsleepForTimeInterval:1];
NSLog(@"dispatch_async3");
});
打印结果:
2012-09-25 16:20:33.967 gcdTest[45547:11203] dispatch_async1
2012-09-25 16:20:35.967 gcdTest[45547:11303] dispatch_async2
2012-09-25 16:20:35.967 gcdTest[45547:11303] dispatch_barrier_async
2012-09-25 16:20:40.970 gcdTest[45547:11303] dispatch_async3
请注意执行的时间,可以看到执行的顺序如上所述。
4、dispatch_apply
执行某个代码片段N次。
dispatch_apply(5, globalQ, ^(size_t index) {
// 执行5次
});
copy与retain:
1、copy其实是建立了一个相同的对象,而retain不是;
2、copy是内容拷贝,retain是指针拷贝;
3、copy是内容的拷贝 ,对于像NSString,的确是这样,但是如果copy的是一个NSArray呢?这时只是copy了指向array中相对应元素的指针.这便是所谓的"浅复制".
4、copy的情况:NSString *newPt = [pt copy];
此时会在堆上重新开辟一段内存存放@"abc" 比如0X1122 内容为@"abc 同时会在栈上为newPt分配空间 比如地址:0Xaacc 内容为0X1122 因此retainCount增加1供newPt来管理0X1122这段内存;
assign与retain:
1、assign: 简单赋值,不更改索引计数;
2、assign的情况:NSString *newPt = [pt assing];
此时newPt和pt完全相同 地址都是0Xaaaa 内容为0X1111 即newPt只是pt的别名,对任何一个操作就等于对另一个操作, 因此retainCount不需要增加;
3、assign就是直接赋值;
4、retain使用了引用计数,retain引起引用计数加1, release引起引用计数减1,当引用计数为0时,dealloc函数被调用,内存被回收;
5、retain的情况:NSString *newPt = [pt retain];
此时newPt的地址不再为0Xaaaa,可能为0Xaabb 但是内容依然为0X1111。 因此newPt 和 pt 都可以管理"abc"所在的内存,因此 retainCount需要增加1;
readonly:
1、属性是只读的,默认的标记是读写,如果你指定了只读,在@implementation中只需要一个读取器。或者如果你使用@synthesize关键字,也是有读取器方法被解析
readwrite:
1、说明属性会被当成读写的,这也是默认属性。设置器和读取器都需要在@implementation中实现。如果使用@synthesize关键字,读取器和设置器都会被解析;
nonatomic:
1、非原子性访问,对属性赋值的时候不加锁,多线程并发访问会提高性能。如果不加此属性,则默认是两个访问方法都为原子型事务访问;
weak and strong property (强引用和弱引用的区别):
1、weak 和 strong 属性只有在你打开ARC时才会被要求使用,这时你是不能使用retain release autorelease 操作的,因为ARC会自动为你做好这些操作,但是你需要在对象属性上使用weak 和strong,其中strong就相当于retain属性,而weak相当于assign。
2、只有一种情况你需要使用weak(默认是strong),就是为了避免retain cycles(就是父类中含有子类{父类retain了子类},子类中又调用了父类{子类又retain了父类},这样都无法release)
3、声明为weak的指针,指针指向的地址一旦被释放,这些指针都将被赋值为nil。这样的好处能有效的防止野指针。
ARC(Automatic Reference Counting):
1、就是代码中自动加入了retain/release,原先需要手动添加的用来处理内存管理的引用计数的代码可以自动地由编译器完成了。
该机能在 iOS 5/ Mac OS X 10.7 开始导入,利用 Xcode4.2 以后可以使用该特性。
strong,weak,copy 具体用法:
1.具体一点:IBOutlet可以为weak,NSString为copy,Delegate一般为weak,其他的看情况。一般来说,类“内部”的属性设置为strong,类“外部”的属性设置为weak。说到底就是一个归属权的问题。小心出现循环引用导致内存无法释放。
2.不用ARC的话就会看到很多retian。
3.如果你写了@synthesize abc = _abc;的话,系统自动帮你声明了一个_abc的实例变量。
使用assign: 对基础数据类型 (NSInteger)和C数据类型(int, float, double, char,等)
使用copy: 对NSString
使用retain: 对其他NSObject和其子类
1.写一个NSString类的实现
+(id)initWithCString:(c*****t char *)nullTerminatedCString encoding:(NSStringEncoding)encoding;

  1. ojc-c是通过一种"referring counting"(引用计数)的方式来管理内存的,对象在开始分配内存(alloc)的时候引用计数为一,以后每当碰到有copy,retain的时候引用计数都会加一,每当碰到release和autorelease的时候引用计数就会减一,如果此对象的计数变为了0,就会被系统销毁.
  2. NSAutoreleasePool就是用来做引用计数的管理工作的,这个东西一般不用你管的.
  3. autorelease和release没什么区别,只是引用计数减一的时机不同而已,autorelease会在对象的使用真正结束的时候才做引用计数减一.
    9类工厂方法是什么
    类工厂方法的实现是为了向客户提供方便,它们将分配和初始化合在一个步骤中,返回被创建的对象,并
    进行自动释放处理。这些方法的形式是+ (type)className...(其中className不包括任何前缀)。
    工厂方法可能不仅仅为了方便使用。它们不但可以将分配和初始化合在一起,还可以为初始化过程提供对
    象的分配信息。
    类工厂方法的另一个目的是使类(比如NSWorkspace)提供单件实例。虽然init...方法可以确认一
    个类在每次程序运行过程只存在一个实例,但它需要首先分配一个“生的”实例,然后还必须释放该实例。
    工厂方法则可以避免为可能没有用的对象盲目分配内存。
    10单件实例是什么
    Foundation和Application Kit框架中的一些类只允许创建单件对象,即这些类在当前进程中的唯一实例。举例来说,NSFileManager和NSWorkspace类在使用时都是基于进程进行单件对象的实例化。当向这些类请求实例的时候,它们会向您传递单一实例的一个引用,如果该实例还不存在,则首先进行实例的分配和初始化。单件对象充当控制中心的角色,负责指引或协调类的各种服务。如果类在概念上只有一个实例(比如
    NSWorkspace),就应该产生一个单件实例,而不是多个实例;如果将来某一天可能有多个实例,您可
    以使用单件实例机制,而不是工厂方法或函数。
    11动态绑定
    —在运行时确定要调用的方法
    动态绑定将调用方法的确定也推迟到运行时。在编译时,方法的调用并不和代码绑定在一起,只有在消实发送出来之后,才确定被调用的代码。通过动态类型和动态绑定技术,您的代码每次执行都可以得到不同的结果。运行时因子负责确定消息的接收者和被调用的方法。运行时的消息分发机制为动态绑定提供支持。当您向一个动态类型确定了的对象发送消息时,运行环境系统会通过接收者的isa指针定位对象的类,并以此为起点确定被调用的方法,方法和消息是动态绑定的。而且,您不必在Objective-C代码中做任何工作,就可以自动获取动态绑定的好处。您在每次发送消息时,
    特别是当消息的接收者是动态类型已经确定的对象时,动态绑定就会例行而透明地发生。
    12obj-c的优缺点
    objc优点:
  1. Cateogies
  2. Posing
    3)动态识别
    4)指标计算
    5)弹性讯息传递
    6)不是一个过度复杂的C衍生语言
  3. Objective-C与C++可混合编程
    缺点:
    1)不支援命名空间
    2)不支持运算符重载
    3)不支持多重继承
    4)使用动态运行时类型,所有的方法都是函数调用,所以很多编译时优化方法都用不到。(如内联函数等),性能低劣。
    13sprintf,strcpy,memcpy使用上有什么要注意的地方
    strcpy是一个字符串拷贝的函数,它的函数原型为strcpy(char *dst, c*****t char *src);
    将src开始的一段字符串拷贝到dst开始的内存中去,结束的标志符号为'\0',由于拷贝的长度不是由我们自己控制的,所以这个字符串拷贝很容易出错。具备字符串拷贝功能的函数有memcpy,这是一个内存拷贝函数,它的函数原型为memcpy(char dst, c*****t char src, unsigned int len);
    将长度为len的一段内存,从src拷贝到dst中去,这个函数的长度可控。但是会有内存叠加的问题。
    sprintf是格式化函数。将一段数据通过特定的格式,格式化到一个字符串缓冲区中去。sprintf格式化的函数的长度不可控,有可能格式化后的字符串会超出缓冲区的大小,造成溢出。
    14答案是:
    a) int a; // An integer
    b) int *a; // A pointer to an integer
    c) int a; // A pointer to a pointer to an integer
    d) int a[10]; // An array of 10 integers
    e) int a[10]; // An array of 10 pointers to integers
    f) int (
    a)[10]; // A pointer to an array of 10 integers
    g) int (
    a)(int); // A pointer to a function a that takes an integer argument and returns an integer
    h) int (
    a[10])(int); // An array of 10 pointers to functi***** that take an integer argument and return an integer
    15.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。这是为了减少对上下文的依赖而引入的机制。
    copy是在你不希望a和b共享一块内存时会使用到。a和b各自有自己的内存。
    5,nonatomic,非原子性访问,不加同步,多线程并发访问会提高性能。注意,如果不加此属性,则默认是两个访问方法都为原子型事务访问。锁被加到所属对象实例级(我是这么理解的...)。
    atomic和nonatomic用来决定编译器生成的getter和setter是否为原子操作。在多线程环境下,原子操作是必要的,否则有可能引起错 误的结果。加了atomic,setter函数会变成下面这样:
    16什么时候用delegate,什么时候用Notification?答:delegate针对one-to-one关系,并且reciever可以返回值给sender,notification可以针对one-to-one/many/none,reciever无法返回值给sender.所以,delegate用于sender希望接受到reciever的某个功能反馈值,notification用于通知多个object某个事件。
    17什么是KVC和KVO?答:KVC(Key-Value-Coding)内部的实现:一个对象在调用setValue的时候,(1)首先根据方法名找到运行方法的时候所需要的环境参数。(2)他会从自己isa指针结合环境参数,找到具体的方法实现的接口。(3)再直接查找得来的具体的方法实现。KVO(Key-Value- Observing):当观察者为一个对象的属性进行了注册,被观察对象的isa指针被修改的时候,isa指针就会指向一个中间类,而不是真实的类。所以isa指针其实不需要指向实例对象真实的类。所以我们的程序最好不要依赖于isa指针。在调用类的方法的时候,最好要明确对象实例的类名
    18ViewController的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。
    19
    "NSMutableString *"这个数据类型则是代表"NSMutableString"对象本身,这两者是有区别的。
    而NSString只是对象的指针而已。
    面向过程就是分析出解决问题所需要的步骤,然后用函数把这些步骤一步一步实现,使用的时候一个一个依次调用就可以了。
    面向对象是把构成问题事务分解成各个对象,建立对象的目的不是为了完成一个步骤,而是为了描叙某个事物在整个解决问题的步骤中的行为。;
    20类别的作用
    类别主要有3个作用:
    (1)将类的实现分散到多个不同文件或多个不同框架中。
    (2)创建对私有方法的前向引用。
    (3)向对象添加非正式协议。
    类别的局限性
    有两方面局限性:
    (1)无法向类中添加新的实例变量,类别没有位置容纳实例变量。
    (2)名称冲突,即当类别中的方法与原始类方法名称冲突时,类别具有更高的优先级。类别方法将完全取代初始方法从而无法再使用初始方法。
    无法添加实例变量的局限可以使用字典对象解决
    21关键字volatile有什么含意?并给出三个不同的例子:
    一个定义为volatile的变量是说这变量可能会被意想不到地改变,这样,编译器就不会去假设这个变量的值了。精确地说就是,优化器在用到
    这个变量时必须每次都小心地重新读取这个变量的值,而不是使用保存在寄存器里的备份。下面是volatile变量的几个例子:
    ?并行设备的硬件寄存器(如:状态寄存器)
    ?一个中断服务子程序中会访问到的非自动变量(Non-automatic variables)
    ?多线程应用中被几个任务共享的变量
    ?一个参数既可以是const还可以是volatile吗?解释为什么。
    ?一个指针可以是volatile吗?解释为什么。
    下面是答案:
    ?是的。一个例子是只读的状态寄存器。它是volatile因为它可能被意想不到地改变。它是const因为程序不应该试图去修改它。
    ?是的。尽管这并不很常见。一个例子是当一个中服务子程序修该一个指向一个buffer的指针时。
    22@synthesize是系统自动生成getter和setter属性声明
    @dynamic是开发者自已提供相应的属性声明
    @dynamic意思是由开发人员提供相应的代码:对于只读属性需要提供setter,对于读写属性需要提供setter和getter。@synthesize意思是,除非开发人员已经做了,否则由编译器生成相应的代码,以满足属性声明。
    查阅了一些资料确定@dynamic的意思是告诉编译器,属性的获取与赋值方法由用户自己实现,不自动生成。
    23Difference between shallow copy and deep copy?
    浅复制和深复制的区别?
    答案:浅层复制:只复制指向对象的指针,而不复制引用对象本身。
    深层复制:复制引用对象本身。
    意思就是说我有个A对象,复制一份后得到A_copy对象后,对于浅复制来说,A和A_copy指向的是同一个内存资源,复制的只不过是是一个指针,对象本身资源
    还是只有一份,那如果我们对A_copy执行了修改操作,那么发现A引用的对象同样被修改,这其实违背了我们复制拷贝的一个思想。深复制就好理解了,内存中存在了
    两份独立对象本身。
    用网上一哥们通俗的话将就是:
    浅复制好比你和你的影子,你完蛋,你的影子也完蛋
    深复制好比你和你的克隆人,你完蛋,你的克隆人还活着。
    24What is advantage of categories? What is difference between implementing a category and inheritance?
    类别的作用?继承和类别在实现中有何区别?
    答案:category可以在不获悉,不改变原来代码的情况下往里面添加新的方法,只能添加,不能删除修改。
    并且如果类别和原来类中的方法产生名称冲突,则类别将覆盖原来的方法,因为类别具有更高的优先级。
    类别主要有3个作用:
    (1)将类的实现分散到多个不同文件或多个不同框架中。
    (2)创建对私有方法的前向引用。
    (3)向对象添加非正式协议。
    继承可以增加,修改或者删除方法,并且可以增加属性。
    25.Difference between categories and extensions?
    类别和类扩展的区别。
    答案:category和extensions的不同在于后者可以添加属性。另外后者添加的方法是必须要实现的。
    extensions可以认为是一个私有的Category。
    26.Difference between protocol in objective c and interfaces in java?
    oc中的协议和java中的接口概念有何不同?
    答案:OC中的代理有2层含义,官方定义为formal和informal protocol。前者和Java接口一样。
    informal protocol中的方法属于设计模式考虑范畴,不是必须实现的,但是如果有实现,就会改变类的属性。
    其实关于正式协议,类别和非正式协议我很早前学习的时候大致看过,也写在了学习教程里
    “非正式协议概念其实就是类别的另一种表达方式“这里有一些你可能希望实现的方法,你可以使用他们更好的完成工作”。
    这个意思是,这些是可选的。比如我门要一个更好的方法,我们就会申明一个这样的类别去实现。然后你在后期可以直接使用这些更好的方法。
    这么看,总觉得类别这玩意儿有点像协议的可选协议。"
    现在来看,其实protocal已经开始对两者都统一和规范起来操作,因为资料中说“非正式协议使用interface修饰“,
    现在我们看到协议中两个修饰词:“必须实现(@requied)”和“可选实现(@optional)”。
    26What are KVO and KVC?
    答案:kvc:键-值编码是一种间接访问对象的属性使用字符串来标识属性,而不是通过调用存取方法,直接或通过实例变量访问的机制。
    很多情况下可以简化程序代码。apple文档其实给了一个很好的例子。
    kvo:键值观察机制,他提供了观察某一属性变化的方法,极大的简化了代码。
    具体用看到嗯哼用到过的一个地方是对于按钮点击变化状态的的监控。
    比如我自定义的一个button
    [cpp]
    [self addObserver:self forKeyPath:@"highlighted" options:0 context:nil];

pragma mark KVO

  1. Internet采用哪种网络协议?该协议的主要层次结构?
    tcp/ip应用层/传输层/网络层/数据链路层/物理层
  2. Internet物理地址和IP地址转换采用什么协议?
    ARP (Address Resolution Protocol)(地址解析協議)
    18.IP地址的编码分为哪俩部分?
    IP地址由两部分组成,网络号和主机号。不过是要和“子网掩码”按位与上之后才能区
    分哪些是网络位哪些是主机位。
    2.用户输入M,N值,从1至N开始顺序循环数数,每数到M输出该数值,直至全部输出。写
    出C程序。
    循环链表,用取余操作做
    3.不能做switch()的参数类型是:
    switch的参数不能为实型。
    華為
    1、局部变量能否和全局变量重名?
    答:能,局部会屏蔽全局。要用全局变量,需要使用"::"
    局部变量可以与全局变量同名,在函数内引用这个变量时,会用到同名的局部变量,而
    不会用到全局变量。对于有些编译器而言,在同一个函数内可以定义多个同名的局部变
    量,比如在两个循环体内都定义一个同名的局部变量,而那个局部变量的作用域就在那
    个循环体内
    2、如何引用一个已经定义过的全局变量?
    答:extern
    可以用引用头文件的方式,也可以用extern关键字,如果用引用头文件方式来引用某个
    在头文件中声明的全局变理,假定你将那个变写错了,那么在编译期间会报错,如果你
    用extern方式引用时,假定你犯了同样的错误,那么在编译期间不会报错,而在连接期
    间报错
    3、全局变量可不可以定义在可被多个.C文件包含的头文件中?为什么?
    答:可以,在不同的C文件中以static形式来声明同名全局变量。
    可以在不同的C文件中声明同名的全局变量,前提是其中只能有一个C文件中对此变量赋
    初值,此时连接不会出错
    4、语句for(;1;)有什么问题?它是什么意思?
    答:和while(1)相同。
    5、do……while和while……do有什么区别?
    答:前一个循环一遍再判断,后一个判断以后再循环
    661.IP Phone的原理是什么?
    IPV6
    2.TCP/IP通信建立的过程怎样,端口有什么作用?
    三次握手,确定是哪个应用程序使用该协议
    3.1号信令和7号信令有什么区别,我国某前广泛使用的是那一种?
    4.列举5种以上的电话新业务?
    微软亚洲技术中心的面试题!!!
    1.进程和线程的差别。
    线程是指进程内的一个执行单元,也是进程内的可调度实体.
    与进程的区别:
    (1)调度:线程作为调度和分配的基本单位,进程作为拥有资源的基本单位
    (2)并发性:不仅进程之间可以并发执行,同一个进程的多个线程之间也可并发执行
    (3)拥有资源:进程是拥有资源的一个独立单位,线程不拥有系统资源,但可以访问隶属
    于进程的资源.
    (4)系统开销:在创建或撤消进程时,由于系统都要为之分配和回收资源,导致系统的开
    销明显大于创建或撤消线程时的开销。
    2.测试方法
    人工测试:个人复查、抽查和会审
    机器测试:黑盒测试和白盒测试
    2.Heap与stack的差别。
    Heap是堆,stack是栈。
    Stack的空间由操作系统自动分配/释放,Heap上的空间手动分配/释放。
    Stack空间有限,Heap是很大的自由存储区
    C中的malloc函数分配的内存空间即在堆上,C++中对应的是new操作符。
    程序在编译期对变量和函数分配内存都在栈上进行,且程序运行过程中函数调用时参数的
    传递也在栈上进行
    3.Windows下的内存是如何管理的?
    4.介绍.Net和.Net的安全性。
    5.客户端如何访问.Net组件实现Web Service?
    6.C/C++编译器中虚表是如何完成的?
    7.谈谈COM的线程模型。然后讨论进程内/外组件的差别。
    8.谈谈IA32下的分页机制
    小页(4K)两级分页模式,大页(4M)一级
    9.给两个变量,如何找出一个带环单链表中是什么地方出现环的?
    一个递增一,一个递增二,他们指向同一个接点时就是环出现的地方
    10.在IA32中一共有多少种办法从用户态跳到内核态?
    通过调用门,从ring3到ring0,中断从ring3到ring0,进入vm86等等
    11.如果只想让程序有一个实例运行,不能运行两个。像winamp一样,只能开一个窗
    口,怎样实现?
    用内存映射或全局原子(互斥变量)、查找窗口句柄..
    FindWindow,互斥,写标志到文件或注册表,共享内存。
    67如何截取键盘的响应,让所有的‘a’变成‘b’?
    键盘钩子SetWindowsHookEx
    13.Apartment在COM中有什么用?为什么要引入?
    14.存储过程是什么?有什么用?有什么优点?
    我的理解就是一堆sql的集合,可以建立非常复杂的查询,编译运行,所以运行一次后,
    以后再运行速度比单独执行SQL快很多
    15.Template有什么特点?什么时候用?
    16.谈谈Windows DNA结构的特点和优点。
    网络编程中设计并发服务器,使用多进程与多线程,请问有什么区别?
    1,进程:子进程是父进程的复制品。子进程获得父进程数据空间、堆和栈的复制品。
    2,线程:相对与进程而言,线程是一个更加接近与执行体的概念,它可以与同进程的其
    他线程共享数据,但拥有自己的栈空间,拥有独立的执行序列。
    两者都可以提高程序的并发度,提高程序运行效率和响应时间。
    线程和进程在使用上各有优缺点:线程执行开销小,但不利于资源管理和保护;而进程
    正相反。同时,线程适合于在SMP机器上运行,而进程则可以跨机器迁移。
    思科
    682.找错题
    试题1:
    void test1()
    {
    char string[10];
    char* str1 = "0123456789";
    strcpy( string, str1 );
    }
    试题2:
    void test2()
    {
    char string[10], str1[10];
    int i;
    for(i=0; i<10; i++)
    {
    str1= 'a';
    }
    strcpy( string, str1 );
    }
    试题3:
    void test3(char* str1)
    {
    char string[10];
    if( strlen( str1 ) <= 10 )
    {
    strcpy( string, str1 );
    }
    }
    解答:
    试题1字符串str1需要11个字节才能存放下(包括末尾的’\0’),而string只有10个字节的空间,strcpy会导致数组越界;
    对试题2,如果面试者指出字符数组str1不能在数组内结束可以给3分;如果面试者指出strcpy(string, str1)调用使得从str1[url=]内存[/url]起复制到string内存起所复制的字节数具有不确定性可以给7分,在此基础上指出库函数strcpy工作方式的给10分;
    对试题3,if(strlen(str1) <= 10)应改为if(strlen(str1) < 10),因为strlen的结果未统计’\0’所占用的1个字节。
    剖析:
    考查对基本功的掌握:
    (1)字符串以’\0’结尾;
    (2)对数组越界把握的敏感度;
    (3)库函数strcpy的工作方式,如果编写一个标准strcpy函数的总分值为10,下面给出几个不同得分的答案:
    2分
    void strcpy( char *strDest, char strSrc )
    {
    while( (
    strDest++ = * strSrc++) != ‘\0’ );
    }
    4分
    void strcpy( char *strDest, const char strSrc )
    //将源字符串加const,表明其为输入参数,加2分
    {
    while( (
    strDest++ = * strSrc++) != ‘\0’ );
    }
    7分
    void strcpy(char *strDest, const char strSrc)
    {
    //对源地址和目的地址加非0断言,加3分
    assert( (strDest != NULL) && (strSrc != NULL) );
    while( (
    strDest++ = * strSrc++) != ‘\0’ );
    }
    10分
    //为了实现链式操作,将目的地址返回,加3分!
    char * strcpy( char *strDest, const char *strSrc )
    {
    assert( (strDest != NULL) && (strSrc != NULL) );
    char address = strDest;
    while( (
    strDest++ = * strSrc++) != ‘\0’ );
    return address;
    }
    从2分到10分的几个答案我们可以清楚的看到,小小的strcpy竟然暗藏着这么多玄机,真不是盖的!需要多么扎实的基本功才能写一个完美的strcpy啊!
    (4)对strlen的掌握,它没有包括字符串末尾的'\0'。
    读者看了不同分值的strcpy版本,应该也可以写出一个10分的strlen函数了,完美的版本为:int strlen( const char str ) //输入参数const
    {
    assert( strt != NULL ); //断言字符串地址非0
    int len;
    while( (
    str++) != '\0' )
    {
    len++;
    }
    return len;
    }
    试题4:
    void GetMemory( char *p )
    {
    p = (char *) malloc( 100 );
    }
    void Test( void )
    {
    char *str = NULL;
    GetMemory( str );
    strcpy( str, "hello world" );
    printf( str );
    }
    试题5:
    char *GetMemory( void )
    {
    char p[] = "hello world";
    return p;
    }
    void Test( void )
    {
    char *str = NULL;
    str = GetMemory();
    printf( str );
    }
    试题6:
    void GetMemory( char **p, int num )
    {
    *p = (char *) malloc( num );
    }
    void Test( void )
    {
    char *str = NULL;
    GetMemory( &str, 100 );
    strcpy( str, "hello" );
    printf( str );
    }
    试题7:
    void Test( void )
    {
    char *str = (char *) malloc( 100 );
    strcpy( str, "hello" );
    free( str );
    ... //省略的其它语句
    }
    解答:
    试题4传入中GetMemory( char *p )函数的形参为字符串指针,在函数内部修改形参并不能真正的改变传入形参的值,执行完
    char *str = NULL;
    GetMemory( str );
    后的str仍然为NULL;
    试题5中
    char p[] = "hello world";
    return p;
    的p[]数组为函数内的局部自动变量,在函数返回后,内存已经被释放。这是许多程序员常犯的错误,其根源在于不理解变量的生存期。
    试题6的GetMemory避免了试题4的问题,传入GetMemory的参数为字符串指针的指针,但是在GetMemory中执行申请内存及赋值语句
    *p = (char *) malloc( num );
    后未判断内存是否申请成功,应加上:
    if ( *p == NULL )
    {
    ...//进行申请内存失败处理
    }
    试题7存在与试题6同样的问题,在执行
    char str = (char ) malloc(100);
    后未进行内存是否申请成功的判断;另外,在free(str)后未置str为空,导致可能变成一个“野”指针,应加上:
    str = NULL;
    试题6的Test函数中也未对malloc的内存进行释放。
    剖析:
    试题4~7考查面试者对内存操作的理解程度,基本功扎实的面试者一般都能正确的回答其中50~60的错误。但是要完全解答正确,却也绝非易事。
    对内存操作的考查主要集中在:
    (1)指针的理解;
    (2)变量的生存期及作用范围;
    (3)良好的动态内存申请和释放习惯。
    再看看下面的一段程序有什么错误:
    swap( int
    p1,int
    p2 )
    {
    int *p;
    *p = *p1;
    *p1 = *p2;
    p2 = p;
    }
    在swap函数中,p是一个“野”指针,有可能指向系统区,导致程序运行的崩溃。在VC++中DEBUG运行时提示错误“Access Violation”。该程序应该改为:
    swap( int
    p1,int
    p2 )
    {
    int p;
    p = *p1;
    *p1 = *p2;
    *p2 = p;
    }[img=12,12]file:///D:/鱼鱼软件/鱼鱼多媒体日记本/temp/{56068A28-3D3B-4D8B-9F82-AC1C3E9B128C}_arc_d[1].gif[/img]3.内功题
    试题1:分别给出BOOL,int,float,指针变量与“零值”比较的if语句(假设变量名为var)
    解答:
    BOOL型变量:if(!var)
    int型变量:if(var==0)
    float型变量:
    const float EPSINON = 0.00001;
    if ((x >= - EPSINON) && (x <= EPSINON)
    指针变量:  if(var==NULL)
    剖析:
    考查对0值判断的“内功”,BOOL型变量的0判断完全可以写成if(var==0),而int型变量也可以写成if(!var),指针变量的判断也可以写成if(!var),上述写法虽然程序都能正确运行,但是未能清晰地表达程序的意思。
    一般的,如果想让if判断一个变量的“真”、“假”,应直接使用if(var)、if(!var),表明其为“逻辑”判断;如果用if判断一个数值型变量(short、int、long等),应该用if(var==0),表明是与0进行“数值”上的比较;而判断指针则适宜用if(var==NULL),这是一种很好的编程习惯。
    浮点型变量并不精确,所以不可将float变量用“==”或“!=”与数字比较,应该设法转化成“>=”或“<=”形式。如果写成if (x == 0.0),则判为错,得0分。
    试题2:以下为Windows NT下的32位C++程序,请计算sizeof的值
    void Func ( char str[100] )
    {
    sizeof( str ) = ?
    }
    void *p = malloc( 100 );
    sizeof ( p ) = ?
    解答:
    sizeof( str ) = 4
    sizeof ( p ) = 4
    剖析:
    Func ( char str[100] )函数中数组名作为函数形参时,在函数体内,数组名失去了本身的内涵,仅仅只是一个指针;在失去其内涵的同时,它还失去了其常量特性,可以作自增、自减等操作,可以被修改。
    数组名的本质如下:
    (1)数组名指代一种数据结构,这种数据结构就是数组;
    例如:
    char str[10];
    cout<

输出结果为10,str指代数据结构char[10]。
(2)数组名可以转换为指向其指代实体的指针,而且是一个指针常量,不能作自增、自减等操作,不能被修改;
char str[10];
str++; //编译出错,提示str不是左值
(3)数组名作为函数形参时,沦为普通指针。
Windows NT 32位平台下,指针的长度(占用内存的大小)为4字节,故sizeof( str )、sizeof ( p )都为4。
试题3:写一个“标准”宏MIN,这个宏输入两个参数并返回较小的一个。另外,当你写下面的代码时会发生什么事?
least = MIN(*p++, b);
解答:

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

MIN(*p++, b)会产生宏的副作用
剖析:
这个面试题主要考查面试者对宏定义的使用,宏定义可以实现类似于函数的功能,但是它终归不是函数,而宏定义中括弧中的“参数”也不是真的参数,在宏展开的时候对“参数”进行的是一对一的替换。
程序员对宏定义的使用要非常小心,特别要注意两个问题:
(1)谨慎地将宏定义中的“参数”和整个宏用用括弧括起来。所以,严格地讲,下述解答:

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

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

都应判0分;
(2)防止宏的副作用。
宏定义#define MIN(A,B) ((A) <= (B) ? (A) : (B))对MIN(p++, b)的作用结果是:
((
p++) <= (b) ? (p++) : (p++))
这个表达式会产生副作用,指针p会作三次++自增操作。
除此之外,另一个应该判0分的解答是:

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

这个解答在宏定义的后面加“;”,显示编写者对宏的概念模糊不清,只能被无情地判0分并被面试官淘汰。
试题4:为什么标准头文件都有类似以下的结构?

ifndef __INCvxWorksh

define __INCvxWorksh

ifdef __cplusplus

extern "C" {

endif

/.../

ifdef __cplusplus

}

endif

endif /* __INCvxWorksh */

解答:
头文件中的编译宏

ifndef __INCvxWorksh

define __INCvxWorksh

endif

的作用是防止被重复引用。
作为一种面向对象的语言,C++支持函数重载,而过程式语言C则不支持。函数被C++编译后在symbol库中的名字与C语言的不同。例如,假设某个函数的原型为:
void foo(int x, int y);
该函数被C编译器编译后在symbol库中的名字为_foo,而C++编译器则会产生像_foo_int_int之类的名字。_foo_int_int这样的名字包含了函数名和函数参数数量及类型信息,C++就是考这种机制来实现函数重载的。
为了实现C和C++的混合编程,C++提供了C连接交换指定符号extern "C"来解决名字匹配问题,函数声明前加上extern "C"后,则编译器就会按照C语言的方式将该函数编译为_foo,这样C语言中就可以调用C++的函数了。[img=12,12]file:///D:/鱼鱼软件/鱼鱼多媒体日记本/temp/{C74A38C4-432E-4799-B54D-73E2CD3C5206}_arc_d[1].gif[/img]
试题5:编写一个函数,作用是把一个char组成的字符串循环右移n个。比如原来是“abcdefghi”如果n=2,移位后应该是“hiabcdefgh”
函数头是这样的:
//pStr是指向以'\0'结尾的字符串的指针
//steps是要求移动的n
void LoopMove ( char * pStr, int steps )
{
//请填充...
}
解答:
正确解答1:
void LoopMove ( char *pStr, int steps )
{
int n = strlen( pStr ) - steps;
char tmp[MAX_LEN];
strcpy ( tmp, pStr + n );
strcpy ( tmp + steps, pStr);
*( tmp + strlen ( pStr ) ) = '\0';
strcpy( pStr, tmp );
}
正确解答2:
void LoopMove ( char *pStr, int steps )
{
int n = strlen( pStr ) - steps;
char tmp[MAX_LEN];
memcpy( tmp, pStr + n, steps );
memcpy(pStr + steps, pStr, n );
memcpy(pStr, tmp, steps );
}
剖析:
这个试题主要考查面试者对标准库函数的熟练程度,在需要的时候引用库函数可以很大程度上简化程序编写的工作量。
最频繁被使用的库函数包括:
(1)strcpy
(2)memcpy
(3)memset

上一篇 下一篇

猜你喜欢

热点阅读