[置顶] iOS面试题

1.写一个标准宏,这个宏输入两个参数并返回较小的一个


#define MIN(A,B) ((A) <= (B)? (A) : (B))
这个测试是为下面的目的而设的:
1). 标识#define在宏中应用的基本知识。这是很重要的,因为直到嵌入(inline)操作符变为标准C的一部分,宏是方便产生嵌入代码的唯一方法,
对于嵌入式系统来说,为了能达到要求的性能,嵌入代码经常是必须的方法。
2). 三目运算符的知识。这个运算符存在C语言中的原因是它使得编译器能产生比if-then-else更优化的代码,了解这个用法是很重要的。
3). 懂得在宏中小心地把参数用括号括起来
4). 我也用这个问题开始讨论宏的副作用,例如:当你写下面的代码时会发生什么事?
least = MIN(*p++, b);

2.static关键字的作用有哪些

(1)函数体内 static 变量的作用范围为该函数体,不同于 auto 变量,该变量的内存只被分配一次,
因此其值在下次调用时仍维持上次的值; 
(2)在模块内的 static 全局变量可以被模块内所用函数访问,但不能被模块外其它函数访问; 
(3)在模块内的 static 函数只可被这一模块内的其它函数调用,这个函数的使用范围被限制在声明
它的模块内; 
(4)在类中的 static 成员变量属于整个类所拥有,对类的所有对象只有一份拷贝; 
(5)在类中的 static 成员函数属于整个类所拥有,这个函数不接收 this 指针,因而只能访问类的static 成员变量。

3.extern c 的作用有哪些

(1)被 extern "C"限定的函数或变量是 extern 类型的;
     extern 是 C/C++语言中表明函数和全局变量作用范围(可见性)的关键字,该关键字告诉编译器,
     其声明的函数和变量可以在本模块或其它模块中使用。

(2)被 extern "C"修饰的变量和函数是按照 C 语言方式编译和连接的; 
 extern "C"的惯用法 

(1)在 C++中引用 C 语言中的函数和变量,在包含 C 语言头文件(假设为 cExample.h)时,需进
       行下列处理: 
  extern "C"  {  
   #include "cExample.h"  
  }  
而在 C 语言的头文件中,对其外部函数只能指定为 extern 类型,C 语言中不支持 extern "C"声明,
在.c 文件中包含了 extern "C"时会出现编译语法错误。

(2)在 C 中引用 C++语言中的函数和变量时,C++的头文件需添加 extern "C",但是在 C 语言中不
能直接引用声明了 extern "C"的该头文件,应该仅将 C 文件中将 C++中定义的 extern "C"函数声明为
extern 类型。

4.#import与#include的区别,@class呢

(1)#import和#include的区别

当我们在代码中使用两次#include的时候会报错:因为#include相当于拷贝头文件中的声明内容,所以会报重复定义的错误

但是使用两次#import的话,不会报错,所以他可以解决重复导入的问题,他会做一次判断,如果已经导入一次就不导入了

(2) OC中在相互导入头文件的时候编译是不通过的,这样就会导致错误。当然解决办法就是使用@class关键字,防止相互导入导致报错
5.c和objective-c如何混用
 @class一般用于头文件中需要声明该类的某个实例变量的时候用到,在m文件中还是需要使用#import。
 而#import比起#include的好处就是不会引起交叉编译。

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.

6.比较http和socket通信的区别

http是客户端用http协议进行请求,发送请求时候需要封装http请求头,并绑定请求的数据,服务器一般有web服务器配合(当然也非绝对)。 http请求方式为客户端主动发起请求,服务器才能给响应,一次请求完毕后则断开连接,以节省资源。服务器不能主动给客户端响应(除非采取http长连接技术)。iphone主要使用类是NSUrlConnection。


scoket是客户端跟服务器直接使用socket“套接字”进行连接,并没有规定连接后断开,所以客户端和服务器可以保持连接通道,双方都可以主动发送数据。一般在游戏开发或股票开发这种要求即时性很强并且保持发送数据量比较大的场合使用。主要使用类是CFSocketRef。


由于通常情况下Socket连接就是TCP连接,因此Socket连接一旦建立,通信双方即可开始相互发送数据内容,直到双方连接断开。但在实际网络应用中,客户端到服务器之间的通信往往需要穿越多个中间节点,例如路由器、网关、防火墙等,大部分防火墙默认会关闭长时间处于非活跃状态的连接而导致 Socket 连接断连,因此需要通过轮询告诉网络,该连接处于活跃状态。

而HTTP连接使用的是“请求—响应”的方式,不仅在请求时需要先建立连接,而且需要客户端向服务器发出请求后,服务器端才能回复数据。 

很多情况下,需要服务器端主动向客户端推送数据,保持客户端与服务器数据的实时与同步。此时若双方建立的是Socket连接,服务器就可以直接将数据传送给客户端;若双方建立的是HTTP连接,则服务器需要等到客户端发送一次请求后才能将数据传回给客户端,因此,客户端定时向服务器端发送连接请求,不仅可以保持在线,同时也是在“询问”服务器是否有新的数据,如果有就将数据传给客户端。

7.堆和栈的区别

管理方式:对于栈来讲,是由编译器自动管理,无需我们手工控制;对于堆来说,释放工作由程序员控制,容易产生memory leak。

(1).申请大小:
栈:在Windows下,栈是向低地址扩展的数据结构,是一块连续的内存的区域。这句话的意思是栈顶的地址和栈的最大容量是系统预先规定好的,在WINDOWS下,栈的大小是2M(也有的说是1M,总之是一个编译时就确定的常数),如果申请的空间超过栈的剩余空间时,将提示 overflow。因此,能从栈获得的空间较小。


堆:堆是向高地址扩展的数据结构,是不连续的内存区域。这是由于系统是用链表来存储的空闲内存地址的,自然是不连续的,而链表的遍历方向是由低地址向高地址。堆的大小受限于计算机系统中有效的虚拟内存。由此可见,堆获得的空间比较灵活,也比较大。

(2).碎片问题:对于堆来讲,频繁的new/delete势必会造成内存空间的不连续,从而造成大量的碎片,使程序效率降低。对于栈来讲,则不会存在这个问题,因为栈是先进后出的队列,他们是如此的一一对应,以至于永远都不可能有一个内存块从栈中间弹出

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

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

8.sprintf,strcpy,memcpy使用上有什么要注意的地方

strcpy是一个字符串拷贝的函数,它的函数原型为strcpy(char *dst, const char *src);

将src开始的一段字符串拷贝到dst开始的内存中去,结束的标志符号为 '\0',由于拷贝的长度不是由我们自己控制的,

所以这个字符串拷贝很容易出错。具备字符串拷贝功能的函数有memcpy,这是一个内存拷贝函数,它的函数原型

为memcpy(char *dst, const char* src, unsigned int len);

将长度为len的一段内存,从src拷贝到dst中去,这个函数的长度可控。但是会有内存叠加的问题。

sprintf是格式化函数。将一段数据通过特定的格式,格式化到一个字符串缓冲区中去。sprintf格式化的函数的长度不可控,

有可能格式化后的字符串会超出缓冲区的大小,造成溢出。

9.什么是沙箱模型?哪些操作是属于私有api范畴?

某个iphone工程进行文件操作有此工程对应的指定的位置,不能逾越。

iphone沙箱模型的有四个文件夹,分别是什么,永久数据存储一般放在什么位置,得到模拟器的路径的简单方式是什么.

documents,tmp,app,Library。

(NSHomeDirectory()),

手动保存的文件在documents文件里

Nsuserdefaults保存的文件在tmp文件夹里

Documents 目录:您应该将所有de应用程序数据文件写入到这个目录下。这个目录用于存储用户数据或其它应该定期备份信息。
AppName.app 目录:这是应用程序程序包目录,包含应用程序本身。由于应用程序必须经过签名,

所以您在运行时不能对这个目录中内容进行修改,否则可能会使应用程序无法启动。
Library 目录:这个目录下有两个子目录:Caches 和 Preferences
Preferences 目录包含应用程序偏好设置文件。您不应该直接创建偏好设置文件,而是应该使用NSUserDefaults类来取得和设置应用程序偏好.
Caches 目录用于存放应用程序专用支持文件,保存应用程序再次启动过程中需要信息。

tmp 目录:这个目录用于存放临时文件,保存应用程序再次启动过程中不需要信息。
获取这些目录路径方法:
1,获取家目录路径函数:
NSString *homeDir = NSHomeDirectory();
2,获取Documents目录路径方法:
NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
NSString *docDir = [paths objectAtIndex:0];
3,获取Caches目录路径方法:
NSArray *paths = NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES);
NSString *cachesDir = [paths objectAtIndex:0];
4,获取tmp目录路径方法:
NSString *tmpDir = NSTemporaryDirectory();
5,获取应用程序程序包中资源文件路径方法:
例如获取程序包中一个图片资源(apple.png)路径方法:
NSString *imagePath = [[NSBundle mainBundle] pathForResource:@”apple” ofType:@”png”];
UIImage *appleImage = [[UIImage alloc] initWithContentsOfFile:imagePath];
代码中mainBundle类方法用于返回一个代表应用程序包对象。


文件IO写入
1,将数据写到Documents目录:
- (BOOL)writeApplicationData:(NSData *)data toFile:(NSString *)fileName {


   NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);

   NSString *docDir = [paths objectAtIndex:0];

   if (!docDir) {

    NSLog(@”Documents directory not found!”); return NO;

   }

   NSString *filePath = [docDir stringByAppendingPathComponent:fileName];
   return [data writeToFile:filePath atomically:YES];

}

2,从Documents目录读取数据:
- (NSData *)applicationDataFromFile:(NSString *)fileName {
   NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);

   NSString *docDir = [paths objectAtIndex:0];

   NSString *filePath = [docDir stringByAppendingPathComponent:fileName];

   NSData *data = [[NSData alloc] initWithContentsOfFile:filePath]];

   return data;}

NSSearchPathForDirectoriesInDomains这个主要就是返回一个绝对路径用来存放我们需要储存文件。

- (NSString *)dataFilePath {

   NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
   NSString *documentsDirectory = [paths objectAtIndex:0];
   return [documentsDirectory stringByAppendingPathComponent:@"shoppingCar.plist"];
}

NSFileManager* fm=[NSFileManager defaultManager];
if(![fm fileExistsAtPath:[self dataFilePath]]){

//下面是对该文件进行制定路径保存
[fm createDirectoryAtPath:[self dataFilePath] withIntermediateDirectories:YES attributes:nil error:nil];

//取得一个目录下得所有文件名
NSArray *files = [fm subpathsAtPath: [self dataFilePath] ];

//读取某个文件
NSData *data = [fm contentsAtPath:[self dataFilePath]];

//或者
NSData *data = [NSData dataWithContentOfPath:[self dataFilePath]];
}

iphone常见私有api的应用(比如直接发送短信,访问沙箱之外的磁盘文件).

10.线程的常见方法有哪些,你是如何处理多线程的,多线程同步问题你了解么?

线程创建的几种方式,线程的加锁,休眠,唤醒,解锁,退出,

多线程要考虑同步问题,解决同步问题的方式就是对某一资源加锁,当一个线程操作本资源时,其他线程不能操作 。

系统自带线程池(NSOpertionQueue)的作用:

凡是需要启动多个线程的地方都可以使用NSOpertionQueue,加入到NSOpertionQueue中的对象都需要继承NSOpertion。 NSOpertionQueue会在系统内部启动一个独立线程去执行这个被加入对象的main方法。常用的地方是用nsoprationqueue 下载图片,文件。如果是自己创建一个线程池,无非就是启动多个线程的时候,

把这些线程对象放到一个大数组中,如果需要启动线程的时候,先从数组中找空闲线程来使用。

自己管理线程池最大的难题是不好处理当启动多个线程后,用户在多个界面的跳转的时候,对线程方法的回调管理。

而NSOpertionQueue可以很好的处理他。

11.你在开发大型项目的时候,如何进行内存泄露检测的?

  可以通过xcode的自带工具run---start with performance tool里有instruments下有个leaks工具,

  启动此工具后,运行项目,工具里可以显示内存泄露的情况,双击可找到源码位置,可以帮助进行内存泄露的处理。

12.为什么很多内置类如UITableViewController的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方式的,那基本上就没有机会释放这两个对象了。自己在设计使用delegate模式时,也要注意这点。

13.以下每行代码执行后,person对象的retain count分别是多少?

      Person *person = [[Person alloc] init]; count 1

      [person retain]; retain  count 2

      [person release];retain count 1

      [person release];retain count = 0

14.在一个对象的方法里面:

      self.name = “object”;和  name =”object”有什么不同吗?  

答:self.name = "object"会调用对象的setName()方法,会使object引用计数加1,name = "object"会直接把object赋值给当前对象的name 属性,引用计数不增加。

15:下面一段代码的执行结果是

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].

16。id

声明的对象有什么特性? 

(1)没有 * 号

(2)动态数据类型

(3)可以指向任何类的对象(设置是nil),⽽而不关⼼心其具体类型

(4)在运⾏行时检查其具体类型 

(5)可以对其发送任何(存在的)消息 


17.Objective-C中创建线程的方法是什么?如果在主线程中执行代码,方法是什么?如果想延时执行代码方法是什么?

答:线程创建有三种方法:使用NSThread创建、使用 GCD的dispatch、使用子类化的NSOperation,然后将其加入NSOperationQueue;在主线程执行代码,方法是 performSelectorOnMainThread,如果想延时执行代码可以用performSelector:onThread:withObject:waitUntilDone: 

18:iOS中有哪些回调机制,并做简单的比较

1)代理:也叫委托,当某个对象收到多个事件,并要求同一个对象来处理所有事件时。委托机制依赖于某个协议定义的方法来发送消息。

2)通知:当需要多个对象或两个无关对象处理同一个事件时。

3)Block:适用于回调只发生一次的简单任务。

19.在iOS开发中什么时候用delegate,什么时候用Notification?
elegate针对one-to-one关系,并且reciever可以返回值给 sender,notification 可以针对one-to-one/many/none,reciever无法返回值给sender.所以,delegate用于sender希望接受到 reciever的某个功能反馈值,notification用于通知多个object某个事件。
20.线程常见的方法有哪些,你是如何处理多线程的?(同上)
22. ios iap支付验单流程是怎么样的,需要注意些什么
用户点击内购按钮——>用户确认购买内容——>用户通过App Store账户验证——>苹果服务器验证用户请求——>苹果服务器从用户帐号扣款——>苹果向用户返回购买成功信息——>软件接收并显示用户购买信息
IAP的全称是In-App Purchase,应用内付费。这种业务模式允许用户免费下载试用,对应用内提供的商品选择消费,比如购买游戏道具,购买游戏等级等等。相比完全收费的应 用而言,应用内付费给用户试用的机会,不会让优秀的应用因为缺乏用户的认知而丧失消费者; 由于IAP相关文档有限,为此特整理在IAP开发过程中的一些注意事项: 一、产品类型的选择 IAP的商品从消费性质上分为四种: 1.消耗型商品(Consumable),比如游戏道具,子弹,药品等等。由于这类商品可以被消耗,所以支持重复购买。苹果应用商店不保存此类商品的购买记录,如果要保存则需要开发者同步到自己的服务器上。 2.非消耗型商品(Non-Consumable),比如游戏关卡,隐藏地图等等。这类商品只要购买一次便可以了,苹果应用商店里每一个用户对非消耗型商品的购买都有记录,可以在不同的设备上恢复购买状态,这个恢复的过程叫做Restore。 3.自动重置型订阅(Auto-Renewable Subscriptions),比如电子杂志,读物等。消费者购买这类商品时会从列表中选择一个有效期限,卖家在定义商品的时候从一群固定的选项 中选择添加一个有效期,比如7天,一个月,两个月。过了有效期之后,商品的购买状态会被自动重置成未购买,要想继续获得内容则需要再次订阅。这种类型的商品和非消耗型商品一样,会在苹果商店内保存购买记录。 4.非自动重置型订阅(Non-Renewing Subscription),比如用户订阅电子杂志和读物报刊时需要从自定义的期限列表中选择期限,而不是苹果提供的固定选项,比如9天,一个半月或任意时间。在这种情况下,苹果商店无法根据期限来控制订阅的到期行为,所以一切都需要开发商自己编写相应的逻辑来实现。 在创建产品类型的选择决定着客户端/server的处理流程,为此一定要在选择类型时考虑所创建的产品特性,选择时,如果对于自动重置型订阅,特别需要注意,此类型的商品的必须依据苹果设置的使用期限进行创建,如一个月、三个月、半年等,不能创建任意时间段,同时,在此类型中,经过在沙盒测试发现,当产品到期时,苹果服务器会自动进行续订操作,因此,客户端需根据监听处理苹果的续订事件进行续订结果提示用户; 二、产品的支付验证服务器选择 当创建好产品后,客户端进行IAP服务监听后,由于IAP在支付后成功后,会收到苹果服务器的支付凭证,客户端在获取到支付凭证后,需要将支付凭证反馈给server服务器进行支付验证确认。此时,不管是采用客户端APP的server验证方式还是客户端APP验证方式,都需要根据当前APP的支付环境选择正确的验证地址,在苹果服务器中,沙盒测试环境的IAP验证地址为:https://sandbox.itunes.apple.com/verifyReceipt,正常线上交易的验证地址为:https://buy.itunes.apple.com/verifyReceipt,为保证审核的通过,需要在客户端或server进行双重验证,即,先以线上交易验证地址进行验证,如果苹果正式验证服务器的返回验证码code为21007,则再一次连接沙盒测试服务器进行验证即可。 在应用提审时,苹果IAP提审验证时是在沙盒环境的进行的,即:苹果在审核App时,只会在sandbox环境购买,其产生的购买凭证,也只能连接苹果的测试验证服务器,如果没有做双验证,需要特别注意此问题,否则会被拒; 三、产品线上支付过程中的不同环境处理 IAP沙盒环境及线上环境在处理过程中有些问题,需进行特殊处理; 在沙盒环境下,进行支付时,无银行支付验证过程,此时应用一直处于IAP支付过程中,直至支付完成; 而在线环境下,由于IOS6添加了支付确认过程,导致在银行密码确认过程中确认完毕后,应用未能及时返回APP,且此时会收到server下推的SKPaymentTransactionStateFailed事件,当返回到应用后,如果此时已经注册了IAP支付消息处理,当刚才的支付成功后,苹果服务器会反馈SKPaymentTransactionStatePurchased或SKPaymentTransactionStateRestored事件,此时客户端在此事件中获取凭证并进行支付确认; 由于部分类型具有购买恢复操作Restore,所以当删除APP后,又重新安装APP,此时需要恢复之前的购买时,在IAP处理中仍需进行SKPaymentTransactionStateRestored事件的处理,如果通过server方式进行支付凭证验证的,需要判断当前的Restore事件是恢复支付还是购买支付,以保证servver的统计正确;此时可由server根据验证凭证中的有效信息(如有效期信息)进行判断是为新的购买还是以往支付的恢复; 四、IAP事件注册时机 对于IAP支付,当支付成功时但由于网络等引起的支付凭证验证失败或未进行验证时,在IAP事件注册后,苹果服务器会通过SKPaymentTransactionStatePurchased或SKPaymentTransactionStateRestored事件进行返回支付凭证,客户端需对此支付凭证进行验证,以保证支付完整性;为此,在app启动时,应直接进行IAP事件注册;即:[SKPaymentQueuedefaultQueue]addTransactionObserver操作; 五、越狱手机的IAP问题 由于越狱手机可能安装了黑客的恶意程序,监听网络数据,支付凭证中并不包含任何用户的apple id信息,所以我们的app和服务器无法知道这个凭证是谁买的,如果恶意程序截获苹果服务器的有效支付凭证,但恶意程序将假的支付凭证发给后台server导致原支付的账号验证失败,而此时恶意程序将截获的有效支付凭证对应到另外的支付账号上,就会导致该恶意程序设置的账号通过正确的支付凭证而获取server的认证。 所以,对于越狱的手机可禁用IAP支付,采用第三方支付平台进行支付的方式。
 

你可能感兴趣的:(ios,面试题)