1、Size Classes 具體使用
對(duì)屏幕進(jìn)行分類
2、UIView和 CALayer是什么關(guān)系?
UIView 顯示在屏幕上歸功于 CALayer,通過調(diào)用 drawRect 方法來渲染自身的內(nèi)容,調(diào)節(jié) CALayer 屬性可以調(diào)整 UIView 的外觀,UIView 繼承自 UIResponder,比起CALayer 可以響應(yīng)用戶事件,Xcode6 之后可以方便的通過視圖調(diào)試功能查看圖層之間的關(guān)系。
UIView 是 iOS 系統(tǒng)中界面元素的基礎(chǔ),所有的界面元素都繼承自它。它內(nèi)部是由Core Animation 來實(shí)現(xiàn)的,它真正的繪圖部分,是由一個(gè)叫 CALayer(Core Animation Layer)的類來管理。UIView 本身,更像是一個(gè) CALayer 的管理器,訪問它的跟繪圖和坐標(biāo)有關(guān)的屬性,如 frame,bounds 等,實(shí)際上內(nèi)部都是訪問它所在 CALayer 的相關(guān)屬性。
UIView 有個(gè) layer 屬性,可以返回它的主 CALayer 實(shí)例,UIView 有一個(gè)layerClass方法,返回主 layer所使用的類,UIView 的子類,可以通過重載這個(gè)方法,來讓 UIView 使用不同的 CALayer 來顯示,如:
- (class) layerClass {
// 使某個(gè) UIView的子類使用 GL來進(jìn)行繪制
return ([CAEAGLLayer class]);
}
UIView 的 CALayer 類似 UIView 的子 View 樹形結(jié)構(gòu),也可以向它的 layer 上添加子layer,來完成某些特殊的顯示。例如下面的代碼會(huì)在目標(biāo) View 上敷上一層黑色的透明薄膜。
grayCover = [[CALayer alloc]init];
grayCover.backgroudColor = [[UIColor
blackColor]colorWithAlphaComponent:0.2].CGColor;
[self.layer addSubLayer:grayCover];
補(bǔ)充部分:這部分有深度了,大致了解一下吧,UIView 的 layer 樹形在系統(tǒng)內(nèi)部被系統(tǒng)維護(hù)著三份 copy
1.邏輯樹,就是代碼里可以操縱的,例如更改 layer 的屬性等等就在這一份
2.動(dòng)畫樹,這是一個(gè)中間層,系統(tǒng)正是在這一層上更改屬性,進(jìn)行各種渲染操作。
3.顯示樹,這棵樹的內(nèi)容是當(dāng)前正被顯示在屏幕上的內(nèi)容。這三棵樹的邏輯結(jié)構(gòu)都是一樣的,區(qū)別只有各自的屬性
3、loadView的作用?
loadView 用來自定義 view,只要實(shí)現(xiàn)了這個(gè)方法,其他通過 xib 或 storyboard 創(chuàng)建的 view 都不會(huì)被加載
看懂控制器view創(chuàng)建的這個(gè)圖就行
4、IBOutlet連出來的視圖屬性為什么可以被設(shè)置成 weak?
因?yàn)楦缚丶?subViews 數(shù)組已經(jīng)對(duì)它有一個(gè)強(qiáng)引用
5、IB中 User Defined Runtime Attributes如何使用?
User Defined Runtime Attributes 是一個(gè)不被看重但功能非常強(qiáng)大的的特性,它能夠通過 KVC 的方式配置一些你在 interface builder 中不能配置的屬性。當(dāng)你希望在 IB 中作盡可能多得事情,這個(gè)特性能夠幫助你編寫更加輕量級(jí)的viewcontroller
6、沙盒目錄結(jié)構(gòu)是怎樣的?各自用于那些場(chǎng)景?
- Application:存放程序源文件,上架前經(jīng)過數(shù)字簽名,上架后不可修改
- Documents:常用目錄,iCloud 備份目錄,存放數(shù)據(jù)
- Library
1.Caches:存放體積大又不需要備份的數(shù)據(jù)
2.Preference:設(shè)置目錄,iCloud 會(huì)備份設(shè)置信息 - tmp:存放臨時(shí)文件,不會(huì)被備份,而且這個(gè)文件下的數(shù)據(jù)有可能隨時(shí)被清除的可能
7、pushViewController和presentViewController有什么區(qū)別
兩者都是在多個(gè)試圖控制器間跳轉(zhuǎn)的函數(shù)
presentViewController 提供的是一個(gè)模態(tài)視圖控制器(modal)
pushViewController 提供一個(gè)棧控制器數(shù)組,push/pop
8、請(qǐng)簡(jiǎn)述 UITableView的復(fù)用機(jī)制
每次創(chuàng)建 cell 的時(shí)候通過 dequeueReusableCellWithIdentifier:方法創(chuàng)建 cell,它先到緩存池中找指定標(biāo)識(shí)的 cell,如果沒有就直接返回 nil,如果沒有找到指定標(biāo)識(shí)的 cell,那么會(huì)通過initWithStyle:reuseIdentifier:創(chuàng)建一個(gè)
cell,當(dāng) cell 離開界面就會(huì)被放到緩存池中,以供下次復(fù)用
9、如何高性能的給 UIImageView 加個(gè)圓角?
不好的解決方案
使用下面的方式會(huì)強(qiáng)制 Core Animation提前渲染屏幕的離屏繪制, 而離屏繪制就會(huì)給性能帶來負(fù)面影響,會(huì)有卡頓的現(xiàn)象出現(xiàn)
self.view.layer.cornerRadius = 5;
self.view.layer.masksToBounds = YES;
正確的解決方案:使用繪圖技術(shù)
- (UIImage *)circleImage
{
// NO代表透明
UIGraphicsBeginImageContextWithOptions(self.size, NO,
0.0);
// 獲得上下文
CGContextRef ctx = UIGraphicsGetCurrentContext();
// 添加一個(gè)圓
CGRect rect = CGRectMake(0, 0, self.size.width,
self.size.height);
CGContextAddEllipseInRect(ctx, rect);
// 裁剪
CGContextClip(ctx);
// 將圖片畫上去
[self drawInRect:rect];
UIImage *image =
UIGraphicsGetImageFromCurrentImageContext();
// 關(guān)閉上下文
UIGraphicsEndImageContext();
return image;
}
還有一種方案:使用了貝塞爾曲線"切割"個(gè)這個(gè)圖片, 給 UIImageView 添加了的圓角,其實(shí)也是通過繪圖技術(shù)來實(shí)現(xiàn)的
UIImageView *imageView = [[UIImageView alloc]
initWithFrame:CGRectMake(0, 0, 100, 100)];
imageView.center = CGPointMake(200, 300);
UIImage *anotherImage = [UIImage imageNamed:@"image"];
UIGraphicsBeginImageContextWithOptions(imageView.bounds.
size, NO, 1.0);
[[UIBezierPath
bezierPathWithRoundedRect:imageView.bounds
cornerRadius:50] addClip];
[anotherImage drawInRect:imageView.bounds];
imageView.image = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
[self.view addSubview:imageView];
10、使用 drawRect有什么影響?
drawRect 方法依賴 Core Graphics 框架來進(jìn)行自定義的繪制
缺點(diǎn):它處理 touch 事件時(shí)每次按鈕被點(diǎn)擊后,都會(huì)用 setNeddsDisplay 進(jìn)行強(qiáng)制重繪;而且不止一次,每次單點(diǎn)事件觸發(fā)兩次執(zhí)行。這樣的話從性能的角度來說,對(duì) CPU 和內(nèi)存來說都是欠佳的。特別是如果在我們的界面上有多個(gè)這樣的UIButton 實(shí)例,那就會(huì)很糟糕了。這個(gè)方法的調(diào)用機(jī)制也是非常特別. 當(dāng)你調(diào)用 setNeedsDisplay 方法時(shí), UIKit 將會(huì)把當(dāng)前圖層標(biāo)記為 dirty,但還是會(huì)顯示原來的內(nèi)容,直到下一次的視圖渲染周期,才會(huì)將標(biāo)記為 dirty 的圖層重新建立 Core Graphics 上下文,然后將內(nèi)存中的數(shù)據(jù)恢復(fù)出來, 再使用 CGContextRef 進(jìn)行繪制
11、描述下 SDWebImage里面給 UIImageView加載圖片的邏輯
- 1.入口setImageWithURL:placeholderImage:options: 會(huì)先把placeholderImage 顯示,然后 SDWebImageManager 根據(jù) URL 開始處理圖片。
- 2.進(jìn)入 SDWebImageManager-downloadWithURL: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)存過小,會(huì)先清空內(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 完成,不會(huì)拖慢主線程 UI。如果有需要對(duì)下載的圖片進(jìn)行二次處理,最好也在這里完成,效率會(huì)好很多。
- 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í)候會(huì)注冊(cè)一些消息通知,在內(nèi)存警告或退到后臺(tái)的時(shí)候清理內(nèi)存圖片緩存,應(yīng)用結(jié)束的時(shí)候清理過期圖片。
- 19.SDWebImage 也提供了UIButton+WebCache 和 MKAnnotationView+WebCache,方便使用。
- 20.SDWebImagePrefetcher 可以預(yù)先下載圖片,方便后續(xù)使用。
12、設(shè)計(jì)個(gè)簡(jiǎn)單的圖片內(nèi)存緩存器
類似上面 SDWebImage 實(shí)現(xiàn)原理即可
一定要有移除策略:釋放數(shù)據(jù)模型對(duì)象
13、控制器的生命周期
就是問的 view 的生命周期,下面已經(jīng)按方法執(zhí)行順序進(jìn)行了排序
// 自定義控制器 view,這個(gè)方法只有實(shí)現(xiàn)了才會(huì)執(zhí)行
- (void)loadView
{
self.view = [[UIView alloc] init];
self.view.backgroundColor = [UIColor orangeColor];
}
// view 是懶加載,只要 view 加載完畢就調(diào)用這個(gè)方法
- (void)viewDidLoad
{
[super viewDidLoad];
NSLog(@"%s",__func__);
}
// view 即將顯示
- (void)viewWillAppear:(BOOL)animated
{
[super viewWillAppear:animated];
NSLog(@"%s",__func__);
}
// view 即將開始布局子控件
- (void)viewWillLayoutSubviews
{
[super viewWillLayoutSubviews];
NSLog(@"%s",__func__);
}
// view 已經(jīng)完成子控件的布局
- (void)viewDidLayoutSubviews
{
[super viewDidLayoutSubviews];
NSLog(@"%s",__func__);
}
// view 已經(jīng)出現(xiàn)
- (void)viewDidAppear:(BOOL)animated
{
[super viewDidAppear:animated];
NSLog(@"%s",__func__);
}
// view 即將消失
- (void)viewWillDisappear:(BOOL)animated
{
[super viewWillDisappear:animated];
NSLog(@"%s",__func__);
}
// view 已經(jīng)消失
- (void)viewDidDisappear:(BOOL)animated
{
[super viewDidDisappear:animated];
NSLog(@"%s",__func__);
}
// 收到內(nèi)存警告
- (void)didReceiveMemoryWarning
{
[super didReceiveMemoryWarning];
NSLog(@"%s",__func__);
}
// 方法已過期,即將銷毀 view
- (void)viewWillUnload
{
}
// 方法已過期,已經(jīng)銷毀 view
- (void)viewDidUnload
{
}
14、你是怎么封裝一個(gè) view的
可以通過純代碼或者 xib 的方式來封裝子控件
建立一個(gè)跟 view 相關(guān)的模型,然后將模型數(shù)據(jù)傳給 view,通過模型上的數(shù)據(jù)給 view的子控件賦值
/**
* 純代碼初始化控件時(shí)一定會(huì)走這個(gè)方法
*/
- (instancetype)initWithFrame:(CGRect)frame
{
if(self = [super initWithFrame:frame])
{
[self setup];
}
return self;
}
/**
* 通過 xib初始化控件時(shí)一定會(huì)走這個(gè)方法
*/
- (id)initWithCoder:(NSCoder *)aDecoder
{
if(self = [super initWithCoder:aDecoder])
{
[self setup];
}
return self;
}
- (void)setup
{
// 初始化代碼
}
15、如何進(jìn)行適配
通過判斷版本來控制,來執(zhí)行響應(yīng)的代碼
功能適配:保證同一個(gè)功能在哪里都能用
UI 適配:保證各自的顯示風(fēng)格
// iOS版本適配
#define iOS11 ([[UIDevice currentDevice].systemVersion
doubleValue]>=11.0)
16、如何渲染 UILabel的文字?
通過 NSAttributedString/NSMutableAttributedString(富文本)
17、UIScrollView 的 contentSize能否在viewDidLoad中設(shè)置?
能
因?yàn)?UIScrollView 的內(nèi)容尺寸是根據(jù)其內(nèi)部的內(nèi)容來決定的,所以是可以在viewDidLoad 中設(shè)置的。
補(bǔ)充:(這僅僅是一種特殊情況)
前提,控制器 B 是控制器 A 的一個(gè)子控制器,且控制器 B 的內(nèi)容只在控制器 A 的view 的部分區(qū)域中顯示。假設(shè)控制器 B 的 view 中有一個(gè) UIScrollView 這樣一個(gè)子控件。如果此時(shí)在控制器 B 的 viewDidLoad 中設(shè)置 UIScrollView 的 contentSize 的話會(huì)導(dǎo)致不準(zhǔn)確的問題。因?yàn)槿魏慰刂破鞯?view 在 viewDidLoad 的時(shí)候的尺寸都是不準(zhǔn)確的,如果有子控件的尺寸依賴父控件的尺寸,在這個(gè)方法中設(shè)置會(huì)導(dǎo)致子控件的 frame 不準(zhǔn)確,所以這時(shí)應(yīng)該-(void)viewDidLayoutSubviews;方法中設(shè)置子控件的尺寸。
18、觸摸事件的傳遞
觸摸事件的傳遞是從父控件傳遞到子控件。
如果父控件不能接收觸摸事件,那么子控件就不可能接收到觸摸事件。
不能接受觸摸事件的四種情況:
1.不接收用戶交互,即:userInteractionEnabled = NO
2.隱藏,即:hidden = YES
3.透明,即:alpha <= 0.01
4.未啟用,即:enabled = NO
提示:UIImageView 的 userInteractionEnabled 默認(rèn)就是 NO,因此 UIImageView 以及它的子控件默認(rèn)是不能接收觸摸事件的。
如何找到最合適處理事件的控件:
首先,判斷自己能否接收觸摸事件,可以通過重寫 hitTest:withEvent:方法驗(yàn)證。其次,判斷觸摸點(diǎn)是否在自己身上,對(duì)應(yīng)方法 pointInside:withEvent:。從后往前(先遍歷最后添加的子控件)遍歷子控件,重復(fù)前面的兩個(gè)步驟。如果沒有符合條件的子控件,那么就自己處理。
19、事件響應(yīng)者鏈
如果當(dāng)前 view 是控制器的 view,那么就傳遞給控制器,如果控制器不存在,則將其傳遞給它的父控件,在視圖層次結(jié)構(gòu)的最頂層視圖也不能處理接收到的事件或消息,則將事件或消息傳遞給 UIWindow 對(duì)象進(jìn)行處理,如果 UIWindow 對(duì)象也不處理,則將事件或消息傳遞給 UIApplication 對(duì)象,如果 UIApplication 也不能處理該事件或消息,則將其丟棄。
補(bǔ)充:如何判斷上一個(gè)響應(yīng)者
如果當(dāng)前這個(gè) view 是控制器的 view,那么控制器就是上一個(gè)響應(yīng)者
如果當(dāng)前這個(gè) view 不是控制器的 view ,那么 父控件就是上一個(gè)響應(yīng)者
20、如何實(shí)現(xiàn)三角形頭像
Quartz2D
使用 coreGraphics 裁剪出一個(gè)三角形
21、核心動(dòng)畫里包含什么?
iOS動(dòng)畫篇_CoreAnimation(超詳細(xì)解析核心動(dòng)畫)
22、如何使用核心動(dòng)畫?
創(chuàng)建
設(shè)置相關(guān)屬性
添加到 CALayer 上,會(huì)自動(dòng)執(zhí)行動(dòng)畫