[转]那些著名或非著名的iOS面试题(中)

1. 反转二叉树,不用递归node

1
2
3
4
5
6
7
8
9
/**
  * Definition for a binary tree node.
  * public class TreeNode {
  *     int val;
  *     TreeNode left;
  *     TreeNode right;
  *     TreeNode(int x) { val = x; }
  * }
  */

递归方式:git

1
2
3
4
5
6
7
8
9
10
11
12
13
public class Solution {
     public TreeNode invertTree(TreeNode root) {
         if  (root ==  null ) {
             return  null ;
         }
         root.left = invertTree(root.left);
         root.right = invertTree(root.right);
         TreeNode tmp = root.left;
         root.left = root.right;
         root.right = tmp;
         return  root;
     }
}

Objective-C实现:github

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
/** 
  * 翻转二叉树(又叫:二叉树的镜像) 
  *
  * @param rootNode 根节点
  *
  * @return 翻转后的树根节点(其实就是原二叉树的根节点) 
  */
  + (BinaryTreeNode *)invertBinaryTree:(BinaryTreeNode *)rootNode {
     if  (!rootNode) {   return  nil; } 
     if  (!rootNode.leftNode && !rootNode.rightNode) {   return  rootNode; } 
     [self invertBinaryTree:rootNode.leftNode];
     [self invertBinaryTree:rootNode.rightNode]; 
     BinaryTreeNode *tempNode = rootNode.leftNode; 
     rootNode.leftNode = rootNode.rightNode;
     rootNode.rightNode = tempNode; 
     return  rootNode;
   }

非递归方式:算法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
+ (BinaryTreeNode *)invertBinaryTree:(BinaryTreeNode *)rootNode {
     if  (!rootNode) {   return  nil; }
     if  (!rootNode.leftNode && !rootNode.rightNode) {   return  rootNode; }
     NSMutableArray *queueArray = [NSMutableArray array];  //数组当成队列
     [queueArray addObject:rootNode];  //压入根节点
     while  (queueArray.count > 0) {
         BinaryTreeNode *node = [queueArray firstObject];
         [queueArray removeObjectAtIndex:0];  //弹出最前面的节点,仿照队列先进先出原则
         BinaryTreeNode *pLeft = node.leftNode;
         node.leftNode = node.rightNode;
         node.rightNode = pLeft;
         if  (node.leftNode) {
             [queueArray addObject:node.leftNode];
         }
         if  (node.rightNode) {
             [queueArray addObject:node.rightNode];
         }
     }
     return  rootNode;
}

示例代码参考:二叉树数组

2. 写一个单例模式xcode

1
2
3
4
5
6
7
8
9
+ (AccountManager *)sharedManager
{
     static AccountManager *sharedAccountManagerInstance = nil;
     static dispatch_once_t predicate;
     dispatch_once(&predicate, ^{
             sharedAccountManagerInstance = [[self alloc] init]; 
     });
     return  sharedAccountManagerInstance;
}

3. iOS应用生命周期安全

应用程序的状态:服务器

  • Not running未运行:程序没启动。网络

  • Inactive未激活:程序在前台运行,不过没有接收到事件。在没有事件处理状况下程序一般停留在这个状态。并发

  • Active激活:程序在前台运行并且接收到了事件。这也是前台的一个正常的模式。

  • Backgroud后台:程序在后台并且能执行代码,大多数程序进入这个状态后会在在这个状态上停留一会。时间到以后会进入挂起状态(Suspended)。有的程序通过特殊的请求后能够长期处于Backgroud状态。

  • Suspended挂起:程序在后台不能执行代码。系统会自动把程序变成这个状态并且不会发出通知。当挂起时,程序仍是停留在内存中的,当系统内存低时,系统就把挂起的程序清除掉,为前台程序提供更多的内存。

iOS的入口在main.m文件:

1
2
3
4
5
6
int main(int argc, char *argv[])
{
     @autoreleasepool {
         return  UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class]));
     }
}

main函数的两个参数,iOS中没有用到,包括这两个参数是为了与标准ANSI C保持一致。 UIApplicationMain函数,前两个和main函数同样,重点是后两个。

后两个参数分别表示程序的主要类(principal class)和代理类(delegate class)。若是主要类(principal class)为nil,将从Info.plist中获取,若是Info.plist中不存在对应的key,则默认为UIApplication;若是代理类(delegate class)将在新建工程时建立。

根据UIApplicationMain函数,程序将进入AppDelegate.m,这个文件是xcode新建工程时自动生成的。下面看一下AppDelegate.m文件,这个关乎着应用程序的生命周期。

一、application didFinishLaunchingWithOptions:当应用程序启动时执行,应用程序启动入口,只在应用程序启动时执行一次。若用户直接启动,lauchOptions内无数据,若经过其余方式启动应用,lauchOptions包含对应方式的内容。

二、applicationWillResignActive:在应用程序将要由活动状态切换到非活动状态时候,要执行的委托调用,如 按下 home 按钮,返回主屏幕,或全屏之间切换应用程序等。

三、applicationDidEnterBackground:在应用程序已进入后台程序时,要执行的委托调用。

四、applicationWillEnterForeground:在应用程序将要进入前台时(被激活),要执行的委托调用,恰好与applicationWillResignActive 方法相对应。

五、applicationDidBecomeActive:在应用程序已被激活后,要执行的委托调用,恰好与applicationDidEnterBackground 方法相对应。

六、applicationWillTerminate:在应用程序要彻底推出的时候,要执行的委托调用,这个须要要设置UIApplicationExitsOnSuspend的键值。

初次启动:

1
2
iOS_didFinishLaunchingWithOptions
iOS_applicationDidBecomeActive

按下home键:

1
2
iOS_applicationWillResignActive
iOS_applicationDidEnterBackground

点击程序图标进入:

1
2
iOS_applicationWillEnterForeground
iOS_applicationDidBecomeActive

当应用程序进入后台时,应该保存用户数据或状态信息,全部没写到磁盘的文件或信息,在进入后台时,最后都写到磁盘去,由于程序可能在后台被杀死。释放尽量释放的内存。

1
- (void)applicationDidEnterBackground:(UIApplication *)application

方法有大概5秒的时间让你完成这些任务。若是超过期间还有未完成的任务,你的程序就会被终止并且从内存中清除。

若是还须要长时间的运行任务,能够在该方法中调用:

1
2
3
[application beginBackgroundTaskWithExpirationHandler:^{ 
     NSLog(@ "begin Background Task With Expiration Handler" ); 
}];

程序终止

程序只要符合如下状况之一,只要进入后台或挂起状态就会终止:

①iOS 4.0之前的系统

②app是基于iOS 4.0以前系统开发的。

③设备不支持多任务

④在Info.plist文件中,程序包含了 UIApplicationExitsOnSuspend 键。

系统经常是为其余app启动时因为内存不足而回收内存最后须要终止应用程序,但有时也会是因为app很长时间才响应而终止。若是app当时运行在后台而且没有暂停,系统会在应用程序终止以前调用app的代理的方法 - (void)applicationWillTerminate:(UIApplication *)application,这样可让你能够作一些清理工做。你能够保存一些数据或app的状态。这个方法也有5秒钟的限制。超时后方法会返回程序从内存中清除。

注意:用户能够手工关闭应用程序。

4. 一工人给老板打7天工要求一块金条 这金条只能切2次 工人天天要1/7金条 怎么分?

这道题解决的主要难点在于:不是给出去的就收不回来了,能够用交换的方法。  

把金条分红三段(就是分两次,或者切两刀),分别是整根金条的1/七、2/七、 4/7。  

第一天:给1/7的, 次日:给2/7的,收回1/7的; 第三天,给1/7的; 第四天:给4/7的,收回1/7和2/7的 ;第五天:给1/7的 ;第六天:给2/7的,收回1/7的;第七天发1/7。

5. iOS中socket使用

Socket是对TCP/IP协议的封装,Socket自己并非协议,而是一个调用接口(API),经过Socket,咱们才能使用TCP/IP协议。

  • http协议:对应于应用层

  • tcp协议:对应于传输层

  • ip协议:对应于网络层

三者本质上没有可比性。 况且HTTP协议是基于TCP链接的。

TCP/IP是传输层协议,主要解决数据如何在网络中传输;而HTTP是应用层协议,主要解决如何包装数据。

我 们在传输数据时,能够只使用传输层(TCP/IP),可是那样的话,因为没有应用层,便没法识别数据内容,若是想要使传输的数据有意义,则必须使用应用层 协议,应用层协议不少,有HTTP、FTP、TELNET等等,也能够本身定义应用层协议。WEB使用HTTP做传输层协议,以封装HTTP文本信息,然 后使用TCP/IP作传输层协议将它发送到网络上。

SOCKET原理

一、套接字(socket)概念

套接字(socket)是通讯的基石,是支持TCP/IP协议的网络通讯的基本操做单元。它是网络通讯过程当中端点的抽象表示,包含进行网络通讯必须的五种信息:链接使用的协议,本地主机的IP地址,本地进程的协议端口,远地主机的IP地址,远地进程的协议端口。

应 用层经过传输层进行数据通讯时,TCP会遇到同时为多个应用程序进程提供并发服务的问题。多个TCP链接或多个应用程序进程可能须要经过同一个 TCP协议端口传输数据。为了区别不一样的应用程序进程和链接,许多计算机操做系统为应用程序与TCP/IP协议交互提供了套接字(Socket)接口。应 用层能够和传输层经过Socket接口,区分来自不一样应用程序进程或网络链接的通讯,实现数据传输的并发服务。

二、创建socket链接

创建Socket链接至少须要一对套接字,其中一个运行于客户端,称为ClientSocket,另外一个运行于服务器端,称为ServerSocket。

套接字之间的链接过程分为三个步骤:服务器监听,客户端请求,链接确认。

服务器监听:服务器端套接字并不定位具体的客户端套接字,而是处于等待链接的状态,实时监控网络状态,等待客户端的链接请求。

客户端请求:指客户端的套接字提出链接请求,要链接的目标是服务器端的套接字。为此,客户端的套接字必须首先描述它要链接的服务器的套接字,指出服务器端套接字的地址和端口号,而后就向服务器端套接字提出链接请求。

连 接确认:当服务器端套接字监听到或者说接收到客户端套接字的链接请求时,就响应客户端套接字的请求,创建一个新的线程,把服务器端套接字的描述发给客户 端,一旦客户端确认了此描述,双方就正式创建链接。而服务器端套接字继续处于监听状态,继续接收其余客户端套接字的链接请求。

三、SOCKET链接与TCP链接

建立Socket链接时,能够指定使用的传输层协议,Socket能够支持不一样的传输层协议(TCP或UDP),当使用TCP协议进行链接时,该Socket链接就是一个TCP链接。

四、Socket链接与HTTP链接

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

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

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

6. 网络请求中post和get的区别

GET是用于获取数据的,POST通常用于将数据发给服务器之用。

广泛答案

1.GET使用URL或Cookie传参。而POST将数据放在BODY中。

2.GET的URL会有长度上的限制,则POST的数据则能够很是大。

3.POST比GET安全,由于数据在地址栏上不可见。

不过也有文章说其实上面的是错误的,具体参考这篇文章

7. 时间复杂度和空间复杂度

因为打不出数字符号,只能贴图了。

1170656-21358344d6f9d53e.jpg

时间复杂度

求时间复杂度

【1】若是算法的执行时间不随着问题规模n的增长而增加,即便算法中有上千条语句,其执行时间也不过是一个较大的常数。此类算法的时间复杂度是O(1)。

1
x=91; y=100; while (y>0)  if (x>100) {x=x-10;y--;}  else  x++;解答: T(n)=O(1)

这段程序的运行是和n无关的,就算它再循环一万年,咱们也无论他,只是一个常数阶的函数。

【2】当有若干个循环语句时,算法的时间复杂度是由嵌套层数最多的循环语句中最内层语句的频度f(n)决定的。

1
2
3
4
5
  x=1; 
for (i=1;i<=n;i++) 
     for (j=1;j<=i;j++)
        for (k=1;k<=j;k++)
            x++;

该程序段中频度最大的语句是(5),内循环的执行次数虽然与问题规模n没有直接关系,可是却与外层循环的变量取值有关,而最外层循环的次数直接与n有关,所以能够从内层循环向外层分析语句(5)的执行次数: 则该程序段的时间复杂度为

1170656-b640f2553e52fe12.png

【3】算法的时间复杂度不只仅依赖于问题的规模,还与输入实例的初始状态有关。

在数值A[0..n-1]中查找给定值K的算法大体以下:

1
2
3
4
i=n-1;            
while (i>=0&&(A[i]!=k))       
   i--;        
return  i;

此算法中的语句(3)的频度不只与问题规模n有关,还与输入实例中A的各元素取值及K的取值有关: ①若A中没有与K相等的元素,则语句(3)的频度f(n)=n; ②若A的最后一个元素等于K,则语句(3)的频度f(n)是常数0。

空间复杂度

一个程序的空间复杂度是指运行完一个程序所需内存的大小。利用程序的空间复杂度,能够对程序的运行所须要的内存多少有个预先估计。一个程序执行时除了须要存储空间和存储自己所使用的指令、常数、变量和输入数据外,还须要一些对数据进行操做的工做单元和存储一些为现实计算所需信息的辅助空间。程序执行时所需存储空间包括如下两部分。 

(1)固定部分。这部分空间的大小与输入/输出的数据的个数多少、数值无关。主要包括指令空间(即代码空间)、数据空间(常量、简单变量)等所占的空间。这部分属于静态空间。

(2)可变空间,这部分空间的主要包括动态分配的空间,以及递归栈所需的空间等。这部分的空间大小与算法有关。

一个算法所需的存储空间用f(n)表示。S(n)=O(f(n))  其中n为问题的规模,S(n)表示空间复杂度。

8. 支付宝SDK使用

使用支付宝进行一个完整的支付功能,大体有如下步骤:向支付宝申请, 与支付宝签约,得到商户ID(partner)和帐号ID(seller)和私钥(privateKey)。下载支付宝SDK,生成订单信息,签名加密调用支付宝客户端,由支付宝客户端跟支付宝安全服务器打交道。支付完毕后,支付宝客户端会自动跳回到原来的应用程序,在原来的应用程序中显示支付结果给用户看。

集成以后可能遇到的问题

1)集成SDK编译时找不到 openssl/asn1.h 文件

1170656-9cd15fc846eef8ee.jpg

解决方案:Build Settings --> Search Paths --> Header Search paths : $(SRCROOT)/支付宝集成/Classes/Alipay

1170656-0c846782413bc42c.jpg

2)连接时:找不到 SystemConfiguration.framework 这个库

1170656-017d3f15dea91b82.jpg

解决方案:

1170656-9c1c8eb51640d3a2.jpg

打开支付宝客户端进行支付(用户没有安装支付宝客户端,直接在应用程序中添加一个WebView,经过网页让用户进行支付)

1
2
// 注意:若是是经过网页支付完成,那么会回调该block:callback
[[AlipaySDK defaultService] payOrder:orderString fromScheme:@ "jingdong"  callback:^(NSDictionary *resultDic) { }];

在AppDelegate.m

1
2
3
// 当经过别的应用程序,将该应用程序打开时,会调用该方法
- (BOOL)application:(UIApplication *)app openURL:(NSURL *)url options:(NSDictionary *)options{  // 当用户经过支付宝客户端进行支付时,会回调该block:standbyCallback 
[[AlipaySDK defaultService] processOrderWithPaymentResult:url standbyCallback:^(NSDictionary *resultDic) { NSLog(@ "result = %@" ,resultDic); }];  return  YES;}

9. 远程推送

当服务端远程向APNS推送至一台离线的设备时,苹果服务器Qos组件会自动保留一份最新的通知,等设备上线后,Qos将把推送发送到目标设备上。

远程推送的基本过程:

1.客户端的app须要将用户的UDID和app的bundleID发送给apns服务器,进行注册,apns将加密后的device Token返回给app

2.app得到device Token后,上传到公司服务器

3.当须要推送通知时,公司服务器会将推送内容和device Token一块儿发给apns服务器

4.apns再将推送内容送到客户端上

建立证书的流程:

1.打开钥匙串,生成CertificateSigningRequest.certSigningRequest文件

2.将CertificateSigningRequest.certSigningRequest上传进developer,导出.cer文件

3.利用CSR导出P12文件

4.须要准备下设备token值(无空格)

5.使用OpenSSL合成服务器所使用的推送证书

本地app代码参考

1.注册远程通知

1
2
3
4
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions //中注册远程通知
{
     [[UIApplication sharedApplication] registerForRemoteNotificationTypes:(UIRemoteNotificationTypeAlert | UIRemoteNotificationTypeBadge | UIRemoteNotificationTypeSound)];
}

2,实现几个代理方法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
//获取deviceToken令牌  
-(void)application:(UIApplication *)application didRegisterForRemoteNotificationsWithDeviceToken:(NSData *)deviceToken  
{  
     //获取设备的deviceToken惟一编号  
     NSLog(@ "deviceToken=%@" ,deviceToken);  
     NSString *realDeviceToken=[NSString stringWithFormat:@ "%@" ,deviceToken];  
     //去除<>  
     realDeviceToken = [realDeviceToken stringByReplacingOccurrencesOfString:@ "<"  withString:@ "" ];  
     realDeviceToken = [realDeviceToken stringByReplacingOccurrencesOfString:@ ">"  withString:@ "" ];  
     NSLog(@ "realDeviceToken=%@" ,realDeviceToken);  
     [[NSUserDefaults standardUserDefaults] setValue:realDeviceToken forKey:@ "DeviceToken" ];   //要发送给服务器
}  
  //获取令牌出错  
-(void)application:(UIApplication *)application didFailToRegisterForRemoteNotificationsWithError:(NSError *)error  
{  
     //注册远程通知设备出错  
     NSLog(@ "RegisterForRemoteNotification error=%@" ,error);  
}  
//在应用在前台时受到消息调用  
-(void)application:(UIApplication *)application didReceiveRemoteNotification:(NSDictionary *)userInfo  
{  
    //打印推送的消息  
     NSLog(@ "%@" ,[[userInfo objectForKey:@ "aps" ] objectForKey:@ "alert" ]):  
}

配置后台模式

通常咱们是使用开发版本的Provisioning作推送测试,若是没有问题,再使用发布版本证书的时候通常也应该是没有问题的。为了以防万一,咱们能够在越狱的手机上安装咱们的使用发布版证书的ipa文件(最好使用debug版本,并打印出获取到的deviceToken),安装成功后在;XCode->Window->Organizer-找到对应的设备查看console找到打印的deviceToken。

在后台的推送程序中使用发布版制做的证书并使用该deviceToken作推送服务.

使用开发和发布证书获取到的deviceToken是不同的。

10. @protocol 和 category 中如何使用 @property

1)在protocol中使用property只会生成setter和getter方法声明,咱们使用属性的目的,是但愿遵照我协议的对象能实现该属性

2)category 使用 @property 也是只会生成setter和getter方法的声明,若是咱们真的须要给category增长属性的实现,须要借助于运行时的两个函数:

1
2
①objc_setAssociatedObject
②objc_getAssociatedObject
相关文章
相关标签/搜索