iOS面試題,看看你究竟知道多少(二)

1、反轉二叉樹,不用遞歸

 

/**

 * Definition for a binary tree node.

 * public class TreeNode {

 *     int val;

 *     TreeNode left;

 *     TreeNode right;

 *     TreeNode(int x) { val = x; }

 * }

 */

 

遞歸方式:

 

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;

}

}

 

object-c實現:

 

/** 

 * 翻轉二叉樹(又叫:二叉樹的鏡像) 

 *

 * @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;

  }

 

非遞歸方式:

 

+ (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、寫一個單例模式

 

+ (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文件:

 

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文件,這個關乎着應用程序的生命週期。

 

1、application didFinishLaunchingWithOptions:當應用程序啓動時執行,應用程序啓動入口,只在應用程序啓動時執行一次。若用戶直接啓動,lauchOptions內無數據,若通過其他方式啓動應用,lauchOptions包含對應方式的內容。

 

2、applicationWillResignActive:在應用程序將要由活動狀態切換到非活動狀態時候,要執行的委託調用,如 按下 home 按鈕,返回主屏幕,或全屏之間切換應用程序等。

 

3、applicationDidEnterBackground:在應用程序已進入後臺程序時,要執行的委託調用。

 

4、applicationWillEnterForeground:在應用程序將要進入前臺時(被激活),要執行的委託調用,剛好與applicationWillResignActive 方法相對應。

 

5、applicationDidBecomeActive:在應用程序已被激活後,要執行的委託調用,剛好與applicationDidEnterBackground 方法相對應。

 

6、applicationWillTerminate:在應用程序要完全推出的時候,要執行的委託調用,這個需要要設置UIApplicationExitsOnSuspend的鍵值。

 

初次啓動:

iOS_didFinishLaunchingWithOptions

iOS_applicationDidBecomeActive

 

按下home鍵:

iOS_applicationWillResignActive

iOS_applicationDidEnterBackground

 

點擊程序圖標進入:

iOS_applicationWillEnterForeground

iOS_applicationDidBecomeActive

 

當應用程序進入後臺時,應該保存用戶數據或狀態信息,所有沒寫到磁盤的文件或信息,在進入後臺時,最後都寫到磁盤去,因爲程序可能在後臺被殺死。釋放盡可能釋放的內存。

 

- (void)applicationDidEnterBackground:(UIApplication *)application

方法有大概5秒的時間讓你完成這些任務。如果超過時間還有未完成的任務,你的程序就會被終止而且從內存中清除。

 

如果還需要長時間的運行任務,可以在該方法中調用

 

[application beginBackgroundTaskWithExpirationHandler:^{ 

 

    NSLog(@"begin Background Task With Expiration Handler"); 

 

}];

 

程序終止

 

程序只要符合以下情況之一,只要進入後臺或掛起狀態就會終止:

 

①iOS4.0以前的系統

②app是基於iOS4.0之前系統開發的。

③設備不支持多任務

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

 

系統常常是爲其他app啓動時由於內存不足而回收內存最後需要終止應用程序,但有時也會是由於app很長時間才響應而終止。如果app當時運行在後臺並且沒有暫停,系統會在應用程序終止之前調用app的代理的方法 - (void)applicationWillTerminate:(UIApplication *)application,這樣可以讓你可以做一些清理工作。你可以保存一些數據或app的狀態。這個方法也有5秒鐘的限制。超時後方法會返回程序從內存中清除。

 

注意:用戶可以手工關閉應用程序。

 

4、一工人給老闆打7天工要求一塊金條 這金條只能切2次 工人每天要1/7金條 怎麼分?

 

這道題解決的主要難點在於:不是給出去的就收不回來了,可以用交換的方法。  

 

把金條分成三段(就是分兩次,或者切兩刀),分別是整根金條的1/7、2/7、 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原理

 

1、套接字(socket)概念

 

套接字(socket)是通信的基石,是支持TCP/IP協議的網絡通信的基本操作單元。它是網絡通信過程中端點的抽象表示,包含進行網絡通信必須的五種信息:連接使用的協議,本地主機的IP地址,本地進程的協議端口,遠地主機的IP地址,遠地進程的協議端口。

 

應 用層通過傳輸層進行數據通信時,TCP會遇到同時爲多個應用程序進程提供併發服務的問題。多個TCP連接或多個應用程序進程可能需要通過同一個 TCP協議端口傳輸數據。爲了區別不同的應用程序進程和連接,許多計算機操作系統爲應用程序與TCP/IP協議交互提供了套接字(Socket)接口。應 用層可以和傳輸層通過Socket接口,區分來自不同應用程序進程或網絡連接的通信,實現數據傳輸的併發服務。

 

2 、建立socket連接

 

建立Socket連接至少需要一對套接字,其中一個運行於客戶端,稱爲ClientSocket,另一個運行於服務器端,稱爲ServerSocket。

 

套接字之間的連接過程分爲三個步驟:服務器監聽,客戶端請求,連接確認。

 

服務器監聽:服務器端套接字並不定位具體的客戶端套接字,而是處於等待連接的狀態,實時監控網絡狀態,等待客戶端的連接請求。

 

客戶端請求:指客戶端的套接字提出連接請求,要連接的目標是服務器端的套接字。爲此,客戶端的套接字必須首先描述它要連接的服務器的套接字,指出服務器端套接字的地址和端口號,然後就向服務器端套接字提出連接請求。

 

連 接確認:當服務器端套接字監聽到或者說接收到客戶端套接字的連接請求時,就響應客戶端套接字的請求,建立一個新的線程,把服務器端套接字的描述發給客戶 端,一旦客戶端確認了此描述,雙方就正式建立連接。而服務器端套接字繼續處於監聽狀態,繼續接收其他客戶端套接字的連接請求。

 

3、SOCKET連接與TCP連接

 

創建Socket連接時,可以指定使用的傳輸層協議,Socket可以支持不同的傳輸層協議(TCP或UDP),當使用TCP協議進行連接時,該Socket連接就是一個TCP連接。

 

4、Socket連接與HTTP連接

 

由 於通常情況下Socket連接就是TCP連接,因此Socket連接一旦建立,通信雙方即可開始相互發送數據內容,直到雙方連接斷開。但在實際網絡應用 中,客戶端到服務器之間的通信往往需要穿越多箇中間節點,例如路由器、網關、防火牆等,大部分防火牆默認會關閉長時間處於非活躍狀態的連接而導致 Socket 連接斷連,因此需要通過輪詢告訴網絡,該連接處於活躍狀態。

 

而HTTP連接使用的是“請求—響應”的方式,不僅在請求時需要先建立連接,而且需要客戶端向服務器發出請求後,服務器端才能回覆數據。

 

很 多情況下,需要服務器端主動向客戶端推送數據,保持客戶端與服務器數據的實時與同步。此時若雙方建立的是Socket連接,服務器就可以直接將數據傳送給 客戶端;若雙方建立的是HTTP連接,則服務器需要等到客戶端發送一次請求後才能將數據傳回給客戶端,因此,客戶端定時向服務器端發送連接請求,不僅可以 保持在線,同時也是在“詢問”服務器是否有新的數據,如果有就將數據傳給客戶端。

 

下面這篇文章是AsyncSocket的使用教程,大家可以看看。

 

6、網絡請求中post和get的區別

 

GET是用於獲取數據的,POST一般用於將數據發給服務器之用。

 

普遍答案

 

1.GET使用URL或Cookie傳參。而POST將數據放在BODY中。

2.GET的URL會有長度上的限制,則POST的數據則可以非常大。

3.POST比GET安全,因爲數據在地址欄上不可見。

 

不過也有文章說其實上面的是錯誤的,具體參考這篇文章

 

7、時間複雜度和空間複雜度

 

由於打不出數字符號,只能貼圖了。

 

時間複雜度

 

求時間複雜度

 

【1】如果算法的執行時間不隨着問題規模n的增加而增長,即使算法中有上千條語句,其執行時間也不過是一個較大的常數。此類算法的時間複雜度是O(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)決定的。

 

 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)的執行次數: 則該程序段的時間複雜度爲

 

2

 

【3】算法的時間複雜度不僅僅依賴於問題的規模,還與輸入實例的初始狀態有關。

 

在數值A[0..n-1]中查找給定值K的算法大致如下:

 

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 文件

 

技術分享

 

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

 

技術分享

 

2)鏈接時:找不到 SystemConfiguration.framework 這個庫

技術分享


解決方案:

 

技術分享

 

打開支付寶客戶端進行支付(用戶沒有安裝支付寶客戶端,直接在應用程序中添加一個WebView,通過網頁讓用戶進行支付)

// 注意:如果是通過網頁支付完成,那麼會回調該block:callback

 

 [[AlipaySDK defaultService] payOrder:orderString fromScheme:@"jingdong" callback:^(NSDictionary *resultDic) { }];

 

在AppDelegate.m

 

// 當通過別的應用程序,將該應用程序打開時,會調用該方法

- (BOOL)application:(UIApplication *)app openURL:(NSURL *)url options:(NSDictionary<NSString *,id> *)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、註冊遠程通知

 

 - (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions//中註冊遠程通知

{

[[UIApplication sharedApplication] registerForRemoteNotificationTypes:(UIRemoteNotificationTypeAlert | UIRemoteNotificationTypeBadge | UIRemoteNotificationTypeSound)];

}

 

2、實現幾個代理方法:

 

zterForRemoteNotificationsWithDeviceToken:(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增加屬性的實現,需要藉助於運行時的兩個函數:

 

①objc_setAssociatedObject

②objc_getAssociatedObject



歡迎大家關注我的微信公衆號,有什麼問題可以隨時聯繫,掃描下方二維碼添加: 
這裏寫圖片描述
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章