iOS面試題分享群:群號:129018636?
1.使用了第三方庫, 有看它們是怎么實(shí)現(xiàn)的嗎僻弹?
2.強(qiáng)連通量算法了解嘛伐债?
3.遇到tableView卡頓嘛阅悍?會造成卡頓的原因大致有哪些附鸽?
4.M屯断、V文虏、C相互通訊規(guī)則你知道的有哪些侣诺?
5.NStimer準(zhǔn)嗎?談?wù)勀愕目捶ㄑ趺兀咳绻粶?zhǔn)該怎樣實(shí)現(xiàn)一個(gè)精確的NSTimer?
1.使用了第三方庫, 有看他們是怎么實(shí)現(xiàn)的嗎年鸳?
例:SD、YY丸相、AFN搔确、MJ等!
<1>.SD為例:
1.入口 setImageWithURL:placeholderImage:options:
會先把 placeholderImage 顯示已添,然后 SDWebImageManager 根據(jù) URL 開始處理圖片妥箕。
2.進(jìn)入 SDWebImageManagerdownloadWithURL:delegate:options:userInfo:,
交給 SDImageCache 從緩存查找圖片是否已經(jīng)下載 queryDiskCacheForKey:delegate:userInfo:.
3.先從內(nèi)存圖片緩存查找是否有圖片更舞,
如果內(nèi)存中已經(jīng)有圖片緩存畦幢,SDImageCacheDelegate 回調(diào) imageCache:didFindImage:forKey:userInfo: 到 SDWebImageManager。
4.SDWebImageManagerDelegate 回調(diào) webImageManager:didFinishWithImage:
到 UIImageView+WebCache 等前端展示圖片缆蝉。
5.如果內(nèi)存緩存中沒有宇葱,生成 NSInvocationOperation
添加到隊(duì)列開始從硬盤查找圖片是否已經(jīng)緩存。
6.根據(jù) URLKey 在硬盤緩存目錄下嘗試讀取圖片文件刊头。
這一步是在 NSOperation 進(jìn)行的操作黍瞧,所以回主線程進(jìn)行結(jié)果回調(diào) notifyDelegate:。
7.如果上一操作從硬盤讀取到了圖片原杂,將圖片添加到內(nèi)存緩存中
(如果空閑內(nèi)存過小印颤,會先清空內(nèi)存緩存)。
SDImageCacheDelegate 回調(diào) imageCache:didFindImage:forKey:userInfo:穿肄。
進(jìn)而回調(diào)展示圖片年局。
8.如果從硬盤緩存目錄讀取不到圖片,
說明所有緩存都不存在該圖片咸产,需要下載圖片矢否,
回調(diào) imageCache:didNotFindImageForKey:userInfo:。
9.共享或重新生成一個(gè)下載器 SDWebImageDownloader 開始下載圖片脑溢。
10.圖片下載由 NSURLConnection 來做僵朗,
實(shí)現(xiàn)相關(guān) delegate 來判斷圖片下載中、下載完成和下載失敗屑彻。
11.connection:didReceiveData: 中
利用 ImageIO 做了按圖片下載進(jìn)度加載效果验庙。
12.connectionDidFinishLoading: 數(shù)據(jù)下載完成后交給 SDWebImageDecoder 做圖片解碼處理。
13.圖片解碼處理在一個(gè) NSOperationQueue 完成酱酬,
不會拖慢主線程 UI壶谒。如果有需要對下載的圖片進(jìn)行二次處理,
最好也在這里完成膳沽,效率會好很多汗菜。
14.在主線程 notifyDelegateOnMainThreadWithInfo:
宣告解碼完成让禀,
imageDecoder:didFinishDecodingImage:userInfo:
回調(diào)給 SDWebImageDownloader。
15.imageDownloader:didFinishWithImage:
回調(diào)給 SDWebImageManager 告知圖片下載完成陨界。
16.通知所有的 downloadDelegates 下載完成巡揍,
回調(diào)給需要的地方展示圖片。
17.將圖片保存到 SDImageCache 中菌瘪,
內(nèi)存緩存和硬盤緩存同時(shí)保存腮敌。
寫文件到硬盤也在以單獨(dú) NSInvocationOperation 完成,
避免拖慢主線程俏扩。
18.SDImageCache 在初始化的時(shí)候會注冊一些消息通知糜工,
在內(nèi)存警告或退到后臺的時(shí)候清理內(nèi)存圖片緩存,
應(yīng)用結(jié)束的時(shí)候清理過期圖片录淡。
19.SDWI 也提供了 UIButton+WebCache 和
MKAnnotationView+WebCache捌木,方便使用。
20.SDWebImagePrefetcher 可以預(yù)先下載圖片嫉戚,
方便后續(xù)使用刨裆。
2.強(qiáng)連通分量了解嘛?
概念:
有向圖強(qiáng)連通分量:在有向圖G中彬檀,如果兩個(gè)頂點(diǎn)vi,vj間(vi>vj)有一條從vi到vj的有向路徑帆啃,同時(shí)還有一條從vj到vi的有向路徑,則稱兩個(gè)頂點(diǎn)強(qiáng)連通(strongly connected)窍帝。如果有向圖G的每兩個(gè)頂點(diǎn)都強(qiáng)連通努潘,稱G是一個(gè)強(qiáng)連通圖。有向圖的極大強(qiáng)連通子圖坤学,稱為強(qiáng)連通分量(strongly connected components)慈俯。
定義:
有向圖強(qiáng)連通分量:
在有向圖G中,如果兩個(gè)頂點(diǎn)間至少存在一條路徑拥峦,稱兩個(gè)頂點(diǎn)強(qiáng)連通(strongly connected)。
如果有向圖G的每兩個(gè)頂點(diǎn)都強(qiáng)連通卖子,則稱G是一個(gè)強(qiáng)連通圖略号。
非強(qiáng)連通圖有向圖的極大強(qiáng)連通子圖,成為強(qiáng)連通分量(strongly connected components)洋闽。
下圖中玄柠,子圖{1,2,3,4}為一個(gè)強(qiáng)連通分量,因?yàn)轫旤c(diǎn)1,2,3,4兩兩可達(dá)诫舅,{5}羽利,{6}也分別是兩個(gè)強(qiáng)連通分量。
直接根據(jù)定義刊懈,用雙向遍歷取交際的方法求強(qiáng)連通分量这弧,時(shí)間復(fù)雜度為O(N^2+M)娃闲。更好的方法是Kosaraju算法或者Tarjan算法。
兩者的時(shí)間復(fù)雜度都是O(N+M)匾浪。本文介紹的是Tarjan算法皇帮。
算法原理:(Tarjan)
need-to-insert-img
Tarjan算法是基于對圖深度優(yōu)先搜索的算法,每個(gè)強(qiáng)連通分量為搜索樹中的一顆子樹蛋辈。
搜索時(shí)属拾,把當(dāng)前搜索樹中未處理的節(jié)點(diǎn)加入一個(gè)堆棧,回溯時(shí)可以盤對棧頂?shù)綏V械墓?jié)點(diǎn)是否為一個(gè)強(qiáng)連通分量冷溶。
定義DFN(u)為節(jié)點(diǎn)u搜索的次序編號(時(shí)間戳)渐白。Low(u)為u或者u的子樹能夠追溯到的最早的棧中的節(jié)點(diǎn)的次序號。
由定義可以得出:
Low(u)= Min { DFN(u)逞频, Low(v)} ((u纯衍,v)為樹枝邊,u為v的父節(jié)點(diǎn)DFN(v)虏劲,(u托酸,v)為指向棧中節(jié)點(diǎn)的后向邊(非橫叉邊))
當(dāng)DFN(u)=Low(u)時(shí),以u為根的搜索子樹上所有節(jié)點(diǎn)是一個(gè)強(qiáng)連通分量柒巫。
代碼實(shí)現(xiàn):
need-to-insert-img
[cpp]:
#include?
#include?
#include?
#include?
using?namespace?std;
#define?MIN(a,b)?((a)<(b)?(a):(b))
#define?N?10005?????????????//?題目中可能的最大點(diǎn)數(shù)
stacksta;????????????????//?存儲已遍歷的結(jié)點(diǎn)
vectorgra[N];????????????//?鄰接表表示圖
int?dfn[N];?????????????????//?深度優(yōu)先搜索訪問次序
int?low[N];?????????????????//?能追溯到的最早的次序
int?InStack[N];?????????????//?檢查是否在棧中(2為在棧中励堡,1為已訪問,且不在棧中堡掏,0為不在)
vector?Component[N];?????//?獲得強(qiáng)連通分量結(jié)果
int?InComponent[N];?????????//?記錄每個(gè)點(diǎn)在第幾號強(qiáng)連通分量里
int?index,ComponentNumber;??//?索引號应结,強(qiáng)連通分量個(gè)數(shù)
int?n,?m;???????????????????//?點(diǎn)數(shù),邊數(shù)
void?init(void)
{
memset(dfn,?0,?sizeof(dfn));
memset(low,?0,?sizeof(low));
memset(InStack,?0,?sizeof(InStack));
index?=?ComponentNumber?=?0;
for?(int?i?=?1;?i?<=?n;?++?i)
{
gra[i].clear();
Component[i].clear();
}
while(!sta.empty())
sta.pop();
}
void?tarjan(int?u)
{
Instack[u]?=?2;
low[u]?=?dfn[u]?=?++?index;
sta.push(u);
for?(int?i?=?0;?i?<?gra[u].size();?++?i)
{
int?t?=?gra[u][i];
if?(dfn[t]?==?0)
{
tarjan(t);
low[u]?=?MIN(low[u],?low[t]);
}
else?if?(InStack[t]?==?2)
{
low[u]?=?MIN(low[u],?dfn[t]);
}
}
if?(low[u]?==?dfn[u])
{
++?ComponentNumber;
while?(!sta.empty())
{
int?j?=?sta.top();
sta.pop();
InStack[j]?=?1;
Component[ComponentNumber].push_back(j);
InComponent[j]=ComponentNumber;
if?(j?==?u)
binputak;
}
}
}
void?input(void)
{
for(int?i=1;i<=m;i++)
{
int?a,b;
scanf("%d%d",&a,&b);
gra[a].push_back(b);
}
}
void?solve(void)
{
for(int?i=1;i<=n;i++)
if(!dfn[i])
tarjan(i);
if(ComponentNumber>1)
puts("No");
else
puts("Yes");
}
int?main()
{
while(scanf("%d%d",&n,&m),n+m)
{
init();
input();
solve();
}
}
3.遇到tableView卡頓嘛泉唁?會造成卡頓的原因大致有哪些鹅龄?
可能造成tableView卡頓的原因有:
1.最常用的就是cell的重用, 注冊重用標(biāo)識符
如果不重用cell時(shí)亭畜,每當(dāng)一個(gè)cell顯示到屏幕上時(shí)扮休,就會重新創(chuàng)建一個(gè)新的cell;
如果有很多數(shù)據(jù)的時(shí)候拴鸵,就會堆積很多cell玷坠。
如果重用cell,為cell創(chuàng)建一個(gè)ID劲藐,每當(dāng)需要顯示cell 的時(shí)候八堡,都會先去緩沖池中尋找可循環(huán)利用的cell,如果沒有再重新創(chuàng)建cell
2.避免cell的重新布局
cell的布局填充等操作 比較耗時(shí)聘芜,一般創(chuàng)建時(shí)就布局好
如可以將cell單獨(dú)放到一個(gè)自定義類兄渺,初始化時(shí)就布局好
3.提前計(jì)算并緩存cell的屬性及內(nèi)容
當(dāng)我們創(chuàng)建cell的數(shù)據(jù)源方法時(shí),編譯器并不是先創(chuàng)建cell 再定cell的高度
而是先根據(jù)內(nèi)容一次確定每一個(gè)cell的高度汰现,高度確定后挂谍,再創(chuàng)建要顯示的cell叔壤,滾動時(shí),每當(dāng)cell進(jìn)入憑虛都會計(jì)算高度凳兵,提前估算高度告訴編譯器百新,編譯器知道高度后,緊接著就會創(chuàng)建cell庐扫,這時(shí)再調(diào)用高度的具體計(jì)算方法饭望,這樣可以方式浪費(fèi)時(shí)間去計(jì)算顯示以外的cell
4.減少cell中控件的數(shù)量
盡量使cell得布局大致相同,不同風(fēng)格的cell可以使用不用的重用標(biāo)識符形庭,初始化時(shí)添加控件铅辞,
不適用的可以先隱藏
5.不要使用ClearColor,無背景色萨醒,透明度也不要設(shè)置為0
渲染耗時(shí)比較長
6.使用局部更新
如果只是更新某組的話斟珊,使用reloadSection進(jìn)行局部更新
7.加載網(wǎng)絡(luò)數(shù)據(jù),下載圖片富纸,使用異步加載囤踩,并緩存
8.少使用addView 給cell動態(tài)添加view
9.按需加載cell,cell滾動很快時(shí)晓褪,只加載范圍內(nèi)的cell
10.不要實(shí)現(xiàn)無用的代理方法堵漱,tableView只遵守兩個(gè)協(xié)議
11.緩存行高:estimatedHeightForRow不能和HeightForRow里面的layoutIfNeed同時(shí)存在,這兩者同時(shí)存在才會出現(xiàn)“竄動”的bug涣仿。所以我的建議是:只要是固定行高就寫預(yù)估行高來減少行高調(diào)用次數(shù)提升性能勤庐。如果是動態(tài)行高就不要寫預(yù)估方法了,用一個(gè)行高的緩存字典來減少代碼的調(diào)用次數(shù)即可
12.不要做多余的繪制工作好港。在實(shí)現(xiàn)drawRect:的時(shí)候愉镰,它的rect參數(shù)就是需要繪制的區(qū)域,這個(gè)區(qū)域之外的不需要進(jìn)行繪制钧汹。例如上例中丈探,就可以用CGRectIntersectsRect、CGRectIntersection或CGRectContainsRect判斷是否需要繪制image和text拔莱,然后再調(diào)用繪制方法类嗤。
13.預(yù)渲染圖像。當(dāng)新的圖像出現(xiàn)時(shí)辨宠,仍然會有短暫的停頓現(xiàn)象。解決的辦法就是在bitmap context里先將其畫一遍货裹,導(dǎo)出成UIImage對象嗤形,然后再繪制到屏幕;
14.使用正確的數(shù)據(jù)結(jié)構(gòu)來存儲數(shù)據(jù)弧圆。
4.M赋兵、V笔咽、C相互通訊規(guī)則你知道的有哪些?
MVC 是一種設(shè)計(jì)思想霹期,一種框架模式叶组,是一種把應(yīng)用中所有類組織起來的策略,它把你的程序分為三塊历造,分別是:
M(Model):實(shí)際上考慮的是“什么”問題甩十,你的程序本質(zhì)上是什么,獨(dú)立于 UI 工作吭产。是程序中用于處理應(yīng)用程序邏輯的部分侣监,通常負(fù)責(zé)存取數(shù)據(jù)。
C(Controller):控制你 Model 如何呈現(xiàn)在屏幕上臣淤,當(dāng)它需要數(shù)據(jù)的時(shí)候就告訴 Model橄霉,你幫我獲取某某數(shù)據(jù);當(dāng)它需要 UI 展示和更新的時(shí)候就告訴 View邑蒋,你幫我生成一個(gè) UI 顯示某某數(shù)據(jù)姓蜂,是 Model 和 View 溝通的橋梁。
V(View):Controller 的手下医吊,是 Controller 要使用的類钱慢,用于構(gòu)建視圖,通常是根據(jù) Model 來創(chuàng)建視圖的遮咖。
要了解 MVC 如何工作滩字,首先需要了解這三個(gè)模塊間如何通信。
MVC通信規(guī)則
Controller to Model
可以直接單向通信御吞。Controller 需要將 Model 呈現(xiàn)給用戶麦箍,因此需要知道模型的一切,還需要有同 Model 完全通信的能力陶珠,并且能任意使用 Model 的公共 API挟裂。
Controller to View
可以直接單向通信。Controller 通過 View 來布局用戶界面揍诽。
Model to View
永遠(yuǎn)不要直接通信诀蓉。Model 是獨(dú)立于 UI 的,并不需要和 View 直接通信暑脆,View 通過 Controller 獲取 Model 數(shù)據(jù)渠啤。
View to Controller
View 不能對 Controller 知道的太多,因此要通過間接的方式通信添吗。
Target action沥曹。首先 Controller 會給自己留一個(gè) target,再把配套的 action 交給 View 作為聯(lián)系方式。那么 View 接收到某些變化時(shí)妓美,View 就會發(fā)送 action 給 target 從而達(dá)到通知的目的僵腺。這里 View 只需要發(fā)送 action,并不需要知道 Controller 如何去執(zhí)行方法壶栋。
代理辰如。有時(shí)候 View 沒有足夠的邏輯去判斷用戶操作是否符合規(guī)范,他會把判斷這些問題的權(quán)力委托給其他對象贵试,他只需獲得答案就行了琉兜,并不會管是誰給的答案。
DataSoure锡移。View 沒有擁有他們所顯示數(shù)據(jù)的權(quán)力呕童,View 只能向 Controller 請求數(shù)據(jù)進(jìn)行顯示,Controller 則獲取 Model 的數(shù)據(jù)整理排版后提供給 View淆珊。
Model 訪問 Controller
同樣的 Model 是獨(dú)立于 UI 存在的夺饲,因此無法直接與 Controller 通信,但是當(dāng) Model 本身信息發(fā)生了改變的時(shí)候施符,會通過下面的方式進(jìn)行間接通信往声。
Notification & KVO一種類似電臺的方法,Model 信息改變時(shí)會廣播消息給感興趣的人 戳吝,只要 Controller 接收到了這個(gè)廣播的時(shí)候就會主動聯(lián)系 Model浩销,獲取新的數(shù)據(jù)并提供給 View。
從上面的簡單介紹中我們來簡單概括一下 MVC 模式的優(yōu)點(diǎn)听哭。
1.低耦合性
2.有利于開發(fā)分工
3.有利于組件重用
4.可維護(hù)性
5.NStimer準(zhǔn)嗎慢洋?談?wù)勀愕目捶ǎ咳绻粶?zhǔn)該怎樣實(shí)現(xiàn)一個(gè)精確的NSTimer?
1.不準(zhǔn)
2.不準(zhǔn)的原因如下:
1陆盘、NSTimer加在main runloop中普筹,模式是NSDefaultRunLoopMode,main負(fù)責(zé)所有主線程事件隘马,例如UI界面的操作太防,復(fù)雜的運(yùn)算,這樣在同一個(gè)runloop中timer就會產(chǎn)生阻塞酸员。
2蜒车、模式的改變。主線程的 RunLoop 里有兩個(gè)預(yù)置的 Mode:kCFRunLoopDefaultMode 和 UITrackingRunLoopMode幔嗦。
當(dāng)你創(chuàng)建一個(gè) Timer 并加到 DefaultMode 時(shí)酿愧,Timer 會得到重復(fù)回調(diào),但此時(shí)滑動一個(gè)ScrollView時(shí)邀泉,RunLoop 會將 mode 切換為 TrackingRunLoopMode嬉挡,這時(shí) Timer 就不會被回調(diào)叛氨,并且也不會影響到滑動操作。所以就會影響到NSTimer不準(zhǔn)的情況棘伴。
PS:DefaultMode 是 App 平時(shí)所處的狀態(tài),rackingRunLoopMode 是追蹤 ScrollView 滑動時(shí)的狀態(tài)屁置。
方法一:
1焊夸、在主線程中進(jìn)行NSTimer操作,但是將NSTimer實(shí)例加到main runloop的特定mode(模式)中蓝角。避免被復(fù)雜運(yùn)算操作或者UI界面刷新所干擾阱穗。
self.timer = [NSTimer timerWithTimeInterval:1 target:self selector:@selector(showTime) userInfo:nil repeats:YES];
[[NSRunLoop currentRunLoop] addTimer:self.timer forMode:NSRunLoopCommonModes];
2、在子線程中進(jìn)行NSTimer的操作使鹅,再在主線程中修改UI界面顯示操作結(jié)果揪阶;
- (void)timerMethod2 {
NSThread *thread = [[NSThread alloc] initWithTarget:self selector:@selector(newThread) object:nil];
[thread start];
}
- (void)newThread
{
@autoreleasepool
{
[NSTimer scheduledTimerWithTimeInterval:1.0 target:self selector:@selector(showTime) userInfo:nil repeats:YES];
[[NSRunLoop currentRunLoop] run];
}
}
總結(jié):
一開始的時(shí)候系統(tǒng)就為我們將主線程的main runloop隱式的啟動了。
在創(chuàng)建線程的時(shí)候患朱,可以主動獲取當(dāng)前線程的runloop鲁僚。每個(gè)子線程對應(yīng)一個(gè)runloop
方法二:
使用示例
使用mach內(nèi)核級的函數(shù)可以使用mach_absolute_time()獲取到CPU的tickcount的計(jì)數(shù)值,可以通過”mach_timebase_info”函數(shù)獲取到納秒級的精確度 裁厅。然后使用mach_wait_until(uint64_t deadline)函數(shù)冰沙,直到指定的時(shí)間之后,就可以執(zhí)行指定任務(wù)了执虹。
關(guān)于數(shù)據(jù)結(jié)構(gòu)mach_timebase_info的定義如下:
struct mach_timebase_info {uint32_t numer;uint32_t denom;};
#include
#include
static const uint64_t NANOS_PER_USEC = 1000ULL;
static const uint64_t NANOS_PER_MILLISEC = 1000ULL * NANOS_PER_USEC;
static const uint64_t NANOS_PER_SEC = 1000ULL * NANOS_PER_MILLISEC;
static mach_timebase_info_data_t timebase_info;
static uint64_t nanos_to_abs(uint64_t nanos) {
return nanos * timebase_info.denom / timebase_info.numer;
}
void example_mach_wait_until(int seconds)
{
mach_timebase_info(&timebase_info);
uint64_t time_to_wait = nanos_to_abs(seconds * NANOS_PER_SEC);
uint64_t now = mach_absolute_time();
mach_wait_until(now + time_to_wait);
}
方法三:直接使用GCD替代拓挥!
轉(zhuǎn)載:http://www.reibang.com/p/0e9e7486e1a7
iOS面試題分享群:群號:129018636