首页 > 代码库 > IOS 常规面试题目整理

IOS 常规面试题目整理

1.Object-C有多继承吗?没有的话用什么代替?

  cocoa 中所有的类都是NSObject 的子类,多继承在这里是用protocol 委托代理实现的,不需要考虑繁琐的多继承 ,虚基类的概念。ood的多态特性 在 obj-c 中通过委托来实现.

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

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

@interface Controller : NSObject {

  NSString *something;

}
+ (void)thisIsAStaticMethod;    //实例方法
– (void)thisIsAnInstanceMethod;
@end
@interface Controller (private) 
-(void)thisIsAPrivateMethod;
@end

@private可以用来修饰私有变量
在Objective‐C中,所有实例变量默认都是私有的,所有实例方法默认都是公有的

 

3.关键字const什么含义

const意味着”只读”,下面的声明都是什么意思?
const int a;   //a是一个常整型数。
int const a;   //a是一个常整型数。
const int *a; //是一个指向常整型数的指针(也就是,整型数是不可修改的,但指针可以)
int * const a;//a是一个指向整型数的常指针(也就是说,指针指向的整型数是可以修改的,但指针是不可修改的)
int const * a const;//是一个指向常整型数的常指针(也就是说,指针指向的整型数是不可修改的,同时指针也是不可修改的)

结论:
(1)关键字const的作用是为给读你代码的人传达非常有用的信息,实际上,声明一个参数为常量是为了告诉了用户这个参数的应用目的。如果
你曾花很多时间清理其它人留下的垃圾,你就会很快学会感谢这点多余的信息。(当然,懂得用const的程序员很少会留下的垃圾让别人来清
理的。)
•; 通过给优化器一些附加的信息,使用关键字const也许能产生更紧凑的代码。
•; 合理地使用关键字const可以使编译器很自然地保护那些不希望被改变的参数,防止其被无意的代码修改。简而言之,这样可以减少bug的出现。

欲阻止一个变量被改变,可以使用 const 关键字。在定义该 const 变量时,通常需要对它进行初始化,因为以后就没有机会再去改变它了;

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

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

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

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

4.关键字volatile有什么含义?并给出三个不同例子?

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

  • 并行设备的硬件寄存器(如:状态寄存器)
  • 一个中断服务子程序中会访问到的非自动变量(Non-automatic variables)
  • 多线程应用中被几个任务共享的变量

  • 一个参数既可以是const还可以是volatile吗?解释为什么。

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

  • 一个指针可以是volatile 吗?解释为什么。

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

 

  ps:1、中断服务程序中修改的供其它程序检测的变量需要加volatile;2、多任务环境下各任务间共享的标志应该加volatile;3、存储器映射的硬件寄存器通常也要加volatile说明,因为每次对它的读写都可能有不同意义;另外,以上这几种情况经常还要同时考虑数据的完整性(相互关联的几个标志读了一半被打断了重写),在1中可以通过关中断来实现,2 中可以禁止任务调度,3中则只能依靠硬件的良好设计了。

 

  

5.static作用?  

  (1)函数体内 static 变量的作用范围为该函数体,不同于 auto 变量,该变量的内存只被分配一次,因此其值在下次调用时仍维持上次的值;

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

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

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

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

6.#import和#include的区别,@class代表什么?

  @class一般用于头文件中需要声明该类的某个实例变量的时候用到,在m文件中还是需要使用#import而#import比起#include的好处就是不会引起重复包含.

7.线程和进程的区别?

  进程是一个可执行的程序,由私有虚拟地址空间、代码、数据和其他操作系统资源(如进程创建的文件、管道、同步对象等)组成。一个应用程序可以有一个或多个进程,一个进程可以有一个或多个线程,其中一个是主线程。线程是操作系统分时调度分配CPU时间的基本实体。一个线程可以执行程序的任意部分的代码,即使这部分代码被另一个线程并发地执行;一个进程的所有线程共享它的虚拟地址空间、全局变量和操作系统资源。

8.堆和栈的区别

//main.cpp int a = 0; 全局初始化区 

char *p1; 全局未初始化区 
main() 

int b; 栈 
char s[] = "abc"; 栈 
char *p2; 栈 
char *p3 = "123456"; 123456\0在常量区,p3在栈上。 
static int c =0; 全局(静态)初始化区 
p1 = (char *)malloc(10); 
p2 = (char *)malloc(20); 
分配得来得10和20字节的区域就在堆区。 
strcpy(p1, "123456"); 123456\0放在常量区,编译器可能会将它与p3所指向的"123456" 
优化成一个地方。 

堆和栈的理论知识 
申请方式 
stack: 
由系统自动分配。 例如,声明在函数中一个局部变量 int b; 系统自动在栈中为b开辟空 
间 
heap: 
需要程序员自己申请,并指明大小,在c中malloc函数 
如p1 = (char *)malloc(10); 
在C++中用new运算符 
如p2 = new char[10]; 
但是注意p1、p2本身是在栈中的。 

申请后系统的响应 
栈:只要栈的剩余空间大于所申请空间,系统将为程序提供内存,否则将报异常提示栈溢 
出。 
堆:首先应该知道操作系统有一个记录空闲内存地址的链表,当系统收到程序的申请时, 
会遍历该链表,寻找第一个空间大于所申请空间的堆结点,然后将该结点从空闲结点链表 
中删除,并将该结点的空间分配给程序,另外,对于大多数系统,会在这块内存空间中的 
首地址处记录本次分配的大小,这样,代码中的delete语句才能正确的释放本内存空间。 
另外,由于找到的堆结点的大小不一定正好等于申请的大小,系统会自动的将多余的那部 
分重新放入空闲链表中。 

申请大小的限制 
栈:在Windows下,栈是向低地址扩展的数据结构,是一块连续的内存的区域。这句话的意 
思是栈顶的地址和栈的最大容量是系统预先规定好的,在WINDOWS下,栈的大小是2M(也有 
的说是1M,总之是一个编译时就确定的常数),如果申请的空间超过栈的剩余空间时,将 
提示overflow。因此,能从栈获得的空间较小。 
堆:堆是向高地址扩展的数据结构,是不连续的内存区域。这是由于系统是用链表来存储 
的空闲内存地址的,自然是不连续的,而链表的遍历方向是由低地址向高地址。堆的大小 
受限于计算机系统中有效的虚拟内存。由此可见,堆获得的空间比较灵活,也比较大。 

申请效率的比较: 
栈由系统自动分配,速度较快。但程序员是无法控制的。 
堆是由new分配的内存,一般速度比较慢,而且容易产生内存碎片,不过用起来最方便. 
另外,在WINDOWS下,最好的方式是用VirtualAlloc分配内存,他不是在堆,也不是在栈是 
直接在进程的地址空间中保留一块内存,虽然用起来最不方便。但是速度快,也最灵活。 


堆和栈中的存储内容 
栈: 在函数调用时,第一个进栈的是主函数中后的下一条指令(函数调用语句的下一条可 
执行语句)的地址,然后是函数的各个参数,在大多数的C编译器中,参数是由右往左入栈 
的,然后是函数中的局部变量。注意静态变量是不入栈的。 
当本次函数调用结束后,局部变量先出栈,然后是参数,最后栈顶指针指向最开始存的地 
址,也就是主函数中的下一条指令,程序由该点继续运行。 
堆:一般是在堆的头部用一个字节存放堆的大小。堆中的具体内容由程序员安排。 

存取效率的比较 

char s1[] = "aaaaaaaaaaaaaaa"; 
char *s2 = "bbbbbbbbbbbbbbbbb"; 
aaaaaaaaaaa是在运行时刻赋值的; 
而bbbbbbbbbbb是在编译时就确定的; 
但是,在以后的存取中,在栈上的数组比指针所指向的字符串(例如堆)快。 
比如: 
#include 
void main() 

char a = 1; 
char c[] = "1234567890"; 
char *p ="1234567890"; 
a = c[1]; 
a = p[1]; 
return; 

对应的汇编代码 
10: a = c[1]; 
00401067 8A 4D F1 mov cl,byte ptr [ebp-0Fh] 
0040106A 88 4D FC mov byte ptr [ebp-4],cl 
11: a = p[1]; 
0040106D 8B 55 EC mov edx,dword ptr [ebp-14h] 
00401070 8A 42 01 mov al,byte ptr [edx+1] 
00401073 88 45 FC mov byte ptr [ebp-4],al 
第一种在读取时直接就把字符串中的元素读到寄存器cl中,而第二种则要先把指针值读到 
edx中,再根据edx读取字符,显然慢了。  

ps:感谢 http://www.cnblogs.com/Kevin_z/archive/2010/03/05/1679031.html

 

9.Object-C的内存管理?

基本概念:

  iPhone系统中的Objective-C的内存管理机制是比较灵活的,即可以拿来像C/C++一样用,也可以加个AutoreleasePool让它升级为半自动化的内存管理语言;引用计数是实例对象的内存回收唯一参考,引用计数(retainCount)是Objective-C管理对象引用的唯一依据。调用实例的release方法后,此属性减一,减到为零时对象的dealloc方法被自动调用,进行内存回收操作,也就是说我们永不该手动调用对象的dealloc方法;“拥有的概念”,拥有一个对象的使用权,我们称为拥有这个对象;对象的拥有者个数至少为1,对象才得以存在,否则它应该立即销毁,获得一个对象所有权的方法:当对对象做alloc,copy,和retain操作之后;“引用”的概念,面向对象领域里有个引用的概念,区别于继承,引用常被用来当做偶合性更小的设计。一个实例拥有另一个实例的时候,我们称它为引用了另一个实例。

 比如ClassA类的一个属性对象的Setter方法:

- ( void )setMyArray:(NSMutableArray *)newArray {

     if (myArray != newArray) {

         [ myArray setMyArray:nil ];// 这里不用release思考为什么,(在实例的dealloc方法中会调用myArray的realse方法)

         myArray = [newArray retain];

     }

}

 内存管理API及使用准则:

(1) alloc:为一个新对象分配内存,并且它的引用记数为1;调用alloc方法,你便拥有新对象的所有权;

(2) copy:制造一个对象的副本,改副本的retainCount为1,调用者拥有对副本的所有权;

(3) retain: 使retainCount+1;并且拥有对象所有权;

(4) release:使retainCount-1;

(5) autorealse: 未来的某个时刻使retainCount-1;

(6) dealloc:不要手动调用,而是在系统retainCount为0时自动调用;

-(void)dealloc{

[name  release];

[super dealloc];

}//变量的release顺序与初始顺序相反;

 内存管理的原则:

以 1 2 3为A类,(retainCount+1);

以 4,5为B类:retainCount-1

  • 对于同一个对象所做的,A与B的调用次数保持一致;
  • 凡是通过alloc,retain,copy等手段获得对象的所有权;必须在不适用的 使用自己调用release或autoRelease释放;
  • 不要释放不属于自己的对象;
  • autorelease只是意味着延迟发送一个release消息;
  • 对于便利构造器和访问器来说,不用进行释放,因为没有获得对象的使用权;

使用小例子:

Person *person1 = [[Person alloc] initWithName:@”张三”];

NSLog(@”name is %@”,person1.name); //假设从这往后,我们一直都不使用person1 了,应该把对象给释放了。

[person1 release];

Person *person2 = [Person alloc]initWithName:@”李四”];

NSString *name = person2.name;

NSLog(@”%@”,name); //假设从这以后,我们也不使用person2了。

[person2 release];

//不应该释放name,因为name是我们间接获得的,所以没有它的所有权

 

由便利构造器产生的对象不应当使用者销毁,而是由便利构造器本身完成。
 +(id) personWithName:(NSString *)aName

{

Person *person = [[Person alloc]

initWithName:aName];

return person;

}

①错误,因为返回person对象后,类失去了释放这个对象的机会;

②如果在return语句前加上:[person release];也错误,因为对象已经销毁,不能使用;

③正确做法:return语句前加上:[person autorelease];

 

(二)使用便利构造器创建的对象,不需要进行释放;

如:

-(void) printHello

{

NSString *str = [NSString

stringWithFormat:@”Hello”];

NSLog(@”%@”,str);

}

访问器和设置器:

 在设置器中,保持对新传入对象的所有权,同时放弃旧对象的所有权。

 

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

if(name!= aName){

[name release];//有疑问,会不会造成多次释放;

name = [aName retain];//or copy

}

}

 在访问器中,不需要retain或release.

 

-(NSString *)name{

return name;

}

 

用访问器获得的对象,使用完毕后不需要释放。

 

-(void) printName{

NSString *name = person.name;

NSLog(@”%@”,name); }

 常见错误:

未使用设置器

-(void) reset{

NSString *newName = [[NSString alloc]initWithFormat:@”theNew”];

name = newName;

[newName release]; }

 内存泄露

 

-(void) reset{

NSString *newName = [[NSString alloc] initWithFormat:@”theNew”];

[self setName:newName];

}

 

3) 释放没有所有权的对象

-(void) reset{

NSString *newName = [NSString stringWithFormat:@”theNew”];

[self setName:newName];

[newName release];

}

ps: 感谢 http://www.2cto.com/kf/201211/168247.html

     http://www.cnblogs.com/andyque/archive/2011/08/08/2131236.html

 

10.为什么很多内置的类,如TableViewController的delegate的属性是assign不是retain?

循环引用
  所有的引用计数系统,都存在循环应用的问题。例如下面的引用关系:
    •    对象a创建并引用到了对象b.
    •    对象b创建并引用到了对象c.
    •    对象c创建并引用到了对象b.
这时候b和c的引用计数分别是2和1。当a不再使用b,调用release释放对b的所有权,因为c还引用了b,所以b的引用计数为1,b不会被释放。b不释放,c的引用计数就是1,c也不会被释放。从此,b和c永远留在内存中。
这种情况,必须打断循环引用,通过其他规则来维护引用关系。比如,我们常见的delegate往往是assign方式的属性而不是retain方式的属性,赋值不会增加引用计数,就是为了防止delegation两端产生不必要的循环引用。如果一个UITableViewController 对象a通过retain获取了UITableView对象b的所有权,这个UITableView对象b的delegate又是a, 如果这个delegate是retain方式的,那基本上就没有机会释放这两个对象了。

 

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

  assign用于简单数据类型,如NSInteger,double,bool。

  retain和copy用于对象,copy用于当a指向一个对象,b也想指向同样的对象的时候,如果用assign,a如果释放,再调用b会crash,如果用copy 的方式,a和b各自有自己的内存,就可以解决这个问题。retain 会使计数器加一,也可以解决assign的问题。

  另外:atomic和nonatomic用来决定编译器生成的getter和setter是否为原子操作。在多线程环境下,原子操作是必要的,否则有可能引起错误的结果。加了atomic,setter函数会变成下面这样:

if (property != newValue) {
[property release];
property = [newValue retain];
}

 

12.对象是什么时候被release的?

范围:任何继承了NSObject 的对象,对基本数据类型无效

原理:

  • 每个对象内部都保存了一个与之相关联的整数,称为引用计数器(auto reference count)
  • 每当使用 alloc、new或者copy创建一个对象时,对象的引用计数器被设置为1
  • 给对象发送一条retain消息(即调用retain方法),可以使引用计数器值+1
  • 给对象发送一条release消息,可以使引用计数器值-1
  • 当一个对象的引用计数器值为0时,那么它将被销毁,其占用的内存被系统回收,OC也会自动向对象发送一条dealloc消息。一般会重写dealloc方法,在这里释放相关资源。一定不要直接调用dealloc方法。
  • 可以给对象发送retainCount消息获得当前的引用计数器值。

内存管理原则

  • 谁创建,谁释放(“谁污染,谁治理”)。如果你通过alloc、new或者(mutable)copy来创建一个对象,那么你必须调用release或autorelease。或句话说,不是你创建的,就不用你去释放
  • 一般来说,除了alloc、new或copy之外的方法创建的对象都被声明了autorelease(autorelease是延迟释放内存,不用你自己去手动释放,系统会知道在什么时候该去释放掉它。)
  • 谁retain,谁release。只要你调用了retain,无论这个对象是如何生成的,你都要调用release

 

13.tableView的重用机制? 

  查看UITableView头文件,会找到NSMutableArray*  visiableCells,和NSMutableDictnery* reusableTableCells两个结构。visiableCells内保存当前显示的cells,reusableTableCells保存可重用的cells。

  TableView显示之初,reusableTableCells为空,那么tableViewdequeueReusableCellWithIdentifier:CellIdentifier返回nil。开始的cell都是通过[[UITableViewCell alloc]initWithStyle:UITableViewCellStyleDefault reuseIdentifier:CellIdentifier]来创建,而且cellForRowAtIndexPath只是调用最大显示cell数的次数。

  比如:有100条数据,iPhone一屏最多显示10个cell。程序最开始显示TableView的情况是:

  • 用[[UITableViewCell alloc]initWithStyle:UITableViewCellStyleDefault reuseIdentifier:CellIdentifier]创建10次cell,并给cell指定同样的重用标识(当然,可以为不同显示类型的cell指定不同的标识)。并且10个cell全部都加入到visiableCells数组,reusableTableCells为空。
  • 向下拖动tableView,当cell1完全移出屏幕,并且cell11(它也是alloc出来的,原因同上)完全显示出来的时候。cell11加入到visiableCells,cell1移出visiableCells,cell1加入到reusableTableCells。
  • 接着向下拖动tableView,因为reusableTableCells中已经有值,所以,当需要显示新的cell,cellForRowAtIndexPath再次被调用的时候,tableView dequeueReusableCellWithIdentifier:CellIdentifier,返回cell1。cell1加入到visiableCells,cell1移出reusableTableCells;cell2移出visiableCells,cell2加入到reusableTableCells。之后再需要显示的Cell就可以正常重用了。

 

14.ViewController 的loadView、viewDidLoad、viewDidUnload分别是什么时候调用的,在自定义ViewCointroller时在这几个函数中应该做什么工作?

由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 OS3.0及之后的版本中,还应该重载重写viewDidUnload来释放对view的任何索引
viewDidLoad后调用数据Model

viewDidUnload方法
当系统内存吃紧的时候会调用该方法(注:viewController没有被dealloc)
内存吃紧时,在iPhone OS3.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还是继续做它该做的事情

15.怎么理解MVC,在Cocoa中MVC是怎么实现的?

MVC设 计模式考虑三种对象:模型对象、视图对象、和控制器对象。模型对象代表特别的知识和专业技能,它们负责保有应用程序的数据和定义操作数据的逻辑。视图对象 知道如何显示应用程序的模型数据,而且可能允许用户对其进行编辑。控制器对象是应用程序的视图对象和模型对象之间的协调者。

Model(模型)是应用程序中用于处理应用程序数据逻辑的部分。
  通常模型对象负责在数据库中存取数据。
View(视图)是应用程序中处理数据显示的部分。
  通常视图是依据模型数据创建的。
Controller(控制器)是应用程序中处理用户交互的部分。
  通常控制器负责从视图读取数据,控制用户输入,并向模型发送数据。
 
16.列举Cocoa中常见的集中多线程的实现,并谈谈多线程安全的几种解决办法,一般什么地方会用到多线程?

线程相关的类:NSThread ,NSOperation ,NSOperationQueue

在实际应用中可以分为2种方式:

  • 使用NSThread
  • 使用NSOperation,NSOperationQueue


前者,和过往window下的线程学习类似;后者,是mac以队列的方式来新建线程。

 PageLoadOperation *p = [[PageLoadOperation alloc] initWithUrl:[NSURL URLWithString:@"http://www.sohu.com"]];
//p.targetURL = [NSURL URLWithString:@"http://www.baidu.com"];
[p start];
[p release];


使用NSOperationQueue

NSOperationQueue *_queue = [[NSOperationQueue alloc] init];

PageLoadOperation *plo = [[PageLoadOperation alloc] initWithUrl:[NSURL URLWithString:url]];
plo.delegate = self;
[_queue addOperation:plo];

[plo release];

[_queue release];

17.delegate和notification区别,分别在什么情况下使用?

(1)效率肯定是delegate比nsnotification高。

(2)delegate方法比notification更加直接,最典型的特征是,delegate方法往往需要关注返回值, 也就是delegate方法的结果。比如-windowShouldClose:,需要关心返回的是yes还是no。所以delegate方法往往包含 should这个很传神的词。也就是好比你做我的delegate,我会问你我想关闭窗口你愿意吗?你需要给我一个答案,我根据你的答案来决定如何做下一 步。相反的,notification最大的特色就是不关心接受者的态度, 我只管把通告放出来,你接受不接受就是你的事情,同时我也不关心结果。所以notification往往用did这个词汇,比如 NSWindowDidResizeNotification,那么nswindow对象放出这个notification后就什么都不管了也不会等待接 受者的反应。

简明概要的说明了KVO和NSNotification的区别:

和delegate一样,KVO和NSNotification的作用也是类与类之间的通信,与delegate不同的是:这两个都是负责发出通知,剩下的事情就不管了,所以没有返回值;;delegate只是一对一,而这两个可以一对多。这两者也有各自的特点。

KVO的使用:

被观察者发出  addObserver:forKeyPath:options:context:  方法来添加观察者。

然后只要被观察者的keyPath值变化(注意:单纯改变其值不会调用此方法,只有通过getters和setters来改变值才会触发KVO),就会在观察者里调用方法observeValueForKeyPath:ofObject:change:context:

因此观察者需要实现方法 observeValueForKeyPath:ofObject:change:context: 来对KVO发出的通知做出响应。

这 些代码都只需在观察者里进行实现,被观察者不用添加任何代码,所以谁要监听谁注册,然后对响应进行处理即可,使得观察者与被观察者完全解耦,运用很灵活很 简便;但是KVO只能检测类中的属性,并且属性名都是通过NSString来查找,编译器不会帮你检错和补全,纯手敲所以比较容易出错。
NSNotification的使用
这里的通知不是由被观察者发出,而是由NSNotificationCenter来统一发出,而不同通知通过唯一的通知标识名notificationName来区分,标识名由发送通知的类来起。
首先被观察者自己在必要的方法A里,通过方法postNotificationName:object:来发出通知notificationName这样发送通知者这边的工作就完成了,每次A被调用,就会发送一次通知notificationName。
然后谁要监听A的变化,就通过[NSNotificationCenter defaultCenter]的方法addObserver:selector:name:object:为观察者注册监听name为notificationName的通知然后每次发出name为notificationName的通知时,注册监听后的观察者就会调用其自己定义的方法notificationSelector来进行响应。
NSNotification的特点呢,就是需要被观察者先主动发出通知,然后观察者注册监听后再来进行响应,比KVO多了发送通知的一步,但是其优点是监听不局限于属性的变化,还可以对多种多样的状态变化进行监听,监听范围广,使用也更灵活。

ps:感谢 http://blog.sina.com.cn/s/blog_bf9843bf0101j5px.html

18.id、nil代表什么?

id和void *并非完全一样。在上面的代码中,id是指向struct objc_object的一个指针,这个意思基本上是说,id是一个指向任何一个继承了Object(或NSObject)类的对象。需要注意的是id是一个指针,所以你在使用id的时候不需要加星号。比如id foo=nil定义了一个nil指针,这个指针指向NSObject的一个任意子类。而id*foo=nil则定义了一个指针,这个指针指向另一个指针,被指向的这个指针指向NSObject的一个子类。

nil和C语言的NULL相同,在objc/objc.h中定义。nil表示一个Objctive-C对象,这个对象的指针指向空(没有东西就是空)。

19.objective-c中的数字对象都有哪些,简述它们与基本数据类型的区别是什么

NSNumber包括:char、unsignedchar、short、unsignedshort、int、unsignedint、integer、unsignedinteger、long、unsignedlong、longlong、unsignedlonglong、float、double、bool。

NSNumber是对象,使用方法不同,配合NSArray和NSDictionary配合使用。

在OC中NSNumber是数字对象,可以进行拆装箱操作! 
//将int转为NSNumber 
NSNumber *num = [NSNumber numberWithInt:123]; 
//得到一个int 
int testNum = [num intValue];

IOS 常规面试题目整理