目錄
- OC的理解與特性
- OC內存管理機制
- MVC與MVVM設計模型對比
- 垃圾回收機制
- 協議,分類,KVC,KVO,代理,通知,Block相關試題
- static,self,super,#include,#import,@class,@public、@protected、@private等關鍵詞
- CoreData相關
- 自動釋放池
- 堆和棧的區別
1 OC的理解與特性
OC作為一門面向對象的語言,自然具有面向對象的語言特性:封裝、繼承、多態。它既具有靜態語言的特性(如C++),又有動態語言的效率(動態綁定、動態加載等)。總體來講,OC確實是一門不錯的編程語言,
Objective-C具有相當多的動態特性,表現為三方面:動態類型(Dynamic typing)、動態綁定(Dynamic binding)和動態加載(Dynamic loading)。動態——必須到運行時(run time)才會做的一些事情。
動態類型:即運行時再決定對象的類型,這種動態特性在日常的應用中非常常見,簡單來說就是id類型。事實上,由于靜態類型的固定性和可預知性,從而使用的更加廣泛。靜態類型是強類型,而動態類型屬于弱類型,運行時決定接受者。
動態綁定:基于動態類型,在某個實例對象被確定后,其類型便被確定了,該對象對應的屬性和響應消息也被完全確定。
動態加載:根據需求加載所需要的資源,最基本就是不同機型的適配,例如,在Retina設備上加載@2x的圖片,而在老一些的普通蘋設備上加載原圖,讓程序在運行時添加代碼模塊以及其他資源,用戶可根據需要加載一些可執行代碼和資源,而不是在啟動時就加載所有組件,可執行代碼可以含有和程序運行時整合的新類。
2 簡述內存管理基本原則
之前:OC內存管理遵循“誰創建,誰釋放,誰引用,誰管理”的機制,當創建或引用一個對象的時候,需要向她發送alloc、copy、retain消息,當釋放該對象時需要發送release消息,當對象引用計數為0時,系統將釋放該對象,這是OC的手動管理機制(MRC)。
目前:iOS 5.0之后引用自動管理機制——自動引用計數(ARC),管理機制與手動機制一樣,只是不再需要調用retain、release、autorelease;它編譯時的特性,當你使用ARC時,在適當位置插入release和autorelease;它引用strong和weak關鍵字,strong修飾的指針變量指向對象時,當指針指向新值或者指針不復存在,相關聯的對象就會自動釋放,而weak修飾的指針變量指向對象,當對象的擁有者指向新值或者不存在時weak修飾的指針會自動置為nil。
如果使用alloc、copy(mutableCopy)或者retian一個對象時,你就有義務,向它發送一條release或者autorelease消息。其他方法創建的對象,不需要由你來管理內存。
向一個對象發送一條autorelease消息,這個對象并不會立即銷毀, 而是將這個對象放入了自動釋放池,待池子釋放時,它會向池中每一個對象發送 一條release消息,以此來釋放對象.
向一個對象發送release消息,并不意味著這個對象被銷毀了,而是當這個對象的引用計數為0時,系統才會調用dealloc方法,釋放該對象和對象本身它所擁有的實例。
其他注意事項
- 如果一個對象有一個
_strong
類型的指針指向著,找個對象就不會被釋放。如果一個指針指向超出了它的作用域,就會被指向nil。如果一個指針被指向nil,那么它原來指向的對象就被釋放了。當一個視圖控制器被釋放時,它內部的全局指針會被指向nil。用法“:不管全局變量還是局部變量用_strong描述就行。 - 局部變量:出了作用域,指針會被置為nil。
- 方法內部創建對象,外部使用需要添加_autorelease;
- 連線的時候,用_weak描述。
- 代理使用
unsafe_unretained
就相當于assign
; - block中為了避免循環引用問題,使用
_weak
描述; - 聲明屬性時,不要以
new
開頭。如果非要以new開頭命名屬性的名字,需要自己定制get方法名,如
@property(getter=theString) NSString * newString;
- 如果要使用自動釋放池,用
@autoreleasepool{}
- ARC只能管理
Foundation
框架的變量,如果程序中把Foundation中的變量強制換成COreFoundation
中的變量需要交換管理權; - 在非ARC工程中采用ARC去編譯某些類:
-fobjc-arc
。 - 在ARC下的工程采用非ARC去編譯某些類:
-fno-fobjc-arc
。
3 如何理解MVC設計模式
MVC是一種架構模式,M表示MOdel,V表示視圖View,C表示控制器Controller:
Model
負責存儲、定義、操作數據;View
用來展示書給用戶,和用戶進行操作交互;Controller
是Model和View的協調者,Controller把Model中的數據拿過來給View用。Controller可以直接與Model和View進行通信,而View不能和Controller直接通信。View與Controller通信需要利用代理協議的方式,當有數據更新時,MOdel也要與Controller進行通信,這個時候就要用Notification和KVO,這個方式就像一個廣播一樣,MOdel發信號,Controller設置監聽接受信號,當有數據更新時就發信號給Controller,Model和View不能直接進行通信,這樣會違背MVC設計模式。
4 如何理解MVVM設計模式
-
ViewModel層
就是View和Model層的粘合劑,他是一個放置用戶輸入驗證邏輯,視圖顯示邏輯,發起網絡請求和其他各種各樣的代碼的極好的地方。說白了,就是把原來ViewController層的業務邏輯和頁面邏輯等剝離出來放到ViewModel層。 -
View層
就是ViewController層,他的任務就是從ViewModel層獲取數據,然后顯示。
5 Objective-C 中是否支持垃圾回收機制?
OC是支持垃圾回收機制的
(Garbage collection簡稱GC)
,但是apple的移動終端中,是不支持GC的,Mac桌面系統開發中是支持的.移動終端開發是支持
ARC(Automatic Reference Counting的簡稱)
,ARC是在IOS5之后推出的新技術,它與GC的機制是不同的。我們在編寫代碼時, 不需要向對象發送release或者autorelease方法,也不可以調用delloc方法,編譯器會在合適的位置自動給用戶生成release消息(autorelease),ARC 的特點是自動引用技術簡化了內存管理的難度.
6 協議的基本概念和協議中方法默認為什么類型。
OC中的協議是一個方法列表,且多少有點相關。它的特點是可以被任何類使用(實現),但它
并不是類
(這里我們需要注意),自身不會實現這樣方法, 而是又其他人來實現協議經常用來實現委托對象(委托設計模式)。如果一個類采用了一個協議,那么它必須實現協議中必須需要實現的方法,在協議中的方法
默認
是必須實現(@required)
,添加關鍵字@optional,表明一旦采用該協議,這些“可選”的方法是可以選擇不實現的。一個協議可以擴展自另一個協議,如果需要擴展多個協議中間使用逗號分隔;
和其他高級語言中接口不同的是協議中定義的方法不一定是必須實現的,我們可以通過關鍵字進行@required和@optional進行設置,如果不設置則
默認是@required
(注意ObjC是弱語法,即使不實現必選方法編譯運行也不會報錯)
;協議通過<>進行實現,一個類可以同時實現多個協議,中間通過逗號分隔;
協議的實現只能在類的聲明上,不能放到類的實現上(也就是說必須寫成@interface Person:NSObject<AnimalDelegate>而不能寫成@implementation Person<AnimalDelegate>);
協議中不能定義屬性、成員變量等,只能定義方法;
7 簡述類目category優點和缺點。
-
優點:
- 不需要通過增加子類而增加現有類的行為(方法),且類目中的方法與原始類方法基本沒有區別;
- 通過類目可以將龐大一個類的方法進行劃分,從而便于代碼的日后的維護、更新以及提高代碼的閱讀性;
-
缺點:
- 無法向類目添加實例變量,如果需要添加實例變量,只能通過定義子類的方式;
- 類目中的方法與原始類以及父類方法相比具有更高優先級,如果覆蓋父類的方法,可能導致super消息的斷裂。因此,最好不要覆蓋原始類中的方法。
8 類別的作用
給系統原有類添加方法,不能擴展屬性。如果類別中方法的名字跟系統的方法名一樣,在調用的時候類別中的方法優先級更高;
分散類的實現:如:
+ (NSIndexPath *)indexPathForRow:(NSInteger)row inSection:(NSInteger)section
原本屬于NSIndexPath的方法,但因為這個方法經常使用的表的時候調用、跟表的關系特別密切,因此把這個方法一類別的形式、聲明在UITableView.h中。
聲明私有方法,某一個方法只實現,不聲明,相當于私有方法。
類別不能聲明變量,類別不可以直接添加屬性。property描述setter方法,就不會報錯。
9 循環引用的產生原因,以及解決方法。
產生原因:如下圖所示,對象A和對象B相互引用了對方作為自己的成員變量,只有自己銷毀的時候才能將成員變量的引用計數減1。對象A的銷毀依賴于對象B的銷毀,同時對象B銷毀也依賴與對象A的銷毀,從而形成循環引用,此時,即使外界沒有任何指針訪問它,它也無法釋放。
解決方法:
- 事先知道存在循環引用的地方,在合理的位置主動斷開一個引用,是對象回收;
- 使用弱引用
10 鍵路徑(keyPath)、鍵值編碼(KVC)和鍵值觀察(KVO)
鍵路徑
- 在一個給定的實體中,同一個屬性的所有值具有相同的數據類型。
- 鍵-值編碼技術用于進行這樣的查找—它是一種間接訪問對象屬性的機制。 - 鍵路徑是一個由用點作分隔符的鍵組成的字符串,用于指定一個連接在一起的對象性質序列。第一個鍵的性質是由先前的性質決定的,接下來每個鍵的值也是相對于其前面的性質。
- 鍵路徑使您可以以獨立于模型實現的方式指定相關對象的性質。通過鍵路徑,您可以指定對象圖中的一個任意深度的路徑,使其指向相關對象的特定屬性。
鍵值編碼KVC
- 鍵值編碼是一種間接訪問對象的屬性使用字符串來標識屬性,而不是通過調用存取方法,直接或通過實例變量訪問的機制,非對象類型的變量將被自動封裝或者解封成對象,很多情況下會簡化程序代碼;
- KVC的缺點:一旦使用 KVC 你的編譯器無法檢查出錯誤,即不會對設置的鍵、鍵路徑進行錯誤檢查,且執行效率要低于合成存取器方法和自定的 setter 和 getter 方法。因為使用 KVC 鍵值編碼,它必須先解析字符串,然后在設置或者訪問對象的實例變量。
鍵值觀察KVO
- 鍵值觀察機制是一種能使得對象獲取到其他對象屬性變化的通知 ,極大的簡化了代碼。
- 實現 KVO 鍵值觀察模式,被觀察的對象必須使用 KVC 鍵值編碼來修 改它的實例變量,這樣才能被觀察者觀察到。因此,KVC是KVO的基礎。
KVC機制通過key找到value的原理
- 當通過KVC調用對象時,比如:[self valueForKey:@”someKey”]時,程序會自動試圖通過下面幾種不同的方式解析這個調用。
- 首先查找對象是否帶有
someKey
這個方法,如果沒找到,會繼續查找對象是否帶有someKey這個實例變量(iVar)
,如果還沒有找到,程序會繼續試圖調用-(id) valueForUndefinedKey:
這個方法。如果這個方法還是沒有被實現的話,程序會拋出一個NSUndefinedKeyException
異常錯誤。 - 補充:KVC查找方法的時候,不僅僅會查找someKey這個方法,還會查找
getsomeKey
這個方法,前面加一個get,或者_someKey
以_getsomeKey
這幾種形式。同時,查找實例變量的時候也會不僅僅查找someKey這個變量,也會查找_someKey這個變量是否存在。 - 設計valueForUndefinedKey:方法的主要目的是當你使用-(id)valueForKey方法從對象中請求值時,對象能夠在錯誤發生前,有最后的機會響應這個請求。
11 在 Objective-C 中如何實現 KVO
- 注冊觀察者(注意:觀察者和被觀察者不會被保留也不會被釋放)
- (void)addObserver:(NSObject *)observer forKeyPath:(NSString *)keyPath options:(NSKeyValueObservingOptions)options context:(void *)context;
- 接收變更通知
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context;
- 移除對象的觀察者身份
- (void)removeObserver:(NSObject *)observer forKeyPath:(NSString *)keyPath;
KVO中誰要監聽誰注冊,然后對響應進行處理,使得觀察者與被觀察者完全解耦。KVO只檢測類中的屬性,并且屬性名都是通過NSString來查找,編譯器不會檢錯和補全,全部取決于自己。
12 代理的作用
- 代理又叫
委托
,是一種設計模式,代理是對象與對象之間的通信交互,代理解除了對象之間的耦合性。 - 改變或傳遞控制鏈。允許一個類在某些特定時刻通知到其他類,而不需要獲取到那些類的指針。可以減少框架復雜度。
- 另外一點,代理可以理解為java中的回調監聽機制的一種類似。
- 代理的屬性常是assign的原因:防止循環引用,以至對象無法得到正確的釋放。
13 NSNotification、Block、Delegate和KVO的區別。
- 代理是一種回調機制,且是一對一的關系,通知是一對多的關系,一個對向所有的觀察者提供變更通知;
- 效率:Delegate比NSNOtification高;
- Delegate和Block一般是一對一的通信;
- Delegate需要定義協議方法,代理對象實現協議方法,并且需要建立代理關系才可以實現通信;
- Block:Block更加簡潔,不需要定義繁瑣的協議方法,但通信事件比較多的話,建議使用Delegate;
14 Objective-C中可修改和不可以修改類型
- 可修改不可修改的集合類,就是可動態添加修改和不可動態添加修改。
- 比如NSArray和NSMutableArray,前者在初始化后的內存控件就是固定不可變的,后者可以添加等,可以動態申請新的內存空間
15 當我們調用一個靜態方法時,需要對對象進行 release 嗎?
不需要,靜態方法(類方法)創建一個對象時,對象已被放入自動釋放池。在自動釋放池被釋放時,很有可能被銷毀。
16 當我們釋放我們的對象時,為什么需要調用[super dealloc]方法,它的位置又是如何的呢?
因為子類的某些實例是繼承自父類的,因此需要調用[super dealloc]方法, 來釋放父類擁有的實例,其實也就是子類本身的。一般來說我們優先釋放子類擁 有的實例,最后釋放父類所擁有的實例。
17 對謂詞的認識
Cocoa 中提供了一個NSPredicate的類,該類主要用于指定過濾器的條件, 每一個對象通過謂詞進行篩選,判斷條件是否匹配。如果需要了解使用方法,請看謂詞的具體使用
18 static、self、super關鍵字的作用
- 函數體內static變量的作用范圍為該函數體,不同于auto變量,該變量的內存只被分配一次,因此其值在下次調用時仍維持上次的值.
- 在模塊內的 static 全局變量可以被模塊內所用函數訪問,但不能被模塊外其它函數訪問.
- 在模塊內的static函數只可被這一模塊內的其它函數調用,這個函數的使用范圍被限制在聲明.
- 在類中的static成員變量屬于整個類所擁有,對類的所有對象只有一份拷貝.
- self:當前消息的接收者。
- super:向父類發送消息。
19 #include與#import的區別,#import 與@class 的區別
- #include 和#import其效果相同,都是查詢類中定義的行為(方法);
- #import不會引起交叉編譯,確保頭文件只會被導入一次;
- @class 的表明,只定 義了類的名稱,而具體類的行為是未知的,一般用于.h 文件;
- @class 比#import 編譯效率更高。
- 此外@class 和#import 的主要區別在于解決引用死鎖的問題。
20 @public、@protected、@private 它們的含義與作用
- @public:對象的實例變量的作用域在任意地方都可以被訪問 ;
- @protected:對象的實例變量作用域在本類和子類都可以被訪問 ;
- @private:實例變量的作用域只能在本類(自身)中訪問 .
21 解釋 id 類型
任意類型對象,程序運行時才決定對象的類型。
22 switch 語句 if 語句區別與聯系
- 均表示條件的判斷,switch語句表達式只能處理的是整型、字符型和枚舉類型,而選擇流程語句則沒有這樣的限制。
- 但switch語句比選擇流程控制語句效率更高。
23 isMemberOfClass 和 isKindOfClass 聯系與區別
- 聯系:兩者都能檢測一個對象是否是某個類的成員
- 區別:isKindOfClass 不僅用來確定一個對象是否是一個類的成員,也可以用來確定一個對象是否派生自該類的類的成員 ,而isMemberOfClass 只能做到第一點。
- 舉例:如 ClassA派 生 自NSObject 類 , ClassA *a = [ClassA alloc] init];,[a isKindOfClass:[NSObject class]] 可以檢查出 a 是否是 NSObject派生類 的成員,但 isMemberOfClass 做不到。
24 iOS 開發中數據持久性有哪幾種?
數據存儲的核心都是寫文件。
- 屬性列表:只有NSString、NSArray、NSDictionary、NSData可writeToFile;存儲依舊是plist文件。plist文件可以存儲的7中數據類型:array、dictionary、string、bool、data、date、number。
- 對象序列化(對象歸檔):對象序列化通過序列化的形式,鍵值關系存儲到本地,轉化成二進制流。通過runtime實現自動化歸檔/解檔,請參考這個文章。實現NSCoding協議必須實現的兩個方法:
- 編碼(對象序列化):把不能直接存儲到plist文件中得到數據,轉化為二進制數據,NSData,可以存儲到本地;
- 解碼(對象反序列化):把二進制數據轉化為本來的類型
- SQLite 數據庫:大量有規律的數據使用數據庫。
- CoreData :通過管理對象進行增、刪、查、改操作的。它不是一個數據庫,不僅可以使用SQLite數據庫來保持數據,也可以使用其他的方式來存儲數據。如:XML。
25 CoreData
CoreData的介紹:
- CoreData是面向對象的API,CoreData是iOS中非常重要的一項技術,幾乎在所有編寫的程序中,CoreData都作為數據存儲的基礎。
- CoreData是蘋果官方提供的一套框架,用來解決與對象聲明周期管理、對象關系管理和持久化等方面相關的問題。
- 大多數情況下,我們引用CoreData作為持久化數據的解決方案,并利用它作為持久化數據映射為內存對象。提供的是對象-關系映射功能,也就是說,CoreData可以將Objective-C對象轉換成數據,保存到SQL中,然后將保存后的數據還原成OC對象。
CoreData的特征:
- 通過CoreData管理應用程序的數據模型,可以極大程度減少需要編寫的代碼數量。
- 將對象數據存儲在SQLite數據庫已獲得性能優化。
- 提供NSFetchResultsController類用于管理表視圖的數據,即將Core Data的持久化存儲在表視圖中,并對這些數據進行管理:增刪查改。
- 管理undo/redo操縱;
- 檢查托管對象的屬性值是否正確。
Core Data的6成員對象
-
NSManageObject:
被管理的數據記錄Managed Object Model是描述應用程序的數據模型,這個模型包含實體(Entity)、特性(Property)、讀取請求(Fetch Request)等。 -
NSManageObjectContext:
管理對象上下文,持久性存儲模型對象,參與數據對象進行各種操作的全過程,并監測數據對象的變化,以提供對undo/redo的支持及更新綁定到數據的UI。 -
NSPersistentStoreCoordinator:
連接數據庫的Persistent Store Coordinator相當于數據文件管理器,處理底層的對數據文件的讀取和寫入,一般我們與這個沒有交集。 -
NSManagedObjectModel:
被管理的數據模型、數據結構。 -
NSFetchRequest:
數據請求; -
NSEntityDescription:
表格實體結構,還需知道.xcdatamodel文件編譯后為.momd或者.mom文件。
Core Data的功能
- 對于KVC和KVO完整且自動化的支持,除了為屬性整合KVO和KVC訪問方法外,還整合了適當的集合訪問方法來處理多值關系;
- 自動驗證屬性(property)值;
- 支持跟蹤修改和撤銷操作;
- 關系維護,Core Data管理數據的關系傳播,包括維護對象間的一致性;
- 在內存上和界面上分組、過濾、組織數據;
- 自動支持對象存儲在外部數據倉庫的功能;
- 創建復雜請求:無需動手寫SQL語句,在獲取請求(fetch request)中關聯NSPredicate。NSPreadicate支持基本功能、相關子查詢和其他高級的SQL特性。它支持正確的Unicode編碼、區域感知查詢、排序和正則表達式;
- 延遲操作:Core Data使用懶加載(lazy loading)方式減少內存負載,還支持部分實體化延遲加載和復制對象的數據共享機制;
- 合并策略:Core Data內置版本跟蹤和樂觀鎖(optimistic locking)來支持多用戶寫入沖突的解決,其中,樂觀鎖就是對數據沖突進行檢測,若沖突就返回沖突的信息;
- 數據遷移:Core Data的Schema Migration工具可以簡化應對數據庫結構變化的任務,在某些情況允許你執行高效率的數據庫原地遷移工作;
- 可選擇針對程序Controller層的集成,來支持UI的顯示同步Core Data在IPhone OS之上,提供NSFetchedResultsController對象來做相關工作,在Mac OS X上我們用Cocoa提供的綁定(Binding)機制來完成的。
26 對象可以被copy的條件
- 只有實現了NSCopying和NSMutableCopying協議的類的對象才能被拷貝,分為不可變拷貝和可變拷貝
- NSCopying協議方法為:
- (id)copyWithZone:(NSZone *)zone;
27 自動釋放池工作原理
- 自動釋放池是NSAutorelease類的一個實例,當向一個對象發送autorelease消息時,該對象會自動入池,待池銷毀時,將會向池中所有對象發送一條release消息,釋放對象。
- [pool release]、 [pool drain]表示的是池本身不會銷毀,而是池子中的臨時對象都被發送release,從而將對象銷毀。
28 在某個方法中 self.name = _name,name = _name 它 們有區別嗎,為什么?
- 前者是存在內存管理的setter方法賦值,它會對_name對象進行保留或者拷貝操作
- 后者是普通賦值
- 一般來說,在對象的方法里成員變量和方法都是可以訪問的,我們通常會重寫Setter方法來執行某些額外的工作。比如說,外部傳一個模型過來,那么我會直接重寫Setter方法,當模型傳過來時,也就是意味著數據發生了變化,那么視圖也需要更新顯示,則在賦值新模型的同時也去刷新UI。
29 解釋self = [super init]方法
容錯處理,當父類初始化失敗,會返回一個nil,表示初始化失敗。由于繼承的關系,子類是需要擁有父類的實例和行為,因此,我們必須先初始化父類,然后再初始化子類
30 定義屬性時,什么時候用 assign、retain、copy 以及它們的之間的區別。
-
assign:
普通賦值,一般常用于基本數據類型,常見委托設計模式, 以此來防止循環引用。(我們稱之為弱引用). -
retain:
保留計數,獲得到了對象的所有權,引用計數在原有基礎上加1. -
copy:
一般認為,是在內存中重新開辟了一個新的內存空間,用來 存儲新的對象,和原來的對象是兩個不同的地址,引用計數分別為1。但是當copy對象為不可變對象時,那么copy 的作用相當于retain。因為,這樣可以節約內存空間
31 堆和棧的區別
-
棧區(stack)
由編譯器自動分配釋放 ,存放方法(函數)的參數值, 局部變量的值等,棧是向低地址擴展的數據結構,是一塊連續的內存的區域。即棧頂的地址和棧的最大容量是系統預先規定好的。 -
堆區(heap)
一般由程序員分配釋放, 若程序員不釋放,程序結束時由OS回收,向高地址擴展的數據結構,是不連續的內存區域,從而堆獲得的空間比較靈活。 -
碎片問題:
對于堆來講,頻繁的new/delete勢必會造成內存空間的不連續,從而造成大量的碎片,使程序效率降低。對于棧來講,則不會存在這個問題,因為棧是先進后出的隊列,他們是如此的一一對應,以至于永遠都不可能有一個內存塊從棧中間彈出. -
分配方式:
堆都是動態分配的,沒有靜態分配的堆。棧有2種分配方式:靜態分配和動態分配。靜態分配是編譯器完成的,比如局部變量的分配。動態分配由alloca函數進行分配,但是棧的動態分配和堆是不同的,他的動態分配是由編譯器進行釋放,無需我們手工實現。 -
分配效率:
棧是機器系統提供的數據結構,計算機會在底層對棧提供支持:分配專門的寄存器存放棧的地址,壓棧出棧都有專門的指令執行,這就決定了棧的效率比較高。堆則是C/C++函數庫提供的,它的機制是很復雜的。 -
全局區(靜態區)(static),全局變量和靜態變量
的存儲是放在一塊 的,初始化的全局變量和靜態變量在一塊區域, 未初始化的全局變量和未初始化的靜態變量在相鄰的另一塊區域。程序結束后有系統釋放。 -
文字常量區
—常量字符串就是放在這里的。程序結束后由系統釋放。 -
程序代碼區
—存放函數體的二進制代碼
更多同類型文章請參考
iOS面試進階篇(一)
iOS面試進階篇(二)
iOS面試進階篇(三)
iOS面試進階篇(四)
iOS面試進階篇(五)
書寫整理多不容易,覺得寫的好的打賞一下吧。
更多相關類型文章敬請期待中