1.網(wǎng)絡(luò)
1.網(wǎng)絡(luò)七層協(xié)議有哪些匙睹?
物理層:
主要功能:傳輸比特流埠啃;
典型設(shè)備:集線器、中繼器奸笤;
典型協(xié)議標(biāo)準(zhǔn)和應(yīng)用:V.35惋啃、EIA/TIA-232
數(shù)據(jù)鏈路層:
主要功能:保證無差錯的疏忽鏈路的
典型設(shè)備:交換機(jī)、網(wǎng)橋监右、網(wǎng)卡
典型協(xié)議標(biāo)準(zhǔn)和應(yīng)用:802.2边灭、802.3ATM、HDLC健盒、FRAME RELAY
網(wǎng)絡(luò)層:
主要功能:路由绒瘦、尋址
典型設(shè)備:路由器
典型協(xié)議標(biāo)準(zhǔn)和應(yīng)用:IP、IPX扣癣、APPLETALK惰帽、ICMP
傳輸層:
主要功能:端到端控制
典型設(shè)備:網(wǎng)關(guān)
典型協(xié)議標(biāo)準(zhǔn)和應(yīng)用:TCP、UDP搏色、SPX
會話層:
主要功能:會話的建立和結(jié)束
典型設(shè)備:網(wǎng)關(guān)
典型協(xié)議標(biāo)準(zhǔn)和應(yīng)用:RPC善茎、SQL、NFS频轿、X WINDOWS垂涯、ASP
表示層:
表示層相當(dāng)于一個東西的表示,表示的一些協(xié)議航邢,比如圖片聲音視頻MPEG等
主要功能:數(shù)據(jù)表示耕赘、壓縮和加密
典型設(shè)備:網(wǎng)關(guān)
典型協(xié)議標(biāo)準(zhǔn)和應(yīng)用:ASCLL、PICT膳殷、TIFF操骡、JPEG|MPEG
應(yīng)用層:
主要功能:應(yīng)用接口九火、應(yīng)用程序
典型設(shè)備:網(wǎng)關(guān)
典型協(xié)議標(biāo)準(zhǔn)和應(yīng)用:TELNET、FTP册招、HTTP
2.Http 和 Https 的區(qū)別岔激?Https為什么更加安全?
區(qū)別:
- HTTP是明文傳輸是掰, https是基于SSL進(jìn)行的加密傳輸虑鼎、有身份驗(yàn)證環(huán)節(jié),更加安全
- HTTP默認(rèn)端口號是80键痛,HTTPS默認(rèn)端口號是443
- HTTPS需要CA證書炫彩,極少免費(fèi)。
安全的原因:HTTPS是基于SSL(安全套接字)和TLS(傳輸層安全)絮短,對網(wǎng)絡(luò)進(jìn)行加密處理江兢,保障數(shù)據(jù)的完整性,更加安全丁频。
3.HTTPS的連接建立流程
HTTPS為了兼顧安全與效率杉允,同時(shí)使用了對稱加密和非對稱加密。在傳輸中涉及到3個密鑰:服務(wù)端到公鑰和私鑰用于非對稱加密限府,客戶端生成的隨機(jī)密鑰用來進(jìn)行對稱加密
image.png如圖HTTPS連接過程大致分為8步
- 客戶端訪問HTTPS連接:客戶端把安全協(xié)議版本號夺颤、客戶端支持的加密算法列表、隨機(jī)數(shù)C發(fā)給服務(wù)端胁勺。
- 服務(wù)端發(fā)送證書給客戶端:服務(wù)端接受密鑰算法配件后世澜,會和自己支持的加密算法列表進(jìn)行對比,如果不符合則斷開連接署穗。否則會在該算法列表中選擇一種對稱算法(如AES)寥裂、一種公鑰算法(如具有特定密鑰長度的RSA)和一種MAC算法發(fā)給客戶端。服務(wù)端有一個密鑰對(公鑰和私鑰)用來進(jìn)行非對稱加密使用案疲,服務(wù)端保存著私鑰封恰,公鑰可以發(fā)送給任何人。在發(fā)送加密算法的同時(shí)還會把數(shù)字證書和隨機(jī)數(shù)S發(fā)送給客戶端
- 客戶端驗(yàn)證server證書:客戶端會對server公鑰進(jìn)行檢查褐啡,驗(yàn)證其合法性诺舔,如果公鑰有問題,那么HTTPS傳輸就無法繼續(xù)备畦。
- 客戶端組裝會話密鑰:如果公鑰合格低飒,那么客戶端就會用服務(wù)端公鑰來生成一個前主密鑰(Pre-Master Secret,PMS),并通過該前主密鑰和隨機(jī)數(shù)C、S來組裝成會話密鑰
- 客戶端將前主密鑰發(fā)送給服務(wù)端:通過服務(wù)端端公鑰對前主密鑰進(jìn)行非對稱加密懂盐,發(fā)送給服務(wù)端
- 服務(wù)端通過私鑰解密得到前主密鑰:服務(wù)端接受到加密信息后褥赊,用私鑰解密得到前主密鑰。
- 服務(wù)端組裝會話密鑰:服務(wù)端通過前主密鑰和隨機(jī)數(shù)C莉恼、S來組裝會話密鑰拌喉。至此速那,服務(wù)端和客戶端都已經(jīng)知道了用于此次會話都都主密鑰。
- 數(shù)據(jù)傳輸:客戶端收到服務(wù)器發(fā)送來的秘文尿背,用客戶端密鑰對其進(jìn)行對稱解密端仰,得到服務(wù)器發(fā)送的數(shù)據(jù)。同理残家,服務(wù)端收到客戶端發(fā)送的秘文榆俺,用服務(wù)端密鑰進(jìn)行對稱解密得到客戶端發(fā)送的數(shù)據(jù)。
4.解釋一下 三次握手 和 四次揮手
- 三次握手:
- 客戶端向服務(wù)端發(fā)送SYN同步報(bào)文
- 服務(wù)端收到SYN同步報(bào)文后坞淮,會返回給客戶端SYN同步報(bào)文和ACK確認(rèn)報(bào)文
- 客戶端向服務(wù)端發(fā)送ACK確認(rèn)報(bào)文,此時(shí)客戶端和服務(wù)端的連接正式建立
- 建立連接:
- 這個時(shí)候客戶端就可以通過HTTP請求報(bào)文向服務(wù)端發(fā)送數(shù)據(jù)
- 服務(wù)端收到客戶端的請求之后陪捷,向客戶端回復(fù)HTTP響應(yīng)報(bào)文
- 四次揮手:當(dāng)客戶端和服務(wù)端的連接想要斷開的時(shí)候回窘,要經(jīng)歷四次揮手的過程,客戶端和服務(wù)端雙方都要知道結(jié)束了市袖。
- 先由客戶端向服務(wù)端發(fā)送FIN結(jié)束報(bào)文
- 服務(wù)端會返回給客戶端ACK確認(rèn)報(bào)文啡直。此時(shí),由客戶端發(fā)起的斷開連接已經(jīng)完成苍碟。
- 服務(wù)端會發(fā)送給客戶端FIN結(jié)束報(bào)文和ACK確認(rèn)報(bào)文酒觅。
- 客戶端會返回ACK確認(rèn)報(bào)文到服務(wù)端,至此微峰,由服務(wù)端方向的斷開連接已經(jīng)完成舷丹。
- 三次握手:
5.TCP 和 UDP的區(qū)別
TCP:面向連接、傳輸可靠(保證數(shù)據(jù)正確性蜓肆、保證數(shù)據(jù)順序)颜凯,用于傳輸大量數(shù)據(jù)(流模式),速度慢、建立連接需要開銷較多(時(shí)間仗扬、系統(tǒng)資源)
UDP:面向非連接症概、傳輸不可靠、用于傳輸少量數(shù)據(jù)(數(shù)據(jù)包模式)早芭、速度快
6.Cookie和Session
Cookie:cookie主要用來記錄用戶狀態(tài)彼城,區(qū)分用戶,狀態(tài)保存在客戶端退个。cookie功能需要瀏覽器支持募壕。如果瀏覽器不支持cookie(如大部分手機(jī)中的瀏覽器)或者把cookie禁用了,cookie功能就會失效帜乞。
cookie的使用:首次訪問某網(wǎng)站時(shí)司抱,客戶端會發(fā)送一個HTTP請求到服務(wù)器,服務(wù)器發(fā)送一個HTTP響應(yīng)到客戶端黎烈,其中包含set-cookie頭部习柠;客戶端再發(fā)送HTTP請求時(shí)匀谣,包含cookie頭部,服務(wù)端返回HTTP響應(yīng)到客戶端资溃;隔斷時(shí)間再訪問時(shí)武翎,客戶端會直接發(fā)包含cookie頭部的http請求,服務(wù)端響應(yīng)客戶端
cookie的修改和刪除:在修改cookie的時(shí)候溶锭,只需要新cookie覆蓋舊cookie即可宝恶,在覆蓋的時(shí)候,由于cookie具有不可跨域名性趴捅,主要name垫毙、path、domain需與原cookie一致拱绑。刪除cookie也一樣综芥,設(shè)置cookie的過期時(shí)間為過去的一個時(shí)間點(diǎn)或者maxage=0即可。
cookie的安全:cookie的使用存在爭議猎拨,因?yàn)樗徽J(rèn)為是對用戶隱私的侵害膀藐,并且cookie并不安全。http協(xié)議不僅是無狀態(tài)的红省,而且是不安全的额各,使用http協(xié)議的數(shù)據(jù)使用明文在網(wǎng)絡(luò)傳播有被截獲的可能。如果不希望cookie在http非安全協(xié)議中傳輸可以設(shè)置cookie的Secure屬性為true吧恃,瀏覽器只會在https和ssl等安全協(xié)議中傳輸此類cookie虾啦。Secure屬性并不能對cookie內(nèi)容加密,因而不能保證卻對安全蚜枢。如果需要提高安全性缸逃,需要在程序中對cookie進(jìn)行加解密。除了Secure屬性厂抽,也可設(shè)置cookie為httponly需频,如果設(shè)置此屬性,那么通過js腳本將無法讀取到cookie信息筷凤,能有效防止XSS攻擊(跨站腳本攻擊)
Session:是服務(wù)端使用的 一種記錄客戶端狀態(tài)的機(jī)制昭殉。使用上比cookie簡單,響應(yīng)的增加了服務(wù)器的存儲壓力藐守。不同于cookie保存在客戶端瀏覽器中挪丢,session保存在服務(wù)器上,客戶端訪問瀏覽器時(shí)卢厂,服務(wù)端把客戶端信息以某種形式記錄在服務(wù)器上乾蓬,這就是session∩骱悖客戶端瀏覽器再次訪問時(shí)只需要從該session 中查找該客戶的狀態(tài)就可以了任内。
當(dāng)程序需要為某個客戶端的請求創(chuàng)建session時(shí)股缸,會先檢索客戶端的請求里是否包含session表示(稱為sessionid)如果包含則說明之前已經(jīng)為此客戶端創(chuàng)建過會話强挫,服務(wù)器就按標(biāo)識把這個session檢索出來使用万伤,檢索不到會新建一個牡辽。如果客戶端請求不包含會話標(biāo)識,服務(wù)端會創(chuàng)建一個session并生成關(guān)聯(lián)的會話id越除,這個sessionid將在本次響應(yīng)中返回給客戶端保存节腐。
- cookie和session的區(qū)別
- cookie存儲在客戶端瀏覽器上,session數(shù)據(jù)存在服務(wù)器上
- cookie相比session不是很安全
- session會在一定時(shí)間內(nèi)保存在服務(wù)器上摘盆,當(dāng)訪問增多會占用服務(wù)器性能翼雀,考慮減輕服務(wù)器性能可以使用cookie
- 單個cookie保存數(shù)據(jù)不能超過4k,很多瀏覽器都限制一個站點(diǎn)最多保存20個骡澈。而session沒有限制
- 所以可以將登陸等重要信息存放session锅纺,其他如果需要保留可以放在cookie中
7.DNS是什么?
域名系統(tǒng):domain name system肋殴, 用于 主機(jī)名到ip地址到轉(zhuǎn)換。
因特網(wǎng)上的主機(jī)可以用多種方式標(biāo)識坦弟,比如主機(jī)名或ip地址护锤。主機(jī)名比如www.baidu.com 這種方式便于人們記憶和接受,但這種長度不一沒有規(guī)律的字符串不方便路由器處理酿傍。路由器比較熱衷于 定長的有清晰層次結(jié)構(gòu)的ip地址烙懦。為了這種這兩種方式,我們需要一種能進(jìn)行主機(jī)名到ip地址轉(zhuǎn)換到服務(wù)赤炒,這就是域名系統(tǒng)DNS氯析。
DNS是一個由分層的DNS服務(wù)器實(shí)現(xiàn)的分布式數(shù)據(jù)庫。一個使得主機(jī)能夠查詢分布式數(shù)據(jù)庫的應(yīng)用層協(xié)議: DNS服務(wù)器通常是運(yùn)行BIND軟件的UNIX機(jī)器莺褒,DNS協(xié)議運(yùn)行在UDP上使用53號端口掩缓。DNS通常是由其他應(yīng)用層協(xié)議所使用的,包括HTTP遵岩、SMTP等你辣。其作用是將用戶提供的主機(jī)名解析為ip地址。DNS的一種簡單設(shè)計(jì)就是在因特網(wǎng)上只使用一個DNS服務(wù)器尘执,該服務(wù)器包含所有的映射舍哄。很明顯這種設(shè)計(jì)有很大問題:單點(diǎn)故障:如果該DNS服務(wù)器崩潰全世界網(wǎng)絡(luò)隨之癱瘓;通信容量:單個DNS服務(wù)器必須處理所有的DNS查詢誊锭;遠(yuǎn)距離的集中式數(shù)據(jù)庫:單個DNS服務(wù)器必須面對所有用戶表悬,距離過遠(yuǎn)會有嚴(yán)重時(shí)延。維護(hù):該數(shù)據(jù)庫過于龐大丧靡,還需要對新添加的主機(jī)頻繁更新蟆沫。所以DNS被設(shè)計(jì)成了一個分布式籽暇、層次數(shù)據(jù)庫。
8.DNS解析過程
以www.163.com為例:
- 客戶端打開瀏覽器饥追,輸入一個域名图仓。比如輸入www.163.com,這時(shí)但绕,客戶端會發(fā)出一個DNS請求到本地DNS服務(wù)器救崔。本地DNS服務(wù)器一般都是你的網(wǎng)絡(luò)接入服務(wù)器商提供,比如中國電信捏顺,中國移動六孵。
- 查詢www.163.com的DNS請求到達(dá)本地DNS服務(wù)器之后,本地DNS服務(wù)器會首先查詢它的緩存記錄幅骄,如果緩存中有此條記錄劫窒,就可以直接返回結(jié)果。如果沒有拆座,本地DNS服務(wù)器還要向DNS根服務(wù)器進(jìn)行查詢主巍。
- 根DNS服務(wù)器沒有記錄具體的域名和IP地址的對應(yīng)關(guān)系,而是告訴本地DNS服務(wù)器挪凑,你可以到域服務(wù)器上去繼續(xù)查詢孕索,并給出域服務(wù)器的地址。
- 本地DNS服務(wù)器繼續(xù)向域服務(wù)器發(fā)出請求躏碳,在這個例子中搞旭,請求的對象是.com域服務(wù)器。.com域服務(wù)器收到請求之后菇绵,也不會直接返回域名和IP地址的對應(yīng)關(guān)系肄渗,而是告訴本地DNS服務(wù)器,你的域名的解析服務(wù)器的地址咬最。
- 最后翎嫡,本地DNS服務(wù)器向域名的解析服務(wù)器發(fā)出請求,這時(shí)就能收到一個域名和IP地址對應(yīng)關(guān)系丹诀,本地DNS服務(wù)器不僅要把IP地址返回給用戶電腦钝的,還要把這個對應(yīng)關(guān)系保存在緩存中,以備下次別的用戶查詢時(shí)铆遭,可以直接返回結(jié)果硝桩,加快網(wǎng)絡(luò)訪問。
2.多線程
1.進(jìn)程與線程分別是什么意思枚荣?
進(jìn)程是系統(tǒng)中正在運(yùn)行的程序碗脊,就是一段程序的執(zhí)行過程,我們可以理解為手機(jī)上一個正在運(yùn)行的app。進(jìn)程是操作系統(tǒng)分配資源的基本單元衙伶。每個進(jìn)程之間相互獨(dú)立祈坠,每個進(jìn)程均運(yùn)行在其專用且受保護(hù)的內(nèi)存空間內(nèi),擁有獨(dú)立運(yùn)行所需的全部資源矢劲。
線程是程序執(zhí)行的最小單元赦拘,是進(jìn)程中的一個實(shí)體。一個進(jìn)程想要執(zhí)行任務(wù)必須至少有一條線程芬沉。應(yīng)用程序啟動時(shí)躺同,系統(tǒng)會默認(rèn)開啟一條線程,也就是主線程丸逸。
進(jìn)程和線程的關(guān)系:線程是進(jìn)程的執(zhí)行單元蹋艺,進(jìn)程中的所有任務(wù)都是在線程中執(zhí)行的;線程是CPU分配資源和調(diào)度的最小單元黄刚;一個程序可以對應(yīng)多個進(jìn)程捎谨,一個進(jìn)程至少有一條線程;同一個進(jìn)程內(nèi)的線程共享進(jìn)程資源憔维。
2.什么是多線程涛救?
一個進(jìn)程至少有一個線程,如果有多個線程在執(zhí)行就是多線程业扒。多線程的實(shí)現(xiàn)原理州叠,如果是單核CPU,則是CPU在多個線程之間快速切換凶赁,造成多個線程同時(shí)執(zhí)行的假象。如果是多核CPU逆甜,就真的可以實(shí)現(xiàn)多個線程同時(shí)執(zhí)行虱肄。多線程的目的是為了同步完成多項(xiàng)任務(wù),通過提高系統(tǒng)資源的利用率來提高工作效率交煞。
3.多線程的優(yōu)點(diǎn)和缺點(diǎn)有哪些咏窿?
優(yōu)點(diǎn):能適當(dāng)?shù)奶岣叱绦虻膱?zhí)行效率;能適當(dāng)提高資源的利用率(CPU素征、內(nèi)存利用率)
缺點(diǎn):每個線程都有一定的開銷集嵌,從而影響性能。不僅有創(chuàng)建時(shí)的時(shí)間開銷御毅,還有消耗內(nèi)存根欧。每個線程大約消耗1kb的內(nèi)核內(nèi)存空間,用于存儲與線程有關(guān)的數(shù)據(jù)結(jié)構(gòu)和屬性端蛆,這塊兒內(nèi)存是聯(lián)動內(nèi)存凤粗,無法被分頁。此外還占用一定的椊穸梗空間嫌拣,默認(rèn)主線程占1M柔袁,子線程占512k,注意完整的棧不會被立即創(chuàng)建异逐,實(shí)際消耗的棿匪鳎空間隨著使用而增加,如果開啟大量線程灰瞻,會占用大量內(nèi)存空間降低程序性能腥例;線程越多CPU調(diào)度線程的開銷就越大。程序設(shè)計(jì)更加復(fù)雜比如線程之間的通信多線程的數(shù)據(jù)共享等箩祥。
4.多線程的 并行 和 并發(fā) 有什么區(qū)別院崇?
并行:充分利用計(jì)算機(jī)的多核,在多個線程上同步進(jìn)行
并發(fā):CPU快速切換線程袍祖,讓人感覺在同步進(jìn)行
5.iOS中實(shí)現(xiàn)多線程的幾種方案底瓣,各自有什么特點(diǎn)?
NSThread 面向?qū)ο蠼堵枋謩觿?chuàng)建線程捐凭,但不需要手動銷毀。自線程間通信很難凳鬓;
GCD C語言茁肠,充分利用了設(shè)備的多核,自動管理線程生命周期缩举。比NSOperation高效垦梆;
NSOPeration 基于GCD封裝,更加面向?qū)ο蠼龊ⅰ1菺CD多了一些功能托猩,比如設(shè)置最大并發(fā)數(shù),可以監(jiān)控任務(wù)狀態(tài)辽慕,輕松設(shè)置任務(wù)依賴等京腥。
6.多個網(wǎng)絡(luò)請求完成后如何執(zhí)行?
- 使用GCD的任務(wù)組
創(chuàng)建一個任務(wù)組 dispatch_group_t,每次網(wǎng)絡(luò)請求前先進(jìn)入組 dispatch_group_enter溅蛉,請求回調(diào)后再離開組 dispatch_group_leave公浪。進(jìn)入和離開必須成對使用,否則組會一直存在船侧。當(dāng)所有enter都leave之后欠气,會執(zhí)行組通知dispatch_group_notify的block。
- 使用GCD的信號量
信號量是基于計(jì)數(shù)器的一種多線程同步機(jī)制勺爱。如果信號計(jì)數(shù)大于1晃琳,計(jì)數(shù)-1返回,程序繼續(xù)執(zhí)行。如果計(jì)數(shù)為0則等待卫旱。
增加信號量 dispatch_semaphore_signal(semaphore)為計(jì)數(shù)+1操作人灼。dispatch_semaphore_wait(sema,DISPATCH_TIME_FOREVER)為設(shè)置等待的時(shí)間,這里設(shè)置的是一直等待顾翼。
創(chuàng)建信號量為0投放,等待。等10個網(wǎng)絡(luò)請求都完成了适贸,信號量dispatch_semaphore_signal計(jì)數(shù)為1灸芳,然后計(jì)數(shù)-1返回。
dispatch_semaphore_t sem = dispatch_semaphore_create(0); for (int i=0; i<10; i++) { NSURLSessionDataTask *task = [session dataTaskWithRequest:request completionHandler:^(NSData * _Nullable data, NSURLResponse * _Nullable response, NSError * _Nullable error) { NSLog(@"%d---%d",i,i); count++; if (count==10) { dispatch_semaphore_signal(sem); count = 0; } }]; [task resume]; } dispatch_semaphore_wait(sem, DISPATCH_TIME_FOREVER); dispatch_async(dispatch_get_main_queue(), ^{ NSLog(@"end"); });
7.多個網(wǎng)絡(luò)請求順序執(zhí)行后如何執(zhí)行下一步拜姿?
使用信號量烙样,每一次遍歷,都讓信號量等待蕊肥,阻塞當(dāng)前線程谒获,直到信號增加調(diào)用之后
dispatch_semaphore_t sem = dispatch_semaphore_create(0); for (int i=0; i<10; i++) { NSURLSessionDataTask *task = [session dataTaskWithRequest:request completionHandler:^(NSData * _Nullable data, NSURLResponse * _Nullable response, NSError * _Nullable error) { NSLog(@"%d---%d",i,i); dispatch_semaphore_signal(sem); }]; [task resume]; dispatch_semaphore_wait(sem, DISPATCH_TIME_FOREVER); } dispatch_async(dispatch_get_main_queue(), ^{ NSLog(@"end"); });
8.如何理解多線程中的死鎖?
死鎖是由于多個線程(進(jìn)程)在執(zhí)行過程中壁却,因?yàn)闋帄Z資源而造成的相互等待的現(xiàn)象批狱。可以理解為卡住了展东。產(chǎn)生死鎖的必要條件有4個:
- 互斥條件:指進(jìn)程對分配到的資源進(jìn)行排他性使用赔硫,即在一段時(shí)間內(nèi)某資源只由一個進(jìn)程占用,如果此時(shí)還有其他進(jìn)程請求資源盐肃,則請求者只能等待爪膊,直到占有資源的進(jìn)程用畢釋放。
- 請求和保持條件:指進(jìn)程已經(jīng)保持至少一個資源砸王,但又提出了新的資源請求惊完,而該資源已經(jīng)被其他進(jìn)程占有,此時(shí)請求阻塞处硬,但又對自己已獲得的其他資源保持不放。
- 不可剝奪條件:指進(jìn)程已獲得的資源拇派,在未使用完之前荷辕,不能被剝奪,只能在使用完時(shí)由自己釋放件豌。
- 環(huán)路等待條件:在發(fā)生死鎖時(shí)疮方,必然存在一個進(jìn)程資源的環(huán)形鏈,即進(jìn)程集合P{P0,P1,P2,...,Pn}中的P0正在等待一個P1占用的資源茧彤,P1正在等待P2占用的資源...Pn正在等待已被P0占用的資源
- 最常見的就是同步函數(shù)+主隊(duì)列的組合骡显,本質(zhì)是隊(duì)列阻塞
9.如何去理解GCD執(zhí)行原理?
GCD 維護(hù)著一個線程池,這個池中存放著一個個線程惫谤。這個池中線程是可以重用的壁顶,當(dāng)一個段時(shí)間后這個線程沒有調(diào)用的話,這個線程就會被銷毀溜歪。注意開多少條線程是由底層線程池決定的(線程建議控制在3-5條)若专,池是系統(tǒng)自動維護(hù),不需要程序員干預(yù)蝴猪。程序員需要關(guān)心的是向隊(duì)列中添加任務(wù)调衰,隊(duì)列調(diào)度即可。
如果隊(duì)列中存放的是同步任務(wù)自阱,則任務(wù)出隊(duì)之后嚎莉,底層線程池中會提供一條線程供這個任務(wù)執(zhí)行,任務(wù)執(zhí)行完畢后這條線程再回到線程池沛豌。這樣隊(duì)列中的任務(wù)反復(fù)調(diào)度趋箩,因?yàn)槭峭降模援?dāng)我們調(diào)用current Thread的時(shí)候琼懊,就是同一條線程阁簸。
如果隊(duì)列中存放的是異步任務(wù)(注意異步可以開線程),當(dāng)任務(wù)出隊(duì)之后,底層線程池會提供一個線程供任務(wù)執(zhí)行哼丈,因?yàn)槭钱惒綀?zhí)行启妹,隊(duì)列中的任務(wù)不需要等待當(dāng)前任務(wù)執(zhí)行完畢就可以調(diào)度下一個任務(wù),這時(shí)線程池會再次提供一個線程供第二個任務(wù)執(zhí)行醉旦,執(zhí)行完畢后再回到線程池中饶米。
這樣就對線程完成一個復(fù)用,而不需要每一個任務(wù)都開啟新的線程车胡。也就節(jié)約了系統(tǒng)開銷檬输,提高了效率。
3.架構(gòu) + 設(shè)計(jì)模式
一匈棘,項(xiàng)目架構(gòu)
1.MVC丧慈、MVP、MVVM模式
MVC是比較直觀的架構(gòu)模式主卫,最核心的就是通過Controller層來進(jìn)行調(diào)控逃默,首先看一下官方提供的MVC示意圖:
- Model和View永遠(yuǎn)不能相互通信,只能通過Controller傳遞
- Controller可以直接與Model對話(讀寫調(diào)用Model)簇搅,Model通過NOtification和KVO機(jī)制與Controller間接通信
Controller可以直接與View對話完域,通過IBoutlet直接操作View,IBoutlet直接對應(yīng)View的控件(例如創(chuàng)建一個Button:需聲明一個 IBOutlet UIButton * btn)瘩将,View通過action向Controller報(bào)告時(shí)間的發(fā)生(用戶點(diǎn)擊了按鈕)吟税。Controller是View的直接數(shù)據(jù)源
- 優(yōu)點(diǎn):對于混亂的項(xiàng)目組織方式凹耙,有了一個明確的組織方式。通過Controller來掌控全局肠仪,同時(shí)將View展示和Model的變化分開
- 缺點(diǎn):愈發(fā)笨重的Controller肖抱,隨著業(yè)務(wù)邏輯的增加,大量的代碼放進(jìn)Controller藤韵,導(dǎo)致Controller越來越臃腫虐沥,堆積成千上萬行代碼,后期維護(hù)起來費(fèi)時(shí)費(fèi)力
MVP(Model泽艘、View欲险、Presenter)
MVP模式是MVC模式的一個演化版本,其中Model與MVC模式中Model層沒有太大區(qū)別匹涮,主要提供數(shù)據(jù)存儲功能天试,一般都是用來封裝網(wǎng)絡(luò)獲取的json數(shù)據(jù);View與MVC中的View層有一些差別然低,MVP中的View層可以是viewController喜每、view等控件;Presenter層則是作為Model和View的中介雳攘,從Model層獲取數(shù)據(jù)之后傳給View带兜。
從上圖可以看出,從MVC模式中增加了Presenter層吨灭,將UIViewController中復(fù)雜的業(yè)務(wù)邏輯刚照、網(wǎng)絡(luò)請求等剝離出來。
- 優(yōu)點(diǎn) 模型和視圖完全分離喧兄,可以做到修改視圖而不影響模型无畔;更高效的使用模型,View不依賴Model吠冤,可以說VIew能做到對業(yè)務(wù)邏輯完全分離
- 缺點(diǎn) Presenter中除了處理業(yè)務(wù)邏輯以外及刻,還要處理View-Model兩層的協(xié)調(diào)蛔钙,也會導(dǎo)致Presenter層的臃腫
2.關(guān)于RAC你有怎樣運(yùn)用到解決不同API依賴關(guān)系
3.@weakify和我們宏定義的WeakSelf有什么區(qū)別植影?
- 微服務(wù)的架構(gòu)設(shè)想
MVVM(Model企垦、Controller/View、ViewModel)
在MVVM中涯保,view和ViewCOntroller聯(lián)系在一起饵较,我們把它們視為一個組件,view和ViewController都不能直接引用model遭赂,而是引用是視圖模型即ViewModel。viewModel是一個用來放置用戶輸入驗(yàn)證邏輯横辆、視圖顯示邏輯撇他、網(wǎng)絡(luò)請求等業(yè)務(wù)邏輯的地方茄猫,這樣的設(shè)計(jì)模式划纽,會輕微增加代碼量锌畸,但是會減少代碼的復(fù)雜性
- 優(yōu)點(diǎn) VIew可以獨(dú)立于Model的變化和修改勇劣,一個ViewModel可以綁定到不同的View上,降低耦合潭枣,增加重用
- 缺點(diǎn) 過于簡單的項(xiàng)目不適用比默、大型的項(xiàng)目視圖狀態(tài)較多時(shí)構(gòu)建和維護(hù)成本太大
合理的運(yùn)用架構(gòu)模式有利于項(xiàng)目、團(tuán)隊(duì)開發(fā)工作盆犁,但是到底選擇哪個設(shè)計(jì)模式命咐,哪種設(shè)計(jì)模式更好,就像本文開頭所說谐岁,不同的設(shè)計(jì)模式醋奠,只是讓不同的場景有了更多的選擇方案。根據(jù)項(xiàng)目場景和開發(fā)需求伊佃,選擇最合適的解決方案。
關(guān)于RAC你有怎樣運(yùn)用到解決不同API依賴關(guān)系
- 信號的依賴:使用場景是當(dāng)信號A執(zhí)行完才會執(zhí)行信號B,和請求的依賴很類似,例如請求A請求完畢才執(zhí)行請求B,我們需要注意信號A必須要執(zhí)行發(fā)送完成信號,否則信號B無法執(zhí)行
@weakify和我們宏定義的WeakSelf有什么區(qū)別?
- @weakify 可以多參數(shù)使用
二织咧,設(shè)計(jì)模式
-
iOS有哪些常見的設(shè)計(jì)模式?
單例模式:單例保證了應(yīng)用程序生命周期內(nèi)僅有一個該類的實(shí)例對象,而且易于外界訪問。在iOS SDK中艇搀,UIApplication, NSBundle, NSNotificationCenter, NSFileManager, NSUserDefault, NSURLCache等都是單例.
委托模式:委托代理是協(xié)議的一種芳杏,通過protocol方式實(shí)現(xiàn)吝秕,常見的有tableview及textFiled都采用了委托代理模式容客。
觀察者模式:觀察者模式定義了一種一對多的依賴關(guān)系,讓多個觀察者同時(shí)監(jiān)聽某一個主題對象调煎。在iOS中,觀察者模式的具體實(shí)現(xiàn)有兩種:通知機(jī)制 和 KVO
單例會有什么弊端?
優(yōu)點(diǎn):提供了對唯一實(shí)例的受控訪問赤拒。系統(tǒng)內(nèi)存中只存在一個對象航夺,可以節(jié)約系統(tǒng)資源始衅,對于一些需要頻繁創(chuàng)建和銷毀的對象艺骂,單例模式無疑可以提高系統(tǒng)性能别伏。允許可變數(shù)目的實(shí)例宙址。
缺點(diǎn):單例模式中沒有抽象層大咱,因此但李磊的擴(kuò)展有很大困難丑搔。單例類的職責(zé)過重煮仇,在一定程度上違背了單一職責(zé)原則郑诺。濫用單例會有負(fù)面問題,比如為了節(jié)省資源將數(shù)據(jù)庫連接池對象設(shè)計(jì)為單例類圾亏,可能會導(dǎo)致共享連接池對象過多而出現(xiàn)連接池溢出。
-
編程中的六大設(shè)計(jì)原則?
單一職責(zé)原則:一個類只做一件事陕见,CALayer負(fù)責(zé)動畫和視圖的顯示灰粮。UIView只負(fù)責(zé)事件的傳遞和響應(yīng)
開閉原則:對修改關(guān)閉佩研,對擴(kuò)展開放。要考慮到后續(xù)的擴(kuò)展性硕舆,而不是在原有的基礎(chǔ)上來回修改
接口隔離原則:使用多個專門的協(xié)議淋样,而不是一個龐大臃腫的協(xié)議刊咳,如UITableviewDelegate + UITableViewDataSource
依賴倒置原則:抽象不應(yīng)該依賴于具體實(shí)現(xiàn)捕犬,具體實(shí)現(xiàn)可以依賴于抽象。調(diào)用接口感覺不到內(nèi)部是如何操作的
里式替換原則:父類可以被子類無縫替換贴届,且原有功能不受任何影響。如kvo
迪米特法則:一個對象應(yīng)當(dāng)對其他對象盡可能少的了解畔乙,實(shí)現(xiàn)高聚合氮帐、低耦合
-
如何設(shè)計(jì)一個圖片緩存框架楞艾?
可以模仿sdwebimage蕴侧。圖片的存儲是以圖片的單向hash值為key裹纳。內(nèi)存設(shè)計(jì)需要考慮存儲的大小。因?yàn)閮?nèi)存空間有限已添,我們針對不同的圖片給出不同的方案恨狈。比如 10k以下的50個贝搁,100k以下的20個,100k以上的10個。
內(nèi)存淘汰策略可以采用LRU某宪,最近最少使用算法焚志。觸發(fā)淘汰策略的時(shí)機(jī)有三種:定期檢查(不建議,耗性能)前后臺切換時(shí)汗菜、每次讀寫時(shí)滔灶。
磁盤設(shè)計(jì)需要考慮的問題:存儲方式,移除策略可以設(shè)置為7天或者15天动猬,圖片請求的最大并發(fā)量,請求超時(shí)策略崔拥,請求優(yōu)先級盯桦。
圖片解碼:應(yīng)用策略模式,針對jpg步鉴,png随闪,gif等不同格式的圖片進(jìn)行解碼俏讹。圖片解碼的時(shí)機(jī),在子線程圖片剛下載完時(shí)梯浪,在子線程剛從磁盤讀取完時(shí)眠砾,避免在主線程壓縮励堡、解碼布疼,避免卡頓。
-
如何設(shè)計(jì)一個時(shí)長統(tǒng)計(jì)框架肛炮?
記錄器:頁面式記錄器瘩燥、流式記錄器溶耘、自定義式
記錄管理者:內(nèi)存記錄緩存企软、磁盤存儲、上傳器
如何降低數(shù)據(jù)丟失率:定期寫入磁盤碘勉、每當(dāng)達(dá)到某個值時(shí)寫入磁盤
記錄上傳時(shí)機(jī):前后臺切換的時(shí)候可以上傳、從無網(wǎng)絡(luò)切換到有網(wǎng)絡(luò)的時(shí)候可以上傳
上傳時(shí)機(jī)的選擇:立即上傳、定時(shí)上傳怔锌、延時(shí)上傳
4.組件化+性能優(yōu)化
組件化有什么好處岛杀?
- 業(yè)務(wù)分層、解耦,使代碼變得可維護(hù)
- 有效的拆分、組織日益龐大的工程代碼,使工程目錄變得可維護(hù)
- 便于各業(yè)務(wù)功能拆分、抽離,實(shí)現(xiàn)真正的功能復(fù)用
- 業(yè)務(wù)隔離帕膜,跨團(tuán)隊(duì)開發(fā)代碼控制和版本風(fēng)險(xiǎn)控制的實(shí)現(xiàn)
- 模塊化對代碼的封裝性张弛、合理性都有一定的要求覆糟,提升開發(fā)同學(xué)的設(shè)計(jì)能力
- 在維護(hù)好各級組件的情況下,隨意組合滿足不同客戶需求。只需要將之前的多個業(yè)務(wù)組件模塊在新的主app中進(jìn)行組裝即可快速迭代出下一個全新APP
你是如何組件化解耦的内列?
- 分層
- 基礎(chǔ)功能組件:按功能分庫交排,不設(shè)計(jì)產(chǎn)品業(yè)務(wù)需求,跟庫library類似,通過良好的接口供上層業(yè)務(wù)組件調(diào)用部脚;不寫入產(chǎn)品定制邏輯锡移,通過擴(kuò)展接口完成定制。
- 基礎(chǔ)UI組件:各個業(yè)務(wù)模塊依賴使用钞支,但需要保持好定制擴(kuò)展的設(shè)計(jì)骨坑。
- 業(yè)務(wù)組件:業(yè)務(wù)功能間相互獨(dú)立,相互間沒有model共享的依賴斑芜;業(yè)務(wù)之間的頁面調(diào)用只能通過UIBus總線進(jìn)行跳轉(zhuǎn)沸呐;業(yè)務(wù)之間的邏輯Action調(diào)用只能通過服務(wù)提供。
- 中間件:target-action,url-block焊夸,protocol-class
為什么CTMediator方案優(yōu)于Router方案帅容?
Router的缺點(diǎn):
- 在實(shí)施組件化的過程中麦乞,注冊URL并不是充分必要條件,組件是不需要向組建管理器注冊URL的,注冊了URL之后愿棋,會造成不必要的內(nèi)存常駐甘邀。
- 注冊URL的目的其實(shí)是一個服務(wù)發(fā)現(xiàn)的過程。在iOS領(lǐng)域中,服務(wù)發(fā)現(xiàn)的方式是不需要通過主動注冊的锋八,使用runtime就可以紊服。
- 注冊部分的代碼維護(hù)也是一個相對麻煩的事情煎饼,每一次支持新調(diào)用時(shí),都要去維護(hù)一次注冊列表。如果有調(diào)用被棄用了翅阵,可能會忘記刪除烦周。runtime由于不存在注冊過程,也就不會產(chǎn)生維護(hù)操作怎顾,降低了維護(hù)成本槐雾。
- 由于通過runtime做到了服務(wù)的自動發(fā)現(xiàn)鸠儿,拓展接口調(diào)用的任務(wù)就僅在于各自的模塊贤徒,任何一次新接口的添加挨厚,新業(yè)務(wù)添加城菊,都不必去主工程做操作更耻,十分透明讶隐。
- 在iOS領(lǐng)域里,一定是組件化的中間件為openURL提供服務(wù)茵典,而不是openURL方式為組件化提供服務(wù)。
- 如果在給APP實(shí)現(xiàn)組件化方案的過程中是基于openURL的方案的話赎瑰,有一個致命缺陷:非常規(guī)對象(不能被字符串化到URL中的對象踩娘,例如UIImage)無法參與本地化組件間調(diào)度。
- 在本地調(diào)用中使用URL的方式其實(shí)是不必要的匿级,如果業(yè)務(wù)工程師在本地間調(diào)度時(shí)需要給出RUL,那么就不可避免要提供params,在調(diào)用時(shí)要提供哪些params是業(yè)務(wù)工程師容易懵逼的地方
- 為了支持傳遞非常規(guī)參數(shù)咖杂,蘑菇街的方案采用了protocol,這個會侵入業(yè)務(wù)采章。由于業(yè)務(wù)中的某個對象需要被調(diào)用,因此必須要符合某個可被調(diào)用的protocol进萄,然而這個protocol又不存在當(dāng)前的業(yè)務(wù)領(lǐng)域桂躏,于是當(dāng)前業(yè)務(wù)就不得不依賴public protocol鹦蠕,這對將來的業(yè)務(wù)遷移有非常大的影響酥宴。
CTMediator的優(yōu)點(diǎn):
- 調(diào)用時(shí)魔招,區(qū)分了本地調(diào)用和遠(yuǎn)程應(yīng)用調(diào)用。本地調(diào)用為遠(yuǎn)程調(diào)用提供服務(wù)
- 組件僅通過action暴露可調(diào)用的接口荣回,模塊之間的接口被固化在了target-action這一層阎肝,避免了實(shí)施組件化的改造過程中,對業(yè)務(wù)的入侵纬凤,同時(shí)也提高了組件化接口的可維護(hù)性
- 方便傳遞各種類型的參數(shù)
基于CTMediator的組件化方案谁尸,有哪些核心組成烹笔?
- CTMediator中間件:集成就可以了
- 模塊Target_%@:模塊的實(shí)現(xiàn)及提供對外的方法調(diào)用Action_methodName,需要傳遞參數(shù)時(shí),都統(tǒng)一以nsdictionary的形式傳入
- CTMediator+%@擴(kuò)展:擴(kuò)展里聲明了模塊業(yè)務(wù)的對外接口微谓,參數(shù)明確,這樣外部調(diào)用可以很容易理解如何調(diào)用接口。
性能優(yōu)化:
造成tableview卡頓的原因有哪些鄙煤?
-
最常用的就是cell的復(fù)用,注冊重用標(biāo)識符茶袒。
如果不重用cell時(shí)梯刚,每當(dāng)一個cell顯示到屏幕上時(shí),就會重新創(chuàng)建一個新的cell薪寓,如果有很多數(shù)據(jù)的時(shí)候亡资,就會堆積很多cell。如果重用cell向叉,為cell重建一個id锥腻,每當(dāng)需要顯示的時(shí)候,就先從緩沖池中尋找可循環(huán)利用的cell母谎,如果沒有再新建cell
-
避免cell重新布局
cell的布局填充等操作瘦黑,比較耗時(shí),一般創(chuàng)建時(shí)就布局好。如可以將cell單獨(dú)放到一個自定義類幸斥,初始化時(shí)就布局好
-
提前計(jì)算病患搓cell的屬性及內(nèi)容
當(dāng)我們創(chuàng)建cell的數(shù)據(jù)源方法時(shí)存崖,編譯器并不是先創(chuàng)建cell再定cell高度的,而是先根據(jù)內(nèi)容一次確定每一個cell的高度睡毒,高度確定后来惧,再創(chuàng)建要顯示的cell,滾動時(shí)演顾,每當(dāng)cell進(jìn)入屏幕都會計(jì)算高度供搀,提高估算高度告訴編譯器,編譯器知道高度后钠至,緊接著就會創(chuàng)建cell葛虐,這時(shí)再調(diào)用高度的具體計(jì)算方法。
-
減少cell中控件的數(shù)量
盡量使cell的布局大致相同棉钧,不同風(fēng)格的cell可以使用不同的重用標(biāo)識符屿脐,初始化時(shí)添加控件,不使用的可以先隱藏宪卿。
不要使用clearcolor的诵,無背景色,透明度也不要設(shè)置為0佑钾,渲染消耗事件比較長西疤。
使用局部更新:如果只是更新某組的話,使用reloadsection進(jìn)行局部更新休溶。
加載網(wǎng)絡(luò)數(shù)據(jù)代赁,下載圖片,使用異步加載并緩存
少使用addsubView給cell動態(tài)添加view
按需加載cell兽掰,cell滾動很快時(shí)芭碍,只加載范圍內(nèi)的cell
不要實(shí)現(xiàn)無用的代理方法
緩存行高:estimatedHeightForRow不能和HeightForRow里面的layoutIfNeed同時(shí)存在,這兩者同時(shí)存在才會出現(xiàn)“竄動”的bug孽尽。所以我的建議是:只要是固定行高就寫預(yù)估行高來減少行高調(diào)用次數(shù)提升性能窖壕。如果是動態(tài)行高就不要寫預(yù)估方法了,用一個行高的緩存字典來減少代碼的調(diào)用次數(shù)即可
不要做多余的繪制工作泻云。在實(shí)現(xiàn)drawRect:的時(shí)候艇拍,它的rect參數(shù)就是需要繪制的區(qū)域,這個區(qū)域之外的不需要進(jìn)行繪制宠纯。例如上例中,就可以用CGRectIntersectsRect层释、CGRectIntersection或CGRectContainsRect判斷是否需要繪制image和text婆瓜,然后再調(diào)用繪制方法。
預(yù)渲染圖像。當(dāng)新的圖像出現(xiàn)時(shí)廉白,仍然會有短暫的停頓現(xiàn)象个初。解決的辦法就是在bitmap context里先將其畫一遍,導(dǎo)出成UIImage對象猴蹂,然后再繪制到屏幕院溺;
使用正確的數(shù)據(jù)結(jié)構(gòu)來存儲數(shù)據(jù)
如何提升tableview的流暢度?
- 本質(zhì)上是降低CPU磅轻、GPU的工作珍逸,從這兩大方面去提升性能。
- CPU:對象的創(chuàng)建和銷毀聋溜、對象屬性的調(diào)整谆膳、布局計(jì)算、文本的計(jì)算和排版撮躁、圖片的格式轉(zhuǎn)換和解碼漱病、圖像的繪制
- GPU:紋理的渲染
- 卡頓優(yōu)化在CPU層面
- 盡量使用輕量級對象,比如用不到事件的地方把曼,可以考慮用CALayer取代UIView
- 不要頻繁調(diào)用UIView的相關(guān)屬性杨帽,比如frame,bounds嗤军,transform等屬性睦尽,盡量減少不必要的修改
- 盡量提前好布局,在有需要時(shí)一次性調(diào)整對應(yīng)的屬性型雳,不要多次修改屬性
- autolayout會比直接設(shè)置frame消耗更多的cpu資源
- 圖片的size最好剛好和UIimageview的size一致
- 控制線程的最大并發(fā)數(shù)量
- 盡量把耗時(shí)操作放到子線程当凡,文本處理 尺寸計(jì)算、繪制 圖片的解碼繪制
- 卡頓優(yōu)化在GPU層面
- 盡量避免短時(shí)間內(nèi)大量圖片的顯示纠俭,盡可能將多張圖片合成一張進(jìn)行顯示
- GPU能處理的最大紋理尺寸是4096*4096沿量,一旦超過這個尺寸,會占用CPU資源進(jìn)行處理冤荆,所以紋理盡量不要超過這個尺寸
- 盡量減少視圖數(shù)量和層次
- 減少透明的視圖朴则,不透明的就設(shè)置opaque為YES
- 盡量避免出現(xiàn)離屏渲染
iOS保持界面流暢的技巧
-
預(yù)排版,提前計(jì)算钓简。
在接收到服務(wù)端返回的數(shù)據(jù)后乌妒,盡量將coretext排版的結(jié)果,單個控件的高度外邓,cell整體的高度提前計(jì)算好撤蚊,將其存儲在模型的屬性中,需要使用時(shí)损话,直接從模型中往外取侦啸,避免后續(xù)計(jì)算的過程槽唾。盡量少用UILabel,可以使用CALayer光涂。
-
預(yù)渲染庞萍,提前繪制燎悍。
例如圓形的圖標(biāo)鲜滩,可以提前在接收到網(wǎng)絡(luò)返回的數(shù)據(jù)時(shí)整葡,在后臺線程進(jìn)行處理吨凑,直接存儲在模型數(shù)據(jù)里面斜脂,回到主線程后直接調(diào)用就可以了增热。避免使用CALayer的border担敌、Corner檀何、shadow重虑、mask等技術(shù)践付,這些都會觸發(fā)離屏渲染。
異步繪制
全局并發(fā)線程
高效的圖片異步加載
APP啟動時(shí)間應(yīng)從哪些方面優(yōu)化缺厉?
啟動時(shí)間可以通過xcode提供的工具來度量永高,在xcode的product-scheme-edit scheme - run -auguments中,將環(huán)境變量DYLD_PRINT_STATISTICS設(shè)置為YES提针,優(yōu)化需以下方面入手:
dylib loading time:核心思想時(shí)減少dylibs的引用命爬,合并現(xiàn)有的dylibs(最好是6個以內(nèi)),使用靜態(tài)庫
rebase/binding time:核心思想時(shí)減少DATA塊內(nèi)的指針辐脖;減少objectC元數(shù)據(jù)量饲宛,減少objc類數(shù)量,減少實(shí)例變量和函數(shù)嗜价;減少c++虛函數(shù)艇抠;多使用Swift結(jié)構(gòu)體,推薦使用Swift
objc setup time:核心思想同上久锥,這部分內(nèi)容基本上在上一階段優(yōu)化過后就不會太過耗時(shí)
-
initalizer time: 使用initalize替代load方法家淤;減少使用c/C++的attribute;推薦使用dispatch_once瑟由,pthread_once等方法絮重;推薦使用swift。不要再初始化中調(diào)用dlopen方法歹苦,因?yàn)榧虞d過程是單線程青伤,無鎖,如果調(diào)用dlopen則會變成多線程殴瘦,會開啟鎖的消耗狠角,同時(shí)有可能死鎖
如何降低app包的大小痴施?
降低包大小擎厢,需要從兩方面著手:
-
可執(zhí)行文件:
編譯器優(yōu)化:Strip Linked Product究流、Make Strings Read-Only辣吃、Symbols Hidden by Default 設(shè)置為 YES动遭,去掉異常支持,Enable C++ Exceptions神得、Enable Objective-C Exceptions 設(shè)置為 NO厘惦, Other C Flags 添加 -fno-exceptions 利用 AppCode 檢測未使用的代碼:菜單欄 -> Code -> Inspect Code
- 資源:對資源進(jìn)行無損壓縮、去除無用的資源
怎么檢測圖文混合哩簿?
1宵蕉、模擬器debug中color blended layers紅色區(qū)域表示圖層發(fā)生了混合
2、Instrument-選中Core Animation-勾選Color Blended Layers
避免圖層混合:
- 確苯诎瘢控件的opaque屬性設(shè)置為true羡玛,確保backgroundColor和父視圖顏色一致且不透明
- 如無特殊需要,不要設(shè)置低于1的alpha值
- 確保UIImage沒有alpha通道
UILabel圖層混合解決方法:
iOS8以后設(shè)置背景色為非透明色并且設(shè)置label.layer.masksToBounds=YES讓label只會渲染她的實(shí)際size區(qū)域宗苍,就能解決UILabel的圖層混合問題
iOS8 之前只要設(shè)置背景色為非透明的就行
為什么設(shè)置了背景色但是在iOS8上仍然出現(xiàn)了圖層混合呢稼稿?
UILabel在iOS8前后的變化,在iOS8以前讳窟,UILabel使用的是CALayer作為底圖層让歼,而在iOS8開始,UILabel的底圖層變成了_UILabelLayer丽啡,繪制文本也有所改變谋右。
在背景色的四周多了一圈透明的邊,而這一圈透明的邊明顯超出了圖層的矩形區(qū)域补箍,設(shè)置圖層的masksToBounds為YES時(shí)改执,圖層將會沿著Bounds進(jìn)行裁剪 圖層混合問題解決了
日常如何檢查內(nèi)存泄漏?
目前我知道的方式有以下幾種
- Memory Leaks
- Alloctions
- Analyse
- Debug Memory Graph
- MLeaksFinder
泄露的內(nèi)存主要有以下兩種:
- Leak Memory 這種是忘記 Release 操作所泄露的內(nèi)存坑雅。
- Abandon Memory 這種是循環(huán)引用辈挂,無法釋放掉的內(nèi)存。
如何優(yōu)化app電量霞丧?
- 程序的耗電主要在以下四個方面:
CPU 處理
定位
網(wǎng)絡(luò)
圖像
- 優(yōu)化的途徑主要體現(xiàn)在以下幾個方面:
盡可能降低 CPU呢岗、GPU 的功耗。
盡量少用 定時(shí)器蛹尝。
優(yōu)化 I/O 操作后豫。
不要頻繁寫入小數(shù)據(jù),而是積攢到一定數(shù)量再寫入
讀寫大量的數(shù)據(jù)可以使用 Dispatch_io 突那,GCD 內(nèi)部已經(jīng)做了優(yōu)化挫酿。
數(shù)據(jù)量比較大時(shí),建議使用數(shù)據(jù)庫
- 網(wǎng)絡(luò)方面的優(yōu)化
減少壓縮網(wǎng)絡(luò)數(shù)據(jù) (XML -> JSON -> ProtoBuf)愕难,如果可能建議使用 ProtoBuf早龟。
如果請求的返回?cái)?shù)據(jù)相同惫霸,可以使用 NSCache 進(jìn)行緩存
使用斷點(diǎn)續(xù)傳,避免因網(wǎng)絡(luò)失敗后要重新下載葱弟。
網(wǎng)絡(luò)不可用的時(shí)候壹店,不嘗試進(jìn)行網(wǎng)絡(luò)請求
長時(shí)間的網(wǎng)絡(luò)請求,要提供可以取消的操作
采取批量傳輸芝加。下載視頻流的時(shí)候硅卢,盡量一大塊一大塊的進(jìn)行下載,廣告可以一次下載多個
- 定位層面的優(yōu)化
如果只是需要快速確定用戶位置藏杖,最好用 CLLocationManager 的 requestLocation 方法将塑。定位完成后,會自動讓定位硬件斷電
如果不是導(dǎo)航應(yīng)用蝌麸,盡量不要實(shí)時(shí)更新位置点寥,定位完畢就關(guān)掉定位服務(wù)
盡量降低定位精度,比如盡量不要使用精度最高的 kCLLocationAccuracyBest
需要后臺定位時(shí)来吩,盡量設(shè)置 pausesLocationUpdatesAutomatically 為 YES敢辩,如果用戶不太可能移動的時(shí)候系統(tǒng)會自動暫停位置更新
盡量不要使用 startMonitoringSignificantLocationChanges,優(yōu)先考慮 startMonitoringForRegion:
- 硬件檢測優(yōu)化
用戶移動误褪、搖晃责鳍、傾斜設(shè)備時(shí),會產(chǎn)生動作(motion)事件兽间,這些事件由加速度計(jì)历葛、陀螺儀、磁力計(jì)等硬件檢測嘀略。在不需要檢測的場合恤溶,應(yīng)該及時(shí)關(guān)閉這些硬件
5.Runloop
1.Runloop 和線程的關(guān)系?
一個線程對應(yīng)一個runloop帜羊,主線程的runloop時(shí)默認(rèn)開啟的咒程,子線程的runloop以懶加載的形式創(chuàng)建。runloop存儲在一個全局的可變字典中讼育,線程是key帐姻,runloop是value。
2.RunLoop的運(yùn)行模式
runloop的運(yùn)行模式一共有5種奶段,runloop只會運(yùn)行在一個模式下饥瓷,要切換模式,就要暫停當(dāng)前的模式痹籍,重新啟動一個運(yùn)行模式
- kCFRunLoopDefaultMode, App的默認(rèn)運(yùn)行模式呢铆,通常主線程是在這個運(yùn)行模式下運(yùn)行
- UITrackingRunLoopMode, 跟蹤用戶交互事件(用于 ScrollView 追蹤觸摸滑動,保證界面滑動時(shí)不受其他Mode影響)
- kCFRunLoopCommonModes, 偽模式蹲缠,不是一種真正的運(yùn)行模式
- UIInitializationRunLoopMode:在剛啟動App時(shí)第進(jìn)入的第一個Mode棺克,啟動完成后就不再使用
-GSEventReceiveRunLoopMode:接受系統(tǒng)內(nèi)部事件悠垛,通常用不到
3.runloop內(nèi)部邏輯?
實(shí)際上 RunLoop 就是這樣一個函數(shù)娜谊,其內(nèi)部是一個 do-while 循環(huán)确买。當(dāng)你調(diào)用 CFRunLoopRun() 時(shí),線程就會一直停留在這個循環(huán)里因俐;直到超時(shí)或被手動停止拇惋,該函數(shù)才會返回周偎。
內(nèi)部邏輯:
- 如果是 Timer 事件抹剩,處理 Timer 并重新啟動循環(huán),跳到第 2 步
- 如果輸入源被觸發(fā)蓉坎,處理該事件(文檔上是 deliver the event)
- 如果 RunLoop 被手動喚醒但尚未超時(shí)澳眷,重新啟動循環(huán),跳到第 2 步
- 事件到達(dá)基于端口的輸入源(port-based input sources)(也就是 Source0)
- Timer 到時(shí)間執(zhí)行
- 外部手動喚醒
- 為 RunLoop 設(shè)定的時(shí)間超時(shí)
- 通知 Observer 已經(jīng)進(jìn)入了 RunLoop
- 通知 Observer 即將處理 Timer
- 通知 Observer 即將處理非基于端口的輸入源(即將處理 Source0)
- 處理那些準(zhǔn)備好的非基于端口的輸入源(處理 Source0)
- 如果基于端口的輸入源準(zhǔn)備就緒并等待處理蛉艾,請立刻處理該事件钳踊。轉(zhuǎn)到第 9 步(處理 Source1)
- 通知 Observer 線程即將休眠
- 將線程置于休眠狀態(tài),直到發(fā)生以下事件之一
- 通知 Observer 線程剛被喚醒(還沒處理事件)
- 處理待處理事件
Source1 :基于mach_Port的,來自系統(tǒng)內(nèi)核或者其他進(jìn)程或線程的事件勿侯,可以主動喚醒休眠中的RunLoop(iOS里進(jìn)程間通信開發(fā)過程中我們一般不主動使用)拓瞪。mach_port大家就理解成進(jìn)程間相互發(fā)送消息的一種機(jī)制就好, 比如屏幕點(diǎn)擊, 網(wǎng)絡(luò)數(shù)據(jù)的傳輸都會觸發(fā)sourse1。
? Source0 :非基于Port的 處理事件助琐,什么叫非基于Port的呢祭埂?就是說你這個消息不是其他進(jìn)程或者內(nèi)核直接發(fā)送給你的。一般是APP內(nèi)部的事件, 比如hitTest:withEvent的處理, performSelectors的事件.簡單舉個例子:一個APP在前臺靜止著兵钮,此時(shí)蛆橡,用戶用手指點(diǎn)擊了一下APP界面,那么過程就是下面這樣的:
我們觸摸屏幕,先摸到硬件(屏幕)掘譬,屏幕表面的事件會被IOKit先包裝成Event,通過mach_Port傳給正在活躍的APP , Event先告訴source1(mach_port),source1喚醒RunLoop, 然后將事件Event分發(fā)給source0,然后由source0來處理泰演。
4.autoreleasePool 在何時(shí)被釋放?
- app啟動后葱轩,蘋果在主線程的runloop中注冊了兩個observer睦焕,其回調(diào)都是_wrapRunLoopWithAutoreleasePoolHandler()。
- 第一個observer監(jiān)聽的事件是Entry即將進(jìn)入loop靴拱,其回調(diào)內(nèi)會調(diào)用_objc_autoreleasePoolPush()創(chuàng)建自動釋放池垃喊,其order是-2147483647,優(yōu)先級最高缭嫡,保證創(chuàng)建釋放池發(fā)生在其他所有回調(diào)之前缔御。
- 第二個observer監(jiān)聽了兩個事件:beforewaiting準(zhǔn)備進(jìn)入休眠時(shí)調(diào)用_objc_autoreleasePoolPop() 和 _objc_autoreleasePoolPush() 釋放舊的釋放池病創(chuàng)建新池;Exit即將退出Loop時(shí)調(diào)用_objc_autoreleasePoolPop() 來釋放自動釋放池妇蛀。這個 Observer 的 order 是 2147483647耕突,優(yōu)先級最低笤成,保證其釋放池子發(fā)生在其他所有回調(diào)之后。
- 在主線程執(zhí)行的代碼眷茁,通常是寫在諸如事件回調(diào)炕泳、Timer回調(diào)內(nèi)的。這些回調(diào)會被 RunLoop 創(chuàng)建好的 AutoreleasePool 環(huán)繞著上祈,所以不會出現(xiàn)內(nèi)存泄漏培遵,開發(fā)者也不必顯示創(chuàng)建 Pool 了。
- GCD 在Runloop中的使用登刺?
- GCD由 子線程 返回到 主線程,只有在這種情況下才會觸發(fā) RunLoop籽腕。會觸發(fā) RunLoop 的 Source 1 事件。
6.AFNetworking 中如何運(yùn)用 Runloop?
AFURLConnectionOperation 這個類是基于 NSURLConnection 構(gòu)建的纸俭,其希望能在后臺線程接收 Delegate 回調(diào)皇耗。
為此 AFNetworking 單獨(dú)創(chuàng)建了一個線程,并在這個線程中啟動了一個 RunLoop:
+ (void)networkRequestThreadEntryPoint:(id)__unused object {
@autoreleasepool {
[[NSThread currentThread] setName:@"AFNetworking"];
NSRunLoop *runLoop = [NSRunLoop currentRunLoop];
[runLoop addPort:[NSMachPort port] forMode:NSDefaultRunLoopMode];
[runLoop run];
}
}
+ (NSThread *)networkRequestThread {
static NSThread *_networkRequestThread = nil;
static dispatch_once_t oncePredicate;
dispatch_once(&oncePredicate, ^{
_networkRequestThread = [[NSThread alloc] initWithTarget:self selector:@selector(networkRequestThreadEntryPoint:) object:nil];
[_networkRequestThread start];
});
return _networkRequestThread;
}
runloop啟動前內(nèi)部必須至少有一個 timer揍很、observer郎楼、source。所以AFNetworking在runloop run 之前先創(chuàng)建了一個新的NSMachPort添加進(jìn)去了窒悔。
通常情況下呜袁,調(diào)用者需要持有這個machport并在外部線程通過這個port發(fā)送消息到loop內(nèi),但此處添加port只是為了讓runloop不至于退出简珠,并沒有用于實(shí)際的發(fā)送消息阶界。
當(dāng)需要這個后臺線程執(zhí)行任務(wù)時(shí),AFNetworking通過調(diào)用 [NSObject performSelector:onThread:..] 將這個任務(wù)扔到了后臺線程的 RunLoop 中北救。
- (void)start {
[self.lock lock];
if ([self isCancelled]) {
[self performSelector:@selector(cancelConnection) onThread:[[self class] networkRequestThread] withObject:nil waitUntilDone:NO modes:[self.runLoopModes allObjects]];
} else if ([self isReady]) {
self.state = AFOperationExecutingState;
[self performSelector:@selector(operationDidStart) onThread:[[self class] networkRequestThread] withObject:nil waitUntilDone:NO modes:[self.runLoopModes allObjects]];
}
[self.lock unlock];
}
- PerformSelector 的實(shí)現(xiàn)原理荐操?
- 當(dāng)調(diào)用 NSObject 的 performSelecter:afterDelay: 后,實(shí)際上其內(nèi)部會創(chuàng)建一個 Timer 并添加到當(dāng)前線程的 RunLoop 中珍策。所以如果當(dāng)前線程沒有 RunLoop托启,則這個方法會失效。
- 當(dāng)調(diào)用 performSelector:onThread: 時(shí)攘宙,實(shí)際上其會創(chuàng)建一個 Timer 加到對應(yīng)的線程去屯耸,同樣的,如果對應(yīng)線程沒有 RunLoop 該方法也會失效蹭劈。
8.PerformSelector:afterDelay:這個方法在子線程中是否起作用疗绣?
- 不起作用,子線程默認(rèn)沒有 Runloop铺韧,也就沒有 Timer多矮。可以使用 GCD的dispatch_after來實(shí)現(xiàn)
9.事件響應(yīng)的過程?
- 蘋果注冊了一個 Source1 (基于 mach port 的) 用來接收系統(tǒng)事件塔逃,其回調(diào)函數(shù)為 __IOHIDEventSystemClientQueueCallback()讯壶。
- 當(dāng)一個硬件事件(觸摸/鎖屏/搖晃等)發(fā)生后,首先由 IOKit.framework 生成一個 IOHIDEvent 事件并由 SpringBoard 接收湾盗。這個過程的詳細(xì)情況可以參考這里伏蚊。SpringBoard 只接收按鍵(鎖屏/靜音等),觸摸格粪,加速躏吊,接近傳感器等幾種 Event,隨后用 mach port 轉(zhuǎn)發(fā)給需要的 App 進(jìn)程帐萎。隨后蘋果注冊的那個 Source1 就會觸發(fā)回調(diào)比伏,并調(diào)用 _UIApplicationHandleEventQueue() 進(jìn)行應(yīng)用內(nèi)部的分發(fā)。
- _UIApplicationHandleEventQueue() 會把 IOHIDEvent 處理并包裝成 UIEvent 進(jìn)行處理或分發(fā)吓肋,其中包括識別 UIGesture/處理屏幕旋轉(zhuǎn)/發(fā)送給 UIWindow 等凳怨。通常事件比如 UIButton 點(diǎn)擊、touchesBegin/Move/End/Cancel 事件都是在這個回調(diào)中完成的是鬼。
10.手勢識別的過程?
- 當(dāng) _UIApplicationHandleEventQueue() 識別了一個手勢時(shí)紫新,其首先會調(diào)用 Cancel 將當(dāng)前的 touchesBegin/Move/End 系列回調(diào)打斷均蜜。隨后系統(tǒng)將對應(yīng)的 UIGestureRecognizer 標(biāo)記為待處理。
- 蘋果注冊了一個 Observer 監(jiān)測 BeforeWaiting (Loop即將進(jìn)入休眠) 事件芒率,這個 Observer 的回調(diào)函數(shù)是 _UIGestureRecognizerUpdateObserver()囤耳,其內(nèi)部會獲取所有剛被標(biāo)記為待處理的 GestureRecognizer,并執(zhí)行GestureRecognizer 的回調(diào)偶芍。
- 當(dāng)有 UIGestureRecognizer 的變化(創(chuàng)建/銷毀/狀態(tài)改變)時(shí)充择,這個回調(diào)都會進(jìn)行相應(yīng)處理。
11.CADispalyTimer和Timer哪個更精確匪蟀?
- iOS設(shè)備的屏幕刷新頻率是固定的椎麦,CADisplayLink在正常情況下會在每次刷新結(jié)束都被調(diào)用,精確度相當(dāng)高材彪。
- NSTimer的精確度就顯得低了點(diǎn)观挎,比如NSTimer的觸發(fā)時(shí)間到的時(shí)候,runloop如果在阻塞狀態(tài)段化,觸發(fā)時(shí)間就會推遲到下一個runloop周期嘁捷。并且 NSTimer新增了tolerance屬性,讓用戶可以設(shè)置可以容忍的觸發(fā)的時(shí)間的延遲范圍显熏。
- CADisplayLink使用場合相對專一雄嚣,適合做UI的不停重繪,比如自定義動畫引擎或者視頻播放的渲染喘蟆。NSTimer的使用范圍要廣泛的多缓升,各種需要單次或者循環(huán)定時(shí)處理的任務(wù)都可以使用夷磕。
- 在UI相關(guān)的動畫或者顯示內(nèi)容使用 CADisplayLink比起用NSTimer的好處就是我們不需要在格外關(guān)心屏幕的刷新頻率了,因?yàn)樗旧砭褪歉聊凰⑿峦降摹?/li>
6.Runtime
1.Category 的實(shí)現(xiàn)原理仔沿?
- Category 實(shí)際上是 Category_t的結(jié)構(gòu)體坐桩,在運(yùn)行時(shí),新添加的方法封锉,都被以倒序插入到原有方法列表的最前面绵跷,所以不同的Category,添加了同一個方法成福,執(zhí)行的實(shí)際上是最后一個碾局。
- Category 在剛剛編譯完的時(shí)候,和原來的類是分開的奴艾,只有在程序運(yùn)行起來后净当,通過 Runtime ,Category 和原來的類才會合并到一起蕴潦。
2.isa指針的理解像啼,對象的isa指針指向哪里?isa指針有哪兩種類型潭苞?
實(shí)例對象的 isa 指向類對象
類對象的 isa 指向元類對象
元類對象的 isa 指向元類的基類
-
isa 有兩種類型
純指針忽冻,指向內(nèi)存地址
NON_POINTER_ISA,除了內(nèi)存地址此疹,還存有一些其他信息
3.Objective-C 如何實(shí)現(xiàn)多重繼承僧诚?
Object-c的類沒有多繼承,只支持單繼承,如果要實(shí)現(xiàn)多繼承的話,可使用如下幾種方式間接實(shí)現(xiàn)
- 通過組合實(shí)現(xiàn)
A和B組合蝗碎,作為C類的組件
- 通過協(xié)議實(shí)現(xiàn)
C類實(shí)現(xiàn)A和B類的協(xié)議方法
- 消息轉(zhuǎn)發(fā)實(shí)現(xiàn)
forwardInvocation:方法
4.runtime 如何實(shí)現(xiàn) weak 屬性湖笨?
weak 此特質(zhì)表明該屬性定義了一種「非擁有關(guān)系」(nonowning relationship)。
為這種屬性設(shè)置新值時(shí)蹦骑,設(shè)置方法既不持有新值(新指向的對象)慈省,也不釋放舊值(原來指向的對象)。
runtime 對注冊的類脊串,會進(jìn)行內(nèi)存布局辫呻,從一個粗粒度的概念上來講,這時(shí)候會有一個 hash 表琼锋,這是一個全局表放闺,表中是用 weak 指向的對象內(nèi)存地址作為 key,用所有指向該對象的 weak 指針表作為 value缕坎。
當(dāng)此對象的引用計(jì)數(shù)為 0 的時(shí)候會 dealloc怖侦,假如該對象內(nèi)存地址是 a,那么就會以 a 為 key,在這個 weak 表中搜索匾寝,找到所有以 a 為鍵的 weak 對象搬葬,從而設(shè)置為 nil。
runtime 如何實(shí)現(xiàn) weak 屬性具體流程大致分為 3 步:
- 1艳悔、初始化時(shí):runtime 會調(diào)用 objc_initWeak 函數(shù)急凰,初始化一個新的 weak 指針指向?qū)ο蟮牡刂贰?/li>
- 2、添加引用時(shí):objc_initWeak 函數(shù)會調(diào)用 objc_storeWeak() 函數(shù)猜年,objc_storeWeak() 的作用是更新指針指向(指針可能原來指向著其他對象抡锈,這時(shí)候需要將該 weak 指針與舊對象解除綁定,會調(diào)用到 weak_unregister_no_lock)乔外,如果指針指向的新對象非空床三,則創(chuàng)建對應(yīng)的弱引用表,將 weak 指針與新對象進(jìn)行綁定杨幼,會調(diào)用到 weak_register_no_lock撇簿。在這個過程中,為了防止多線程中競爭沖突差购,會有一些鎖的操作四瘫。
- 3、釋放時(shí):調(diào)用 clearDeallocating 函數(shù)歹撒,clearDeallocating 函數(shù)首先根據(jù)對象地址獲取所有 weak 指針地址的數(shù)組莲组,然后遍歷這個數(shù)組把其中的數(shù)據(jù)設(shè)為 nil,最后把這個 entry 從 weak 表中刪除暖夭,最后清理對象的記錄。
5.講一下 OC 的消息機(jī)制
- OC中的方法調(diào)用其實(shí)都是轉(zhuǎn)成了objc_msgSend函數(shù)的調(diào)用撵孤,給receiver(方法調(diào)用者)發(fā)送了一條消息(selector方法名)
- objc_msgSend底層有3大階段,消息發(fā)送(當(dāng)前類、父類中查找)浅辙、動態(tài)方法解析删顶、消息轉(zhuǎn)發(fā)
6.runtime具體應(yīng)用
- 利用關(guān)聯(lián)對象(AssociatedObject)給分類添加屬性
- 遍歷類的所有成員變量(修改textfield的占位文字顏色、字典轉(zhuǎn)模型闭专、自動歸檔解檔)
- 交換方法實(shí)現(xiàn)(交換系統(tǒng)的方法)
- 利用消息轉(zhuǎn)發(fā)機(jī)制解決方法找不到的異常問題
- KVC 字典轉(zhuǎn)模型
7.runtime如何通過selector找到對應(yīng)的IMP地址奴潘?
每一個類對象中都一個對象方法列表(對象方法緩存)
- 類方法列表是存放在類對象中isa指針指向的元類對象中(類方法緩存)。
- 方法列表中每個方法結(jié)構(gòu)體中記錄著方法的名稱,方法實(shí)現(xiàn),以及參數(shù)類型影钉,其實(shí)selector本質(zhì)就是方法名稱,通過這個方法名稱就可以在方法列表中找到對應(yīng)的方法實(shí)現(xiàn)画髓。
- 當(dāng)我們發(fā)送一個消息給一個NSObject對象時(shí),這條消息會在對象的類對象方法列表里查找平委。
- 當(dāng)我們發(fā)送一個消息給一個類時(shí)奈虾,這條消息會在類的Meta Class對象的方法列表里查找。
8.簡述下Objective-C中調(diào)用方法的過程
Objective-C是動態(tài)語言,每個方法在運(yùn)行時(shí)會被動態(tài)轉(zhuǎn)為消息發(fā)送肉微,即:objc_msgSend(receiver, selector)匾鸥,整個過程介紹如下:
objc在向一個對象發(fā)送消息時(shí),runtime庫會根據(jù)對象的isa指針找到該對象實(shí)際所屬的類
然后在該類中的方法列表以及其父類方法列表中尋找方法運(yùn)行
如果碉纳,在最頂層的父類(一般也就NSObject)中依然找不到相應(yīng)的方法時(shí)勿负,程序在運(yùn)行時(shí)會掛掉并拋出異常unrecognized selector sent to XXX
-
但是在這之前,objc的運(yùn)行時(shí)會給出三次拯救程序崩潰的機(jī)會:
Method resolution
objc運(yùn)行時(shí)會調(diào)用+resolveInstanceMethod:或者 +resolveClassMethod:劳曹,讓你有機(jī)會提供一個函數(shù)實(shí)現(xiàn)奴愉。如果你添加了函數(shù),那運(yùn)行時(shí)系統(tǒng)就會重新啟動一次消息發(fā)送的過程厚者,否則 躁劣,運(yùn)行時(shí)就會移到下一步,消息轉(zhuǎn)發(fā)(Message Forwarding)库菲。
Fast forwarding
如果目標(biāo)對象實(shí)現(xiàn)了-forwardingTargetForSelector:账忘,Runtime 這時(shí)就會調(diào)用這個方法,給你把這個消息轉(zhuǎn)發(fā)給其他對象的機(jī)會熙宇。 只要這個方法返回的不是nil和self鳖擒,整個消息發(fā)送的過程就會被重啟,當(dāng)然發(fā)送的對象會變成你返回的那個對象烫止。否則蒋荚,就會繼續(xù)Normal Fowarding。 這里叫Fast馆蠕,只是為了區(qū)別下一步的轉(zhuǎn)發(fā)機(jī)制期升。因?yàn)檫@一步不會創(chuàng)建任何新的對象,但下一步轉(zhuǎn)發(fā)會創(chuàng)建一個NSInvocation對象互躬,所以相對更快點(diǎn)播赁。
Normal forwarding
這一步是Runtime最后一次給你挽救的機(jī)會。首先它會發(fā)送-methodSignatureForSelector:消息獲得函數(shù)的參數(shù)和返回值類型吼渡。如果-methodSignatureForSelector:返回nil容为,Runtime則會發(fā)出-doesNotRecognizeSelector:消息,程序這時(shí)也就掛掉了寺酪。如果返回了一個函數(shù)簽名坎背,Runtime就會創(chuàng)建一個NSInvocation對象并發(fā)送-forwardInvocation:消息給目標(biāo)對象。
9.load和initialize的區(qū)別寄雀?
兩者都會自動調(diào)用父類的得滤,不需要super操作,且僅會調(diào)用一次(不包括外部顯示調(diào)用).
- load和initialize方法都會在實(shí)例化對象之前調(diào)用咙俩,以main函數(shù)為分水嶺耿戚,前者在main函數(shù)之前調(diào)用湿故,后者在之后調(diào)用。這兩個方法會被自動調(diào)用膜蛔,不能手動調(diào)用它們坛猪。
- load和initialize方法都不用顯示的調(diào)用父類的方法而是自動調(diào)用,即使子類沒有initialize方法也會調(diào)用父類的方法皂股,而load方法則不會調(diào)用父類墅茉。
- load方法通常用來進(jìn)行Method Swizzle,initialize方法一般用于初始化全局變量或靜態(tài)變量呜呐。
- load和initialize方法內(nèi)部使用了鎖就斤,因此它們是線程安全的。實(shí)現(xiàn)時(shí)要盡可能保持簡單蘑辑,避免阻塞線程洋机,不要再使用鎖。
10.怎么理解Objective-C是動態(tài)運(yùn)行時(shí)語言洋魂?
- 主要是將數(shù)據(jù)類型的確定由編譯時(shí),推遲到了運(yùn)行時(shí)绷旗。這個問題其實(shí)淺涉及到兩個概念,運(yùn)行時(shí)和多態(tài)。
- 簡單來說, 運(yùn)行時(shí)機(jī)制使我們直到運(yùn)行時(shí)才去決定一個對象的類別,以及調(diào)用該類別對象指定方法副砍。
- 多態(tài):不同對象以自己的方式響應(yīng)相同的消息的能力叫做多態(tài)衔肢。
- 意思就是假設(shè)生物類(life)都擁有一個相同的方法-eat;那人類屬于生物,豬也屬于生物,都繼承了life后,實(shí)現(xiàn)各自的eat,但是調(diào)用是我們只需調(diào)用各自的eat方法。
也就是不同的對象以自己的方式響應(yīng)了相同的消 息(響應(yīng)了eat這個選擇器)豁翎。因此也可以說,運(yùn)行時(shí)機(jī)制是多態(tài)的基礎(chǔ).
KVO怎么用? 實(shí)現(xiàn)方式是什么角骤。可不可以多次監(jiān)控(addObserve)? 可不可以多次移除?? 線程 A 監(jiān)控心剥,線程 B 改變值邦尊, 最終體現(xiàn)在哪個線程
KVO怎么用:對被觀察的對象添加觀察者,同時(shí)被觀察者要實(shí)現(xiàn) 改變方法的回調(diào)优烧。
KVO的實(shí)現(xiàn)原理:Apple利用isa-swizzling來實(shí)現(xiàn)KVO胳赌,當(dāng)某個類的對象屬性第一次被觀察時(shí),系統(tǒng)會在運(yùn)行期間動態(tài)的創(chuàng)建該類的派生類匙隔,在這個派生類中重寫任何被觀察屬性的setter方法。每個類對象中都有一個isa指針熏版,指向當(dāng)前類纷责。當(dāng)一個類對象第一次被觀察時(shí),系統(tǒng)就會偷偷將isa指針指向動態(tài)生成的派生類撼短,從而在給被監(jiān)控屬性賦值時(shí)再膳,實(shí)際執(zhí)行的是派生類的setter方法。鍵值觀察通知依賴于NSObject的兩個方法:willChangeValueForKey:和didChangeValueForKey:,在一個被觀察屬性發(fā)生改變之前曲横,willChangeValueForKey:一定會被調(diào)用喂柒,這就會記錄舊的值不瓶。而當(dāng)改變發(fā)生后,didChangeValueForKey:會被調(diào)用灾杰,繼而observeValueForKey:ofObject:change:context:也會被調(diào)用
可不可以多次監(jiān)控:可以多次監(jiān)控蚊丐,能收到多次回調(diào)。
可不可以多次移除:不可以多次移除艳吠,添加和移除需要成對出現(xiàn)麦备,只有已經(jīng)注冊了的觀察者才可以被移除,否則會報(bào)錯昭娩。
線程 A 監(jiān)控凛篙,線程 B 改變值, 最終體現(xiàn)在哪個線程:體現(xiàn)在線程B栏渺,這是由派生類的setter方法來決定的呛梆,在哪個線程改變就體現(xiàn)在哪個線程。
7. 內(nèi)存管理
1.什么情況使用weak關(guān)鍵字磕诊,相比assign有什么不同填物?
在ARC中,在有可能出現(xiàn)循環(huán)引用的時(shí)候秀仲,往往需要讓其中一端使用weak來打破循環(huán)引用融痛。比如delegate代理屬性;當(dāng)自身已經(jīng)對某對象進(jìn)行一次強(qiáng)引用神僵,沒必要再強(qiáng)引用一次雁刷,此時(shí)也會使用weak,比如從圖形化界面引入到代碼中 IBOutlet控件一般使用weak保礼,當(dāng)然也可以使用strong沛励。
與assign不同之處在于,weak表明該屬性定義了一種非擁有關(guān)系 nonowning relationship.為這種屬性設(shè)置新值時(shí)炮障,設(shè)置方法既不保留新值目派,也不釋放舊值。此特質(zhì)與assign類似胁赢,不同之處在于 屬性所指的對象銷毀時(shí)企蹭,屬性值會設(shè)置為nil。
assign的設(shè)置方法只會執(zhí)行針對 純量類型(scalar type智末,比如 CGFloat或NSInteger等 )的簡單賦值操作谅摄。assign可以用非OC對象,而weak必須用于OC對象系馆。
2.如何讓自己的類用copy修飾符送漠?如何重寫帶copy關(guān)鍵字的setter?
如果想讓自定義對象具有拷貝功能由蘑,需要實(shí)現(xiàn)nscoping協(xié)議
如果自定義對象分為可變版本與不可變版本闽寡,那么需要同時(shí)實(shí)現(xiàn)NSCopying與NSMutableCopying協(xié)議代兵。
首先,聲明該類遵守NSCopying協(xié)議爷狈,實(shí)現(xiàn)協(xié)議方法:- (id)copyWithZone:(NSZone *)zone;
重寫帶copy關(guān)鍵字的setter方法
- (void)setName:(NSString *)name { //[_name release]; _name = [name copy]; }
3.深拷貝與淺拷貝分別是什么植影?
淺拷貝只是對指針的拷貝,拷貝后兩個指針指向同一個內(nèi)存空間淆院,深拷貝是對指針指向的內(nèi)容進(jìn)行拷貝何乎,經(jīng)深拷貝后的指針指向的是兩個不同地址的指針。當(dāng)對象中存在指針成員時(shí)土辩,除了在復(fù)制對象時(shí)需要考慮自定義拷貝構(gòu)造函數(shù)支救,還應(yīng)該考慮以下兩種情形:
- 當(dāng)函數(shù)的參數(shù)為對象時(shí),實(shí)參傳遞給形參的實(shí)際上是實(shí)參的一個拷貝對象拷淘,系統(tǒng)自動通過構(gòu)造函數(shù)實(shí)現(xiàn)
- 當(dāng)函數(shù)的返回值為一個對象時(shí)各墨,該對象實(shí)際上是函數(shù)內(nèi)對象的一個拷貝,用戶返回函數(shù)調(diào)用處
- 一般來說我們傾向于 copy方法是淺拷貝启涯,mutablecopy是深拷貝贬堵。但也不必然,這取決于copy協(xié)議及可變copy的具體實(shí)現(xiàn)的方法
4.@property的本質(zhì)是什么结洼?ivar黎做、getter、setter是如何生成并添加到這個類中的松忍?
屬性的本質(zhì)是 實(shí)例變量 ivar + 存取方法 getter + setter
屬性作為oc的一項(xiàng)特性蒸殿,主要作用在于封裝對象中的數(shù)據(jù),OC對象通常會把其所需要的數(shù)據(jù)保存為各種實(shí)例變量鸣峭。實(shí)例變量一般通過存取方法來訪問宏所,getter用于讀取變量的值,而setter用于寫入變量的值
ivar摊溶、getter爬骤、setter是自動合成這個類中,完成屬性定義后莫换,編譯器會自動編寫訪問這些屬性所需的方法霞玄,這個過程叫做自動合成autosynthesis,需要強(qiáng)調(diào)的是拉岁,這個過程由編譯器在編譯器執(zhí)行溃列,所以編輯器里看不到這些合成方法synthesized method的源代碼。除了生成方法 getter和setter之外膛薛,編譯器還要自動向類中添加適當(dāng)類型的實(shí)例變量,并在屬性名前加下劃線补鼻,以此作為實(shí)例變量的名字哄啄。也可以在類的實(shí)現(xiàn)代碼里雅任,通過@synthesize語法來制定實(shí)例變量的名字。
5.@protocol和category中如何使用@property
- 協(xié)議中使用屬性只會生成setter和getter方法聲明咨跌。我們在協(xié)議中使用屬性的目的沪么,是希望遵守我們協(xié)議的對象能夠?qū)崿F(xiàn)該屬性。
- 分類使用屬性也是只會生成setter和getter方法的聲明锌半,如果我們真的要給分類添加屬性的實(shí)現(xiàn)禽车,需要借助于運(yùn)行時(shí)的兩個函數(shù):objc_setAssociatedObject和objc_getAssociatedObject
6.使用CADisplayLink、NSTimer有什么注意點(diǎn)刊殉?BAD_ACCESS在什么情況下出現(xiàn)殉摔?
CADisplayLink、NSTimer可能會造成循環(huán)引用记焊,可以使用YYWeakProxy或者為CADisplayLink逸月、NSTimer添加block來解決循環(huán)引用,或者使用GCD的定時(shí)器
BAD_ACCESS會在訪問野指針時(shí)出現(xiàn)遍膜,就是說訪問了一個已經(jīng)被釋放了的對象會出現(xiàn)碗硬。死循環(huán)。
7.iOS內(nèi)存分區(qū)情況
- 棧區(qū)Stack:棧是向低地址擴(kuò)展的數(shù)據(jù)結(jié)構(gòu)瓢颅,是一塊連續(xù)的內(nèi)存空間恩尾。內(nèi)存由編譯器自動分配釋放;存放函數(shù)的參數(shù)挽懦、局部變量的值等翰意;
- 堆區(qū)Heap:堆是向高地址擴(kuò)展的數(shù)據(jù)結(jié)構(gòu),是不連續(xù)的內(nèi)存區(qū)域巾兆,由程序員負(fù)責(zé)內(nèi)存的分配與釋放
- 全局區(qū):全局變量和靜態(tài)變量的存儲是放在一塊兒的猎物,初始化的全局變量和靜態(tài)變量在一塊兒區(qū)域,未初始化的全局變量和未初始化的靜態(tài)變量在相鄰的另一塊區(qū)域角塑。程序結(jié)束后由系統(tǒng)釋放蔫磨。
- 常量區(qū):常量字符串就是放在這里的,程序結(jié)束后由系統(tǒng)釋放
- 代碼區(qū):存放函數(shù)體的二進(jìn)制代碼
注意:在iOS中圃伶,堆區(qū)的內(nèi)存是應(yīng)用程序共享的堤如,堆中的內(nèi)存分配是系統(tǒng)負(fù)責(zé)的。系統(tǒng)使用一個鏈表來維護(hù)所有已經(jīng)分配的內(nèi)存空間(系統(tǒng)緊緊記錄窒朋,并不管理具體的內(nèi)容)搀罢;變量使用結(jié)束后,需要釋放內(nèi)存侥猩,OC中是判斷引用計(jì)數(shù)是否為0榔至,如果是就說明沒有任何變量使用該空間,那么系統(tǒng)將其回收欺劳;當(dāng)一個app啟動后唧取,代碼區(qū)铅鲤、常量區(qū)、全局區(qū)大小就已經(jīng)固定枫弟,因此指向這些區(qū)的指針不會產(chǎn)生崩潰性錯誤邢享。而堆區(qū)和棧區(qū)是時(shí)時(shí)刻刻變化的(堆的創(chuàng)建銷毀,棧的彈入彈出)淡诗,所以當(dāng)使用一個指針指向這個區(qū)域里面的內(nèi)存時(shí)骇塘,一定要注意內(nèi)存是否已經(jīng)被釋放,否則會出現(xiàn)野指針報(bào)錯使程序崩潰韩容。
8.iOS內(nèi)存管理方式
-
Tagged Pointer(小對象)
tagged pointer 專門用來存儲小的對象款违,例如NSNumber和NSDate。Tagged Pointer 指針的值不再是地址了宙攻,而是真正的值奠货,所以,實(shí)際上它不再是一個對象了座掘,它只是一個披著對象皮的普通變量而已递惋,所以它的內(nèi)存并不存儲在堆中,也不需要malloc和free溢陪。在內(nèi)存讀取上有著3倍的效率萍虽,創(chuàng)建時(shí)比以前快106倍。obj_msgSend能識別Tagged Pointer形真,比如NSNumber和intVlaue方法杉编,直接從指針提取數(shù)據(jù)。使用Tagged Pointer后咆霜,指針內(nèi)存儲的數(shù)據(jù)變成了Tag+Data也就是將數(shù)據(jù)直接存儲在了指針中邓馒。NONPOINTER_ISA指針中存放與該對象內(nèi)存相關(guān)的信息。蘋果將isa設(shè)計(jì)成了聯(lián)合體蛾坯,在isa中存儲了與該對象相關(guān)的一些內(nèi)存信息光酣,原因也如上面所說,并不需要64個二進(jìn)制位全部用來存儲指針脉课。isa的結(jié)構(gòu)如下
// arm64 架構(gòu) struct { uintptr_t nonpointer : 1; // 0:普通指針救军,1:優(yōu)化過,使用位域存儲更多信息 uintptr_t has_assoc : 1; // 對象是否含有或曾經(jīng)含有關(guān)聯(lián)引用 uintptr_t has_cxx_dtor : 1; // 表示是否有C++析構(gòu)函數(shù)或OC的dealloc uintptr_t shiftcls : 33; // 存放著 Class倘零、Meta-Class 對象的內(nèi)存地址信息 uintptr_t magic : 6; // 用于在調(diào)試時(shí)分辨對象是否未完成初始化 uintptr_t weakly_referenced : 1; // 是否被弱引用指向 uintptr_t deallocating : 1; // 對象是否正在釋放 uintptr_t has_sidetable_rc : 1; // 是否需要使用 sidetable 來存儲引用計(jì)數(shù) uintptr_t extra_rc : 19; // 引用計(jì)數(shù)能夠用 19 個二進(jìn)制位存儲時(shí)唱遭,直接存儲在這里 };
這里的has_sidetable_rc和extra_rc,has_sidetable_rc表明該指針是否引用了sidetable散列表呈驶,之所以有這個選項(xiàng)拷泽,是因?yàn)樯倭康囊糜?jì)數(shù)不會直接存放在sideTables表中的,對象的引用計(jì)數(shù)會先存放在extra_rc中,當(dāng)其被存滿時(shí)跌穗,才會存入相應(yīng)的sidetables散列表中订晌,sidetables中有很多sidetable,每個sidetable也都是一個散列表蚌吸。而引用計(jì)數(shù)就包含在sidetable中
散列表(引用計(jì)數(shù)表、弱引用表):引用計(jì)數(shù)要么存放在 isa 的 extra_rc 中砌庄,要么存放在引用計(jì)數(shù)表中羹唠,而引用計(jì)數(shù)表包含在一個叫 SideTable 的結(jié)構(gòu)中,它是一個散列表娄昆,也就是哈希表佩微。而 SideTable 又包含在一個全局的 StripeMap 的哈希映射表中,這個表的名字叫 SideTables萌焰。
當(dāng)一個對象訪問sidetables時(shí)哺眯,首先會取得對象的地址,將地址進(jìn)行哈希運(yùn)算扒俯,與sidetables中sidetable的個數(shù)取余奶卓,最后得到的結(jié)果就是該對象所要防問的sidetable;在取得sidetable中的refcountmap表中再進(jìn)行一次hash查找撼玄,找到該對象在引用計(jì)數(shù)表中的位置夺姑;如果該位置存在對應(yīng)的引用計(jì)數(shù),則對其進(jìn)行操作掌猛。如果沒有則創(chuàng)建一個對應(yīng)的size_t對象盏浙,其實(shí)就是一個uint類型的無符號整型;若引用表也是一張hash表的結(jié)構(gòu)荔茬,其內(nèi)部包含了每個對象對應(yīng)的弱引用表weak_entry_t废膘,而weak_entry_t是一個結(jié)構(gòu)體數(shù)組,其中包含的則是每一個弱引用對象所對應(yīng)的弱引用指針慕蔚。
9.循環(huán)引用
循環(huán)引用的實(shí)質(zhì):多個對象相互之間有強(qiáng)引用丐黄,不能釋放讓系統(tǒng)回收。
如何解決循環(huán)引用坊萝?
1孵稽、避免產(chǎn)生循環(huán)引用,通常是將 strong 引用改為 weak 引用十偶。比如在修飾屬性時(shí)用weak 在block內(nèi)調(diào)用對象方法時(shí)菩鲜,使用其弱引用,這里可以使用兩個宏
#define WS(weakSelf) __weak __typeof(&*self)weakSelf = self; // 弱引用
#define ST(strongSelf) __strong __typeof(&*self)strongSelf = weakSelf;
//使用這個要先聲明weakSelf 還可以使用__block來修飾變量 在MRC下惦积,__block不會增加其引用計(jì)數(shù)接校,避免了循環(huán)引用 在ARC下,__block修飾對象會被強(qiáng)引用,無法避免循環(huán)引用蛛勉,需要手動解除鹿寻。
2、在合適時(shí)機(jī)去手動斷開循環(huán)引用诽凌。通常我們使用第一種毡熏。
- 代理(delegate)循環(huán)引用屬于相互循環(huán)引用
delegate 是iOS中開發(fā)中比較常遇到的循環(huán)引用,一般在聲明delegate的時(shí)候都要使用弱引用 weak,或者assign,當(dāng)然怎么選擇使用assign還是weak侣诵,MRC的話只能用assign痢法,在ARC的情況下最好使用weak,因?yàn)閣eak修飾的變量在釋放后自動指向nil杜顺,防止野指針存在
- NSTimer循環(huán)引用屬于相互循環(huán)使用
在控制器內(nèi)财搁,創(chuàng)建NSTimer作為其屬性,由于定時(shí)器創(chuàng)建后也會強(qiáng)引用該控制器對象躬络,那么該對象和定時(shí)器就相互循環(huán)引用了尖奔。如何解決呢?這里我們可以使用手動斷開循環(huán)引用:如果是不重復(fù)定時(shí)器穷当,在回調(diào)方法里將定時(shí)器invalidate并置為nil即可提茁。如果是重復(fù)定時(shí)器,在合適的位置將其invalidate并置為nil即可
3膘滨、block循環(huán)引用
一個簡單的例子:
@property (copy, nonatomic) dispatch_block_t myBlock;
@property (copy, nonatomic) NSString *blockString;
- (void)testBlock {
self.myBlock = ^() {
NSLog(@"%@",self.blockString);
};
}
由于block會對block中的對象進(jìn)行持有操作,就相當(dāng)于持有了其中的對象甘凭,而如果此時(shí)block中的對象又持有了該block,則會造成循環(huán)引用火邓。解決方案就是使用__weak修飾self即可
__weak typeof(self) weakSelf = self;
self.myBlock = ^() {
NSLog(@"%@",weakSelf.blockString);
};
并不是所有block都會造成循環(huán)引用丹弱。只有被強(qiáng)引用了的block才會產(chǎn)生循環(huán)引用 而比如dispatch_async(dispatch_get_main_queue(), ^{}),[UIView animateWithDuration:1 animations:^{}]這些系統(tǒng)方法等 或者block并不是其屬性而是臨時(shí)變量,即棧block
[self testWithBlock:^{
NSLog(@"%@",self);
}];
- (void)testWithBlock:(dispatch_block_t)block {
block();
}
還有一種場景,在block執(zhí)行開始時(shí)self對象還未被釋放铲咨,而執(zhí)行過程中躲胳,self被釋放了,由于是用weak修飾的纤勒,那么weakSelf也被釋放了坯苹,此時(shí)在block里訪問weakSelf時(shí),就可能會發(fā)生錯誤(向nil對象發(fā)消息并不會崩潰摇天,但也沒任何效果)粹湃。
對于這種場景,應(yīng)該在block中對 對象使用__strong修飾泉坐,使得在block期間對 對象持有为鳄,block執(zhí)行結(jié)束后,解除其持有腕让。
__weak typeof(self) weakSelf = self;
self.myBlock = ^() {
__strong __typeof(self) strongSelf = weakSelf;
[strongSelf test];
};
10.ARC 的 retainCount 怎么存儲的孤钦?
存在64張哈希表中,根據(jù)哈希算法去查找所在的位置奢方,無需遍歷哮内,十分快捷
散列表(引用計(jì)數(shù)表范抓、weak表)
- SideTables 表在 非嵌入式的64位系統(tǒng)中侵俗,有 64張 SideTable 表
- 每一張 SideTable 主要是由三部分組成。自旋鎖伐割、引用計(jì)數(shù)表妥箕、弱引用表连躏。
- 全局的 引用計(jì)數(shù) 之所以不存在同一張表中萨惑,是為了避免資源競爭喘帚,解決效率的問題。
- 引用計(jì)數(shù)表 中引入了 分離鎖的概念咒钟,將一張表分拆成多個部分,對他們分別加鎖若未,可以實(shí)現(xiàn)并發(fā)操作朱嘴,
提升執(zhí)行效率
- 引用計(jì)數(shù)表(哈希表)
- 通過指針的地址,查找到引用計(jì)數(shù)的地址粗合,大大提升查找效率
- 通過 DisguisedPtr(objc_object) 函數(shù)存儲萍嬉,同時(shí)也通過這個函數(shù)查找,這樣就避免了循環(huán)遍歷隙疚。
11.ARC
在編譯時(shí)做了哪些工作壤追?
根據(jù)代碼執(zhí)行的上下文語境,在適當(dāng)?shù)奈恢貌迦?retain供屉,release
8.數(shù)據(jù)結(jié)構(gòu)
1.數(shù)據(jù)結(jié)構(gòu)的存儲一般常用的有幾種行冰?各有什么特點(diǎn)?
- 順序存儲:比如數(shù)組伶丐,存儲是按順序的悼做,再比如棧和隊(duì)列等
- 鏈?zhǔn)酱鎯Γ河靡唤M任意的存儲單元存儲線性表的數(shù)據(jù)元素(這組存儲單元可以是連續(xù)的,也可以是不連續(xù)的).它不要求邏輯上相鄰的元素在物理位置上也相鄰.因此它沒有順序存儲結(jié)構(gòu)所具有的弱點(diǎn),但也同時(shí)失去了順序表可隨機(jī)存取的優(yōu)點(diǎn).
2.集合結(jié)構(gòu) 線性結(jié)構(gòu) 樹形結(jié)構(gòu) 圖形結(jié)構(gòu)
- 集合結(jié)構(gòu):一個集合,就是一個圓圈中有很多個元素哗魂,元素之間沒有任何關(guān)系
- 線性結(jié)構(gòu):一條線上站著很多人肛走,這條線不一定是直的,也可以是彎的录别。線性結(jié)構(gòu)是n個數(shù)據(jù)元素的有序(次序)集合朽色,線性結(jié)構(gòu)是一對一關(guān)系。比如從a0到aN
- 樹形結(jié)構(gòu):n個節(jié)點(diǎn)的有限集组题,樹形結(jié)構(gòu)指的是數(shù)據(jù)元素之間存在著“一對多”的樹形關(guān)系的數(shù)據(jù)結(jié)構(gòu)葫男。樹形結(jié)構(gòu)是一對多的關(guān)系
- 圖形結(jié)構(gòu):結(jié)點(diǎn)之間的結(jié)點(diǎn)之間的鄰接關(guān)系可以是任意的,
3.單向鏈表 雙向鏈表 循環(huán)鏈表 分別為什么往踢?
- 單向鏈表:鏈表的鏈接方向是單向的腾誉,對鏈表的訪問要通過順序讀取從頭部開始
- 雙向鏈表:鏈接的方向是雙向的,它的每個數(shù)據(jù)結(jié)點(diǎn)中都有兩個指針,分別指向直接后繼和直接前驅(qū)利职。所以趣效,從雙向鏈表中的任意一個結(jié)點(diǎn)開始,都可以很方便地訪問它的前驅(qū)結(jié)點(diǎn)和后繼結(jié)點(diǎn)猪贪。
- 循環(huán)鏈表:循環(huán)鏈表是與單向鏈表一樣跷敬,是一種鏈?zhǔn)降拇鎯Y(jié)構(gòu),所不同的是热押,循環(huán)鏈表的最后一個結(jié)點(diǎn)的指針是指向該循環(huán)鏈表的第一個結(jié)點(diǎn)或者表頭結(jié)點(diǎn)西傀,從而構(gòu)成一個環(huán)形的鏈。
4.數(shù)組和鏈表區(qū)別有哪些桶癣?
- 數(shù)組:數(shù)組元素在內(nèi)存上連續(xù)存放拥褂,可以通過下標(biāo)查找元素;插入牙寞、刪除需要移動大量元素饺鹃,比較適用于元素很少變化的情況
- 鏈表:鏈表中的元素在內(nèi)存中不是順序存儲,查找慢间雀,插入刪除只需要對指針重新賦值悔详,效率高。
5.堆惹挟、棧和隊(duì)列 分別是什么茄螃?
-
堆:堆是一種經(jīng)過排序的樹形結(jié)構(gòu),每一個節(jié)點(diǎn)都有一個值连锯,通常我們所說的堆的數(shù)據(jù)結(jié)構(gòu)是指二叉樹归苍。所以堆在數(shù)據(jù)結(jié)構(gòu)中通常可以被看做是一棵樹的數(shù)組對象萎庭。而且堆需要滿足以下兩個性質(zhì)
- 1 堆中某個節(jié)點(diǎn)的值總是不大于或者不小于其父節(jié)點(diǎn)的值
- 2 堆總是一棵完全二叉樹
堆分為兩種情況霜医,有最大堆和最小堆。根節(jié)點(diǎn)最大的堆叫做最大堆驳规,根節(jié)點(diǎn)最小的叫做最小堆肴敛。在一個擺放好元素的最小堆中,父節(jié)點(diǎn)的元素一定比子節(jié)點(diǎn)的元素小吗购,但對于左右節(jié)點(diǎn)的大小沒有規(guī)定誰大誰小医男。
堆常用來實(shí)現(xiàn)優(yōu)先隊(duì)列,堆的存取是隨意的捻勉,就像在圖書館的書架上取書镀梭,雖然書的擺放是有順序的,但是我們想取任意一本書時(shí)不必像棧一樣踱启,先取出前面所有的書报账,書架這種機(jī)制不同于箱子研底,我們可以直接取出我們想要的書。
-
棧:棧是限定僅在表尾進(jìn)行插入和刪除操作的線性表透罢。我們把允許插入和刪除的一端稱為棧頂榜晦,另一端稱為棧低,不含任何數(shù)據(jù)元素的棧稱為空棧羽圃。棧的特殊之處在于它限制了這個線性表的插入和刪除位置乾胶,它始終只在棧頂進(jìn)行。
棧是一種具有后進(jìn)先出的數(shù)據(jù)結(jié)構(gòu)朽寞,又稱為后進(jìn)先出線性表识窿,簡稱LIFO last in first out結(jié)構(gòu)。
棧中定義了一些操作脑融,最要的是push和pop喻频。push向棧頂壓入一個元素,pop在棧頂移除一個元素肘迎,并將堆棧的大小減1
棧的應(yīng)用:遞歸
-
隊(duì)列:隊(duì)列是只允許在一端進(jìn)行插入操作而在另一端進(jìn)行刪除操作的線性表半抱。允許插入的一端稱為隊(duì)尾,允許刪除的一端稱為隊(duì)頭膜宋。它是一種特殊 的線性表,特殊在限定插入在隊(duì)尾炼幔,刪除在隊(duì)頭秋茫。和棧一樣,隊(duì)列是一種操作受限的線性表乃秀。
隊(duì)列是一種先進(jìn)先出的數(shù)據(jù)結(jié)構(gòu)肛著,又稱為先進(jìn)先出數(shù)據(jù)表,簡稱FIFO跺讯。
6.輸入一棵二叉樹的根結(jié)點(diǎn)枢贿,求該樹的深度?
二叉樹的節(jié)點(diǎn)定義如下
struct BinaryTreeNode{
int m_nValue;
BinaryTreeNode* m_pLeft;
BinaryTreeNode* m_pRight;
}
如果一棵樹只有一個節(jié)點(diǎn)刀脏,它的深度為1.如果根節(jié)點(diǎn)只有左側(cè)樹而沒有右側(cè)樹局荚,那么樹的深度應(yīng)該是左側(cè)樹的深度+1;同樣愈污,如果根結(jié)點(diǎn)只有右子樹而沒有左子樹耀态,那么樹的深度應(yīng)該是其右子樹的深度加1。如果既有左側(cè)樹又有右側(cè)樹暂雹,那該樹的深度是左右樹深度的較大值再+1
int TreeDepth(TreeNode *pRoot){
if(pRoot == nullptr) return 0;
int left = TreeDepth(pRoot->left);
int right = TreeDepth(pRoot->right);
return left > right ? left + 1 : right + 1
}
7.輸入一課二叉樹的根結(jié)點(diǎn)首装,判斷該樹是不是平衡二叉樹?
-
重復(fù)遍歷節(jié)點(diǎn)
先求出根節(jié)點(diǎn)的左右子樹的深度杭跪,然后判斷他們的深度相差不超過1仙逻,如果否則不是平衡二叉樹驰吓,如果是再用同樣的方法判斷左右子樹是否為平衡二叉樹,如果都是則這就是一棵平衡二叉樹系奉。
-
遍歷一遍節(jié)點(diǎn)
遍歷節(jié)點(diǎn)的同時(shí)記錄下該節(jié)點(diǎn)的深度檬贰,避免重復(fù)訪問。
方法1:
struct TreeNode{ int val; TreeNode* left; TreeNode* right; }; int TreeDepth(TreeNode* pRoot){ if (pRoot == NULL) return 0; int left = TreeDepth(pRoot->left); int right = TreeDepth(pRoot->right); return left>right ? left + 1 : right + 1 } bool IsBalanced(TreeNode* pRoot){ if(pRoot == NULL) return true; int left = TreeDepth(pRoot->left); int right = TreeDepth(pRoot->right); int diff = left - right; if (diff > 1 || diff < -1) return flase; return IsBalanced(pRoot->left) && IsBalanced(pRoot->right); }
方法2:
bool IsBalanced_1(TreeNode* pRoot, int& depth){ if (pRoot==NULL){ depth = 0; return true; } int left,right; int diff; if (IsBalanced_1(pRoot->left,left) && IsBalanced_1(pRoot->right,right)){ diff=left-right; if(diff < 1 || diff >= -1){ depth = left > right ? left + 1 : right + 1; return true; } } return false; } bool IsBalancedTree(TreeNode* pRoot){ int depth = 0; return IsBalanced_1(pRoot,depth); }
8.字符匹配 & 字符去重的方法
給你一個僅包含小寫字母的字符串喜最,請你去除字符串中重復(fù)的字母偎蘸,使得每個字母只出現(xiàn)一次。需保證返回結(jié)果的字典序最兴材凇(要求不能打亂其他字符的相對位置)
1.什么叫字典序:26個英文字母的順序
2.什么叫字符的相對位置迷雪,就是你只能刪除重復(fù)的但是不能左右移動元素
我們來模擬下從第一個元素到最后一個元素的過程算法就出來了:
step1:c
step2:cb
step3:cba
step4:bac
step5:bacd
step6:acdb
按照我們的思維來寫算法:定義一個結(jié)果集,依次遍歷每個元素,結(jié)果集不存在就加入虫蝶,如果存在就判斷兩個元素之間的元素有沒有比當(dāng)前元素小的章咧,有的話就刪掉之前的,比如bacdb能真,兩個b之間有a比b小赁严,所以要刪掉之前的b,改成acdb
這里用另一個方法棧+標(biāo)志來實(shí)現(xiàn)
說明:棧中存的元素滿足兩個條件之一就可以:1.元素都是遞增的粉铐,2疼约,當(dāng)前元素在后續(xù)不在出現(xiàn)(過了這個村就沒這個店了)
public static String removeDuplicateLetters(String s) {
if (s == null || s.equals("")) {
return "";
}
char[] strArr = s.toCharArray();
return getResult(strArr);
}
public static String getResult(char[] strArr) {
String result = "";
//記錄所有幾點(diǎn)的最后一個出現(xiàn)的位置
Map<Character, Integer> memory = new HashMap<>();
//記錄哪些節(jié)點(diǎn)已經(jīng)放入了棧中
Map<Character, Boolean> memory2 = new HashMap<>();
Stack<Character> stack = new Stack();
for (int i = 0; i < strArr.length; i++) {
memory.put(strArr[i], i);
memory2.put(strArr[i], false);
}
//第一個元素特殊處理下便于理解
stack.add(strArr[0]);
memory2.put(strArr[0], true);
for (int i = 1; i < strArr.length; i++) {
//如果節(jié)點(diǎn)沒有出現(xiàn)在棧中
if (!memory2.get(strArr[i])) {
//如果棧頂節(jié)點(diǎn)大于當(dāng)前節(jié)點(diǎn)并且該節(jié)點(diǎn)后續(xù)還會出現(xiàn),取出棧頂節(jié)點(diǎn)然后繼續(xù)取棧第二節(jié)點(diǎn)繼續(xù)比較
while (!stack.isEmpty() && strArr[i] < stack.peek()) {
if (memory.get(stack.peek()) > i) {
memory2.put(stack.peek(), false);
stack.pop();
} else {
break;
}
}
//當(dāng)循環(huán)完了后蝙泼,當(dāng)前節(jié)點(diǎn)肯定大于棧頂元素程剥,入棧,記錄該節(jié)點(diǎn)已經(jīng)入棧了
stack.push(strArr[i]);
memory2.put(strArr[i], true);
}
}
StringBuilder stringBuilder = new StringBuilder();
while (!stack.empty()) {
stringBuilder.insert(0, stack.pop());
}
return stringBuilder.toString();
}
public static void main(String[] args) {
String result = removeDuplicateLetters("bacacca");
System.out.println(result);
}
9.算法
1.時(shí)間復(fù)雜度 / 空間復(fù)雜度
-
時(shí)間復(fù)雜度
時(shí)間頻度:一個算法執(zhí)行所消耗的時(shí)間汤踏,從理論上是不能算出來的织鲸,必須上機(jī)運(yùn)行測試才能知道,但我們不可能也沒有必要對每個算法都上機(jī)測試溪胶。只需要知道哪個算法花費(fèi)時(shí)間最多哪個算法花費(fèi)時(shí)間最少就可以了搂擦。并且一個算法花費(fèi)時(shí)間與算法中語句的執(zhí)行次數(shù)成正比。哪個算法中語句執(zhí)行次數(shù)多哗脖,它花費(fèi)時(shí)間就多瀑踢。一個算法中語句執(zhí)行次數(shù)稱為語句頻度或時(shí)間頻度,記為T(n)
-
時(shí)間復(fù)雜度:一般情況下才避,算法中基本操作重復(fù)執(zhí)行的次數(shù)是問題規(guī)模n的某個函數(shù)丘损,用T(n)表示,若有某個輔助函數(shù)f(n),使得當(dāng)n趨近于無窮大時(shí)工扎,T(n)/f(n)的極限值為不等于0的常數(shù)徘钥,則稱f(n)是T(n)的同數(shù)量級函數(shù)。記作T(n)=O(f(n)) 稱O(f(n)) 為算法的漸進(jìn)時(shí)間復(fù)雜度肢娘,簡稱時(shí)間復(fù)雜度呈础。在各種不同算法中,若算法中語句執(zhí)行次數(shù)為一個常數(shù),則時(shí)間復(fù)雜度為O(1),另外,在時(shí)間頻度不相同時(shí),時(shí)間復(fù)雜度有可能相同,如T(n)=n2+3n+4與T(n)=4n2+2n+1它們的頻度不同,但時(shí)間復(fù)雜度相同,都為O(n2).
按數(shù)量級遞增排列,常見的時(shí)間復(fù)雜度有:
O(1)稱為常量級舆驶,算法的時(shí)間復(fù)雜度是一個常數(shù)。
O(n)稱為線性級而钞,時(shí)間復(fù)雜度是數(shù)據(jù)量n的線性函數(shù)沙廉。
O(n2)稱為平方級,與數(shù)據(jù)量n的二次多項(xiàng)式函數(shù)屬于同一數(shù)量級臼节。
O(n3)稱為立方級撬陵,是n的三次多項(xiàng)式函數(shù)。
O(logn)稱為對數(shù)級网缝,是n的對數(shù)函數(shù)巨税。
O(nlogn)稱為介于線性級和平方級之間的一種數(shù)量級
O(2?)稱為指數(shù)級,與數(shù)據(jù)量n的指數(shù)函數(shù)是一個數(shù)量級粉臊。
O(n!)稱為階乘級草添,與數(shù)據(jù)量n的階乘是一個數(shù)量級。
它們之間的關(guān)系是:O(1)<O(logn)<O(n)<O(nlogn)<O(n2)<O(n3)<O(2?)<O(n!)扼仲,隨著問題規(guī)模n的不斷增大,上述時(shí)間復(fù)雜度不斷增大,算法的執(zhí)行效率越低.
空間復(fù)雜度:評估程序執(zhí)行所需要的存儲空間远寸。可以估算出程序堆計(jì)算機(jī)內(nèi)存的使用程度屠凶。不包括算法程序代碼和所處理的數(shù)據(jù)本身所占空間部分驰后。通常用所使用額外空間的字節(jié)數(shù)表示,其算法比較簡單矗愧,記為 S(n)=O(f(n)),其中n表示問題規(guī)模倡怎。
2.常用的排序算法有哪些?
常用的排序算法有:選擇排序贱枣、冒泡排序、插入排序
都將數(shù)組分為已排序部分和未排序部分颤专。
選擇排序:已排序部分定義在左端纽哥,然后選擇未排序的最小元素和未排序的第一個元素交互。
冒泡排序:已排序部分定義在右端栖秕,在遍歷未排序部分的過程中進(jìn)行交換春塌,將最大元素交換到最右端。
插入排序:已排序部分定義在左端簇捍,將未排序部分的第一個元素插入到已排序部分合適的位置只壳。
//選擇排序:最值出現(xiàn)在起始端,
void selectSort(int * arr,int length){
for (int i = 0; i < length -1 ;i++){//趟數(shù)
for (int j = i+1;j < length; j++)//比較次數(shù)
if (arr[i]>arr[j]){
int temp = arr[i];
arr[i] = arr[j];
arr[j] = temp;
}
}
}
//冒泡排序:最值出現(xiàn)在末尾,比較的是相鄰的元素
void bulletSort(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;
}
}
}
}
/**
* 折半查找:優(yōu)化查找時(shí)間(不用遍歷全部數(shù)據(jù))
*
* 折半查找的原理:
* 1> 數(shù)組必須是有序的
* 2> 必須已知min和max(知道范圍)
* 3> 動態(tài)計(jì)算mid的值暑塑,取出mid對應(yīng)的值進(jìn)行比較
* 4> 如果mid對應(yīng)的值大于要查找的值吼句,那么max要變小為mid-1
* 5> 如果mid對應(yīng)的值小于要查找的值,那么min要變大為mid+1
*
*/
// 已知一個有序數(shù)組, 和一個key, 要求從數(shù)組中找到key對應(yīng)的索引位置
int findKey(int *arr, int length, int key) {
int min = 0, max = length - 1, mid;
while (min <= max) {
mid = (min + max) / 2; //計(jì)算中間值
if (key > arr[mid]) {
min = mid + 1;
} else if (key < arr[mid]) {
max = mid - 1;
} else {
return mid;
}
}
return -1;
}
3.字符串反轉(zhuǎn)
void char_reverse (char *cha) {
// 定義頭部指針
char *begin = cha;
// 定義尾部指針
char *end = cha + strlen(cha) -1;
while (begin < end) {
char temp = *begin;
*(begin++) = *end;
*(end--) = temp;
}
}
4.鏈表反轉(zhuǎn)(頭差法)
struct Node* reverseList(struct Node *head)
{
// 定義遍歷指針事格,初始化為頭結(jié)點(diǎn)
struct Node *p = head;
// 反轉(zhuǎn)后的鏈表頭部
struct Node *newH = NULL;
// 遍歷鏈表
while (p != NULL) {
// 記錄下一個結(jié)點(diǎn)
struct Node *temp = p->next;
// 當(dāng)前結(jié)點(diǎn)的next指向新鏈表頭部
p->next = newH;
// 更改新鏈表頭部為當(dāng)前結(jié)點(diǎn)
newH = p;
// 移動p指針
p = temp;
}
// 返回反轉(zhuǎn)后的鏈表頭結(jié)點(diǎn)
return newH;
}
5.如何查找第一個只出現(xiàn)一次的字符(Hash查找)
char findFirstChar(char* cha)
{
char result = '\0';
// 定義一個數(shù)組 用來存儲各個字母出現(xiàn)次數(shù)
int array[256];
// 對數(shù)組進(jìn)行初始化操作
for (int i=0; i<256; i++) {
array[i] =0;
}
// 定義一個指針 指向當(dāng)前字符串頭部
char* p = cha;
// 遍歷每個字符
while (*p != '\0') {
// 在字母對應(yīng)存儲位置 進(jìn)行出現(xiàn)次數(shù)+1操作
array[*(p++)]++;
}
// 將P指針重新指向字符串頭部
p = cha;
// 遍歷每個字母的出現(xiàn)次數(shù)
while (*p != '\0') {
// 遇到第一個出現(xiàn)次數(shù)為1的字符惕艳,打印結(jié)果
if (array[*p] == 1)
{
result = *p;
break;
}
// 反之繼續(xù)向后遍歷
p++;
}
return result;
}
6.如何查找兩個子視圖的共同父視圖搞隐?
- (NSArray <UIView *> *)findCommonSuperView:(UIView *)viewOne other:(UIView *)viewOther
{
NSMutableArray *result = [NSMutableArray array];
// 查找第一個視圖的所有父視圖
NSArray *arrayOne = [self findSuperViews:viewOne];
// 查找第二個視圖的所有父視圖
NSArray *arrayOther = [self findSuperViews:viewOther];
int i = 0;
// 越界限制條件
while (i < MIN((int)arrayOne.count, (int)arrayOther.count)) {
// 倒序方式獲取各個視圖的父視圖
UIView *superOne = [arrayOne objectAtIndex:arrayOne.count - i - 1];
UIView *superOther = [arrayOther objectAtIndex:arrayOther.count - i - 1];
// 比較如果相等 則為共同父視圖
if (superOne == superOther) {
[result addObject:superOne];
i++;
}
// 如果不相等,則結(jié)束遍歷
else{
break;
}
}
return result;
}
- (NSArray <UIView *> *)findSuperViews:(UIView *)view
{
// 初始化為第一父視圖
UIView *temp = view.superview;
// 保存結(jié)果的數(shù)組
NSMutableArray *result = [NSMutableArray array];
while (temp) {
[result addObject:temp];
// 順著superview指針一直向上查找
temp = temp.superview;
}
return result;
}
7.無序數(shù)組中的中位數(shù)(快排思想)
https://blog.csdn.net/weixin_42109012/article/details/91645051
//求一個無序數(shù)組的中位數(shù)
int findMedian(int a[], int aLen)
{
int low = 0;
int high = aLen - 1;
int mid = (aLen - 1) / 2;
int div = PartSort(a, low, high);
while (div != mid)
{
if (mid < div)
{
//左半?yún)^(qū)間找
div = PartSort(a, low, div - 1);
}
else
{
//右半?yún)^(qū)間找
div = PartSort(a, div + 1, high);
}
}
//找到了
return a[mid];
}
int PartSort(int a[], int start, int end)
{
int low = start;
int high = end;
//選取關(guān)鍵字
int key = a[end];
while (low < high)
{
//左邊找比key大的值
while (low < high && a[low] <= key)
{
++low;
}
//右邊找比key小的值
while (low < high && a[high] >= key)
{
--high;
}
if (low < high)
{
//找到之后交換左右的值
int temp = a[low];
a[low] = a[high];
a[high] = temp;
}
}
int temp = a[high];
a[high] = a[end];
a[end] = temp;
return low;
}
8.如何給定一個整數(shù)數(shù)組和一個目標(biāo)值远搪,找出數(shù)組中和為目標(biāo)值的兩個數(shù)劣纲。
- (void)viewDidLoad {
[super viewDidLoad];
NSArray *oriArray = @[@(2),@(3),@(6),@(7),@(22),@(12)];
BOOL isHaveNums = [self twoNumSumWithTarget:9 Array:oriArray];
NSLog(@"%d",isHaveNums);
}
- (BOOL)twoNumSumWithTarget:(int)target Array:(NSArray<NSNumber *> *)array {
NSMutableArray *finalArray = [NSMutableArray array];
for (int i = 0; i < array.count; i++) {
for (int j = i + 1; j < array.count; j++) {
if ([array[i] intValue] + [array[j] intValue] == target) {
[finalArray addObject:array[i]];
[finalArray addObject:array[j]];
NSLog(@"%@",finalArray);
return YES;
}
}
}
return NO;
}