iOS進階面試題
之前看了這邊文章面試經歷 自己整理的面試答案
1、說一下OC的反射機制
在動態執行下我們可以構建任何一個類,然後我們通過這個類知道這個類的所有的屬性和方法,並且如果我們建立一個物件,我們也可以通過物件找到這個類的任意一個方法,這就是反射機制。
比如NSClassFormString,NSStringFormSelector,NSSelectorFormString
ofollow,noindex">參考連結
2、block的本質是什麼?有幾種block?分別是怎樣產生的?
block與函式類似,只不過是直接定義在另一個函式裡,和定義它的那個函式共享同一個範圍內的東西,
block的強大之處是:在宣告它的範圍裡,所有變數都可以為其捕獲,這也就是說,那個範圍內的全部變數,在block依然可以用,預設情況下,為block捕獲的變數,是不可以在block裡修改的,不過宣告的時候可以加上__block修飾符,這樣就可以再block內修改了。
block本身和其他物件一樣,有引用計數,當最後一個指向block的引用移走之後,block就回收了,回收時也釋放block所捕獲的變數。
Block的實現是通過結構體的方式實現,在編譯的過程中,將Block生成對應的結構體,在結構體中記錄Block的匿名函式,以及使用到的自動變數,在最後的使用中,通過Block結構體例項訪問成員中存放的匿名函式地址呼叫匿名函式,並將自身作為引數傳遞。
block其實就是C語言的擴充功能,實現了對C的閉包實現,一個帶有區域性變數的匿名函式,
block的本質也是一個OC物件,它內部也有一個isa指標,block是封裝了函式呼叫以及函式呼叫環境的OC物件,為了保證block內部能夠正常訪問外部的變數,block有一個變數捕獲機制。static 修飾的變數為指標傳遞,同樣會被block捕獲。區域性變數因為跨函式訪問所以需要捕獲,全域性變數在哪裡都可以訪問 ,所以不用捕獲。
當block內部訪問了物件型別的auto變數時,如果block在棧上,block內部不會對變數產生強應用,不論block的結構體內部的變數時__strong修飾還是__weak修飾,都不會對變數產生強引用
預設情況下block不能修改外部的區域性變數
1.static修飾
static修飾的age變數傳遞到block內部的是指標,在__main_block_func_0函式內部就可以拿到age變數的記憶體地址,因此就可以在block內部修改age的值。
有三種類型
__NSGlobalBlock__ ( _NSConcreteGlobalBlock ) __NSStackBlock__ ( _NSConcreteStackBlock ) __NSMallocBlock__ ( _NSConcreteMallocBlock )
__block記憶體管理
當block記憶體在棧上時,並不會對__block變數產生記憶體管理,當block被copy到堆上時會呼叫block內部的copy函式,copy函式內部會滴啊用_Block_object_assign函式,_Block_object_assign函式會對__block變數形成強引用(相當於retain)。
當block被copy到堆上時,block內部引用的__block變數也會被複制到堆上,並且持有變數,如果block複製到堆上的同時,__block變數已經存在堆上了,則不會複製。
當block從堆中移除的話,就會呼叫dispose函式,也就是__main_block_dispose_0函式,__main_block_dispose_0函式內部會呼叫_Block_object_dispose函式,會自動釋放引用的__block變數。
解決迴圈引用問題
使用__weak和__unsafe_unretained修飾符合一解決迴圈引用的問題,__weak會使block內部將指標變為弱指標。
__weak 和 __unsafe_unretained的區別。
__weak不會產生強引用,指向的物件銷燬時,會自動將指標置為nil
__unsafe_unretained不會產生強引用,不安全,指向的物件銷燬時,指標儲存的地址值不變
__strong 和 __weak
在block內部重新使用__strong修飾self變數是為了在block內部有一個強指標指向weakSelf避免在block呼叫的時候weakSelf已經被銷燬。
2.__block修飾的變數為什麼能在block裡面能改變其值?
__block用於解決block內部不能修改auto變數值的問題,__block不能修飾靜態變數和全域性變數
_block 所起到的作用就是隻要觀察到該變數被 block 所持有,就將“外部變數”在棧中的記憶體地址放到了堆中。進而在block內部也可以修改外部變數的值。
3.NSDictionary使用原理
NSDictionary是使用hash表來實現key和vaLue之間的對映和儲存的
hash原理
hash概念:雜湊表的本質是一個數組,陣列中沒一個元素稱為一個箱子,箱子中存放的是鍵值對。
雜湊表的儲存過程:
1.根據key計算出它的雜湊值h
2.假設箱子的個數為n,那麼這個鍵值對應應該在第(h % n)個箱子中。
3.如果該箱子中已經有了鍵值對,就使用開放定址法或者拉鍊法解決衝突。
在使用拉鍊法解決雜湊衝突時,每個箱子其實是一個連結串列,屬於同一個箱子的所有鍵值對都會排列在連結串列中。
雜湊表還有一個重要的屬性:負載因子(load factor),它用來衡量雜湊表的空/滿程度,一定程度上也可以體現查詢的效率,計算公式為:
4.NSCache優於NSDictionary的幾點?
NSCache是一個容器,類似於NSDictionary,通過key-value形式儲存和查詢值,用於臨時儲存物件。
NSCache勝過NSDictionary之處在於,當系統資源將要耗盡時,它可以自動刪減快取。如果採用普通的字典,那麼就要自己編寫掛鉤,在系統發出“低記憶體”通知時手工刪減快取。
NSCache並不會“拷貝”鍵,而是會“保留”它。此行為用NSDictionary也可以實現,然而需要編寫相當複雜的程式碼。NSCache物件不拷貝鍵的原因在於:很多時候,鍵都是不支援拷貝操作的物件來充當的。因此,NSCache不會自動拷貝鍵,所以說,在鍵不支援拷貝操作的情況下,該類用起來比字典更方便。另外,NSCache是執行緒安全的,而NSDictionary則絕對不具備此優勢。
5.屬性
屬性是OC的一項特性,用於封裝物件的資料,OC物件通常會把其所需要的資料儲存為各種例項物件,例項物件一般通過存取方法來訪問,其中獲取方法用於讀取變數值,而設定方法用於寫入變數值,開發者可以令編譯器自動編寫與屬性相關的存取方法。
6.理解objc_msgSend的作用
objc_msgSend叫做訊息傳遞,訊息有名稱或選擇子,可以接受引數
Runtime時執行的流程是這樣的:
一個物件的方法像這樣[obj foo],編譯器轉成訊息傳送objc_msgSend(obj, foo),Runtime時執行的流程是這樣的:
首先,通過obj的isa指標找到它的 class ;
在 class 的 method list 找 foo ;
如果 class 中沒到 foo,繼續往它的 superclass 中找 ;
一旦找到 foo 這個函式,就去執行它的實現IMP 。
7.什麼是指標常量和常量指標
指標常量:(指標變數前加const) int *const p;指標本身是一個常量。在宣告的時候初始化,裡面的值(存放的地址)不能更改。
常量指標:(在型別前加const) const int *p;指標本身是一個變數,初始化是最好給一個常量的地址,它裡面值(存放的地址)可以改變。
8.若你去設計一個通知中心,你會怎樣設計?
NSNotification:這是一個包裝通知資訊的類,類似一個model,儲存了notificationName,object,userInfo等資訊.
NSNotificationCenter:顧名思義~通知中心,就是用來管理通知的接收和傳送的類.
1.定義一個類TestNotification,用來儲存notificationName,object,userInfo等資訊.這裡我們仿照系統的API進行設計.這裡另外加了兩個引數observer和selector.
2.設計通知中心類TestNotificationCenter,同樣仿照系統的API進行設計.
9. KVO、KVC的實現原理
KVO是基於runtime機制實現的
當某個類的屬性物件第一次被觀察時,系統就會在執行期動態地建立該類的一個派生類,在這個派生類中重寫基類中任何被觀察屬性的setter 方法。派生類在被重寫的setter方法內實現真正的通知機制
如果原類為Person,那麼生成的派生類名為NSKVONotifying_Person
每個類物件中都有一個isa指標指向當前類,當一個類物件的第一次被觀察,那麼系統會偷偷將isa指標指向動態生成的派生類,從而在給被監控屬性賦值時執行的是派生類的setter方法
鍵值觀察通知依賴於NSObject 的兩個方法: willChangeValueForKey: 和 didChangevlueForKey:;在一個被觀察屬性發生改變之前, willChangeValueForKey:一定會被呼叫,這就 會記錄舊的值。而當改變發生後,didChangeValueForKey:會被呼叫,繼而 observeValueForKey:ofObject:change:context: 也會被呼叫。
補充:KVO的這套實現機制中蘋果還偷偷重寫了class方法,讓我們誤認為還是使用的當前類,從而達到隱藏生成的派生類
KVC底層實現原理(如下)
KVC運用了一個isa-swizzling技術. isa-swizzling就是型別混合指標機制, 將2個物件的isa指標互相調換, 就是俗稱的黑魔法.
KVC主要通過isa-swizzling, 來實現其內部查詢定位的. 預設的實現方法�由NSOject提供isa指標, 如其名稱所指,(就是is a kind of的意思), 指向分發表物件的類. 該分發表實際上包含了指向實現類中的方法的指標, 和其它資料。
首先搜尋setKey:方法.(key指成員變數名, 首字母大寫)
2、上面的setter方法沒找到, 如果類方法accessInstanceVariablesDirectly返回YES. 那麼按 _key, _isKey,key, iskey的順序搜尋成員名.(NSKeyValueCodingCatogery中實現的類方法, 預設實現為返回YES)
3、如果沒有找到成員變數, 呼叫setValue:forUnderfinedKey:
HTTP和HTTPs的請求過程?
https://www.jianshu.com/p/55cb014f6079
說說你理解weak屬性?
Runtime維護了一個weak表,用於儲存指向某個物件的所有weak指標。weak表其實是一個hash(雜湊)表,Key是所指物件的地址,Value是weak指標的地址(這個地址的值是所指物件的地址)陣列。
1、初始化時:runtime會呼叫objc_initWeak函式,初始化一個新的weak指標指向物件的地址。
2、新增引用時:objc_initWeak函式會呼叫 objc_storeWeak() 函式, objc_storeWeak() 的作用是更新指標指向,建立對應的弱引用表。
3、釋放時,呼叫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指標的呢?
1、呼叫objc_release
2、因為物件的引用計數為0,所以執行dealloc
3、在dealloc中,呼叫了_objc_rootDealloc函式
4、在_objc_rootDealloc中,呼叫了object_dispose函式
5、呼叫objc_destructInstance
6、最後呼叫objc_clear_deallocating,詳細過程如下:
a. 從weak表中獲取廢棄物件的地址為鍵值的記錄
b. 將包含在記錄中的所有附有 weak修飾符變數的地址,賦值為 nil
c. 將weak表中該記錄刪除
d. 從引用計數表中刪除廢棄物件的地址為鍵值的記錄
10.iOS本地資料儲存安全
11.BAD_ACCESS的錯誤嗎?你是怎樣除錯的?
BAD_ACCESS:不管什麼時候當你遇到BAD_ACCESS這個錯誤,那就意味著你向一個已經釋放的物件傳送訊息。
BAD_ACCESS的本質:
在C和OC中,你一直在處理指標,指標無非是儲存另一個變數的記憶體地址的變數。當向一個物件傳送訊息時,指向該物件的指標將會被引用,這意味著,你獲取了指標所指的記憶體地址,並訪問該儲存區域的值。
當該儲存器區域不再對映到你的應用時,或者換句話說,該記憶體區域在你認為使用的時候沒有使用,該記憶體區域是無法訪問的,這時核心會丟擲一個異常(EXC),表明你的應用程式不能訪問該儲存器區域(BAD_ACCESS).
當你碰到BAD_ACCESS,這意味著你試圖傳送訊息到的記憶體塊,但記憶體塊無法執行該訊息。但是,在某些情況下,BAD_ACCESS是由被損壞的指標引起的,每當你的應用程式嘗試引用損壞的指標,一個異常就會被核心丟擲。
除錯請看12、不借用第三個變數,如何交換兩個變數的值?要求手動寫出交換過程。
int a = 10,b = 20; a = a+b; b = a - b; a = a - b; //第二種方法,位異或運算 a = a^b; b = a^b; a = a^b; //第三種方法,使用指標 int *pa = &a; int *pb = &b; *pa = b; *pb = a; NSLog(@"after,a = %d",a); NSLog(@"after,b = %d",b);
13.用遞迴演算法求1到n的和
int sum(int n) { if (n==1) return 1; else return sum(n-1)+n; }
14.category為什麼不能新增屬性?
Category不能新增成員變數,可以新增屬性,但是屬性要手動實現setter和getter方法。
Category的原理
簡單地說就是通過runtime動態的吧Category中的方法等新增到類中,
從category的定義也可以看出category的可為(可以新增例項方法,類方法,甚至可以實現協議,新增屬性)和不可為(無法新增例項變數)。
經過編譯的類在程式啟動後就被runtime載入,沒有機會呼叫addIvar。程式在執行時動態構建的類需要在呼叫objc_registerClassPair
之後才可以被使用,同樣沒有機會再新增成員變數。
category為什麼只能新增方法
因為方法和屬性並不“屬於”類例項,而成員變數“屬於”類例項。我們所說的“類例項”概念,指的是一塊記憶體區域,包含了isa指標和所有的成員變數。所以假如允許動態修改類成員變數佈局,已經創建出的類例項就不符合類定義了,變成了無效物件。但方法定義是在objc_class中管理的,不管如何增刪類方法,都不影響類例項的記憶體佈局,已經創建出的類例項仍然可正常使用。
Category注意事項:
1、category的方法沒有“完全替換掉”原來類已經有的方法,也就是說如果category和原來類都有methodA,那麼category附加完成之後,類的方法列表裡會有兩個methodA
2、category的方法被放到了新方法列表的前面,而原來類的方法被放到了新方法列表的後面,這也就是我們平常所說的category的方法會“覆蓋”掉原來類的同名方法,這是因為執行時在查詢方法的時候是順著方法列表的順序查詢的,它只要一找到對應名字的方法,就會罷休_,殊不知後面可能還有一樣名字的方法。
詳細請點選15.runloop和執行緒的關係
runloop 正如其名,loop是一種迴圈,和run放在一起就是表示一直在執行著迴圈,實際上Runloop和執行緒是緊密相連的,可以這樣說run loop是為了執行緒而生,沒有執行緒,它就沒有存在必要。每個執行緒,包括程式的主執行緒( main thread )都有與之相應的 run loop 物件。
主執行緒是預設開啟的,其他執行緒需要手動開啟
16、說一下autoreleasePool的實現原理。
autoreleasePool自動釋放池是OC的一種記憶體自動回收機制,它可以延時加入autoreleasePool中的變數release的時機,在正常情況下,建立的變數會在超出其作用於的時候release,但是如果將變數加入autoreleasePool,那麼release將延遲執行。
AutoreleasePool建立是在一個RunLoop事件開始之前(push),AutoreleasePool釋放是在一個RunLoop事件即將結束之前(pop)。
AutoreleasePool裡的Autorelease物件的加入是在RunLoop事件中,AutoreleasePool裡的Autorelease物件的釋放是在AutoreleasePool釋放時。
單個自動釋放池的執行過程就是objc_autoreleasePoolPush() —> [object autorelease] —> objc_autoreleasePoolPop(void *)
17、說一下簡單工廠模式,工廠模式以及抽象工廠模式?
18、如何設計一個網路請求庫?
19、delegate
代理(delegate)的主旨是:定義一套介面,某個物件若想接受另一個物件的委託,則需遵從此介面,以便成為其委託物件,而這另一個物件的委託,則需遵從此介面,以便成為其委託物件,而這另一個物件則可以給其委託物件回傳一些資訊,也可以發生相關事件時通知委託物件。
注意delegate需定義成weak。因為兩者之間必須為"非擁有關係",通常情況下,扮演delegate的那個物件也要持有本物件。
20、說一下多執行緒,你平常是怎麼用的?
21、說一下UITableViewCell的卡頓你是怎麼優化的?
1.UITableViewCell重用機制?
UITableView只會建立一螢幕(或者一螢幕多一點)的cell,其他都是取出來重用的。每當cell滑出螢幕的時候,就會放到一個集合中,當要顯示某一位置的cell時,會先去集合中取,有的話,就直接拿出來顯示,沒有在建立。
2.tableView滑動為什麼會卡頓?
cell賦值內容時,會根據內容設定佈局,也就可以知道cell的高度,若有1000行,就會呼叫1000次 cellForRow方法,而我們對cell的處理操作,都是在這個方法中賦值,佈局等等,開銷很大。
3.優化方法?
3.1優化:heightForRow方法處理cell高度。
思路:賦值和計算佈局分離。cellForRow負責賦值,heightRorRow負責計算高度。
3.2自定義cell繪製:
各個資訊都是根據之前算好的佈局進行繪製的。需要非同步繪製。重寫draeRect方法就不需要非同步繪製了,因為drawRect本來就是非同步繪製的。圖文混排的繪製,coreText繪製。
3.3按需載入(UIScrollView方面):
如果目標行與當前行相差超過指定行數,只在目標滾動範圍的前後制定n行載入。滾動很快時,只加載目標範圍內得cell,這樣按需載入,極大地提高了流暢性。
4.總結
1.提前計算並快取好高度,因為heightForRow最頻繁的呼叫。
2.非同步繪製,遇到複雜介面,效能瓶頸時,可能是突破口。
3.滑動時按需載入,這個在大量圖片展示,網路載入時,很管用。(SDWebImage已經實現非同步載入)。
4.重用cells。
5.如果cell內顯示得內容來自web,使用非同步載入,快取結果請求。
6.少用或不用透明圖層,使用不透明檢視。
7.儘量使所有的view opaque,包括cell本身。
8.減少subViews
9.少用addView給cell動態新增view,可以初始化的時候就新增,然後通過hide控制是否顯示。
22、看過哪些三方庫?說一下實現原理以及好在哪裡?
23、說一下HTTP協議以及經常使用的code碼的含義。
24、設計一套快取策略。
不清楚 有知道的可以回答下
25、HTTP協議 HTTPS
HTTP協議:即超文字傳輸協議,是一種詳細規定了瀏覽器和全球資訊網伺服器之間互相通訊的規則,通過因特網傳送全球資訊網文件的資料傳送協議
HTTP協議作用:HTTP協議是用於從www伺服器傳輸超文字到本地瀏覽器的傳送協議,它可以使瀏覽器更加高效,使網路傳輸減少,它不僅保證計算機正確快速的傳輸超文字文件,還確定傳輸文件的哪一部分,以及哪部分內容首先顯顯示等。
URL:我們在瀏覽器的位址列裡輸入的網站地址叫做URL (Uniform Resource Locator,統一資源定位符)。就像每家每戶都有一個門牌地址一樣,每個網頁也都有一個Internet地址。當你在瀏覽器的地址框中輸入一個URL或是單擊一個超級連結時,URL就確定了要瀏覽的地址。瀏覽器通過超文字傳輸協議(HTTP),將Web伺服器上站點的網頁程式碼提取出來,並翻譯成漂亮的網頁。
HTTPS::是以安全為目標的HTTP通道,簡單講HTTP的安全版,即HTTP下加入SSL層,HTTPS的安全基礎是SSL,因此加密的詳細內容就需要SSL。
26、設計一個檢測主線和卡頓的方案。
27、說一下runtime,工作是如何使用的?看過runtime原始碼嗎?
28、說幾個你在工作中使用到的執行緒安全的例子。
29、用過哪些鎖?哪些鎖的效能比較高?
30、說一下HTTP和HTTPs的請求過程?
在HTTP/1.1協議中,定義了8種傳送HTTP請求的方法
GET、POST、OPTIONS、HEAD、PUT、DELETE、TRACE、CONNECT、PATCH
各個方法的解釋如下(所有方法全為大寫):
GET: 請求獲取Request-URI所標識的資源
POST: 在Request-URI所標識的資源後附加新的資料
HEAD: 請求獲取由Request-URI所標識的資源的響應訊息報頭
PUT: 請求伺服器儲存一個資源,並用Request-URI作為其標識
DELETE: 請求伺服器刪除Request-URI所標識的資源
TRACE: 請求伺服器回送收到的請求資訊,主要用於測試或診斷
CONNECT: 保留將來使用
OPTIONS: 請求查詢伺服器的效能,或者查詢與資源相關的選項和需求
根據HTTP協議的設計初衷,不同的方法對資源有不同的操作方式
PUT :增
DELETE :刪
POST:改
GET:查
最常用的是GET和POST(實際上GET和POST都能辦到增刪改查)
31、說一下TCP和UDP
32、說一下靜態庫和動態庫之間的區別
33、load和initialize方法分別在什麼時候呼叫的?
34、NSNotificationCenter是在哪個執行緒傳送的通知?
35、用過swift嗎?如果沒有,平常有學習嗎?
36、說一下你對架構的理解?
37、為什麼一定要在主執行緒裡面更新UI?
像UIKit這樣大的框架上確保執行緒安全是一個重大的任務,會帶來巨大的成本。UIKit不是執行緒安全的,假如在兩個執行緒中設定了同一張背景圖片,很有可能就會由於背景圖片被釋放兩次,使得程式崩潰。或者某一個執行緒中遍歷找尋某個subView,然而在另一個執行緒中刪除了該subView,那麼就會造成錯亂。apple有對大部分的繪圖方法和諸如UIColor等類改寫成執行緒安全可用,可還是建議將UI操作保證在主執行緒中。