【2019年最新大廠面試題】 iOS面試題附答案

推薦文章

200道iOS面試題整理,底層、技術亮點公司須要的這裏都有

若是你依然在編程的世界裏迷茫,不知道本身的將來規劃,小編給你們推薦一個iOS高級交流羣:458839238 裏面能夠與大神一塊兒交流並走出迷茫。小白可進羣免費領取學習資料,看看前輩們是如何在編程的世界裏傲然前行! 羣內提供數據結構與算法、底層進階、swift、逆向、整合面試題等免費資料 附上一份收集的各大廠面試題(附答案) ! 羣文件直接獲取 各大廠面試題ios

一.設計模式是什麼? 你知道哪些設計模式,並簡要敘述?面試

設計模式是一種編碼經驗,就是用比較成熟的邏輯去處理某一種類型的事情。 1). MVC模式:Model View Control,把模型 視圖 控制器 層進行解耦合編寫。 2). MVVM模式:Model View ViewModel 把模型 視圖 業務邏輯 層進行解耦和編寫。 3). 單例模式:經過static關鍵詞,聲明全局變量。在整個進程運行期間只會被賦值一次。 4). 觀察者模式:KVO是典型的通知模式,觀察某個屬性的狀態,狀態發生變化時通知觀察者。 5). 委託模式:代理+協議的組合。實現1對1的反向傳值操做。 6). 工廠模式:經過一個類方法,批量的根據已有模板生產對象。 MVC 和 MVVM 的區別objective-c

在MVC下,Controller基本是沒法測試的,裏面混雜了個各類邏輯,並且分散在不一樣的地方。有了MVVM咱們就能夠測試裏面的viewModel,來驗證咱們的處理結果對不對(Xcode7的測試已經愈來愈完善了)。算法

好比iOS裏面有iPhone版本和iPad版本,除了交互展現不同外,業務邏輯的model是一致的。這樣,咱們就能夠以很小的代價去開發另外一個app。數據庫

MVVM是MVC的一個升級版,目前的MVC也能夠很快的轉換到MVVM這個模式。VC能夠省去一大部分展現邏輯。編程

缺點:swift

每一個VC都附帶一個viewModel,類的數量*2設計模式

咱們把邏輯給了viewModel,那勢必Model也會變得很複雜,裏面的屬性和方法愈來愈多。可能重寫的方法比較多,由於涉及到一些數據的轉換以及和controller之間的通訊。數組

因爲數據都是從viewModel來,想一想忽然來了一個新人,一看代碼,不知道真實的模型是誰。好比經常使用tableview的數據源,通常都是一個數組,若是不斷的經過viewModel去取,溝通上沒有那麼直接。何況每封一層,意味着要寫不少代碼去融合他們的轉換。瀏覽器

Model負責存儲、定義、操做數據;

View用來展現給用戶,而且和用戶進行交互;

Controller是Model和View的協調者,Controller把Model中的數據拿過來給View使用。Controller能夠直接與Model和View進行通訊,而View不能與Controller直接通訊。,當有數據更新時,Model也要與Controller進行通訊,這個時候就要用Notification和KVO,這個方式就像發廣播同樣,Model發信號,Controller設置接收監聽信號,當有數據更新是就發信號給Controller,Model和View不能直接通訊,這樣違背MVC設計原則。View與Controller通訊須要利用代理協議的方式,Controller能夠直接根據Model決定View的展現。View若是接受響應事件則經過delegate,target-action,block等方式告訴Controller的狀態變化。Controller進行業務的處理,而後再控制View的展現。

關於MVVM的優勢:

方便測試

便於代碼的移植

兼容MVC

類會增多

viewModel會愈來愈龐大

調用複雜度增長

二.#import跟 #include 有什麼區別,@class呢,#import<> 跟 #import」」有什麼區別?

(1)#import指令是Object-C針對@include的改進版本,能確保引用的文件只會被引用一次,不會陷入遞歸包含的問題中;

(2)@import與@class的區別:

#import會鏈入該頭文件的所有信息,包括實體變量和方法等;二@class只是告訴編譯器,其後面聲明的名稱是類的名稱,至於這些類如何定義的,暫時不用考慮。在頭文件中,通常只須要知道被引用的類的名稱就能夠了,不須要知道其內部的實體變量和方法,因此在頭文件中通常使用@class來聲明這個名稱是類的名稱;而在實現類裏面,由於會用到這個引用類的內部的實體變量和方法,因此須要使用#import類包含這個被引用類的頭文件。

@class還能夠解決循環包含的問題

(3)#import<>跟#import""的區別:

#import<>用來包含系統自帶的文件,#import""用來包含自定義的文件

(4)屬性readwrite,readonly,assign,retain,copy,nonatomic 各是什麼做用,在那種狀況下用?

• readwrite:是可讀可寫特性,同時生成get方法和set方法的聲明和實現(補充:默認屬性,將生成不帶額外參數的getter和setter方法(setterff只有一個參數))

• readonly:只讀特性,只會生成get方法的聲明和實現;不但願屬性在類外改變

• assign:是賦值特性,set方法的實現是直接賦值,用於基本數據類型;僅設置變量時

• retain:表示持有特性,set方法將傳入參數先保留,再賦值,傳入參數的retaincount會+1;

• copy:表示拷貝特性,set方法的實現是release舊值,copy新值,用於NSString、block等類型(set方法將傳入的對象複製一份;須要徹底一份新的變量時使用);

• nonatomic:非原子操做,決定編譯器生成的setter getter是不是原子操做,atomic表示多線程安全,通常使用nonatomic

三.frame 和 bounds 有什麼不一樣?

frame指的是:該view在父view座標系統中的位置和大小。(參照點是父view的座標系統) bounds指的是:該view在自己座標系統中的位置和大小。(參照點是自己座標系統)

四.Objective-C的類能夠多重繼承麼?能夠實現多個接口麼?Category是什麼?重寫一個類的方式用繼承好仍是分類好?爲何?

答:Objective-C的類不能夠多重繼承;能夠實現多個接口(協議);Category是類別;通常狀況用分類好,用Category去重寫類的方法,僅對本Category有效,不會影響到其餘類與原有類的關係。

五.@property 的本質是什麼?ivar、getter、setter 是如何生成並添加到這個類中的

@property 的本質是什麼? @property = ivar + getter + setter; 「屬性」 (property)有兩大概念:ivar(實例變量)、getter+setter(存取方法)

「屬性」 (property)做爲 Objective-C 的一項特性,主要的做用就在於封裝對象中的數據。 Objective-C 對象一般會把其所須要的數據保存爲各類實例變量。實例變量通常經過「存取方法」(access method)來訪問。其中,「獲取方法」 (getter)用於讀取變量值,而「設置方法」 (setter)用於寫入變量值。

六.@property中有哪些屬性關鍵字?/ @property 後面能夠有哪些修飾符?

屬性能夠擁有的特質分爲四類: 1.原子性--- nonatomic 特質 2.讀/寫權限---readwrite(讀寫)、readonly (只讀) 3.內存管理語義---assign、strong、 weak、unsafe_unretained、copy 4.方法名---getter= 、setter= 5.不經常使用的:nonnull,null_resettable,nullable

七.屬性關鍵字 readwrite,readonly,assign,retain,copy,nonatomic 各是什麼做用,在那種狀況下用?

答: 1). readwrite 是可讀可寫特性。須要生成getter方法和setter方法。 2). readonly 是隻讀特性。只會生成getter方法,不會生成setter方法,不但願屬性在類外改變。 3). assign 是賦值特性。setter方法將傳入參數賦值給實例變量;僅設置變量時,assign用於基本數據類型。 4). retain(MRC)/strong(ARC) 表示持有特性。setter方法將傳入參數先保留,再賦值,傳入參數的retaincount會+1。 5). copy 表示拷貝特性。setter方法將傳入對象複製一份,須要徹底一份新的變量時。 6). nonatomic 非原子操做。決定編譯器生成的setter和getter方法是不是原子操做,atomic表示多線程安全,通常使用nonatomic,效率高。

八.什麼狀況使用 weak 關鍵字,相比 assign 有什麼不一樣?

1.在 ARC 中,在有可能出現循環引用的時候,每每要經過讓其中一端使用 weak 來解決,好比: delegate 代理屬性。 2.自身已經對它進行一次強引用,沒有必要再強引用一次,此時也會使用 weak,自定義 IBOutlet 控件屬性通常也使用 weak;固然,也能夠使用strong。

IBOutlet連出來的視圖屬性爲何能夠被設置成weak? 由於父控件的subViews數組已經對它有一個強引用。

不一樣點: assign 能夠用非 OC 對象,而 weak 必須用於 OC 對象。 weak 代表該屬性定義了一種「非擁有關係」。在屬性所指的對象銷燬時,屬性值會自動清空(nil)。

九.怎麼用 copy 關鍵字?

用途:

  1. NSString、NSArray、NSDictionary 等等常用copy關鍵字,是由於他們有對應的可變類型:NSMutableString、NSMutableArray、NSMutableDictionary;
  2. block 也常用 copy 關鍵字。

說明: block 使用 copy 是從 MRC 遺留下來的「傳統」,在 MRC 中,方法內部的 block 是在棧區的,使用 copy 能夠把它放到堆區.在 ARC 中寫不寫都行:對於 block 使用 copy 仍是 strong 效果是同樣的,但寫上 copy 也無傷大雅,還能時刻提醒咱們:編譯器自動對 block 進行了 copy 操做。若是不寫 copy ,該類的調用者有可能會忘記或者根本不知道「編譯器會自動對 block 進行了 copy 操做」,他們有可能會在調用以前自行拷貝屬性值。這種操做多餘而低效。

十.用@property聲明的 NSString / NSArray / NSDictionary 常用 copy 關鍵字,爲何?若是改用strong關鍵字,可能形成什麼問題?

答:用 @property 聲明 NSString、NSArray、NSDictionary 常用 copy 關鍵字,是由於他們有對應的可變類型:NSMutableString、NSMutableArray、NSMutableDictionary,他們之間可能進行賦值操做(就是把可變的賦值給不可變的),爲確保對象中的字符串值不會無心間變更,應該在設置新屬性值時拷貝一份。

  1. 由於父類指針能夠指向子類對象,使用 copy 的目的是爲了讓本對象的屬性不受外界影響,使用 copy 不管給我傳入是一個可變對象仍是不可對象,我自己持有的就是一個不可變的副本。
  2. 若是咱們使用是 strong ,那麼這個屬性就有可能指向一個可變對象,若是這個可變對象在外部被修改了,那麼會影響該屬性。

//總結:使用copy的目的是,防止把可變類型的對象賦值給不可變類型的對象時,可變類型對象的值發送變化會無心間篡改不可變類型對象原來的值。

十一.淺拷貝和深拷貝的區別?

答: 淺拷貝:只複製指向對象的指針,而不復制引用對象自己。 深拷貝:複製引用對象自己。內存中存在了兩份獨立對象自己,當修改A時,A_copy不變。

十二.這個寫法會出什麼問題:@property (nonatomic, copy) NSMutableArray *arr;

問題:添加,刪除,修改數組內的元素的時候,程序會由於找不到對應的方法而崩潰。

緣由:是由於 copy 就是複製一個不可變 NSArray 的對象,不能對 NSArray 對象進行添加/修改。

十三.如何讓本身的類用 copy 修飾符?如何重寫帶 copy 關鍵字的 setter?

若想令本身所寫的對象具備拷貝功能,則需實現 NSCopying 協議。若是自定義的對象分爲可變版本與不可變版本,那麼就要同時實現 NSCopying 與 NSMutableCopying 協議。 具體步驟: 1. 需聲明該類聽從 NSCopying 協議 2. 實現 NSCopying 協議的方法。

十四.@synthesize 和 @dynamic 分別有什麼做用?

@property有兩個對應的詞,一個是@synthesize(合成實例變量),一個是@dynamic。 若是@synthesize和@dynamic都沒有寫,那麼默認的就是 @synthesize var = _var; // 在類的實現代碼裏經過 @synthesize 語法能夠來指定實例變量的名字。(@synthesize var = _newVar;)

  1. @synthesize 的語義是若是你沒有手動實現setter方法和getter方法,那麼編譯器會自動爲你加上這兩個方法。
  2. @dynamic 告訴編譯器,屬性的setter與getter方法由用戶本身實現,不自動生成(如,@dynamic var)。

十五.常見的 Objective-C 的數據類型有那些,和C的基本數據類型有什麼區別?如:NSInteger和int

答: Objective-C的數據類型有NSString,NSNumber,NSArray,NSMutableArray,NSData等等,這些都是class,建立後即是對象,而C語言的基本數據類型int,只是必定字節的內存空間,用於存放數值;NSInteger是基本數據類型,並非NSNumber的子類,固然也不是NSObject的子類。NSInteger是基本數據類型Int或者Long的別名(NSInteger的定義typedef long NSInteger),它的區別在於,NSInteger會根據系統是32位仍是64位來決定是自己是int仍是long。

十六.id 聲明的對象有什麼特性?

答:id 聲明的對象具備運行時的特性,便可以指向任意類型的Objcetive-C的對象。

十七.Objective-C 如何對內存管理的,說說你的見解和解決方法?

答:Objective-C的內存管理主要有三種方式ARC(自動內存計數)、手動內存計數、內存池。 1). 自動內存計數ARC:由Xcode自動在App編譯階段,在代碼中添加內存管理代碼。 2). 手動內存計數MRC:遵循內存誰申請、誰釋放;誰添加,誰釋放的原則。 3). 內存釋放池Release Pool:把須要釋放的內存統一放在一個池子中,當池子被抽乾後(drain),池子中全部的內存空間也被自動釋放掉。內存池的釋放操做分爲自動和手動。自動釋放受runloop機制影響。

十八.Objective-C 中建立線程的方法是什麼?若是在主線程中執行代碼,方法是什麼?若是想延時執行代碼、方法又是什麼?

答:線程建立有三種方法:使用NSThread建立、使用GCD的dispatch、使用子類化的NSOperation,而後將其加入NSOperationQueue;在主線程執行代碼,方法是performSelectorOnMainThread,若是想延時執行代碼能夠用performSelector:onThread:withObject:waitUntilDone:

十九.Category(類別)、 Extension(擴展)和繼承的區別

區別:

  1. 分類有名字,類擴展沒有分類名字,是一種特殊的分類。
  2. 分類只能擴展方法(屬性僅僅是聲明,並沒真正實現),類擴展能夠擴展屬性、成員變量和方法。
  3. 繼承能夠增長,修改或者刪除方法,而且能夠增長屬性。

二十.咱們說的OC是動態運行時語言是什麼意思?

答:主要是將數據類型的肯定由編譯時,推遲到了運行時。簡單來講, 運行時機制使咱們直到運行時纔去決定一個對象的類別,以及調用該類別對象指定方法。

二十一.爲何咱們常見的delegate屬性都用是week而不是retain/strong?

答:是爲了防止delegate兩端產生沒必要要的循環引用。 @property (nonatomic, weak) id delegate;

二十二。何時用delete,何時用Notification?

Delegate(委託模式):1對1的反向消息通知功能。 Notification(通知模式):只想要把消息發送出去,告知某些狀態的變化。可是並不關心誰想要知道這個。

二十三.什麼是 KVO 和 KVC?

1). KVC(Key-Value-Coding):鍵值編碼 是一種經過字符串間接訪問對象的方式(即給屬性賦值) 舉例說明: stu.name = @"張三" // 點語法給屬性賦值 [stu setValue:@"張三" forKey:@"name"]; // 經過字符串使用KVC方式給屬性賦值 stu1.nameLabel.text = @"張三"; [stu1 setValue:@"張三" forKey:@"nameLabel.text"]; // 跨層賦值 2). KVO(key-Value-Observing):鍵值觀察機制 他提供了觀察某一屬性變化的方法,極大的簡化了代碼。 KVO只能被KVC觸發,包括使用setValue:forKey:方法和點語法。 // 經過下方方法爲屬性添加KVO觀察

  • (void)addObserver:(NSObject *)observer forKeyPath:(NSString *)keyPath options:(NSKeyValueObservingOptions)options context:(nullable void *)context; // 當被觀察的屬性發送變化時,會自動觸發下方方法
  • (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context{}

KVC 和 KVO 的 keyPath 能夠是屬性、實例變量、成員變量。

二十四.KVC的底層實現?

當一個對象調用setValue方法時,方法內部會作如下操做: 1). 檢查是否存在相應的key的set方法,若是存在,就調用set方法。 2). 若是set方法不存在,就會查找與key相同名稱而且帶下劃線的成員變量,若是有,則直接給成員變量屬性賦值。 3). 若是沒有找到_key,就會查找相同名稱的屬性key,若是有就直接賦值。 4). 若是尚未找到,則調用valueForUndefinedKey:和setValue:forUndefinedKey:方法。 這些方法的默認實現都是拋出異常,咱們能夠根據須要重寫它們。

二十五.ViewController生命週期

按照執行順序排列:

  1. initWithCoder:經過nib文件初始化時觸發。
  2. awakeFromNib:nib文件被加載的時候,會發生一個awakeFromNib的消息到nib文件中的每一個對象。
  3. loadView:開始加載視圖控制器自帶的view。
  4. viewDidLoad:視圖控制器的view被加載完成。
  5. viewWillAppear:視圖控制器的view將要顯示在window上。
  6. updateViewConstraints:視圖控制器的view開始更新AutoLayout約束。
  7. viewWillLayoutSubviews:視圖控制器的view將要更新內容視圖的位置。
  8. viewDidLayoutSubviews:視圖控制器的view已經更新視圖的位置。
  9. viewDidAppear:視圖控制器的view已經展現到window上。
  10. viewWillDisappear:視圖控制器的view將要從window上消失。
  11. viewDidDisappear:視圖控制器的view已經從window上消失。

二十六.你是否接觸過OC中的反射機制?簡單聊一下概念和使用

1). class反射 經過類名的字符串形式實例化對象。 Class class = NSClassFromString(@"student"); Student *stu = [[class alloc] init]; 將類名變爲字符串。 Class class =[Student class]; NSString className = NSStringFromClass(class); 2). SEL的反射 經過方法的字符串形式實例化方法。 SEL selector = NSSelectorFromString(@"setName");
[stu performSelector:selector withObject:@"Mike"]; 將方法變成字符串。 NSStringFromSelector(@selector
(setName:));

二十七.如何對iOS設備進行性能測試?

1.app使用過程當中,接聽電話。能夠測試不一樣的通話時間的長短,對於通話結束後,原先打開的app的響應,好比是否停留在原先界面,繼續操做時的相應速度等。

2.app使用過程當中,有推送消息時,對app的使用影響

3.設備在充電時,app的響應以及操做流暢度

4.設備在不一樣電量時(低於10%,50%,95%),app的響應以及操做流暢度

5.意外斷電時,app數據丟失狀況

6.網絡環境變化時,app的應對狀況如何:是否有適當提示?從有網絡環境到無網絡環境時,app的反饋如何?從無網絡環境回到有網絡環境時,是否能自動加載數據,多久才能開始加載數據

7.多點觸摸的狀況

8.跟其餘app之間互相切換時的響應

9.進程關閉再從新打開的反饋

10.IOS系統語言環境變化時

二十八.開發項目時你是怎麼檢查內存泄露?

1 內存泄漏

內存泄漏是編程中經常見到的一個問題,內存泄漏每每會一種奇怪的方式來表現出來,基本上每一個程序都表現出不一樣的方式。 可是通常最後的結果只有兩個,一個是程序當掉,一個是系統內存不足。 還有一種就是比較介於中間的結果程序不會當,可是系統的反映時間明顯下降,須要定時的Reboot纔會正常。

有一個很簡單的辦法來檢查一個程序是否有內存泄漏。就是是用Windows的任務管理器(Task Manager)。運行程序,而後在任務管理器裏面查看 「內存使用」和」虛擬內存大小」兩項,當程序請求了它所須要的內存以後,若是虛擬內存仍是持續的增加的話,就說明了這個程序有內存泄漏問題。 固然若是內存泄漏的數目很是的小,用這種方法可能要過很長時間才能看的出來。

2 緣由

內存泄漏產生的緣由通常是三種狀況:

1. 內存忘記回收,這個是不該該的事情。可是也是在代碼種很常見的問題。分配內存以後,用完以後,就必定要回收。若是不回收,那就形成了內存的泄漏,形成內存泄漏的Code若是被常常調用的話,那內存泄漏的數目就會愈來愈多的。從而影響整個系統的運行。

3 檢查方法

通常的內存泄漏檢查的確是很困難,可是也不是徹底沒有辦法。若是你用VC的庫來寫東西的話,那麼很幸運的是,你已經有了不少檢查內存泄漏的工具,只是你想不想用的問題了。Visual C++的Debug版本的C運行庫(C Runtime Library)。它已經提供好些函數來幫助你診斷你的代碼和跟蹤內存泄漏。 並且最方便的地方是這些函數在Release版本中徹底不起任何做用,這樣就不會影響你的Release版本程序的運行效率。

好比下面的例子裏面,有一個明細的內存泄漏。固然若是隻有這麼幾行代碼的話,是很容易看出有內存泄漏的。可是想在成千上萬行代碼裏面檢查內存泄漏問題就不是那麼容易了。

若是你雙擊包含行文件名的輸出行,指針將會跳到源文件中內存被分配地方的行。當沒法肯定那些代碼產生了內存泄漏的時候,咱們就須要進行內存狀態比較。在可疑的代碼段的先後設置內存檢查點,比較內存使用是否有可疑的變化。以肯定內存是否有泄漏。爲此要先定義三個_CrtMemState 對象來保存要比較的內存狀態。兩個是用來比較,一個用了保存前面兩個之間的區別

把光標移到DEBUG_NEW上面 按F12,就能夠進入Afx.h中定義這些Class和函數的代碼部分。 VC中內存泄漏的常規檢查辦法主要是上面的兩種。固然這兩種方法只是針對於Debug版本的Heap的檢查。若是Release版本中還有內存泄漏,那麼檢查起來就麻煩不少了。

分配完內存以後忘了回收;

程序Code有問題,形成沒有辦法回收;

某些API函數操做不正確,形成內存泄漏。

二十九.什麼是懶加載?

答:懶加載就是隻在用到的時候纔去初始化。也能夠理解成延時加載。 我以爲最好也最簡單的一個例子就是tableView中圖片的加載顯示了, 一個延時加載, 避免內存太高,一個異步加載,避免線程堵塞提升用戶體驗。

三十.類變量的 @public,@protected,@private,@package 聲明各有什麼含義?

@public 任何地方都能訪問; @protected 該類和子類中訪問,是默認的; @private 只能在本類中訪問; @package 本包內使用,跨包不能夠。

三十一.什麼是謂詞?

謂詞就是經過NSPredicate給定的邏輯條件做爲約束條件,完成對數據的篩選。 //定義謂詞對象,謂詞對象中包含了過濾條件(過濾條件比較多) NSPredicate *predicate = [NSPredicate predicateWithFormat:@"age<%d",30]; //使用謂詞條件過濾數組中的元素,過濾以後返回查詢的結果 NSArray *array = [persons filteredArrayUsingPredicate:predicate];

三十二.isa指針問題

isa:是一個Class 類型的指針. 每一個實例對象有個isa的指針,他指向對象的類,而Class裏也有個isa的指針, 指向meteClass(元類)。元類保存了類方法的列表。當類方法被調 用時,先會從自己查找類方法的實現,若是沒有,元類會向他父類查找該方法。同時注意的是:元類(meteClass)也是類,它也是對象。元類也有isa指針,它的isa指針最終指向的是一個根元類(root meteClass)。根元類的isa指針指向自己,這樣造成了一個封閉的內循環。

三十三.如何訪問並修改一個類的私有屬性?

1.KVC

咱們能夠用setValue:的方法設置私有屬性,並利用valueForKey:的方法訪問私有屬性。假設咱們有一個類Person,而且這個類有一個私有屬性name。看代碼:

Person * ls = [[Person alloc] init];

[ls setValue:@"wo" forKey:@"name"];

2.runtime

咱們能夠利用runtime獲取某個類的全部屬性(私有屬性、非私有屬性),在獲取到某個類的屬性後就能夠對該屬性進行訪問以及修改了。

三十四.一個objc對象的isa的指針指向什麼?有什麼做用?

isa 指的就是 是個什麼,對象的isa指向類,類的isa指向元類(meta class),元類isa指向元類的根類。isa幫助一個對象找到它的方法。isa:是一個Class 類型的指針. 每一個實例對象有個isa的指針,他指向對象的類,而Class裏也有個isa的指針, 指向meteClass(元類)。元類保存了類方法的列表。當類方法被調用時,先會從自己查找類方法的實現,若是沒有,元類會向他父類查找該方法。同時注意的是:元類(meteClass)也是類,它也是對象。元類也有isa指針,它的isa指針最終指向的是一個根元類(root meteClass).根元類的isa指針指向自己,這樣造成了一個封閉的內循環。

三十五.isKindOfClass、isMemberOfClass、selector做用分別是什麼

isKindOfClass:做用是某個對象屬於某個類型或者繼承自某類型。 isMemberOfClass:某個對象確切屬於某個類型。 selector:經過方法名,獲取在內存中的函數的入口地址。

三十六.delegate 和 notification 的區別

1). 兩者都用於傳遞消息,不一樣之處主要在於一個是一對一的,另外一個是一對多的。 2). notification經過維護一個array,實現一對多消息的轉發。 3). delegate須要二者之間必須創建聯繫,否則無法調用代理的方法;notification不須要二者之間有聯繫。

三十七.什麼是block?

閉包(block):閉包就是獲取其它函數局部變量的匿名函數。

三十八.block的注意點

1). 在block內部使用外部指針且會形成循環引用狀況下,須要用__week修飾外部指針: __weak typeof(self) weakSelf = self; 2). 在block內部若是調用了延時函數還使用弱指針會取不到該指針,由於已經被銷燬了,須要在block內部再將弱指針從新強引用一下。 __strong typeof(self) strongSelf = weakSelf; 3). 若是須要在block內部改變外部棧區變量的話,須要在用__block修飾外部變量。

三十九.BAD_ACCESS在什麼狀況下出現?

答:這種問題在開發時常常遇到。緣由是訪問了野指針,好比訪問已經釋放對象的成員變量或者發消息、死循環等。

四十.lldb(gdb)經常使用的控制檯調試命令?

1). p 輸出基本類型。是打印命令,須要指定類型。是print的簡寫 p (int)[[[self view] subviews] count] 2). po 打印對象,會調用對象description方法。是print-object的簡寫 po [self view] 3). expr 能夠在調試時動態執行指定表達式,並將結果打印出來。經常使用於在調試過程當中修改變量的值。 4). bt:打印調用堆棧,是thread backtrace的簡寫,加all可打印全部thread的堆棧 5). br l:是breakpoint list的簡寫

四十一.你通常是怎麼用Instruments的?

Instruments裏面工具不少,經常使用: 1). Time Profiler: 性能分析 2). Zombies:檢查是否訪問了殭屍對象,可是這個工具只能從上往下檢查,不智能。 3). Allocations:用來檢查內存,寫算法的那批人也用這個來檢查。 4). Leaks:檢查內存,看是否有內存泄露。

四十二.iOS中經常使用的數據存儲方式有哪些?

數據存儲有四種方案:NSUserDefault、KeyChain、file、DB。 其中File有三種方式:plist、Archive(歸檔) DB包括:SQLite、FMDB、CoreData

四十三.iOS的沙盒目錄結構是怎樣的?

沙盒結構: 1). Application:存放程序源文件,上架前通過數字簽名,上架後不可修改。 2). Documents:經常使用目錄,iCloud備份目錄,存放數據。(這裏不能存緩存文件,不然上架不被經過) 3). Library: Caches:存放體積大又不須要備份的數據。(經常使用的緩存路徑) Preference:設置目錄,iCloud會備份設置信息。 4). tmp:存放臨時文件,不會被備份,並且這個文件下的數據有可能隨時被清除的可能。

四十四.iOS多線程技術有哪幾種方式?

答:pthread、NSThread、GCD、NSOperation

四十五.GCD 與 NSOperation 的區別:

GCD 和 NSOperation 都是用於實現多線程: GCD 基於C語言的底層API,GCD主要與block結合使用,代碼簡潔高效。 NSOperation 屬於Objective-C類,是基於GCD更高一層的封裝。複雜任務通常用NSOperation實現。

四十六.寫出使用GCD方式從子線程回到主線程的方法代碼

答:dispatch_sync(dispatch_get_main_queue(), ^{ });

四十七.如何用GCD同步若干個異步調用?(如根據若干個url異步加載多張圖片,而後在都下載完成後合成一張整圖)

// 使用Dispatch Group追加block到Global Group Queue,這些block若是所有執行完畢,就會執行Main Dispatch Queue中的結束處理的block。 // 建立隊列組 dispatch_group_t group = dispatch_group_create(); // 獲取全局併發隊列 dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0); dispatch_group_async(group, queue, ^{ /*加載圖片1 */ }); dispatch_group_async(group, queue, ^{ /*加載圖片2 */ }); dispatch_group_async(group, queue, ^{ /*加載圖片3 */ }); // 當併發隊列組中的任務執行完畢後纔會執行這裏的代碼 dispatch_group_notify(group, dispatch_get_main_queue(), ^{ // 合併圖片 });

四十八.dispatch_barrier_async(柵欄函數)的做用是什麼?

函數定義:dispatch_barrier_async(dispatch_queue_t queue, dispatch_block_t block); 做用: 1.在它前面的任務執行結束後它才執行,它後面的任務要等它執行完成後纔會開始執行。 2.避免數據競爭

// 1.建立併發隊列 dispatch_queue_t queue = dispatch_queue_create("myQueue", DISPATCH_QUEUE_CONCURRENT); // 2.向隊列中添加任務 dispatch_async(queue, ^{ // 1.2是並行的 NSLog(@"任務1, %@",[NSThread currentThread]); }); dispatch_async(queue, ^{ NSLog(@"任務2, %@",[NSThread currentThread]); });

dispatch_barrier_async(queue, ^{ NSLog(@"任務 barrier, %@", [NSThread currentThread]); });

dispatch_async(queue, ^{ // 這兩個是同時執行的 NSLog(@"任務3, %@",[NSThread currentThread]); }); dispatch_async(queue, ^{ NSLog(@"任務4, %@",[NSThread currentThread]); });

// 輸出結果: 任務1 任務2 ——》 任務 barrier ——》任務3 任務4 // 其中的任務1與任務2,任務3與任務4 因爲是並行處理前後順序不定。

四十九.什麼是 RunLoop

從字面上講就是運行循環,它內部就是do-while循環,在這個循環內部不斷地處理各類任務。 一個線程對應一個RunLoop,基本做用就是保持程序的持續運行,處理app中的各類事件。經過runloop,有事運行,沒事就休息,能夠節省cpu資源,提升程序性能。

主線程的run loop默認是啓動的。iOS的應用程序裏面,程序啓動後會有一個以下的main()函數 int main(int argc, char * argv[]) { @autoreleasepool { return UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class])); } }

五十.什麼是 Runtime

Runtime又叫運行時,是一套底層的C語言API,其爲iOS內部的核心之一,咱們平時編寫的OC代碼,底層都是基於它來實現的。

五十一.Runtime實現的機制是什麼,怎麼用,通常用於幹嗎?

1). 使用時須要導入的頭文件 <objc/message.h> <objc/runtime.h> 2). Runtime 運行時機制,它是一套C語言庫。 3). 實際上咱們編寫的全部OC代碼,最終都是轉成了runtime庫的東西。 好比: 類轉成了 Runtime 庫裏面的結構體等數據類型, 方法轉成了 Runtime 庫裏面的C語言函數, 平時調方法都是轉成了 objc_msgSend 函數(因此說OC有個消息發送機制) // OC是動態語言,每一個方法在運行時會被動態轉爲消息發送,即:objc_msgSend(receiver, selector)。 // [stu show]; 在objc動態編譯時,會被轉意爲:objc_msgSend(stu, @selector(show)); 4). 所以,能夠說 Runtime 是OC的底層實現,是OC的幕後執行者。

有了Runtime庫,能作什麼事情呢? Runtime庫裏面包含了跟類、成員變量、方法相關的API。 好比: (1)獲取類裏面的全部成員變量。 (2)爲類動態添加成員變量。 (3)動態改變類的方法實現。 (4)爲類動態添加新的方法等。 所以,有了Runtime,想怎麼改就怎麼改。

五十二.什麼是 Method Swizzle(黑魔法),什麼狀況下會使用?

1). 在沒有一個類的實現源碼的狀況下,想改變其中一個方法的實現,除了繼承它重寫、和藉助類別重名方法暴力搶先以外,還有更加靈活的方法 Method Swizzle。 2). Method Swizzle 指的是改變一個已存在的選擇器對應的實現的過程。OC中方法的調用可以在運行時經過改變,經過改變類的調度表中選擇器到最終函數間的映射關係。 3). 在OC中調用一個方法,實際上是向一個對象發送消息,查找消息的惟一依據是selector的名字。利用OC的動態特性,能夠實如今運行時偷換selector對應的方法實現。 4). 每一個類都有一個方法列表,存放着selector的名字和方法實現的映射關係。IMP有點相似函數指針,指向具體的方法實現。 5). 咱們能夠利用 method_exchangeImplementations 來交換2個方法中的IMP。 6). 咱們能夠利用 class_replaceMethod 來修改類。 7). 咱們能夠利用 method_setImplementation 來直接設置某個方法的IMP。 8). 歸根結底,都是偷換了selector的IMP。

五十三._objc_msgForward 函數是作什麼的,直接調用它將會發生什麼?

答:_objc_msgForward是 IMP 類型,用於消息轉發的:當向一個對象發送一條消息,但它並無實現的時候,_objc_msgForward會嘗試作消息轉發。

五十四.什麼是 TCP / UDP ?

TCP:傳輸控制協議。 UDP:用戶數據協議。

TCP 是面向鏈接的,創建鏈接須要經歷三次握手,是可靠的傳輸層協議。 UDP 是面向無鏈接的,數據傳輸是不可靠的,它只管發,無論收不收穫得。 簡單的說,TCP注重數據安全,而UDP數據傳輸快點,但安全性通常。

五十五.通訊底層原理(OSI七層模型)

OSI採用了分層的結構化技術,共分七層: 物理層、數據鏈路層、網絡層、傳輸層、會話層、表示層、應用層。

五十六.OC中建立線程的方法是什麼?若是在主線程中執行代碼,方法是什麼?

// 建立線程的方法

  • [NSThread detachNewThreadSelector:nil toTarget:nil withObject:nil]
  • [self performSelectorInBackground:nil withObject:nil];
  • [[NSThread alloc] initWithTarget:nil selector:nil object:nil];
  • dispatch_async(dispatch_get_global_queue(0, 0), ^{});
  • [[NSOperationQueue new] addOperation:nil];

// 主線程中執行代碼的方法

  • [self performSelectorOnMainThread:nil withObject:nil waitUntilDone:YES];
  • dispatch_async(dispatch_get_main_queue(), ^{});
  • [[NSOperationQueue mainQueue] addOperation:nil];

五十七.用僞代碼寫一個線程安全的單例模式

static id _instance;

  • (id)allocWithZone:(struct _NSZone *)zone { static dispatch_once_t onceToken; dispatch_once(&onceToken, ^{ _instance = [super allocWithZone:zone]; }); return _instance; }

  • (instancetype)sharedData { static dispatch_once_t onceToken; dispatch_once(&onceToken, ^{ _instance = [[self alloc] init]; }); return _instance; }

  • (id)copyWithZone:(NSZone *)zone { return _instance; }

五十八.在手勢對象基礎類UIGestureRecognizer的經常使用子類手勢類型中哪兩個手勢發生後,響應只會執行一次?

答:UITapGestureRecognizer,UISwipeGestureRecognizer是一次性手勢,手勢發生後,響應只會執行一次。

五十九.HTTP協議中 POST 方法和 GET 方法有那些區別?

  1. GET用於向服務器請求數據,POST用於提交數據
  2. GET請求,請求參數拼接形式暴露在地址欄,而POST請求參數則放在請求體裏面,所以GET請求不適合用於驗證密碼等操做
  3. GET請求的URL有長度限制,POST請求不會有長度限制

六十.請簡單的介紹下APNS發送系統消息的機制

APNS優點:杜絕了相似安卓那種爲了接受通知不停在後臺喚醒程序保持長鏈接的行爲,由iOS系統和APNS進行長鏈接替代。 APNS的原理: 1). 應用在通知中心註冊,由iOS系統向APNS請求返回設備令牌(device Token) 2). 應用程序接收到設備令牌併發送給本身的後臺服務器 3). 服務器把要推送的內容和設備發送給APNS 4). APNS根據設備令牌找到設備,再由iOS根據APPID把推送內容展現

第三方框架

AFNetworking 底層原理分析

AFNetworking主要是對NSURLSession和NSURLConnection(iOS9.0廢棄)的封裝,其中主要有如下類: 1). AFHTTPRequestOperationManager:內部封裝的是 NSURLConnection, 負責發送網絡請求, 使用最多的一個類。(3.0廢棄) 2). AFHTTPSessionManager:內部封裝是 NSURLSession, 負責發送網絡請求,使用最多的一個類。 3). AFNetworkReachabilityManager:實時監測網絡狀態的工具類。當前的網絡環境發生改變以後,這個工具類就能夠檢測到。 4). AFSecurityPolicy:網絡安全的工具類, 主要是針對 HTTPS 服務。

5). AFURLRequestSerialization:序列化工具類,基類。上傳的數據轉換成JSON格式 (AFJSONRequestSerializer).使用很少。 6). AFURLResponseSerialization:反序列化工具類;基類.使用比較多: 7). AFJSONResponseSerializer; JSON解析器,默認的解析器. 8). AFHTTPResponseSerializer; 萬能解析器; JSON和XML以外的數據類型,直接返回二進 制數據.對服務器返回的數據不作任何處理. 9). AFXMLParserResponseSerializer; XML解析器;

描述下SDWebImage裏面給UIImageView加載圖片的邏輯

SDWebImage 中爲 UIImageView 提供了一個分類UIImageView+WebCache.h, 這個分類中有一個最經常使用的接口sd_setImageWithURL:placeholderImage:,會在真實圖片出現前會先顯示佔位圖片,當真實圖片被加載出來後再替換佔位圖片。

加載圖片的過程大體以下: 1.首先會在 SDWebImageCache 中尋找圖片是否有對應的緩存, 它會以url 做爲數據的索引先在內存中尋找是否有對應的緩存 2.若是緩存未找到就會利用經過MD5處理過的key來繼續在磁盤中查詢對應的數據, 若是找到了, 就會把磁盤中的數據加載到內存中,並將圖片顯示出來 3.若是在內存和磁盤緩存中都沒有找到,就會向遠程服務器發送請求,開始下載圖片 4.下載後的圖片會加入緩存中,並寫入磁盤中 5.整個獲取圖片的過程都是在子線程中執行,獲取到圖片後回到主線程將圖片顯示出來

SDWebImage原理: 調用類別的方法: 1. 從內存(字典)中找圖片(當這個圖片在本次使用程序的過程當中已經被加載過),找到直接使用。 2. 從沙盒中找(當這個圖片在以前使用程序的過程當中被加載過),找到使用,緩存到內存中。 3. 從網絡上獲取,使用,緩存到內存,緩存到沙盒。

友盟統計接口統計的全部功能

APP啓動速度,APP停留頁面時間等

算法

求最大公約數

/** 1.直接遍歷法 / int maxCommonDivisor(int a, int b) {     int max = 0;     for (int i = 1; i <=b; i++) {         if (a % i == 0 && b % i == 0) {             max = i;         }     }     return max; } /* 2.展轉相除法 */ int maxCommonDivisor(int a, int b) {     int r;     while(a % b > 0) {         r = a % b;         a = b;         b = r;     }     return b; }

// 擴展:最小公倍數 = (a * b)/最大公約數

模擬棧操做

/**  *  棧是一種數據結構,特色:先進後出  *  練習:使用全局變量模擬棧的操做  */ #include <stdio.h> #include <stdbool.h> #include <assert.h> //保護全局變量:在全局變量前加static後,這個全局變量就只能在本文件中使用 static int data[1024];//棧最多能保存1024個數據 static int count = 0;//目前已經放了多少個數(至關於棧頂位置)

//數據入棧 push void push(int x){ assert(!full());//防止數組越界 data[count++] = x; } //數據出棧 pop int pop(){ assert(!empty()); return data[--count]; } //查看棧頂元素 top int top(){ assert(!empty()); return data[count-1]; }

//查詢棧滿 full bool full() { if(count >= 1024) { return 1; } return 0; }

//查詢棧空 empty bool empty() { if(count <= 0) { return 1; }     return 0; }

int main(){     //入棧     for (int i = 1; i <= 10; i++) {         push(i);     }

//出棧     while(!empty()){         printf("%d ", top()); //棧頂元素         pop(); //出棧     }     printf("\n");          return 0; }

排序算法

選擇排序、冒泡排序、插入排序三種排序算法能夠總結爲以下:

/**

  • 【選擇排序】:最值出如今起始端
  • 第1趟:在n個數中找到最小(大)數與第一個數交換位置
  • 第2趟:在剩下n-1個數中找到最小(大)數與第二個數交換位置
  • 重複這樣的操做...依次與第三個、第四個...數交換位置
  • 第n-1趟,最終可實現數據的升序(降序)排列。

*/ void selectSort(int *arr, int length) {     for (int i = 0; i < length - 1; i++) { //趟數         for (int j = i + 1; j < length; j++) { //比較次數             if (arr[i] > arr[j]) {                 int temp = arr[i];                 arr[i] = arr[j];                 arr[j] = temp;             }         }     } }

/**

  • 【冒泡排序】:相鄰元素兩兩比較,比較完一趟,最值出如今末尾
  • 第1趟:依次比較相鄰的兩個數,不斷交換(小數放前,大數放後)逐個推動,最值最後出如今第n個元素位置
  • 第2趟:依次比較相鄰的兩個數,不斷交換(小數放前,大數放後)逐個推動,最值最後出如今第n-1個元素位置
  • …… ……
  • 第n-1趟:依次比較相鄰的兩個數,不斷交換(小數放前,大數放後)逐個推動,最值最後出如今第2個元素位置 */ void bublleSort(int *arr, int length) {     for(int i = 0; i < length - 1; i++) { //趟數         for(int j = 0; j < length - i - 1; j++) { //比較次數             if(arr[j] > arr[j+1]) {                 int temp = arr[j];                 arr[j] = arr[j+1];                 arr[j+1] = temp;             }         }     } }

都將數組分爲已排序部分和未排序部分。

  1. 選擇排序將已排序部分定義在左端,而後選擇未排序部分的最小元素和未排序部分的第一個元素交換。
  2. 冒泡排序將已排序部分定義在右端,在遍歷未排序部分的過程執行交換,將最大元素交換到最右端。
  3. 插入排序將已排序部分定義在左端,將未排序部分元的第一個元素插入到已排序部分合適的位置。

選擇排序

冒泡排序

折半查找(二分查找)

/**

  • 折半查找:優化查找時間(不用遍歷所有數據)
  • 折半查找的原理:
  • 1> 數組必須是有序的
  • 2> 必須已知min和max(知道範圍)
  • 3> 動態計算mid的值,取出mid對應的值進行比較
  • 4> 若是mid對應的值大於要查找的值,那麼max要變小爲mid-1
  • 5> 若是mid對應的值小於要查找的值,那麼min要變大爲mid+1

*/

// 已知一個有序數組, 和一個key, 要求從數組中找到key對應的索引位置 int findKey(int *arr, int length, int key) {     int min = 0, max = length - 1, mid;     while (min <= max) {         mid = (min + max) / 2; //計算中間值         if (key > arr[mid]) {             min = mid + 1;         } else if (key < arr[mid]) {             max = mid - 1;         } else {             return mid;         }     }     return -1; }

編碼格式(優化細節)

在 Objective-C 中,enum 建議使用 NS_ENUM 和 NS_OPTIONS 宏來定義枚舉類型。

//定義一個枚舉(比較嚴密) typedef NS_ENUM(NSInteger, BRUserGender) { BRUserGenderUnknown, // 未知 BRUserGenderMale, // 男性 BRUserGenderFemale, // 女性 BRUserGenderNeuter // 無性 };

@interface BRUser : NSObject

@property (nonatomic, readonly, copy) NSString *name; @property (nonatomic, readonly, assign) NSUInteger age; @property (nonatomic, readonly, assign) BRUserGender gender;

  • (instancetype)initWithName:(NSString *)name age:(NSUInteger)age gender:(BRUserGender)gender;

@end

//說明: //既然該類中已經有一個「初始化方法」 ,用於設置 name、age 和 gender 的初始值: 那麼在設計對應 @property 時就應該儘可能使用不可變的對象:其三個屬性都應該設爲「只讀」。用初始化方法設置好屬性值以後,就不能再改變了。 //屬性的參數應該按照下面的順序排列: (原子性,讀寫,內存管理)

避免使用C語言中的基本數據類型,建議使用 Foundation 數據類型,對應關係以下:

int -> NSInteger unsigned -> NSUInteger float -> CGFloat 動畫時間 -> NSTimeInterval

其它知識點

一.什麼是 OpenGL、Quartz 2D?

Quatarz 2d 是Apple提供的基本圖形工具庫。只是適用於2D圖形的繪製。 OpenGL,是一個跨平臺的圖形開發庫。適用於2D和3D圖形的繪製。

ffmpeg框架:​ffmpeg 是音視頻處理工具,既有音視頻編碼解碼功能,又能夠做爲播放器使用。

談談 UITableView 的優化

1). 正確的複用cell。 2). 設計統一規格的Cell 3). 提早計算並緩存好高度(佈局),由於heightForRowAtIndexPath:是調用最頻繁的方法; 4). 異步繪製,遇到複雜界面,遇到性能瓶頸時,可能就是突破口; 4). 滑動時按需加載,這個在大量圖片展現,網絡加載的時候很管用! 5). 減小子視圖的層級關係 6). 儘可能使全部的視圖不透明化以及作切圓操做。 7). 不要動態的add 或者 remove 子控件。最好在初始化時就添加完,而後經過hidden來控制是否顯示。 8). 使用調試工具分析問題。

二如何實行cell的動態的行高

若是但願每條數據顯示自身的行高,必須設置兩個屬性,1.預估行高,2.自定義行高。 設置預估行高 tableView.estimatedRowHeight = 200。 設置定義行高 tableView.estimatedRowHeight = UITableViewAutomaticDimension。 若是要讓自定義行高有效,必須讓容器視圖有一個自下而上的約束。

三說說你對 block 的理解

棧上的自動複製到堆上,block 的屬性修飾符是 copy,循環引用的原理和解決方案。

四說說你對 runtime 的理解

主要是方法調用時如何查找緩存,如何找到方法,找不到方法時怎麼轉發,對象的內存佈局。

五什麼是野指針、空指針?

野指針:不知道指向了哪裏的指針叫野指針。即指針指向不肯定,指針存的地址是一個垃圾值,未初始化。 空指針:不指向任何位置的指針叫空指針。即指針沒有指向,指針存的地址是一個空地址,NULL。

六什麼是 OOA / OOD / OOP ?

OOA(Object Oriented Analysis)   --面向對象分析 OOD(Object Oriented Design)     --面向對象設計 OOP(Object Oriented Programming)--面向對象編程

七 多線程是什麼

多線程是個複雜的概念,按字面意思是同步完成多項任務,提升了資源的使用效率,從硬件、操做系統、應用軟件不一樣的角度去看,多線程被賦予不一樣的內涵,對於硬件,如今市面上多數的CPU都是多核的,多核的CPU運算多線程更爲出色;從操做系統角度,是多任務,如今用的主流操做系統都是多任務的,能夠一邊聽歌、一邊寫博客;對於應用來講,多線程可讓應用有更快的迴應,能夠在網絡下載時,同時響應用戶的觸摸操做。在iOS應用中,對多線程最初的理解,就是併發,它的含義是原來先作燒水,再摘菜,再炒菜的工做,會變成燒水的同時去摘菜,最後去炒菜。

八. iOS 中的多線程

iOS中的多線程,是Cocoa框架下的多線程,經過Cocoa的封裝,可讓咱們更爲方便的使用線程,作過C++的同窗可能會對線程有更多的理解,好比線程的創立,信號量、共享變量有認識,Cocoa框架下會方便不少,它對線程作了封裝,有些封裝,可讓咱們建立的對象,自己便擁有線程,也就是線程的對象化抽象,從而減小咱們的工程,提供程序的健壯性。

GCD是(Grand Central Dispatch)的縮寫 ,從系統級別提供的一個易用地多線程類庫,具備運行時的特色,能充分利用多核心硬件。GCD的API接口爲C語言的函數,函數參數中多數有Block,關於Block的使用參看這裏,爲咱們提供強大的「接口」,對於GCD的使用參見本文

NSOperation與Queue

NSOperation是一個抽象類,它封裝了線程的細節實現,咱們能夠經過子類化該對象,加上NSQueue來同面向對象的思惟,管理多線程程序。具體可參看這裏:一個基於NSOperation的多線程網絡訪問的項目。

NSThread

NSThread是一個控制線程執行的對象,它不如NSOperation抽象,經過它咱們能夠方便的獲得一個線程,並控制它。但NSThread的線程之間的併發控制,是須要咱們本身來控制的,能夠經過NSCondition實現。

參看 iOS多線程編程之NSThread的使用

其餘多線程

在Cocoa的框架下,通知、Timer和異步函數等都有使用多線程,(待補充).

九. 在項目何時選擇使用GCD,何時選擇NSOperation?

項目中使用NSOperation的優勢是NSOperation是對線程的高度抽象,在項目中使用它,會使項目的程序結構更好,子類化NSOperation的設計思路,是具備面向對象的優勢(複用、封裝),使得實現是多線程支持,而接口簡單,建議在複雜項目中使用。

項目中使用GCD的優勢是GCD自己很是簡單、易用,對於不復雜的多線程操做,會節省代碼量,而Block參數的使用,會是代碼更爲易讀,建議在簡單項目中使用。

十 KVO,NSNotification,delegate及block區別

十一 將一個函數在主線程執行的4種方法

dispatch_async(dispatch_get_main_queue(), ^{

//須要執行的方法

});

NSOperationQueue *mainQueue = [NSOperationQueue mainQueue];  //主隊列

NSBlockOperation *operation = [NSBlockOperation blockOperationWithBlock:^{

//須要執行的方法

}];

[mainQueue addOperation:operation];

[self performSelector:@selector(method) onThread:[NSThread mainThread] withObject:nil waitUntilDone:YES modes:nil];

[self performSelectorOnMainThread:@selector(method) withObject:nil waitUntilDone:YES];

[[NSThread mainThread] performSelector:@selector(method) withObject:nil]; [[NSRunLoop mainRunLoop] performSelector:@selector(method) withObject:nil];

十二 如何讓計時器調用一個類方法

十三 如何重寫類方法

十四 NSTimer建立後,會在哪一個線程運行。

十五 id和NSObject*的區別

typedef struct objc_object *id

十六.ios開發逆向傳值的幾種方法整理

第一種:代理傳值

第二個控制器:

@protocol WJSecondViewControllerDelegate

  • (void)changeText:(NSString*)text; @end @property(nonatomic,assign)iddelegate;

  • (IBAction)buttonClick:(UIButton*)sender { _str = sender.titleLabel.text; [self.delegate changeText:sender.titleLabel.text]; [self.navigationController popViewControllerAnimated:YES]; } 第一個控制器:

  • (IBAction)pushToSecond:(id)sender { WJSecondViewController *svc = [[WJSecondViewController alloc]initWithNibName:@"WJSecondViewController" bundle:nil]; svc.delegate = self; svc.str = self.navigationItem.title; [self.navigationController pushViewController:svc animated:YES]; [svc release]; }

  • (void)changeText:(NSString *)text{ self.navigationItem.title = text; } 第二種:通知傳值

第一個控制器:

//註冊監聽通知 [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(limitDataForModel:) name:@"NOV" object:nil];

  • (void)limitDataForModel:(NSNotification *)noti{ self.gamesInfoArray = noti.object; } 第二個控制器:

//發送通知 [[NSNotificationCenter defaultCenter] postNotificationName:@"NOV" object:gameArray]; 第三種:單例傳值

Single是一個單例類,而且有一個字符串類型的屬性titleName

在第二個控制器:

  • (IBAction)buttonClick:(UIButton*)sender { Single *single = [Single sharedSingle]; single.titleName = sender.titleLabel.text; [self.navigationController popViewControllerAnimated:YES]; } 第一個控制器:

  • (void)viewWillAppear:(BOOL)animated{ [super viewWillAppear:animated]; Single *single = [Single sharedSingle]; self.navigationItem.title = single.titleName; } 第四種:block傳值

第二個控制器:

@property (nonatomic,copy) void (^changeText_block)(NSString*);

  • (IBAction)buttonClick:(UIButton*)sender { _str = sender.titleLabel.text; self.changeText_block(sender.titleLabel.text); [self.navigationController popViewControllerAnimated:YES]; } 第一個控制器:

  • (IBAction)pushToSecond:(id)sender { WJSecondViewController *svc = [[WJSecondViewController alloc]initWithNibName:@"WJSecondViewController" bundle:nil]; svc.str = self.navigationItem.title; [svc setChangeText_block:^(NSString *str) {

    self.navigationItem.title = str; }]; [self.navigationController pushViewController:svc animated:YES]; } 第五種:extern傳值

第二個控制器:

extern NSString *btn;

  • (IBAction)buttonClick:(UIButton*)sender { btn = sender.titleLabel.text; [self.navigationController popViewControllerAnimated:YES]; } 第一個控制器:

NSString *btn = nil;

  • (void)viewWillAppear:(BOOL)animated{ [super viewWillAppear:animated]; self.navigationItem.title = btn; } 第六種:KVO傳值

第一個控制器:

  • (void)viewDidLoad { [super viewDidLoad]; _vc =[[SecondViewController alloc]init]; //self監聽vc裏的textValue屬性 [_vc addObserver:self forKeyPath:@"textValue" options:0 context:nil];
    } 第二個控制器:

  • (IBAction)buttonClicked:(id)sender { self.textValue = self.textField.text; [self.navigationController popViewControllerAnimated:YES]; }

Method1:performSelector

[self performSelector:@selector(delayMethod) withObject:nil/可傳任意類型參數/ afterDelay:2.0];

注:此方法是一種非阻塞的執行方式,未找到取消執行的方法。

Method2:NSTimer定時器

NSTimer *timer = [NSTimer scheduledTimerWithTimeInterval:2.0 target:self selector:@selector(delayMethod) userInfo:nil repeats:NO];

注:此方法是一種非阻塞的執行方式,

Method3:NSThread線程的sleep

[NSThread sleepForTimeInterval:2.0];

注:此方法是一種阻塞執行方式,建議放在子線程中執行,不然會卡住界面。但有時仍是須要阻塞執行,如進入歡迎界面須要沉睡3秒才進入主界面時。

沒有找到取消執行方式。

Method4:GCD

__block ViewController/主控制器/ *weakSelf = self;

dispatch_time_t delayTime = dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2.0/延遲執行時間/ * NSEC_PER_SEC));

dispatch_after(delayTime, dispatch_get_main_queue(), ^{ [weakSelf delayMethod]; });`

十七.NSPersistentStoreCoordinator  ,   NSManaged0bjectContext 和NSManaged0bject中的那些須要在線程中建立或者傳遞

答:NSPersistentStoreCoordinator是持久化存儲協調者,主要用於協調託管對象上下文和持久化存儲區之間的關係。NSManagedObjectContext使用協調者的託管對象模型將數據保存到數據庫,或查詢數據。

十八.您是否作過一部的網絡處理和通信方面的工做?若是有,能具體介紹一下實現策略麼

答:使用NSOperation發送異步網絡請求,使用NSOperationQueue管理線程數目及優先級,底層是用NSURLConnetion,

81.你使用過Objective-C的運行時編程(Runtime Programming)麼?若是使用過,你用它作了什麼?你還能記得你所使用的相關的頭文件或者某些方法的名稱嗎?

答:Objecitve-C的重要特性是Runtime(運行時),在#import <objc/runtime.h> 下能看到相關的方法,用過objc_getClass()和class_copyMethodList()獲取過私有API;使用

Method method1 = class_getInstanceMethod(cls, sel1);

Method method2 = class_getInstanceMethod(cls, sel2);

method_exchangeImplementations(method1, method2);  

```   

代碼交換兩個方法,在寫unit test時使用到。

 

82.Core開頭的系列的內容。是否使用過CoreAnimation和CoreGraphics。UI框架和CA,CG框架的聯繫是什麼?分別用CA和CG作過些什麼動畫或者圖像上的內容。(有須要的話還能夠涉及Quartz的一些內容)

 

答:UI框架的底層有CoreAnimation,CoreAnimation的底層有CoreGraphics。    

UIKit | 

------------ | 

Core Animation | 

Core Graphics |

Graphics Hardware|  

使用CA作過menu菜單的展開收起(太遜了)  

 

十九.是否使用過CoreText或者CoreImage等?若是使用過,請談談你使用CoreText或者CoreImage的體驗。

 

答:CoreText能夠解決複雜文字內容排版問題。CoreImage能夠處理圖片,爲其添加各類效果。體驗是很強大,挺複雜的。

 

二十.NSNotification和KVO的區別和用法是什麼?何時應該使用通知,何時應該使用KVO,它們的實現上有什麼區別嗎?若是用protocol和delegate(或者delegate的Array)來實現相似的功能可能嗎?若是可能,會有什麼潛在的問題?若是不能,爲何?(雖然protocol和delegate這種東西面試已經面爛了…)

 

答:NSNotification是通知模式在iOS的實現,KVO的全稱是鍵值觀察(Key-value observing),其是基於KVC(key-value coding)的,KVC是一個經過屬性名訪問屬性變量的機制。例如將Module層的變化,通知到多個Controller對象時,能夠使用NSNotification;若是是隻須要觀察某個對象的某個屬性,能夠使用KVO。

對於委託模式,在設計模式中是對象適配器模式,其是delegate是指向某個對象的,這是一對一的關係,而在通知模式中,每每是一對多的關係。委託模式,從技術上能夠如今改變delegate指向的對象,但不建議這樣作,會讓人迷惑,若是一個delegate對象不斷改變,指向不一樣的對象。



二十一.你用過NSOperationQueue麼?若是用過或者瞭解的話,你爲何要使用NSOperationQueue,實現了什麼?請描述它和G.C.D的區別和相似的地方(提示:能夠從二者的實現機制和適用範圍來描述)。

 

答:使用NSOperationQueue用來管理子類化的NSOperation對象,控制其線程併發數目。GCD和NSOperation均可以實現對線程的管理,區別是 NSOperation和NSOperationQueue是多線程的面向對象抽象。項目中使用NSOperation的優勢是NSOperation是對線程的高度抽象,在項目中使用它,會使項目的程序結構更好,子類化NSOperation的設計思路,是具備面向對象的優勢(複用、封裝),使得實現是多線程支持,而接口簡單,建議在複雜項目中使用。

項目中使用GCD的優勢是GCD自己很是簡單、易用,對於不復雜的多線程操做,會節省代碼量,而Block參數的使用,會是代碼更爲易讀,建議在簡單項目中使用。

 

二十二.既然提到G.C.D,那麼問一下在使用G.C.D以及block時要注意些什麼?它們兩是一回事兒麼?block在ARC中和傳統的MRC中的行爲和用法有沒有什麼區別,須要注意些什麼?

 

答:使用block是要注意,若將block作函數參數時,須要把它放到最後,GCD是Grand Central Dispatch,是一個對線程開源類庫,而Block是閉包,是可以讀取其餘函數內部變量的函數。

 

 

二十三. 對於Objective-C,你認爲它最大的優勢和最大的不足是什麼?對於不足之處,如今有沒有可用的方法繞過這些不足來實現需求。若是能夠的話,你有沒有考慮或者實踐太重新實現OC的一些功能,若是有,具體會如何作?

 

答:最大的優勢是它的運行時特性,不足是沒有命名空間,對於命名衝突,能夠使用長命名法或特殊前綴解決,若是是引入的第三方庫之間的命名衝突,能夠使用link命令及flag解決衝突。

  
 

二十四. 你實現過一個框架或者庫以供別人使用麼?若是有,請談一談構建框架或者庫時候的經驗;若是沒有,請設想和設計框架的public的API,並指出大概須要如何作、須要注意一些什麼方面,來使別人容易地使用你的框架。

答:抽象和封裝,方便使用。首先是對問題有充分的瞭解,好比構建一個文件解壓壓縮框架,從使用者的角度出發,只需關注發送給框架一個解壓請求,框架完成複雜文件的解壓操做,而且在適當的時候通知給是哦難過者,如解壓完成、解壓出錯等。在框架內部去構建對象的關係,經過抽象讓其更爲健壯、便於更改。其次是API的說明文檔。

KVO就是cocoa框架實現的觀察者模式,通常同KVC搭配使用,經過KVO能夠監測一個值的變化,好比View的高度變化。是一對多的關係,一個值的變化會通知全部的觀察者。

NSNotification是通知,也是一對多的使用場景。在某些狀況下,KVO和NSNotification是同樣的,都是狀態變化以後告知對方。NSNotification的特色,就是須要被觀察者先主動發出通知,而後觀察者註冊監聽後再來進行響應,比KVO多了發送通知的一步,可是其優勢是監聽不侷限於屬性的變化,還能夠對多種多樣的狀態變化進行監聽,監聽範圍廣,使用也更靈活。

delegate 是代理,就是我不想作的事情交給別人作。好比狗須要吃飯,就經過delegate通知主人,主人就會給他作飯、盛飯、倒水,這些操做,這些狗都不須要關心,只須要調用delegate(代理人)就能夠了,由其餘類完成所須要的操做。因此delegate是一對一關係。

block是delegate的另外一種形式,是函數式編程的一種形式。使用場景跟delegate同樣,相比delegate更靈活,並且代理的實現更直觀。

KVO通常的使用場景是數據,需求是數據變化,好比股票價格變化,咱們通常使用KVO(觀察者模式)。delegate通常的使用場景是行爲,需求是須要別人幫我作一件事情,好比買賣股票,咱們通常使用delegate。

Notification通常是進行全局通知,好比利好消息一出,通知你們去買入。delegate是強關聯,就是委託和代理雙方互相知道,你委託別人買股票你就須要知道經紀人,經紀人也不要知道本身的顧客。Notification是弱關聯,利好消息發出,你不須要知道是誰發的也能夠作出相應的反應,同理發消息的人也不須要知道接收的人也能夠正常發出消息。

GCD方法,經過向主線程隊列發送一個block塊,使block裏的方法能夠在主線程中執行。

NSOperation 方法

NSThread 方法

RunLoop方法

計時器只能調用實例方法,可是能夠在這個實例方法裏面調用靜態方法。

使用計時器須要注意,計時器必定要加入RunLoop中,而且選好model才能運行。scheduledTimerWithTimeInterval方法建立一個計時器並加入到RunLoop中因此能夠直接使用。

若是計時器的repeats選擇YES說明這個計時器會重複執行,必定要在合適的時機調用計時器的invalid。不能在dealloc中調用,由於一旦設置爲repeats 爲yes,計時器會強持有self,致使dealloc永遠不會被調用,這個類就永遠沒法被釋放。好比能夠在viewDidDisappear中調用,這樣當類須要被回收的時候就能夠正常進入dealloc中了。

在子類中實現一個同基類名字同樣的靜態方法

在調用的時候不要使用類名調用,而是使用[self class]的方式調用。原理,用類名調用是早綁定,在編譯期綁定,用[self class]是晚綁定,在運行時決定調用哪一個方法。

用scheduledTimerWithTimeInterval建立的,在哪一個線程建立就會被加入哪一個線程的RunLoop中就運行在哪一個線程

本身建立的Timer,加入到哪一個線程的RunLoop中就運行在哪一個線程。

id是一個 objc_object 結構體指針,定義是

id能夠理解爲指向對象的指針。全部oc的對象 id均可以指向,編譯器不會作類型檢查,id調用任何存在的方法都不會在編譯階段報錯,固然若是這個id指向的對象沒有這個方法,該崩潰仍是會崩潰的。

NSObject *指向的必須是NSObject的子類,調用的也只能是NSObjec裏面的方法不然就要作強制類型轉換。

不是全部的OC對象都是NSObject的子類,還有一些繼承自NSProxy。NSObject *可指向的類型是id的子集。

 

 

 

二十五.說說你理解weak屬性?

weak實現原理:

Runtime維護了一個weak表,用於存儲指向某個對象的全部weak指針。weak表實際上是一個hash(哈希)表,Key是所指對象的地址,Value是weak指針的地址(這個地址的值是所指對象的地址)數組。

一、初始化時:runtime會調用objc_initWeak函數,初始化一個新的weak指針指向對象的地址。

二、添加引用時:objc_initWeak函數會調用 objc_storeWeak() 函數, objc_storeWeak() 的做用是更新指針指向,建立對應的弱引用表。

三、釋放時,調用clearDeallocating函數。clearDeallocating函數首先根據對象地址獲取全部weak指針地址的數組,而後遍歷這個數組把其中的數據設爲nil,最後把這個entry從weak表中刪除,最後清理對象的記錄。

 

 

 

追問的問題一:

1.實現weak後,爲何對象釋放後會自動爲nil?

runtime?對註冊的類, 會進行佈局,對於?weak?對象會放入一個?hash?表中。 用?weak?指向的對象內存地址做爲?key,當此對象的引用計數爲?0?的時候會?dealloc,假如?weak?指向的對象內存地址是?a?,那麼就會以?a?爲鍵, 在這個?weak?表中搜索,找到全部以?a?爲鍵的?weak?對象,從而設置爲?nil?。

 

追問的問題二:

2.當weak引用指向的對象被釋放時,又是如何去處理weak指針的呢?

一、調用objc_release

二、由於對象的引用計數爲0,因此執行dealloc

三、在dealloc中,調用了_objc_rootDealloc函數

四、在_objc_rootDealloc中,調用了object_dispose函數

五、調用objc_destructInstance

六、最後調用objc_clear_deallocating,詳細過程以下:

a. 從weak表中獲取廢棄對象的地址爲鍵值的記錄

b. 將包含在記錄中的全部附有 weak修飾符變量的地址,賦值爲 nil

c. 將weak表中該記錄刪除

d. 從引用計數表中刪除廢棄對象的地址爲鍵值的記錄

 

二十六.假如Controller太臃腫,如何優化?

1.將網絡請求抽象到單獨的類中

方便在基類中處理公共邏輯;

方便在基類中處理緩存邏輯,以及其它一些公共邏輯;

方便作對象的持久化。

2.將界面的封裝抽象到專門的類中

構造專門的 UIView 的子類,來負責這些控件的拼裝。這是最完全和優雅的方式,不過稍微麻煩一些的是,你須要把這些控件的事件回調先接管,再都一一暴露回 Controller。

3.構造 ViewModel

借鑑MVVM。具體作法就是將 ViewController 給 View 傳遞數據這個過程,抽象成構造 ViewModel 的過程。

4.專門構造存儲類

專門來處理本地數據的存取。

5.整合常量

二十七.項目中網絡層如何作安全處理?

一、儘可能使用https

https能夠過濾掉大部分的安全問題。https在證書申請,服務器配置,性能優化,客戶端配置上都須要投入精力,因此缺少安全意識的開發人員容易跳過https,或者拖到之後遇到問題再優化。https除了性能優化麻煩一些之外其餘都比想象中的簡單,若是沒精力優化性能,至少在註冊登陸模塊須要啓用https,這部分業務對性能要求比較低。

二、不要傳輸明文密碼

不知道如今還有多少app後臺是明文存儲密碼的。不管客戶端,server仍是網絡傳輸都要避免明文密碼,要使用hash值。客戶端不要作任何密碼相關的存儲,hash值也不行。存儲token進行下一次的認證,並且token須要設置有效期,使用refresh

token去申請新的token。

三、Post並不比Get安全

事實上,Post和Get同樣不安全,都是明文。參數放在QueryString或者Body沒任何安全上的差異。在Http的環境下,使用Post或者Get都須要作加密和簽名處理。

四、不要使用301跳轉

301跳轉很容易被Http劫持攻擊。移動端http使用301比桌面端更危險,用戶看不到瀏覽器地址,沒法察覺到被重定向到了其餘地址。若是必定要使用,確保跳轉發生在https的環境下,並且https作了證書綁定校驗。

五、http請求都帶上MAC

全部客戶端發出的請求,不管是查詢仍是寫操做,都帶上MAC(Message Authentication

Code)。MAC不但能保證請求沒有被篡改(Integrity),還能保證請求確實來自你的合法客戶端(Signing)。固然前提是你客戶端的key沒有被泄漏,如何保證客戶端key的安全是另外一個話題。MAC值的計算能夠簡單的處理爲hash(request

params+key)。帶上MAC以後,服務器就能夠過濾掉絕大部分的非法請求。MAC雖然帶有簽名的功能,和RSA證書的電子簽名方式卻不同,緣由是MAC簽名和簽名驗證使用的是同一個key,而RSA是使用私鑰簽名,公鑰驗證,MAC的簽名並不具有法律效應。

六、http請求使用臨時密鑰

高延遲的網絡環境下,不經優化https的體驗確實會明顯不如http。在不具有https條件或對網絡性能要求較高且缺少https優化經驗的場景下,http的流量也應該使用AES進行加密。AES的密鑰能夠由客戶端來臨時生成,不過這個臨時的AES

key須要使用服務器的公鑰進行加密,確保只有本身的服務器才能解開這個請求的信息,固然服務器的response也須要使用一樣的AES

key進行加密。因爲http的應用場景都是由客戶端發起,服務器響應,因此這種由客戶端單方生成密鑰的方式能夠必定程度上便捷的保證通訊安全。

七、AES使用CBC模式

不要使用ECB模式,記得設置初始化向量,每一個block加密以前要和上個block的祕文進行運算。

二十八.main()以前的過程有哪些?

一、main以前的加載過程

1)dyld 開始將程序二進制文件初始化

2)交由ImageLoader 讀取 image,其中包含了咱們的類,方法等各類符號(Class、Protocol 、Selector、 IMP)

3)因爲runtime 向dyld 綁定了回調,當image加載到內存後,dyld會通知runtime進行處理

4)runtime 接手後調用map_images作解析和處理

5)接下來load_images 中調用call_load_methods方法,遍歷全部加載進來的Class,按繼承層次依次調用Class的+load和其餘Category的+load方法

6)至此 全部的信息都被加載到內存中

7)最後dyld調用真正的main函數



一、Runtime相關面試問題

Runtime是什麼?見名知意,其概念無非就是「由於 Objective-C 是一門動態語言,因此它須要一個運行時系統……這就是 Runtime 系統」云云。對博主這種菜鳥而言,Runtime 在實際開發中,其實就是一組C語言的函數。胡適說:「多研究些問題,少談些主義」,雲山霧罩的概念聽多了老是容易頭暈,接下來咱們直接上runtime思惟導圖幫助你們理清思路:

runtime思惟導圖

![iOS開發之家](https://upload-images.jianshu.io/upload_images/16555213-4213ced28da70859.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)


二、多線程相關面試問題

多線程是一個比較輕量級的方法來實現單個應用程序內多個代碼執行路徑, 從技術角度來看,一個線程就是一個須要管理執行代碼的內核級和應用級數據結 構組合。

多線程思惟導圖

![iOS開發之家](https://upload-images.jianshu.io/upload_images/16555213-7ceba091aa775dd8.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)


三、RunLoop相關面試問題

我相信大多數開發者同樣,迷惑於runloop,最初只瞭解能夠經過runloop一些監聽事件的通知來作一些事情,優化性能。關於runloop源碼的基礎知識,能夠參考下面的思惟導圖:

runloop思惟導圖

![iOS開發之家](https://upload-images.jianshu.io/upload_images/16555213-262968ddfe8be488.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)


四、設計模式相關面試問題

設計模式(Design pattern)是一套被反覆使用、多數人知曉的、通過分類編目的、代碼設計經驗的總結。
使用設計模式是爲了可重用代碼、讓代碼更容易被他人理解、保證代碼可靠性。 毫無疑問,設計模式於己於他人於系統都是多贏的;模式使代碼編制真正工程化;設計模式是軟件工程的基石脈絡,如同大廈的結構同樣。

設計模式思惟導圖

![iOS開發之家](https://upload-images.jianshu.io/upload_images/16555213-08e9b4d446eeff37.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)


五、架構/框架相關面試問題

「100個讀者就有100個哈姆雷特」同樣,對於架構的理解不一樣的軟件工程師有不一樣的見解。架構設計每每是一個權衡的過程,每個架構設計者都要考慮到各個因素,好比團隊成員的技術水平、具體的業務場景、項目的成長階段和開發週期。下圖是小編的一些架構理念,僅供參考:

架構/框架思惟導圖

![iOS開發之家](https://upload-images.jianshu.io/upload_images/16555213-aba4b745be5433bb.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)


六、算法相關面試問題

算法思惟導圖

![iOS開發之家](https://upload-images.jianshu.io/upload_images/16555213-ab6cc63bd9c5d5b5.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)


七、第三方庫相關面試問題

![iOS開發之家](https://upload-images.jianshu.io/upload_images/16555213-34a780c1046ac641.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)





複製代碼
相關文章
相關標籤/搜索