2020年的春天,成都的天氣格外的悶熱。
因為公司的原因,導致我不得不考慮去面試一家穩定成熟的大公司,自己從一開始也一直夢想著進入一家可以得到廣闊平臺的公司發展,有朝一日可以當上技術負責人,可是現實總是那么殘酷,4年了,4年了,每次面試感覺自己還是只是會回答:我知道,但是沒有深入研究過。這個我了解過,但是還沒有處理過。然后就是一個勁的出汗來排解內心的壓力。3次換工作,面試過30-40次,每次都只是為了面試而面試(背題),很多東西都記住不。哎~ 我太難了~ 太難了~
不管面試是否通過,還是來總結一下面試經常遇到的問題吧。
Object-C系列面試題總結
基礎題:
1.Objective-C的類可以多重繼承么?可以實現多個接口么?Category是什么?重寫一個類的方式用繼承好還是分類好?為什么不要在category中重寫一個類原有的方法?
<meta charset="utf-8">
答:
Objective-c的類不可以有多繼承,OC里面都是單繼承,多繼承可以用protocol委托代理來模擬實現
可以實現多個接口,可以通過實現多個接口完成OC的多重繼承
Category是類別;
??重寫一個類的方式用繼承好還是分類好:
重寫一個類的方式用繼承還是分類.取決于具體情況.假如目標類有許多的子類.我們需要拓展這個類又不希望影響到原有的代碼.繼承后比較好.
如果僅僅是拓展方法.分類更好.(不需要涉及到原先的代碼)
分類中方法的優先級比原來類中的方法高,也就是說,在分類中重寫了原來類中的方法,那么分類中的方法會覆蓋原來類中的方法
??為什么不要在category中重寫一個類原有的方法:
1、category沒有辦法去代替子類,它不能像子類一樣通過super去調用父類的方法實現。如果category中重寫覆蓋了當前類中的某個方法,那么這個當前類中的原始方法實現,將永遠不會被執行,這在某些方法里是致命的。(ps:這里提一下,+(void)load方法是一個特例,它會在當前類執行完之后再在category中執行。)
2、同時,一個category也不能可靠的覆蓋另一個category中相同的類的相同的方法。例如UIViewController+A與UIViewController+B,都重寫了viewDidLoad,我們就無法控制誰覆蓋了誰。
3、通過觀察頭文件我們可以發現,Cocoa框架中的許多類都是通過category來實現功能的,可能不經意間你就覆蓋了這些方法中的其一,有時候就會產生一些無法排查的異常原因。
4、category的誕生只是為了讓開發者更加方便的去拓展一個類,它的初衷并不是讓你去改變一個類。
結論:
要重寫方法,當然我們首推通過子類重寫父類的方法,在一些不方便重寫的情況下,我們也可以在category中用runtime進行method swizzling(方法的偷梁換柱)來實現。
2.請說明并比較以下關鍵詞:strong, weak, assign, copy。
答:
- strong表示指向并擁有該對象。其修飾的對象引用計數會增加1。該對象只要引用計數不為0則不會被銷毀。當然強行將其設為nil可以銷毀它。
- weak表示指向但不擁有該對象。其修飾的對象引用計數不會增加。無需手動設置,該對象會自行在內存中銷毀。
- assign主要用于修飾基本數據類型,如NSInteger和CGFloat,這些數值主要存在于棧上。
- weak 一般用來修飾對象,assign一般用來修飾基本數據類型。原因是assign修飾的對象被釋放后,指針的地址依然存在,造成野指針,在堆上容易造成崩潰。而棧上的內存系統會自動處理,不會造成野指針。
- copy與strong類似。不同之處是strong的復制是多個指針指向同一個地址,而copy的復制每次會在內存中拷貝一份對象,指針指向不同地址。copy一般用在修飾有可變對應類型的不可變對象上,如NSString, NSArray, NSDictionary。
- Objective-C 中,基本數據類型的默認關鍵字是atomic, readwrite, assign;普通屬性的默認關鍵字是atomic, readwrite, strong。
3.用@property聲明的 NSString / NSArray / NSDictionary 經常使用 copy 關鍵字,為什么?如果改用strong關鍵字,可能造成什么問題?
答:
用 @property 聲明 NSString、NSArray、NSDictionary 經常使用 copy 關鍵字,是因為他們有對應的可變類型:NSMutableString、NSMutableArray、NSMutableDictionary,他們之間可能進行賦值操作(就是把可變的賦值給不可變的),為確保對象中的字符串值不會無意間變動,應該在設置新屬性值時拷貝一份。
1\. 因為父類指針可以指向子類對象,使用 copy 的目的是為了讓本對象的屬性不受外界影響,使用 copy 無論給我傳入是一個可變對象還是不可對象,我本身持有的就是一個不可變的副本。
2\. 如果我們使用是 strong ,那么這個屬性就有可能指向一個可變對象,如果這個可變對象在外部被修改了,那么會影響該屬性。
//總結:使用copy的目的是,防止把可變類型的對象賦值給不可變類型的對象時,可變類型對象的值發送變化會無意間篡改不可變類型對象原來的值。
復制代碼
4.淺拷貝和深拷貝的區別?
答:
淺拷貝:只復制指向對象的指針,而不復制引用對象本身。
深拷貝:復制引用對象本身。內存中存在了兩份獨立對象本身,當修改A時,A_copy不變。
復制代碼
5.Objective-C 如何對內存管理的,說說你的看法和解決方法?
答:Objective-C的內存管理主要有三種方式ARC(自動內存計數)、手動內存計數、內存池。
1). 自動內存計數ARC:由Xcode自動在App編譯階段,在代碼中添加內存管理代碼。
2). 手動內存計數MRC:遵循內存誰申請、誰釋放;誰添加,誰釋放的原則。
3). 內存釋放池Release Pool:把需要釋放的內存統一放在一個池子中,當池子被抽干后(drain),池子中所有的內存空間也被自動釋放掉。內存池的釋放操作分為自動和手動。自動釋放受runloop機制影響。
復制代碼
6.繼承、分類和類擴展
答:
1\. 分類有名字,類擴展沒有分類名字,是一種特殊的分類。
2\. 分類只能擴展方法(屬性僅僅是聲明,并沒真正實現),類擴展可以擴展屬性、成員變量和方法。
3\. 繼承可以增加,修改或者刪除方法,并且可以增加屬性。
復制代碼
IOS 分類(category)、擴展(Extension)和繼承(inheritance)的區別?
7.我們說的OC是動態運行時語言是什么意思?
答:
主要是將數據類型的確定由編譯時,推遲到了運行時。簡單來說, 運行時機制使我們直到運行時才去決定一個對象的類別,以及調用該類別對象指定方法。
復制代碼
8.什么是 KVO 和 KVC?談談 KVC 以及 KVO 的理解?
答:
KVC(key-value-coding)鍵值編碼,是一種間接訪問實例變量的方法。提供一種機制來間接訪問對象的屬性。
1、給私有變量賦值。
2、給控件的內部屬性賦值(如自定義UITextFiled的clearButton,或placeholder的顏色,一般可利用runtime獲取控件的內部屬性名,Ivar *ivar = class_getInstanceVariable獲取實例成員變量)。
[textField setValue:[UIColor redColor] forKeyPath:@"placeholderLabel.textColor"];
3、結合Runtime,model和字典的轉換(setValuesForKeysWithDictionary,class_copyIvarList獲取指定類的Ivar成員列表)
KVO是一種基于KVC實現的觀察者模式。當指定的被觀察的對象的屬性更改了,KVO會以自動或手動方式通知觀察者。
事例:監聽 ScrollView 的 contentOffSet屬性
[scrollview addObserver:self forKeyPath:@"contentOffset" options:NSKeyValueObservingOptionNew context:nil];
復制代碼
9.block的注意點
答:
1\. 在block內部使用外部指針且會造成循環引用情況下,需要用__weak修飾外部指針:
__weak typeof(self) weakSelf = self;
2\. 在block內部如果調用了延時函數還使用弱指針會取不到該指針,因為已經被銷毀了,需要在block內部再將弱指針重新強引用一下。
__strong typeof(self) strongSelf = weakSelf;
3\. 如果需要在block內部改變外部棧區變量的話,需要在用__block修飾外部變量。
復制代碼
10.堆、棧和隊列
答:
1.從管理方式來講
對于棧來講,是由編譯器自動管理,無需我們手工控制;
對于堆來說,釋放工作由程序員控制,容易產生內存泄露(memory leak)
2.從申請大小大小方面講
棧空間比較小
堆控件比較大
3.從數據存儲方面來講
棧空間中一般存儲基本類型,對象的地址
堆空間一般存放對象本身,block的copy等
復制代碼
答:
# 堆
堆是一種經過排序的樹形數據結構,每個節點都有一個值,通常我們所說的堆的數據結構是指二叉樹。所以堆在數據結構中通常可以被看做是一棵樹的數組對象。而且堆需要滿足一下兩個性質:
1)堆中某個節點的值總是不大于或不小于其父節點的值;
2)堆總是一棵完全二叉樹。
堆分為兩種情況,有最大堆和最小堆。將根節點最大的堆叫做最大堆或大根堆,根節點最小的堆叫做最小堆或小根堆,在一個擺放好元素的最小堆中,父結點中的元素一定比子結點的元素要小,但對于左右結點的大小則沒有規定誰大誰小。
堆常用來實現優先隊列,堆的存取是隨意的,這就如同我們在圖書館的書架上取書,雖然書的擺放是有順序的,但是我們想取任意一本時不必像棧一樣,先取出前面所有的書,書架這種機制不同于箱子,我們可以直接取出我們想要的書。
# 棧
棧是限定僅在表尾進行插入和刪除操作的線性表。我們把允許插入和刪除的一端稱為棧頂,另一端稱為棧底,不含任何數據元素的棧稱為空棧。棧的特殊之處在于它限制了這個線性表的插入和刪除位置,它始終只在棧頂進行。
棧是一種具有后進先出的數據結構,又稱為后進先出的線性表,簡稱 LIFO(Last In First Out)結構。也就是說后存放的先取,先存放的后取,這就類似于我們要在取放在箱子底部的東西(放進去比較早的物體),我們首先要移開壓在它上面的物體(放進去比較晚的物體)。
堆棧中定義了一些操作。兩個最重要的是PUSH和POP。PUSH操作在堆棧的頂部加入一個元素。POP操作相反,在堆棧頂部移去一個元素,并將堆棧的大小減一。
棧的應用—遞歸
# 隊列
隊列是只允許在一端進行插入操作、而在另一端進行刪除操作的線性表。允許插入的一端稱為隊尾,允許刪除的一端稱為隊頭。它是一種特殊的線性表,特殊之處在于它只允許在表的前端進行刪除操作,而在表的后端進行插入操作,和棧一樣,隊列是一種操作受限制的線性表。
隊列是一種先進先出的數據結構,又稱為先進先出的線性表,簡稱 FIFO(First In First Out)結構。也就是說先放的先取,后放的后取,就如同行李過安檢的時候,先放進去的行李在另一端總是先出來,后放入的行李會在最后面出來。
復制代碼
11.什么是多態?什么是分類?什么是協議?
答:
多態在面向對象語言中指同一個接口有多種不同的實現方式,在OC中,多態則是不同對象對同一消息的不同響應方式;子類通過重寫父類的方法來改變同一方法的實現.體現多態性
通俗來講: 多態就父類類型的指針指向子類的對象,在函數(方法)調用的時候可以調用到正確版本的函數(方法)。
多態就是某一類事物的多種形態.繼承是多態的前提;
復制代碼
答:
分類: 在不修改原有類代碼的情況下,可以給類添加方法
Categroy 給類擴展方法,或者關聯屬性, Categroy底層結構也是一個結構體:內部存儲這結構體的名字,那個類的分類,以及對象和類方法列表,協議,屬性信息
通過Runtime加載某個類的所有Category數據
把所有Category的方法、屬性、協議數據,合并到一個大數組中后面參與編譯的Category數據,會在數組的前面
將合并后的分類數據(方法、屬性、協議),插入到類原來數據的前面
復制代碼
答:
協議:協議是一套標準,這個標準中聲明了很多方法,但是不關心具體這些方法是怎么實現的,具體實現是由遵循這個協議的類去完成的。
在OC中,一個類可以實現多個協議,通過協議可以彌補單繼承的缺陷但是協議跟繼承不一樣,協議只是一個方法列表,方法的實現得靠遵循這個協議的類去實現。
復制代碼
12.UIView 和 CALayer 是什么關系?
答:
UIView 繼承 UIResponder,而 UIResponder 是響應者對象,可以對iOS 中的事件響應及傳遞,CALayer 沒有繼承自 UIResponder,所以 CALayer 不具備響應處理事件的能力。CALayer 是 QuartzCore 中的類,是一個比較底層的用來繪制內容的類,用來繪制UI
UIView 對 CALayer 封裝屬性,對 UIView 設置 frame、center、bounds 等位置信息時,其實都是UIView 對 CALayer 進一層封裝,使得我們可以很方便地設置控件的位置;例如圓角、陰影等屬性, UIView 就沒有進一步封裝,所以我們還是需要去設置 Layer 的屬性來實現功能。
UIView 是 CALayer 的代理,UIView 持有一個 CALayer 的屬性,并且是該屬性的代理,用來提供一些 CALayer 行的數據,例如動畫和繪制。
復制代碼
13.說一下 JS 和 OC 互相調用的幾種方式?
答:
js調用oc的三種方式:
方式一:通過替換js中的function(方法)
方式二:通過注入對象,直接調用對象方法
方式三:利用網頁重定向,截取字符串.
oc調用js代碼兩種方式
1.通過webVIew調用 webView stringByEvaluatingJavaScriptFromString: 調用
2.通過JSContext調用[context evaluateScript:];
復制代碼
14.如何理解HTTP?/Http 和 Https 的區別?Https為什么更加安全?
答:
HTTP本質上是一種協議,全稱是Hypertext Transfer Protocol,即超文本傳輸協議。HTTP是一個基于TCP/IP通信協議來傳遞數據, 該協議用于規定客戶端與服務端之間的傳輸規則,所傳輸的內容不局限于文本(其實可以傳輸任意類型的數據)。
一次HTTP可以看做是一個事務,其工作過程分為4步:
1.客戶端與服務器建立連接
2.建立連接后,客戶端給服務端發送請求
3.服務器收到消息后,給與響應操作
4.客戶端收到消息后,展示到屏幕上,斷開連接.
復制代碼
答:
區別
1.HTTPS 需要向機構申請 CA 證書,極少免費。
2.HTTP 屬于明文傳輸,HTTPS基于 SSL 進行加密傳輸。
3.HTTP 端口號為 80,HTTPS 端口號為 443 。
4.HTTPS 是加密傳輸,有身份驗證的環節,更加安全。
安全
SSL(安全套接層) TLS(傳輸層安全)
以上兩者在傳輸層之上,對網絡連接進行加密處理,保障數據的完整性,更加的安全。
復制代碼
15.編程中的六大設計原則?
答:
1.單一職責原則
通俗地講就是一個類只做一件事
CALayer:動畫和視圖的顯示。
UIView:只負責事件傳遞、事件響應。
2.開閉原則
對修改關閉,對擴展開放。 要考慮到后續的擴展性,而不是在原有的基礎上來回修改
3.接口隔離原則
使用多個專門的協議、而不是一個龐大臃腫的協議,如 UITableviewDelegate + UITableViewDataSource
4.依賴倒置原則
抽象不應該依賴于具體實現、具體實現可以依賴于抽象。 調用接口感覺不到內部是如何操作的
5.里氏替換原則
父類可以被子類無縫替換,且原有的功能不受任何影響 如:KVO
6.迪米特法則
一個對象應當對其他對象盡可能少的了解,實現高聚合、低耦合
復制代碼
16.Objective-C與Swift的異同?
答:
1.1、swift和OC的共同點:
- OC出現過的絕大多數概念,比如引用計數、ARC(自動引用計數)、屬性、協議、接口、初始化、擴展類、命名參數、匿名函數等,在Swift中繼續有效(可能最多換個術語)。
- Swift和Objective-C共用一套運行時環境,Swift的類型可以橋接到Objective-C(下面我簡稱OC),反之亦然
1.2、swift的優點:
- swift注重安全,OC注重靈活
- swift注重面向協議編程、函數式編程、面向對象編程,OC注重面向對象編程
- swift注重值類型,OC注重指針和引用
- swift是靜態類型語言,OC是動態類型語言
- swift容易閱讀,文件結構和大部分語法簡易化,只有.swift文件,結尾不需要分號
- swift中的可選類型,是用于所有數據類型,而不僅僅局限于類。相比于OC中的nil更加安全和簡明
- swift中的泛型類型更加方便和通用,而非OC中只能為集合類型添加泛型
- swift中各種方便快捷的高階函數(函數式編程) (Swift的標準數組支持三個高階函數:map,filter和reduce,以及map的擴展flatMap)
- swift新增了兩種權限,細化權限。open > public > internal(默認) > fileprivate > private
- swift中獨有的元組類型(tuples),把多個值組合成復合值。元組內的值可以是任何類型,并不要求是相同類型的。
復制代碼
17.沙盒目錄結構是怎樣的?各自用于那些場景?
答:
Application:存放程序源文件,上架前經過數字簽名,上架后不可修改
Documents:常用目錄,iCloud備份目錄,存放數據
Library
Caches:存放體積大又不需要備份的數據
Preference:設置目錄,iCloud會備份設置信息
tmp:存放臨時文件,不會被備份,而且這個文件下的數據有可能隨時被清除的可能
復制代碼
17.iOS中數據持久化方案有哪些?
答:
NSUserDefault 簡單數據快速讀寫
Property list (屬性列表)文件存儲
Archiver (歸檔)
SQLite 本地數據庫
CoreData(是iOS5之后才出現的一個框架,本質上是對SQLite的一個封裝,它提供了對象-關系映射(ORM)的功能,即能夠將OC對象轉化成數據,保存在SQLite數據庫文件中,也能夠將保存在數據庫中的數據還原成OC對象,通過CoreData管理應用程序的數據模型)
復制代碼
18.單個viewController的生命周期?
答:
- initWithCoder:(NSCoder *)aDecoder:(如果使用storyboard或者xib)
- loadView:加載view
- viewDidLoad:view加載完畢
- viewWillAppear:控制器的view將要顯示
- viewWillLayoutSubviews:控制器的view將要布局子控件
- viewDidLayoutSubviews:控制器的view布局子控件完成
- viewDidAppear:控制器的view完全顯示
- viewWillDisappear:控制器的view即將消失的時候
- viewDidDisappear:控制器的view完全消失的時候
- dealloc 控制器銷毀
復制代碼
19.cocoa 和 cocoa touch是什么?/cocoa touch底層技術架構?
答:
Cocoa包含Foundation和AppKit框架,可用于開發Mac OS X系統的應用程序。
Cocoa Touch包含Foundation和UIKit框架,可用于開發iPhone OS系統的應用程序。
Cocoa是 Mac OS X 的開發環境,Cocoa Touch是 iPhone OS的開發環境。
復制代碼
答:
cocoa touch底層技術架構 主要分為4層:
可觸摸層 Cocoa Touch : UI組件,觸摸事件和事件驅動,系統接口
媒體層 Media: 音視頻播放,動畫,2D和3D圖形
Core Server: 核心服務層,底層特性,文件,網絡,位置服務區等
Core OS: 內存管理,底層網絡,硬盤管理
復制代碼
20. 如何選擇delegate、notification、KVO?
答:
三種模式都是一個對象傳遞事件給另外一個對象,并且不要他們有耦合。
delegate. 一對一
notification 一對多,多對多
KVO 一對一
三者各有自己的特點:
delegate 語法簡潔,方便閱讀,易于調試
notification 靈活多變,可以跨越多個類之間進行使用
KVO 實現屬性監聽,實現model和view同步
可以根據實際開發遇到的場景來使用不同的方式
復制代碼
21.平時開發有沒有玩過 Instrument ?
答:
Instruments里面工具很多,常用的有:
(1).Time Profiler:性能分析,用來檢測應用CPU的使用情況.可以看到應用程序中各個方法正在消耗CPU時間。
(2).Zoombies:檢查是否訪問了僵尸對象,但是這個工具只能從上往下檢查,不智能
(3).Allocations:用來檢查內存,寫算法的那批人也用這個來檢查
(4).Leaks:檢查內存,看是否有內存泄漏
(5).Core Animation:評估圖形性能,這個選項檢查了圖片是否被縮放,以及像素是否對齊。被放縮的圖片會被標記為黃色,像素不對齊則會標注為紫色。黃色、紫色越多,性能越差。
復制代碼
常用的排序算法
答:
選擇排序、冒泡排序、插入排序三種排序算法可以總結為如下:
都將數組分為已排序部分和未排序部分。
選擇排序將已排序部分定義在左端,然后選擇未排序部分的最小元素和未排序部分的第一個元素交換。
冒泡排序將已排序部分定義在右端,在遍歷未排序部分的過程執行交換,將最大元素交換到最右端。
插入排序將已排序部分定義在左端,將未排序部分元的第一個元素插入到已排序部分合適的位置。
/**
* 【選擇排序】:最值出現在起始端
*
* 第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> 必須已知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;
}
復制代碼
SDWebImage加載圖片過程
答:
0、首先顯示占位圖
1、在webimagecache中尋找圖片對應的緩存,它是以url為數據索引先在內存中查找是否有緩存;
2、如果沒有緩存,就通過md5處理過的key來在磁盤中查找對應的數據,如果找到就會把磁盤中的數據加到內存中,并顯示出來;
3、如果內存和磁盤中都沒有找到,就會向遠程服務器發送請求,開始下載圖片;
4、下載完的圖片加入緩存中,并寫入到磁盤中;
5、整個獲取圖片的過程是在子線程中進行,在主線程中顯示。
復制代碼
AFNetworking 底層原理分析
答:
AFNetworking是封裝的NSURLSession的網絡請求,由五個模塊組成:分別由NSURLSession,Security,Reachability,Serialization,UIKit五部分組成
NSURLSession:網絡通信模塊(核心模塊) 對應 AFNetworking中的 AFURLSessionManager和對HTTP協議進行特化處理的AFHTTPSessionManager,AFHTTPSessionManager是繼承于AFURLSessionmanager的
Security:網絡通訊安全策略模塊 對應 AFSecurityPolicy
Reachability:網絡狀態監聽模塊 對應AFNetworkReachabilityManager
Seriaalization:網絡通信信息序列化、反序列化模塊 對應 AFURLResponseSerialization
UIKit:對于iOS UIKit的擴展庫
復制代碼
進階題:
1.KVC的底層實現?
答:
當一個對象調用setValue方法時,方法內部會做以下操作:
1). 檢查是否存在相應的key的set方法,如果存在,就調用set方法。
2). 如果set方法不存在,就會查找與key相同名稱并且帶下劃線的成員變量,如果有,則直接給成員變量屬性賦值。
3). 如果沒有找到_key,就會查找相同名稱的屬性key,如果有就直接賦值。
4). 如果還沒有找到,則調用valueForUndefinedKey:和setValue:forUndefinedKey:方法。
這些方法的默認實現都是拋出異常,我們可以根據需要重寫它們。
復制代碼
2.KVO的底層實現?
答:
KVO-鍵值觀察機制,原理如下:
1.當給A類添加KVO的時候,runtime動態的生成了一個子類NSKVONotifying_A,讓A類的isa指針指向NSKVONotifying_A類,重寫class方法,隱藏對象真實類信息
2.重寫監聽屬性的setter方法,在setter方法內部調用了Foundation 的 _NSSetObjectValueAndNotify 函數
3._NSSetObjectValueAndNotify函數內部
a) 首先會調用 willChangeValueForKey
b) 然后給屬性賦值
c) 最后調用 didChangeValueForKey
d) 最后調用 observer 的 observeValueForKeyPath 去告訴監聽器屬性值發生了改變 .
4.重寫了dealloc做一些 KVO 內存釋放
復制代碼
3.說一下工作中你怎么做性能優化的
答:一般都是說關于tableView的優化處理,
造成tableView卡頓的原因
1.沒有使用cell的重用標識符,導致一直創建新的cell
2.cell的重新布局
3.沒有提前計算并緩存cell的屬性及內容
4.cell中控件的數量過多
5.使用了ClearColor,無背景色,透明度為0
6.更新只使用tableView.reloadData()(如果只是更新某組的話,使用reloadSection進行局部更新)
7.加載網絡數據,下載圖片,沒有使用異步加載,并緩存
8.使用addView 給cell動態添加view
9.沒有按需加載cell(cell滾動很快時,只加載范圍內的cell)
10.實現無用的代理方法(tableView只遵守兩個協議)
11.沒有做緩存行高(estimatedHeightForRow不能和HeightForRow里面的layoutIfNeed同時存在,這兩者同時存在才會出現“竄動”的bug。
建議是:只要是固定行高就寫預估行高來減少行高調用次數提升性能。如果是動態行高就不要寫預估方法了,用一個行高的緩存字典來減少代碼的調用次數即可)
12.做了多余的繪制工作(在實現drawRect:的時候,它的rect參數就是需要繪制的區域,這個區域之外的不需要進行繪制)
13.沒有預渲染圖像。(當新的圖像出現時,仍然會有短暫的停頓現象。解決的辦法就是在bitmap context里先將其畫一遍,導出成UIImage對象,然后再繪制到屏幕)
提升tableView的流暢度
*本質上是降低 CPU、GPU 的工作,從這兩個大的方面去提升性能。
1.CPU:對象的創建和銷毀、對象屬性的調整、布局計算、文本的計算和排版、圖片的格式轉換和解碼、圖像的繪制
2.GPU:紋理的渲染
卡頓優化在 CPU 層面
1.盡量用輕量級的對象,比如用不到事件處理的地方,可以考慮使用 CALayer 取代 UIView
2.不要頻繁地調用 UIView 的相關屬性,比如 frame、bounds、transform 等屬性,盡量減少不必要的修改
3.盡量提前計算好布局,在有需要時一次性調整對應的屬性,不要多次修改屬性
4.Autolayout 會比直接設置 frame 消耗更多的 CPU 資源
5.圖片的 size 最好剛好跟 UIImageView 的 size 保持一致
6.控制一下線程的最大并發數量
7.盡量把耗時的操作放到子線程
8.文本處理(尺寸計算、繪制)
9.圖片處理(解碼、繪制)
卡頓優化在 GPU層面
1.盡量避免短時間內大量圖片的顯示,盡可能將多張圖片合成一張進行顯示
2.GPU能處理的最大紋理尺寸是 4096x4096,一旦超過這個尺寸,就會占用 CPU 資源進行處理,所以紋理盡量不要超過這個尺寸
3.盡量減少視圖數量和層次
4.減少透明的視圖(alpha<1),不透明的就設置 opaque 為 YES
5.盡量避免出現離屏渲染
復制代碼
5.Runtime實現的機制是什么?能做什么事情呢?
答:
runtime簡稱運行時。OC是運行時機制,也就是在運行時才做一些處理。例如:C語言在編譯的時候就知道要調用哪個方法函數,而OC在編譯的時候并不知道要調用哪個方法函數,只有在運行的時候才知道調用的方法函數名稱,來找到對應的方法函數進行調用。
1.發送消息
【場景:方法調用】
2.交換方法實現(交換系統的方法)
【場景:當第三方框架或者系統原生方法功能不能滿足我們的時候,我們可以在保持系統原有方法功能的基礎上,添加額外的功能。】
3.動態添加方法
【場景:如果一個類方法非常多,加載類到內存的時候也比較耗費資源,需要給每個方法生成映射表,可以使用動態給某個類,添加方法解決。】
4.利用關聯對象(AssociatedObject)給分類添加屬性
【
場景:分類是不能自定義屬性和變量的,這時候可以使用runtime動態添加屬性方法;
原理:給一個類聲明屬性,其實本質就是給這個類添加關聯,并不是直接把這個值的內存空間添加到類存空間。
】
5.遍歷類的所有成員變量
【
1.NSCoding自動歸檔解檔
場景:如果一個模型有許多個屬性,實現自定義模型數據持久化時,需要對每個屬性都實現一遍encodeObject 和 decodeObjectForKey方法,比較麻煩。我們可以使用Runtime來解決。
原理:用runtime提供的函數遍歷Model自身所有屬性,并對屬性進行encode和decode操作。
2.字典轉模型
原理:利用Runtime,遍歷模型中所有屬性,根據模型的屬性名,去字典中查找key,取出對應的值,給模型的屬性賦值。
3.修改textfield的占位文字顏色
】
6.利用消息轉發機制解決方法找不到的異常問題
復制代碼
6.iOS圖片設置圓角性能問題
答:
1.直接使用setCornerRadius
【這樣設置會觸發離屏渲染,比較消耗性能。比如當一個頁面上有十幾頭像這樣設置了圓角會明顯感覺到卡頓。
注意:png圖片UIImageView處理圓角是不會產生離屏渲染的。(ios9.0之后不會離屏渲染,ios9.0之前還是會離屏渲染)
】
2.setCornerRadius設置圓角之后,shouldRasterize=YES光柵化
【avatarImageView.layer.shouldRasterize = YES;
avatarImageViewUrl.layer.rasterizationScale=[UIScreen mainScreen].scale; //UIImageView不加這句會產生一點模糊
shouldRasterize=YES設置光柵化,可以使離屏渲染的結果緩存到內存中存為位圖,
使用的時候直接使用緩存,節省了一直離屏渲染損耗的性能。
但是如果layer及sublayers常常改變的話,它就會一直不停的渲染及刪除緩存重新
創建緩存,所以這種情況下建議不要使用光柵化,這樣也是比較損耗性能的。
】
3.直接覆蓋一張中間為圓形透明的圖片(推薦使用)
4.UIImage drawInRect繪制圓角
【這種方式GPU損耗低內存占用大,而且UIButton上不知道怎么繪制,可以用
UIimageView添加個點擊手勢當做UIButton使用。】
5.SDWebImage處理圖片時Core Graphics繪制圓角(暫時感覺是最優方法)
復制代碼
7.什么是 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]));
}
}
復制代碼
答:
RunLoop,是多線程的法寶,即一個線程一次只能執行一個任務,執行完任務后就會退出線程。主線程執行完即時任務時會繼續等待接收事件而不退出。非主線程通常來說就是為了執行某一任務的,執行完畢就需要歸還資源,因此默認是不運行RunLoop的;
每一個線程都有其對應的RunLoop,只是默認只有主線程的RunLoop是啟動的,其它子線程的RunLoop默認是不啟動的,若要啟動則需要手動啟動;
在一個單獨的線程中,如果需要在處理完某個任務后不退出,繼續等待接收事件,則需要啟用RunLoop;
NSRunLoop提供了一個添加NSTimer的方法,可以指定Mode,如果要讓任何情況下都回調,則需要設置Mode為Common模式;
實質上,對于子線程的runloop默認是不存在的,因為蘋果采用了懶加載的方式。如果我們沒有手動調用[NSRunLoop currentRunLoop]的話,就不會去查詢是否存在當前線程的RunLoop,也就不會去加載,更不會創建。
復制代碼
8.以scheduledTimerWithTimeInterval的方式觸發的timer,在滑動頁面上的列表時,timer會暫停,為什么?該如何解決?
答:
原因在于滑動時當前線程的runloop切換了mode用于列表滑動,導致timer暫停。
runloop中的mode主要用來指定事件在runloop中的優先級,有以下幾種:
* Default(NSDefaultRunLoopMode):默認,一般情況下使用;
* Connection(NSConnectionReplyMode):一般系統用來處理NSConnection相關事件,開發者一般用不到;
* Modal(NSModalPanelRunLoopMode):處理modal panels事件;
* Event Tracking(NSEventTrackingRunLoopMode):用于處理拖拽和用戶交互的模式。
* Common(NSRunloopCommonModes):模式合集。默認包括Default,Modal,Event Tracking三大模式,可以處理幾乎所有事件。
回到題中的情境。滑動列表時,runloop的mode由原來的Default模式切換到了Event Tracking模式,timer原來好好的運行在Default模式中,被關閉后自然就停止工作了。
解決方法其一是將timer加入到NSRunloopCommonModes中。其二是將timer放到另一個線程中,然后開啟另一個線程的runloop,這樣可以保證與主線程互不干擾,而現在主線程正在處理頁面滑動。
復制代碼
方法1
[[NSRunLoop currentRunLoop] addTimer:timer forMode:NSRunLoopCommonModes];
方法2
dispatch_async(dispatch_get_global_queue(0, 0), ^{
timer = [NSTimer scheduledTimerWithTimeInterval:1 target:self selector:@selector(repeat:) userInfo:nil repeats:true];
[[NSRunLoop currentRunLoop] run];
});
復制代碼
9.進程與線程
答:
進程:
1.進程是一個具有一定獨立功能的程序關于某次數據集合的一次運行活動,它是操作系統分配資源的基本單元.
2.進程是指在系統中正在運行的一個應用程序,就是一段程序的執行過程,我們可以理解為手機上的一個app.
3.每個進程之間是獨立的,每個進程均運行在其專用且受保護的內存空間內,擁有獨立運行所需的全部資源
線程
1.程序執行流的最小單元,線程是進程中的一個實體.
2.一個進程要想執行任務,必須至少有一條線程.應用程序啟動的時候,系統會默認開啟一條線程,也就是主線程
進程和線程的關系
1.線程是進程的執行單元,進程的所有任務都在線程中執行
2.線程是 CPU 分配資源和調度的最小單位
3.一個程序可以對應多個進程(多進程),一個進程中可有多個線程,但至少要有一條線程
4.同一個進程內的線程共享進程資源
復制代碼
10.iOS中實現多線程的幾種方案,各自有什么特點?講一下具體使用場景/在項目什么時候選擇使用 GCD,什么時候選 擇 NSOperation?
答:
NSThread 面向對象的,需要程序員手動創建線程,但不需要手動銷毀。子線程間通信很難。
GCD c語言,充分利用了設備的多核,自動管理線程生命周期。比NSOperation效率更高。
NSOperation 基于gcd封裝,更加面向對象,比gcd多了一些功能。
【場景:1.多個網絡請求完成后執行下一步 2.多個網絡請求順序執行后執行下一步 3.異步操作兩組數據時, 執行完第一組之后, 才能執行第二組】
復制代碼
答:
項目中使用 NSOperation 的優點是 NSOperation 是對線程的高度抽象,在項目中使 用它,會使項目的程序結構更好,子類化 NSOperation 的設計思路,是具有面向對 象的優點(復用、封裝),使得實現是多線程支持,而接口簡單,建議在復雜項目中 使用。
項目中使用 GCD 的優點是 GCD 本身非常簡單、易用,對于不復雜的多線程操 作,會節省代碼量,而 Block 參數的使用,會是代碼更為易讀,建議在簡單項目中 使用。
復制代碼
11.什么是GCD?GCD 的隊列類型?
答:
GCD(Grand Central Dispatch), 又叫做大中央調度, 它對線程操作進行了封裝,加入了很多新的特性,內部進行了效率優化,提供了簡潔的C語言接口, 使用更加高效,也是蘋果推薦的使用方式.
GCD的隊列可以分為2大類型
1.并發隊列(Concurrent Dispatch Queue)
可以讓多個任務并發(同時)執行(自動開啟多個線程同時執行任務)
并發功能只有在異步(dispatch_async)函數下才有效
2.串行隊列(Serial Dispatch Queue)
讓任務一個接著一個地執行(一個任務執行完畢后,再執行下一個任務),按照FIFO順序執行.
復制代碼
12.什么是同步和異步任務派發(synchronous和asynchronous)?
答:
GCD多線程經常會使用 dispatch_sync和dispatch_async函數向指定隊列添加任務,分別是同步和異步
同步:指阻塞當前線程,既要等待添加的耗時任務塊Block完成后,函數才能返回,后面的代碼才能繼續執行
異步:指將任務添加到隊列后,函數立即返回,后面的代碼不用等待添加的任務完成后即可執行,異步提交無法確定任務執行順序
復制代碼
13.dispatch_barrier_(a)sync使用?
答:
柵欄函數
一個dispatch barrier 允許在一個并發隊列中創建一個同步點。當在并發隊列中遇到一個barrier, 他會延遲執行barrier的block,等待所有在barrier之前提交的blocks執行結束。 這時,barrier block自己開始執行。 之后, 隊列繼續正常的執行操作。
復制代碼
14.對稱加密和非對稱加密的區別?
答:
1、對稱加密又稱公開密鑰加密,加密和解密都會用到同一個密鑰,如果密鑰被攻擊者獲得,此時加密就失去了意義。常見的對稱加密算法有DES、3DES、AES、Blowfish、IDEA、RC5、RC6。
2、非對稱加密又稱共享密鑰加密,使用一對非對稱的密鑰,一把叫做私有密鑰,另一把叫做公有密鑰;公鑰加密只能用私鑰來解密,私鑰加密只能用公鑰來解密。常見的公鑰加密算法有:RSA、ElGamal、背包算法、Rabin(RSA的特例)、迪菲-赫爾曼密鑰交換協議中的公鑰加密算法、橢圓曲線加密算法)。
復制代碼
15.組件化有什么好處?
答:
業務分層、解耦,使代碼變得可維護;
有效的拆分、組織日益龐大的工程代碼,使工程目錄變得可維護;
便于各業務功能拆分、抽離,實現真正的功能復用;
業務隔離,跨團隊開發代碼控制和版本風險控制的實現;
模塊化對代碼的封裝性、合理性都有一定的要求,提升開發同學的設計能力;
在維護好各級組件的情況下,隨意組合滿足不同客戶需求;(只需要將之前的多個業務組件模塊在新的主App中進行組裝即可快速迭代出下一個全新App)
復制代碼
16.你是如何組件化解耦的?
答:
分層
基礎功能組件:按功能分庫,不涉及產品業務需求,跟庫Library類似,通過良好的接口拱上層業務組件調用;不寫入產品定制邏輯,通過擴展接口完成定制;
基礎UI組件:各個業務模塊依賴使用,但需要保持好定制擴展的設計
業務組件:業務功能間相對獨立,相互間沒有Model共享的依賴;業務之間的頁面調用只能通過UIBus進行跳轉;業務之間的邏輯Action調用只能通過服務提供;
中間件:target-action,url-block,protocol-class
復制代碼
17.APP啟動時間應從哪些方面優化?
答:
App啟動時間可以通過xcode提供的工具來度量,在Xcode的Product->Scheme-->Edit Scheme->Run->Auguments中,將環境變量DYLD_PRINT_STATISTICS設為YES,優化需以下方面入手
dylib loading time
核心思想是減少dylibs的引用
合并現有的dylibs(最好是6個以內)
使用靜態庫
rebase/binding time
核心思想是減少DATA塊內的指針
減少Object C元數據量,減少Objc類數量,減少實例變量和函數(與面向對象設計思想沖突)
減少c++虛函數
多使用Swift結構體(推薦使用swift)
ObjC setup time
核心思想同上,這部分內容基本上在上一階段優化過后就不會太過耗時
initializer time
使用initialize替代load方法
減少使用c/c++的attribute((constructor));推薦使用dispatch_once() pthread_once() std:once()等方法
推薦使用swift
不要在初始化中調用dlopen()方法,因為加載過程是單線程,無鎖,如果調用dlopen則會變成多線程,會開啟鎖的消耗,同時有可能死鎖
不要在初始化中創建線程
復制代碼
iOS App 啟動過程(二):從 exec() 到 main()
Swift系列面試題總結
基礎題:
1.類(class) 和 結構體(struct) 有什么區別? 類(class) 和 結構體(struct) 比較,優缺點?
答:
struct是值類型,class是引用類型。
值類型的變量直接包含它們的數據,對于值類型都有它們自己的數據副本,因此對一個變量操作不可能影響另一個變量。
引用類型的變量存儲對他們的數據引用,因此后者稱為對象,因此對一個變量操作可能影響另一個變量所引用的對象。
二者的本質區別:struct是深拷貝,拷貝的是內容;class是淺拷貝,拷貝的是指針。
property的初始化不同:class 在初始化時不能直接把 property 放在 默認的constructor 的參數里,而是需要自己創建一個帶參數的constructor;而struct可以,把屬性放在默認的constructor 的參數里。
變量賦值方式不同:struct是值拷貝;class是引用拷貝。
immutable變量:swift的可變內容和不可變內容用var和let來甄別,如果初始為let的變量再去修改會發生編譯錯誤。struct遵循這一特性;class不存在這樣的問題。
mutating function: struct 和 class 的差別是 struct 的 function 要去改變 property 的值的時候要加上 mutating,而 class 不用。
繼承: struct不可以繼承,class可以繼承。
struct比class更輕量:struct分配在棧中,class分配在堆中。
復制代碼
答:
class 有以下功能,struct 是沒有的:
class可以繼承,子類可以使用父類的特性和方法
類型轉換可以在運行時檢查和解釋一個實例對象
class可以用 deinit來釋放資源
一個類可以被多次引用
struct 優勢:
結構較小,適用于復制操作,相比較一個class 實例被多次引用,struct 更安全
無需擔心內存泄露問題
復制代碼
2.Swift 是面向對象還是函數式的編程語言?
答:
Swift 既是面向對象的,又是函數式的編程語言。
說 Swift 是面向對象的語言,是因為 Swift 支持類的封裝、繼承、和多態,從這點上來看與 Java 這類純面向對象的語言幾乎毫無差別。
說 Swift 是函數式編程語言,是因為 Swift 支持 map, reduce, filter, flatmap 這類去除中間狀態、數學函數式的方法,更加強調運算結果而不是中間過程。
復制代碼
3.什么是泛型,swift哪些地方使用了泛型?
答:
泛型(generic)可以使我們在程序代碼中定義一些可變的部分,在運行的時候指定。使用泛型可以最大限度地重用代碼、保護類型的安全以及提高性能。
泛型可以將類型參數化,提高代碼復用率,減少代碼量。
例如 optional 中的 map、flatMap 、?? (泛型加逃逸閉包的方式,做三目運算)
復制代碼
4.swift 語法糖 ? !的本質(實現原理)
答:
?為optional的語法糖
optional<T> 是一個包含了nil 和普通類型的枚舉,確保使用者在變量為nil的情況下處理
!為optional 強制解包的語法糖
復制代碼
5.什么是可選型(Optional),Optional(可選型) 是用什么實現的
答:
1.在 Swift 中,可選型是為了表達一個變量為空的情況,當一個變量為空,他的值就是 nil
在類型名稱后面加個問號? 來定義一個可選型
值類型或者引用類型都可以是可選型變量
2.Optional 是一個泛型枚舉
大致定義如下:
enum Optional<Wrapped> {
case none
case some(Wrapped)
}
除了使用 let someValue: Int? = nil 之外, 還可以使用let optional1: Optional<Int> = nil 來定義
復制代碼
6.什么是高階函數
答:
一個函數如果可以以某一個函數作為參數, 或者是返回值, 那么這個函數就稱之為高階函數, 如 map, reduce, filter
復制代碼
7.如何解決引用循環
答:
轉換為值類型, 只有類會存在引用循環, 所以如果能不用類, 是可以解引用循環的,
delegate 使用 weak 屬性.
閉包中, 對有可能發生循環引用的對象, 使用 weak 或者 unowned, 修飾
復制代碼
8.定義靜態方法時關鍵字 static 和 class 有什么區別
答:
static 定義的方法不可以被子類繼承, class 則可以
class AnotherClass {
static func staticMethod(){}
class func classMethod(){}
}
class ChildOfAnotherClass: AnotherClass {
override class func classMethod(){}
//override static func staticMethod(){}// error
}
復制代碼
9.請說明并比較以下關鍵詞:Open, Public, Internal, File-private, Private
答:
Swift 有五個級別的訪問控制權限,從高到底依次為比如 Open, Public, Internal, File-private, Private。
他們遵循的基本原則是:高級別的變量不允許被定義為低級別變量的成員變量。比如一個 private 的 class 中不能含有 public 的 String。反之,低級別的變量卻可以定義在高級別的變量中。比如 public 的 class 中可以含有 private 的 Int。
Open 具備最高的訪問權限。其修飾的類和方法可以在任意 Module 中被訪問和重寫;它是 Swift 3 中新添加的訪問權限。
Public 的權限僅次于 Open。與 Open 唯一的區別在于它修飾的對象可以在任意 Module 中被訪問,但不能重寫。
Internal 是默認的權限。它表示只能在當前定義的 Module 中訪問和重寫,它可以被一個 Module 中的多個文件訪問,但不可以被其他的 Module 中被訪問。
File-private 也是 Swift 3 新添加的權限。其被修飾的對象只能在當前文件中被使用。例如它可以被一個文件中的 class,extension,struct 共同使用。
Private 是最低的訪問權限。它的對象只能在定義的作用域內使用。離開了這個作用域,即使是同一個文件中的其他作用域,也無法訪問。
復制代碼
10.swift中,關鍵字 guard 和 defer 的用法 guard也是基于一個表達式的布爾值去判斷一段代碼是否該被執行。與if語句不同的是,guard只有在條件不滿足的時候才會執行這段代碼。
答:
guard let name = self.text else { return }
復制代碼
defer的用法是,這條語句并不會馬上執行,而是被推入棧中,直到函數結束時才再次被調用。
defer {
//函數結束才調用
}
復制代碼
11.關鍵字:Strong,Weak,Unowned 區別?
答:
Swift 的內存管理機制同OC一致,都是ARC管理機制; Strong,和 Weak用法同OC一樣
Unowned(無主引用), 不會產生強引用,實例銷毀后仍然存儲著實例的內存地址(類似于OC中的unsafe_unretained), 試圖在實例銷毀后訪問無主引用,會產生運行時錯誤(野指針)
復制代碼
12.如何理解copy-on-write?
答:
值類型(比如:struct),在復制時,復制對象與原對象實際上在內存中指向同一個對象,當且僅當修改復制的對象時,才會在內存中創建一個新的對象,
為了提升性能,Struct, String、Array、Dictionary、Set采取了Copy On Write的技術
比如僅當有“寫”操作時,才會真正執行拷貝操作
對于標準庫值類型的賦值操作,Swift 能確保最佳性能,所有沒必要為了保證最佳性能來避免賦值
復制代碼
13.什么是屬性觀察?
答:
屬性觀察是指在當前類型內對特性屬性進行監測,并作出響應,屬性觀察是 swift 中的特性,具有2種, willset 和 didset
var title: String {
willSet {
print("willSet", newValue)
}
didSet {
print("didSet", oldValue, title)
}
}
willSet會傳遞新值,默認叫newValue
didSet會傳遞舊值,默認叫oldValue
在初始化器中設置屬性值不會觸發willSet和didSet
復制代碼
14.swift 為什么將 String,Array,Dictionary設計為值類型?
答:
值類型和引用類型相比,最大優勢可以高效的使用內存,值類型在棧上操作,引用類型在堆上操作,棧上操作僅僅是單個指針的移動,而堆上操作牽涉到合并,位移,重鏈接,Swift 這樣設計減少了堆上內存分配和回收次數,使用 copy-on-write將值傳遞與復制開銷降到最低
復制代碼
15..如何將Swift 中的協議(protocol)中的部分方法設計為可選(optional)?
答:
1.在協議和方法前面添加 @objc,然后在方法前面添加 optional關鍵字,改方式實際上是將協議轉為了OC的方式
@objc protocol someProtocol {
@objc optional func test()
}
2.使用擴展(extension),來規定可選方法,在 swift 中,協議擴展可以定義部分方法的默認實現
protocol someProtocol {
func test()
}
extension someProtocol{
func test() {
print("test")
}
}
復制代碼
16.比較Swift 和OC中的初始化方法 (init) 有什么不同?
答:
swift 的初始化方法,更加嚴格和準確, swift初始化方法需要保證所有的非optional的成員變量都完成初始化, 同時 swfit 新增了convenience和 required兩個修飾初始化器的關鍵字
convenience只提供一種方便的初始化器,必須通過一個指定初始化器來完成初始化
required是強制子類重寫父類中所修飾的初始化方法
復制代碼
17.比較 Swift和OC中的 protocol 有什么不同?
答:
Swift 和OC中的 protocol相同點在于: 兩者都可以被用作代理;
不同點: Swift中的 protocol還可以對接口進行抽象,可以實現面向協議,從而大大提高編程效率,Swift中的protocol可以用于值類型,結構體,枚舉;
復制代碼
18.swift 和OC 中的自省 有什么區別?
答:
自省在OC中就是判斷某一對象是否屬于某一個類的操作,有以下2中方式
[obj iskinOfClass:[SomeClass class]]
[obj isMemberOfClass:[SomeClass class]]
在 Swift 中由于很多 class 并非繼承自 NSObject, 故而 Swift 使用 is 來判斷是否屬于某一類型, is 不僅可以作用于class, 還是作用于enum和struct
復制代碼
19.什么是函數重載? swift 支不支持函數重載?
答:
函數重載是指: 函數名稱相同,函數的參數個數不同, 或者參數類型不同,或參數標簽不同, 返回值類型與函數重載無關
swift 支持函數重載
復制代碼
20.swift 中的枚舉,關聯值 和 原始值的區分?
答:
1.關聯值--有時會將枚舉的成員值跟其他類型的變量關聯存儲在一起,會非常有用
// 關聯值
enum Date {
case digit(year: Int, month: Int, day: Int)
case string(String)
}
2.原始值--枚舉成員可以使用相同類型的默認值預先關聯,這個默認值叫做:原始值
// 原始值
enum Grade: String {
case perfect = "A"
case great = "B"
case good = "C"
case bad = "D"
}
復制代碼
21.swift 中的閉包結構是什么樣子的?什么是尾隨閉包?什么是逃逸閉包?什么是自動閉包?
答:
1.
{
(參數列表) -> 返回值類型 in 函數體代碼
}
復制代碼
答:
2.將一個很長的閉包表達式作為函數的最后一個實參
使用尾隨閉包可以增強函數的可讀性
尾隨閉包是一個被書寫在函數調用括號外面(后面)的閉包表達式
// fn 就是一個尾隨閉包參數
func exec(v1: Int, v2: Int, fn: (Int, Int) -> Int) {
print(fn(v1, v2))
}
// 調用
exec(v1: 10, v2: 20) {
$0 + $1
}
復制代碼
答:
3.當閉包作為一個實際參數傳遞給一個函數或者變量的時候,我們就說這個閉包逃逸了,可以在形式參數前寫 @escaping 來明確閉包是允許逃逸的。
非逃逸閉包、逃逸閉包,一般都是當做參數傳遞給函數
非逃逸閉包:閉包調用發生在函數結束前,閉包調用在函數作用域內
逃逸閉包:閉包有可能在函數結束后調用,閉包調用逃離了函數的作用域,需要通過@escaping聲明
// 定義一個數組用于存儲閉包類型
var completionHandlers: [() -> Void] = []
// 在方法中將閉包當做實際參數,存儲到外部變量中
func someFunctionWithEscapingClosure(completionHandler: @escaping () -> Void) {
completionHandlers.append(completionHandler)
}
復制代碼
答:
4.自動閉包是一種自動創建的用來把作為實際參數傳遞給函數的表達式打包的閉包。它不接受任何實際參數,并且當它被調用時,它會返回內部打包的表達式的值。這個語法的好處在于通過寫普通表達式代替顯式閉包而使你省略包圍函數形式參數的括號。
func getFirstPositive(_ v1: Int, _ v2: @autoclosure () -> Int) -> Int? {
return v1 > 0 ? v1 : v2()
}
getFirstPositive(10, 20)
為了避免與期望沖突,使用了@autoclosure的地方最好明確注釋清楚:這個值會被推遲執行
@autoclosure 會自動將 20 封裝成閉包 { 20 }
@autoclosure 只支持 () -> T 格式的參數
@autoclosure 并非只支持最后1個參數
有@autoclosure、無@autoclosure,構成了函數重載
如果你想要自動閉包允許逃逸,就同時使用 @autoclosure 和 @escaping 標志。
復制代碼
22. swift中, 存儲屬性和計算屬性的區別?
答:
Swift中跟實例對象相關的屬性可以分為2大類
存儲屬性(Stored Property)
類似于成員變量這個概念
存儲在實例對象的內存中
結構體、類可以定義存儲屬性
枚舉不可以定義存儲屬性
計算屬性(Computed Property)
本質就是方法(函數)
不占用實例對象的內存
枚舉、結構體、類都可以定義計算屬性
struct Circle {
// 存儲屬性
var radius: Double
// 計算屬性
var diameter: Double {
set {
radius = newValue / 2
}
get {
return radius * 2
}
}
}
復制代碼
23.什么是延遲存儲屬性(Lazy Stored Property)?
答:
使用lazy可以定義一個延遲存儲屬性,在第一次用到屬性的時候才會進行初始化(類似OC中的懶加載)
lazy屬性必須是var,不能是let
let必須在實例對象的初始化方法完成之前就擁有值
如果多條線程同時第一次訪問lazy屬性
無法保證屬性只被初始化1次
class PhotoView {
// 延遲存儲屬性
lazy var image: Image = {
let url = "https://...x.png"
let data = Data(url: url)
return Image(data: data)
}()
}
復制代碼
24.swift 中如何使用單例模式?
答:
可以通過類型屬性+let+private 來寫單例; 代碼如下如下:
public class FileManager {
public static let shared = {
// ....
// ....
return FileManager()
}()
private init() { }
}
復制代碼
25.swift 中的下標是什么?
答:
使用subscript可以給任意類型(枚舉、結構體、類)增加下標功能,有些地方也翻譯為:下標腳本
subscript的語法類似于實例方法、計算屬性,本質就是方法(函數)
使用如下:
class Point {
var x = 0.0, y = 0.0
subscript(index: Int) -> Double {
set {
if index == 0 {
x = newValue
} else if index == 1 {
y = newValue }
}
get {
if index == 0 {
return x
} else if index == 1 {
return y
}
return 0
}
}
}
var p = Point()
// 下標賦值
p[0] = 11.1
p[1] = 22.2
// 下標訪問
print(p.x) // 11.1
print(p.y) // 22.2
復制代碼
26.簡要說明Swift中的初始化器?
答:
類、結構體、枚舉都可以定義初始化器
類有2種初始化器: 指定初始化器(designated initializer)、便捷初始化器(convenience initializer)
// 指定初始化器
init(parameters) {
statements
}
// 便捷初始化器
convenience init(parameters) {
statements
}
規則:
每個類至少有一個指定初始化器,指定初始化器是類的主要初始化器
默認初始化器總是類的指定初始化器
類偏向于少量指定初始化器,一個類通常只有一個指定初始化器
初始化器的相互調用規則
指定初始化器必須從它的直系父類調用指定初始化器
便捷初始化器必須從相同的類里調用另一個初始化器
便捷初始化器最終必須調用一個指定初始化器
復制代碼
27.什么可選鏈?
答:
可選鏈是一個調用和查詢可選屬性、方法和下標的過程,它可能為 nil 。如果可選項包含值,屬性、方法或者下標的調用成功;如果可選項是 nil ,屬性、方法或者下標的調用會返回 nil 。多個查詢可以鏈接在一起,如果鏈中任何一個節點是 nil ,那么整個鏈就會得體地失敗。
多個?可以鏈接在一起
如果鏈中任何一個節點是nil,那么整個鏈就會調用失敗
復制代碼
28.什么是運算符重載(Operator Overload)?
答:
類、結構體、枚舉可以為現有的運算符提供自定義的實現,這個操作叫做:運算符重載
struct Point {
var x: Int
var y: Int
// 重載運算符
static func + (p1: Point, p2: Point) -> Point {
return Point(x: p1.x + p2.x, y: p1.y + p2.y)
}
}
var p1 = Point(x: 10, y: 10)
var p2 = Point(x: 20, y: 20)
var p3 = p1 + p2
復制代碼
參考資料
總結
目前面試行情還行,要求底層這塊較多,其實有時候面試問的問題可能我們對于底層知識了解的很少,但是代碼當中卻用到很多,我就是這一類的典型,面試的時候很是尷尬啊~,也怪自己平時都是已項目為主導,很多都是會用,卻缺少一定的自我總結,導致面試的時候說不出什么來。