那些著名或非著名的iOS面試題(中)

吳白發表於2016-04-10

那些著名或非著名的iOS面試題(上)

那些著名或非著名的iOS面試題(中)

那些著名或非著名的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)的執行次數: 則該程式段的時間複雜度為

【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,實現幾個代理方法:

//獲取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增加屬性的實現,需要藉助於執行時的兩個函式:

①objc_setAssociatedObject

②objc_getAssociatedObject

相關文章