27. SDWebImage是怎么做緩存的?
- 首先說,緩存采用了二級 緩存策略。 圖片緩存的時候, 在內存有緩存, 在磁盤中也有緩存, 其中內存緩存是用NSCache做的 (下面會有NSCache的說明)。
一、如何做緩存的步驟: 0、下載圖片 1、將圖片緩存在內存中 2、判斷圖片的格式png或jpeg,將圖片轉成NSData數據 3、獲取圖片的存儲路徑, 其中圖片的文件名是通過傳入Key經過MD5加密后獲得的 4、將圖片存在進磁盤中。
二、如何獲取圖片的? 1、在內存緩存中找 2、如果內存中找不到, 會去默認磁盤目錄中尋找, 如果找不到,在去自定義磁盤目錄中尋找 3、如果磁盤也找不到就會下載圖片 4、獲取圖片數據之后, 將圖片數據從NSData轉化UIImage。其中轉化根據圖片的類型進行轉化 5、默認對圖片進行解壓縮,生成位圖圖片 6、將位圖圖片返回
三、圖片是如何被解壓縮的? 1、判斷圖片是否是動態圖片,如果是,不能解壓縮 2、判斷圖片是否透明,如果是,不能解壓縮 3、判斷圖片的顏色空間是不是RGB如果不是、不能解壓縮 4、根據圖片的大小創建一個上下文 5、將圖片繪制在上下文中 6、從上下文中讀取一個不透明的位圖圖像,該圖像就是解壓縮后的圖像 7、將位圖圖像返回
如果你正在面試,或者正準備跳槽,不妨看看我精心總結的iOS大廠面試資料:https://gitee.com/Mcci7/i-oser 來獲取一份詳細的大廠面試資料 為你的跳槽加薪多一份保障
接上說 NSCache
- 這個NSCache說白了就是做緩存專用的一個系統類
- 類似可變字典一樣,但是NSCache是線程安全的, 系統類自動做好了加鎖和釋放鎖等一系列的操作, 還有一個重要的是如果內存不足的時候NSCache會自動釋放掉存儲的對象,不需要開發者手動干預。
- 來看一眼NSCache提供的屬性和相關方法
//名稱
@property (copy) NSString *name;
//NSCacheDelegate代理
@property (nullable, assign) id<NSCacheDelegate> delegate;
//通過key獲取value,類似于字典中通過key取value的操作
- (nullable ObjectType)objectForKey:(KeyType)key;
//設置key、value
- (void)setObject:(ObjectType)obj forKey:(KeyType)key; // 0 cost
/*
設置key、value
cost表示obj這個value對象的占用的消耗?可以自行設置每個需要添加進緩存的對象的cost值
這個值與后面的totalCostLimit對應,如果添加進緩存的cost總值大于totalCostLimit就會自動進行刪除
感覺在實際開發中直接使用setObject:forKey:方法就可以解決問題了
*/
- (void)setObject:(ObjectType)obj forKey:(KeyType)key cost:(NSUInteger)g;
//根據key刪除value對象
- (void)removeObjectForKey:(KeyType)key;
//刪除保存的所有的key-value
- (void)removeAllObjects;
/*
當NSCache緩存的對象的總cost值大于這個值則會自動釋放一部分對象直到占用小于該值
非嚴格限制意味著如果保存的對象超出這個大小也不一定會被刪除
這個值就是與前面setObject:forKey:cost:方法對應
*/
@property NSUInteger totalCostLimit; // limits are imprecise/not strict
/*
緩存能夠保存的key-value個數的最大數量
當保存的數量大于該值就會被自動釋放
非嚴格限制意味著如果超出了這個數量也不一定會被刪除
*/
@property NSUInteger countLimit; // limits are imprecise/not strict
/*
這個值與NSDiscardableContent協議有關,默認為YES
當一個類實現了該協議,并且這個類的對象不再被使用時意味著可以被釋放
*/
@property BOOL evictsObjectsWithDiscardedContent;
@end
//NSCacheDelegate協議
@protocol NSCacheDelegate <NSObject>
@optional
//上述協議只有這一個方法,緩存中的一個對象即將被刪除時被回調
- (void)cache:(NSCache *)cache willEvictObject:(id)obj;
@end**
復制代碼
countLimit注意一下這個屬性, 這個屬性就是設置最大緩存數量,啥意思呢? 這玩意就和棧差不多, 先進先出(叫什么FIFO?)原則。比如你countLimit設置為5 那么當你緩存第6個對象的時候, 原本第一個就被移除了。 所以這便就有有一個風險,也可能會是面試點,為什么,通過key去取值的時候,一定要判斷一個獲取的對象是否為nil?答:就因為很有可能某些對象被釋放(頂)掉了。
又又又可能出現的面試題!NSCache里面緩存的對象,在什么場景下會被釋放?
如果你正在面試,或者正準備跳槽,不妨看看我精心總結的iOS大廠面試資料:https://gitee.com/Mcci7/i-oser 來獲取一份詳細的大廠面試資料 為你的跳槽加薪多一份保障
- 回答之前,先說一情況,在某C中創建了NSCache對象,點擊手機的Home或者任何方式進入后臺,會發現NSCache中的代理方法被執行了,于是NSCache對象會釋放掉所有對象,還有的是,如果發生內存警告也會釋放掉所有對象。所以, 這道題應該如下這么回答!
- NSCache自身釋放了,其中存儲的對象也就釋放了。
- 手動調用釋放方法removeObjectForKey、removeAllObjects
- 緩存對象個數大于countLimit
- 緩存總消耗大于totalCostLimit
- 程序進入后臺
- 收到內存警告
28.SDWebImage實現原理是什么? 它是如何解決tableView的復用時出現圖片錯亂問題的呢
- 原理如上,
- 錯亂是在UIImageView+WebCache文件中這個方法每次都會調用 [self sd_cancelCurrentImageLoad];
29. 為什么刷新UI要在主線程操作
UIKit并不是一個線程安全的類,所以涉及多個線程同時對UI進行操作會造成影響。
為什么不把UIKit框架設置為線程安全呢?
因為線程安全需要加鎖,我們都知道加鎖就會消耗性能,影響處理速度,影響渲染速度,我們通常自己在寫@property時都會寫nonatomic來追求高性能高效率。
假設能夠異步設置view的屬性,那我們究竟是希望這些改動能夠同時生效,還是按照各自runloop的進度去改變這個view的屬性呢?
假設UITableView在其他線程去移除了一個cell,而在另一個線程卻對這個cell所在的index進行一些操作,這時候可能就會引發crash。
如果在后臺線程移除了一個view,這個時候runloop周期還沒有完結,用戶在主線程點擊了這個“將要”消失的view,那么究竟該不該響應事件?在哪條線程進行響應?
在Cocoa Touch框架中,UIApplication初始化工作是在主線程進行的。而界面上所有的視圖都是在UIApplication 實例的葉子節點(內存管理角度),所以所有的手勢交互操作都是在主線程上才能響應
30. RunTime
類的結構體:
//Class也表示一個結構體指針的類型
typedef struct objc_class *Class;
struct objc_class {
Class _Nonnull isa OBJC_ISA_AVAILABILITY;
#if !__OBJC2__
Class _Nullable super_class OBJC2_UNAVAILABLE;
const char * _Nonnull name OBJC2_UNAVAILABLE;
long version OBJC2_UNAVAILABLE;
long info OBJC2_UNAVAILABLE;
long instance_size OBJC2_UNAVAILABLE;
struct objc_ivar_list * _Nullable ivars OBJC2_UNAVAILABLE;
struct objc_method_list * _Nullable * _Nullable methodLists OBJC2_UNAVAILABLE;
struct objc_cache * _Nonnull cache OBJC2_UNAVAILABLE;
struct objc_protocol_list * _Nullable protocols OBJC2_UNAVAILABLE;
#endif
} OBJC2_UNAVAILABLE;
復制代碼
分類結構體
struct category_t {
const char *name;
classref_t cls;
struct method_list_t *instanceMethods; // 對象方法
struct method_list_t *classMethods; // 類方法
struct protocol_list_t *protocols; // 協議
struct property_list_t *instanceProperties; // 屬性
// Fields below this point are not always present on disk.
struct property_list_t *_classProperties;
method_list_t *methodsForMeta(bool isMeta) {
if (isMeta) return classMethods;
else return instanceMethods;
}
property_list_t *propertiesForMeta(bool isMeta, struct header_info *hi);
};
復制代碼
引申1. class_copyIvarList與class_copyPropertyList的區別?
- 1.class_copyIvarList:能夠獲取.h和.m中的所有屬性以及大括號中聲明的變量,獲取的屬性名稱有下劃線(大括號中的除外)。
- 2.class_copyPropertyList:只能獲取由property聲明的屬性,包括.m中的,獲取的屬性名稱不帶下劃線。
引申2. class_ro_t和class_rw_t的區別?
- class_rw_t提供了運行時對類拓展的能力,
- class_ro_t存儲的大多是類在編譯時就已經確定的信息。
- 二者都存有類的方法、屬性(成員變量)、協議等信息,不過存儲它們的列表實現方式不同。簡單的說class_rw_t存儲列表使用的二維數組,class_ro_t使用的一維數組。
- class_ro_t存儲于class_rw_t結構體中,是不可改變的。保存著類的在編譯時就已經確定的信息。
- 運行時修改類的方法,屬性,協議等都存儲于class_rw_t中
31. NSNotification
- NSNotificationCent 子線程中發出通知,也要在主線程中刷新UI
// 比如
dispatch_async(dispatch_get_main_queue(), ^{
// 刷新UI
});
復制代碼
- NSNotificationCenter用完之后不移除, 會崩潰么?
- 有時候會導致crash。比如在通知事件中處理數據或者UI事件,但是由于通知的不確定性造成事件的不確定,有異步操作在通知事件中處理等都可能造成崩潰。
- 而且通知的崩潰很難檢測。
32. 什么情況使用 weak 關鍵字,相比 assign 有什么不同?(輪回系列)
- weak 這個詞兒解決了一件事情,就是內存的事情
- 在ARC中weak的出現解決了一些循環引用的問題, 比如delegate, xib連線出來的控件一般也是weak(也可以用strong )
- weak表明了一種“非擁有的關系”,不保留新值,也不釋放舊值。
- assign也是如此,但常用的assign一般用于基本數據類型(CGFloat 或 NSlnteger等)
- assign可以用于非OC對象,也可以用于OC對象(MRC時代使用), 但是weak必須用在OC對象。
引申 1.關鍵字copy 的用法?
- block用Copy是MRC時代留下來的傳統。在MRC中方法內部的block是在棧區的, 使用copy可以把它放到堆區。 在ARC中寫不寫都行,用Strong也是可以的。
- NSString、NSArray、NSDictionary也經常使用copy, 因為里面有對應的可變的子類型,為了確保安全性, 建議使用copy修飾
引申 2.@property 的本質是什么?ivar、getter、setter 是如何生成并添加到這個類中的。
- @property = ivar(實例變量) + getter + setter
- 自動合成
33. 說說內存管理?
- 其實遇到這道題,挺糾結的,有些TMD面試官就是習慣搞人,從這個玩意里面 能往死給你嗑! 你要看過相關內存管理的詳細原理,你會發現這里面的C++操作很多,沒學過C++的人能看個八九不離十,可是也只是能說個大其概,但是內部細節還是得用C++來說,廢話不多說, 直接上說所謂得面試答案。
- 粗糙版本這么回答,
- 版本一: 內存中每一個對象都有一個屬于自己的引用計數器。當某個對象A被另一個對象引用時,A的引用計數器就+1,如果再有一個對象引用到A,那么A的引用計數器就再+1。當其中某個對象不再引用A了,A的引用計數器會-1。直到A的引用計數減到了0,那么就沒有人再需要它了,就是時候把它釋放掉了
- 版本二:對象通過 alloc copy new 生成得得對象在MRC年代需要手動管理內存, 利用得技術是returnCount引用計數器,來管理對象得釋放時機,alloc創建對象引用計數器 + 1, retain持有關系 引用計數器 +1,release 引用計數器 - 1。 如果當前對象得returnCount = 0 對象就會被在dealloc方法里面適當時機進行釋放(啥時候釋放?)如果當前returnCount大于0得時候,就會一直被持有。
- 稍微詳細版本的,首先當 alloc copy new 生成得對象里面 在內部底層源碼也同時和當前對象相關聯得SideTable, 其內部有三個屬性, 一個是一把自旋鎖,一個是引用計數器相關,一個是維護weak生命得屬性得表, 其中retain、release 對利用鍵值對會對當前對象得引用計數器進行加減操作(位移),如果當前引用計數器為0得時候,其dealloc內部會刪除當前的引用計數器,并且釋放當前對象。
- 詳情請查看www.lxweimin.com/p/ef6d9bf8f…
雜項
- 1、imageName與imageWithContentsOfFile區別?
- imageWithContentsOfFile: 加載本地目錄圖片,并不會緩存,占用內存小, 不能加載image.xcassets里面的圖片資源。 相同的圖片會被重復加載到內存中
- imageName:加載到內存中, 會緩存起來, 占用內存較大,相同的圖片不會被重復加載到內存當中,會讀取image.xcassets的圖片圖片資源。
使用 imageNamed 創建的 UIImage 會被立即加入到 NSCache 中(解碼后的 Image Buffer),直到收到內存警告的時候才會釋放不使用的 UIImage。而 imageWithContentsOfFile 會每次重新申請內存,相同圖片不會緩存,所以 xcassets 內的
如果不斷重復讀取同一個圖片,則使用imageName
如果不需要重復讀取同一個圖片,并且需要低內存,則使用imageWithContentsOfFile
2.IBOutlet連出來的視圖屬性為什么可以被設置成weak?
因為鏈接之Xcode 內部把鏈接的控件 放進了一個_topLevelObjectsToKeepAliveFromStoryboard的私有數組中,這個數組強引用這所有top level的對象 所以用weak也無傷大雅。
- id 為什么不能用點語法?
點語法就是setter和getter方法, 然而id類 無法確定所指的類是什么類型, 尋不到setter個getter方法,id類型的對象 只能用【】方法調用方法
-
4.id和NSObject的區別?
- id是struct objc_object結構體指針,可以指向任何OC對象,當然不包括NSInteger等類型,因為這些數據類型不是OC對象。
另外OC的基類不止有NSObject一個,還有個NSProxy虛類。所以說id類型和NSObject并不是等價的。
-
5 . OC中 Null 與 nil的區別
- NULL是指指針是空值,用來判斷C 指針;
- nil是指一個OC對象(指針)為空;
- Nil是指一個OC類為空;
- NSNull則用于填充集合元素;這個類只有一個方法null,并且是單例的;
6 . 自旋鎖和互斥鎖
相同點:都能保證同一時間只有一個線程訪問共享資源,都能保證系統安全
不同點:
互斥鎖:如果共享數據已經有了其他線程加鎖了,線程會進行休眠狀態等待鎖,一旦被訪問的資源被解鎖,則等待資源的線程會被喚醒。信號量dispatch_semaphore 為互斥鎖 @synchronized是NSLock的封裝 屬于互斥鎖 互斥鎖一般用于等待時間較長的情況
**適用于**:線程等待鎖的時間較長
自旋鎖:如果共享數據已經有其他線程加鎖了,線程會以死循環的方式等待鎖,一旦被訪問的資源被解鎖,則等待資源的線程會立即執行。OSSpinLock 屬于自旋鎖 自旋鎖一般用于時間較短的情況,OSSpinLock
**適用于**:線程等待鎖的時間較端
復制代碼
7 . 進程和線程的區別
進程是指在系統中正在運行的一個應用程序
線程是進程中的一個實體,一個進程想要執行任務, 必須至少有一條線程,應程序啟動的時候會默認開啟一條線程,也就是主線程
一個進程擁有多個線程
8 LayoutSubviews和drawRect調用時機
LayoutSubviews調用時機
- init初始化不會調用LayoutSubviews方法
- addsubView 時候會調用
- 改變一個View的frame的時候調用
- 滾動UIScrollView導致UIView重新布局的時候會調用
- 手動調用setNeedsLayout或者layoutIfNeeded
drawRect調用時機
drawRect 掉用是在Controller->loadView, Controller->viewDidLoad 兩方法之后掉用的.所以不用擔心在 控制器中,這些View的drawRect就開始畫了.、
9 cocoaPods里面pod install和update的區別?
**pod install **
- 一般是第一次想要為項目添加pod的時候使用的,當然也可以在添加和移除庫使用
- 每次pod install的時候,pod install 回為每一個安裝的pod庫在Podfile.lock文件中寫入其版本號,并且鎖定當前版本號。
- 如果pod install的時候,不會更新其版本庫,而是去下載新的或者移除當前版本
pod update
- 當執行了pod update的時候,cocoaPods不會考慮Podfile.lock中的版本。直接去更新當前所有的庫到最新,然后Podfile.lock會更新這一次的版本號。
- 10 frame和masonry哪個性能好?為什么
- 有的相對布局最終都會轉換成Frame絕對布局 中間多了一層轉換的操作
- 11 . iOS從iOS9 - 13的特性
iOS9
從HTTP升級到HTTPS App瘦身 下面有講 這里不贅述( App瘦身 ) 新增UIStackView
iOS10
新增通知推送相關的操作。自定義通知彈窗,自定義通知類型(地理位置,時間間隔,日歷等)
iOS11
無線 調試
齊劉海兒,導航條,安全距離等
iOS12
啟動速度優化
應用啟動速度提升40%
鍵盤響應速度提升50%
相機啟動速度提升70%
復制代碼
iOS13
黑暗模式 詳情請查閱 www.lxweimin.com/p/0da3b107f…
二、App包以及啟動過程
App瘦身
1、App如何瘦身?
- 刪除陳舊代碼、刪除陳舊xib/sb,刪除無用的圖片資源(檢測未使用圖片的工具LSUnusedResources )
- 無損壓縮圖片,本地音視頻壓縮。以直接減少圖片大小
- 使用webP格式的圖片(加載速度比較慢,但可以達到瘦身的效果)
- 減小類名稱的長度(高性能的話可以試一試)
- 減少使用靜態庫
- 一些主題類的東西提供下載功能,不直接打包在應用包里面,按需加載資源
- iOS9 之后的新特性 應用程序切片(App Slicing)、中間代碼(Bitcode)和按需加載資源(On Demand Resources)
Slicing: 這個過程是iOS9出來之后 不需要程序員干預的一個瘦身的過程,簡單來說就是我們再上傳IPA包到iTunes Connect,然后AppStore會對app進行切片,切成特定的機型想要的數據,比如@3x給max用,@2x就自動剔除了。 是一個自動的過程、 Bitcode:是一種中間碼,如果配置了Bitcode(Xcode7以后默認開啟)的程序會在App Store Connect上被重新編譯等一系列操作,進而蘋果內部會對可執行文件進行優化,也就是說不需要我們干預什么東西,也操作不了, 如果后面蘋果有更牛逼的優化操作,也是蘋果的事情, 跟我們個人開發者一毛錢關系沒有。 On Demand Resources 按需加載, 是程序員自己手動操作,說白了就是在用的時候去下載某些資源, 但是我們自己在配置的時候都需要配置,要額外寫一些代碼啥的,等我們提交到市場的時候, 蘋果內部會把我們按需加載的資源從包里面做了一些抽離操作啥的, 讓我們的包在下載的時候更小,舉個例子,就是吃雞里面沙漠地圖如果玩家不自己下載, 就玩不了沙漠。
on-demond resource(ODR)具體請查看原理版本:www.lxweimin.com/p/bacedd8a3…
或者詳細使用版本:www.cocoachina.com/articles/12…
關于 slicing, bitcode, on-demond resource(ODR)的參考資源blog.csdn.net/zhuod/artic…
2、app啟動時候都經歷了什么?
啟動分為兩種。 一種是之前啟動過,按了一下home鍵,然后再點啟動,這個啟動叫熱啟動,另外就是第一次啟動app,或者啟動殺死之后的app 叫做冷啟動
根據info.plist里面的設置加載,建立沙箱,權限檢查等
加載可執行文件 加載動態庫 objc運行時的初始化處理(類的注冊,category注冊,selector唯一性檢查等等) 初始化,包括+load方法 執行main函數 Application 初始化,到 applicationDidFinishLaunchingWithOptions 執行完 渲染屏幕,到viewDidAppear 執行完畢,展現給用戶
- mian之前
根據info.plist里面的設置加載,建立沙箱,權限檢查等
加載可執行文件 加載動態庫 objc運行時的初始化處理(類的注冊,category注冊,selector唯一性檢查等等) 初始化,包括+load方法
- mian之后
- 如圖
- 加載流程如下:
圖丟了!!!!!百度去吧!
3、優化啟動時間
- 啟動時間是用戶點擊App圖標,到第一個界面展示的時間。
注意:啟動時間在小于400ms是最佳的,因為從點擊圖標到顯示Launch Screen,到Launch Screen消失這段時間是400ms。啟動時間不可以大于20s,否則會被系統殺掉。
- 以main函數作為分水嶺,啟動時間其實包括了兩部分:
- main函數之前(分析并加載動態庫,注冊需要的類(包括系統的類),Category中的方法也會注冊到對用的類中,執行必要的初始化方法( +load方法)等等
- main函數到第一個界面的viewDidAppear:。
- 所以,優化也是從兩個方面進行的,個人建議優先優化后者,因為絕大多數App的瓶頸在自己的代碼里。
mian函數之前的啟動優化
- 減少動態庫的數量(這是目前為止最耗時的了, 基本上占了95%以上的時間)
- 合并動態庫,比如自己寫的UI控件合并成自己的UIKit
- 確認動態庫是optional還是required。如果該Framework在當前App支持的所有iOS系統版本都存在,那么就設為required,否則就設為optional,因為optional會有些額外的檢查
- 合并Category(UIView+Frame,UIView+AutoLayout合并成一個)
- 將不必需在+load方法中做的事情,延時放到+initialize。
mian函數之后的啟動優化 首先分析一下從main函數開始執行,到第一個頁面顯示, 這段時間做了哪些事情
- 執行didFinishLaunchingWithOptions方法
- 初始化Window,初始化基礎ViewContreoller(一般是UINavigationController+UITabViewController)
- 獲取數據(本地和遠程)
- 最后展示給用戶
- 減少創建線程(高性能iOS開發一書中提到,線程不僅僅有創建時的時間開銷,還會消耗內核的內存,即應用的內存空間。 每個線程大約消耗 1KB 的內核內存空間。線程創建的耗時(不包含啟動時間),其區間范圍在 4000~5000 微秒,即 4~5 毫秒。創建線程后啟動線程的耗時區間為 5~100 毫秒,平均大約在 29 毫秒。這是很大的時間開銷,若在應用啟動時開啟多個線程,則尤為明顯。線程的啟動時間之所以如此之長,是因為多次的上下文切換所帶來的開銷。所以線程在開發過程中也避免濫用)
- 合并或者刪減不必要的類(或者分類)和函數objc的類越多,函數越多啟動越慢
- 在設計師可接受的范文盡量使用小的圖片
- AppDelegate
通常優化的一般來說,還是從AppDelegate先入手優化
didFinishLaunchingWithOptions
applicationDidBecomeActive
復制代碼
優化的核心思想就是,能延時的延時, 不能延時的盡量放到后臺去優化。
- 日志、統計等必須在 APP 一啟動就最先配置的事件。仍然把它留在 didFinishLaunchingWithOptions 里啟動。
- 項目配置、環境配置、用戶信息的初始化 、推送、IM等事件,這些功能在用戶進入 APP 主體的之前是必須要加載完的,把他放到廣告頁面的viewDidAppear啟動。
- 其他 SDK 和配置事件,由于啟動時間不是必須的,所以我們可以放在第一個界面的 viewDidAppear 方法里,這里完全不會影響到啟動時間。
- 每次用NSLog方式打印會隱式的創建一個Calendar,因此需要刪減啟動時各業務方打的log,或者僅僅針對內測版輸出log
- 盡量不要在didFinishLaunchingWithOptions 里面創建和開啟多線程
復制代碼
參考文獻www.lxweimin.com/p/f40fdd879… 其文章內部作者談到了美團關于啟動優化的相關分析,看似似曾相似,沒記錯的畫《高性能iOS應用開發》這本書就是美團這幾個哥們兒翻譯的吧,實現方式和書中頗為相似。
3、App電量消耗
- 1.定位
- 2.網絡請求
- 3.CPU處理
- 4.GPU處理
- 5.Bluetooth
定位優化
1.盡量不要實時更新
2.定位精度盡量不要太高
網絡優化
1.減少、壓縮網絡數據
2.能使用緩存就使用緩存,減少網絡請求 3.斷點續傳 4.批量傳輸 5.設置適合的超時時間,用戶可以取消耗時的網絡請求 6.網絡不可用時就不要再執行網絡請求了
CPU/GPU優化
相關離屏渲染操作盡量避免 內存管理處理好 使用懶加載 使用繪制 圖片與imageView相同大小避免多余運算 Timer的時間間隔不宜太短,滿足需求即可 線程適量,不宜過多,不要阻塞主線程 適當使用多線程 減少視圖刷新:確保必要的時候才刷新,能刷新1行cell最好只刷新一行;
為了優化耗電我們還可以做: 1.盡量不要使用定時器 2.優化I/O操作(文件的讀寫操作) 2.1最好不要頻繁讀寫小數據,最好批量讀寫 2.2數據量比較大的時候可以考慮使用數據庫 2.3讀寫大量重要數據時,考慮用dispatch_io,其提供了基于GCD的異步操作文件I/O的API。用dispatch_io系統會優化磁盤訪問
高性能iOS應用開發中提到一下幾點
- 1、CPU優化
- 數據處理(例如文本格式優化)
- 待處理的數據大小----更大的顯示屏允許軟件在單個視圖中展示更多的信息,但這也意味著要處理更多的數據
- 處理數據的算法和數據結構
- 執行更新的次數,尤其是在數據更新之后,觸發應用的狀態或者UI進行更新(比如刷新單行cell)
- 服務器中的數據盡量不要在客戶端上處理(例如服務器字符串,在客戶端進行拆分操作)
- 按需加載(例如tableViewcell 不需要一下子全部渲染,快速滑動的時候 過程中的留白處理。)
- 2、網絡
- 在進行網絡請求之前,先檢查是否有網絡連接。(沒網絡的時候,不要請求網絡)
- 避免沒有連接WiFi的情況下進行高帶寬的消耗操作(因為3G、4G等手機網絡耗電量遠大于WIFi信號),例如視頻流在4G或者非Wifi情況下應該給出響應的提示。
- 3、定位
- 盡量不要實時更新
- 定位精度盡量不要太高
三、算法
定義相關
- 鏈表和數組的區別是什么? 鏈表和字典的區別是什么?
數組在內存中是逐個存放的,鏈表每隔節點沒有相對固定的位置關系
數組被聲明后,大小就固定了,不能進行動態擴充。 鏈表可以動態生成節點,并且添加到已有的鏈表后面 數組存在越界問題,鏈表則不存在 數組的插入刪除的時間復雜度是O(n),鏈表O(1) 數組的查詢下標時間復雜度為O(1), 鏈表為O(n) 根據值查詢的時間復雜度,鏈表和數組都是O(n)
- 如何檢測鏈表中是否有環?
思路 假設有兩個學生A和B在跑道上跑步,兩人從相同起點出發,假設A的速度為2m/s,B的速度為1m/s,結果會發生什么?
答案很簡單,A繞了跑道一圈之后會追上B! 將這個問題延伸到鏈表中,跑道就是鏈表,我們可以設置兩個指針,a跑的快,b跑的慢,如果鏈表有環,那么當程序執行到某一狀態時,a==b。如果鏈表沒有環,程序會執行到a==NULL,結束。
1、 調換A和B
// int a = 10;
// int b = 20;
//
// a = a + b;
// b = a - b;
// a = a - b;
//
// NSLog(@"a = %d , b = %d", a, b);
//
//
// a = a*b;
// b = a/b;
// a = a/b;
//
// NSLog(@"1 ===== : a = %d , b = %d", a, b);
復制代碼
2、最大公約數
// int n = 20,v = 30,temp = 0,max,min;
//
// if (n>v) {
// max = n;
// min = v;
// } else {
// max = v;
// min = n;
// }
//
//
// while (min != 0) {
// temp = max - min;
// max = min;
// min = temp;
// }
//
// NSLog(@"%d", max);
復制代碼
3、打印2 - 100 的素數(質數) 除了1和自身被整除的.
// NSMutableArray *primeNumberArray =[NSMutableArray array];
// for(int i=2; i<=100; i++) {
//
// NSInteger n = 0;
//
// for(int j = 1; j <= i; j++) {
//
// if(i % j == 0) {
// n = n + 1;
// }
// }
//
// if(n == 2) {
// [primeNumberArray addObject:@(i)];
// }
// }
//
// NSLog(@"primeNumber = %@",primeNumberArray);
//
復制代碼
4、 字符串倒敘
// NSString *string = @"hei Son 我是你father";
// NSMutableString *string1 = [NSMutableString string];
// for (NSInteger i = string.length; i>0; i--) {
// [string1 appendString:[string substringWithRange:NSMakeRange(i -1,1)]];
// }
//
// NSLog(@"%@", string1);
//
復制代碼
5、 尋找出字符串中有那些中文
// for (int i = 0; i < string.length; i++) {
// NSString *str1 = [string substringWithRange:NSMakeRange(i, 1)];
// const char *cStr = [str1 UTF8String];
// if (strlen(cStr) == 3 ) { // oc中 中文三個字節
// NSLog(@"%@", str1);
// }
// }
復制代碼
6. 排序
- 冒泡排序
比較相鄰的元素。如果第一個比第二個大,就交換他們兩個。
對每一對相鄰元素做同樣的工作,從開始第一對到結尾的最后一對。在這一點,最后的元素應該會是最大的數。 針對所有的元素重復以上的步驟,除了最后一個。 持續每次對越來越少的元素重復上面的步驟,直到沒有任何一對數字需要比較
for (int i = 0; i<result.count-1; i++) {
for (int j = 0; j<result.count-1-i; j++) {
NSInteger left = [result[j] integerValue];
NSInteger right = [result[j+1] integerValue];
if (left>right) {
[result exchangeObjectAtIndex:j withObjectAtIndex:j+1];
}
}
}
NSLog(@"%@",result);
時間復雜度O(n^2)
復制代碼
- 選擇排序
選擇排序就是通過遍歷數組找出每次遍歷數組的最小元素的下標,然后將其按順序從第一位依次排列
//self.array = @[@2,@4,@3,@1];
NSMutableArray *mutableArray = [self.array mutableCopy];//oc數組中不能存儲基本數據類型,所以快速賦值完成后,系統默認數組元素為NSNumber類型
if (mutableArray == nil || [mutableArray count] == 0)
{
return;
}
for (int i = 0; i < [mutableArray count]; i++)
{
NSInteger minIdx = i;//默認最小值的索引為i
for (int j = i+1; j < [mutableArray count]; j++)//通過循環尋找當前數組中最小值的索引值
{
if (NSOrderedAscending == [mutableArray[j] compare:mutableArray[minIdx]])//NSNumber類判斷大小方法,這句話的意思是當mutableArray[j] <mutableArray[minIdx]時
{
minIdx = j;//更新數組中最小值的索引值
}
}
[mutableArray exchangeObjectAtIndex:i withObjectAtIndex:minIdx];//將每次循環結束后找到的最小值交換到數組的第i位
NSLog(@"%@",mutableArray);
}
}
時間復雜度為O(n)。最壞情況下仍為O(n^2)
復制代碼
- 升序/降序
NSMutableArray *priceArray = [NSMutableArray arrayWithObjects:@"0.2",@"5",@"44",@"67",@"98.5",@"1.55", nil];
[priceArray sortUsingComparator:^NSComparisonResult(id _Nonnull obj1, id _Nonnull obj2) {
if ([obj1 integerValue] < [obj2 integerValue]){
return NSOrderedAscending;
} else {
return NSOrderedDescending;
}
}];
這里再次得到的priceArray即為升序排列的數組;
若果想要得到降序的調換一下return的位置即可
復制代碼
7 尋找最近的公共View
// 所有View的父View
+ (NSArray *)superViews:(UIView *)view{
if (view==nil) {
return @[];
}
NSMutableArray *result = [NSMutableArray array];
while (view!=nil) {
[result addObject:view];
view = view.superview;
}
return [result copy];
}
// 相互對比兩個節點中的view,出現一樣就返回
+ (UIView *)commonView_1:(UIView *)viewA andView:(UIView *)viewB{
NSArray *arr1 = [self superViews:viewA];
NSArray *arr2 = [self superViews:viewB];
for (NSUInteger i =0; i<arr1.count; ++i) {
UIView *targetView = arr1[i];
for (NSUInteger j=0; j<arr2.count; ++j) {
if (targetView == arr2[j]) {
return targetView;
}
}
}
return nil;
}
// 利用NSSet中的hash表,可以將上面代碼進行進一步優化
+ (UIView *)commomView_2:(UIView *)viewA andView:(UIView *)viewB{
NSArray *arr1 = [self superViews:viewA];
NSArray *arr2 = [self superViews:viewB];
NSSet *set = [NSSet setWithArray:arr2];
for (NSUInteger i =0; i<arr1.count; ++i) {
UIView *targetView = arr1[i];
if ([set containsObject:targetView]) {
return targetView;
}
}
return nil;
}
復制代碼
8.數組題:如何在有序數組中找出和等于給定值的兩個元素?
NSArray *arr = @[@"1", @"12", @"13", @"23", @"31", @"43", @"52", @"66", @"88", @"111", @"127", @"199"];
[self confirmNumbers:arr total:199];
----
- (void)confirmNumbers:(NSArray *)array total:(NSInteger)totalNmuber {
if (array.count <= 1) { return; }
NSInteger tempAddCount = 0;
NSInteger tempDeleltCount = 0;
for (int i = 0; i <= array.count; i++) {
NSInteger tNamber = [array[tempAddCount] integerValue] + [array[array.count-1-tempDeleltCount] integerValue];
if (tNamber == totalNmuber) {
NSLog(@"%ld, %ld, 第一個元素%@ - 后面的元素%@", (long)tempAddCount, (long)tempDeleltCount, array[tempAddCount] ,array[array.count-tempDeleltCount-1]);
break;
} else if (tNamber < totalNmuber) {
tempAddCount ++;
} else {
tempDeleltCount ++;
}
if (i == array.count - 1) {
NSLog(@"啥都沒匹配著");
break;
}
}
}
復制代碼
9 用遞歸寫一個算法,計算從1到100的和。
NSLog(@"%ld", [self getSumResult:100]);
- (NSInteger)getSumResult:(NSInteger)number {
if (number <=0 ) {
return number;
}
return number + [self getSumResult:number - 1];
}
// 遞歸效率差的原因是 每一次調用函數(自己)都是要有內存開銷的,影響CUP的效率
復制代碼
10.打亂一個數組
NSArray* arr = @[@"1",@"2",@"3"];
arr = [arr sortedArrayUsingComparator:^NSComparisonResult(NSString *str1, NSString *str2) {
int seed = arc4random_uniform(2);
if (seed) {
return [str1 compare:str2];
} else {
return [str2 compare:str1];
}
}];
復制代碼
11。 iOS數組去重有那些方案
// 方案一
NSArray *originalArr = @[@1, @2, @3, @1, @3];
NSMutableArray *resultArrM = [NSMutableArray array];
for (NSString *item in originalArr) {
if (![resultArrM containsObject:item]) {
[resultArrM addObject:item];
}
}
NSLog(@"result : %@", resultArrM);
// 方案二
NSArray *originalArr = @[@1, @2, @3, @1, @3];
NSMutableDictionary *dictM = [NSMutableDictionary dictionary];
for (NSNumber *n in originalArr) {
[dict setObject:n forKey:n];
}
NSLog(@"%@",[dictM allValues]);
// 方案三
NSArray *originalArr = @[@1, @2, @3, @1, @3];
NSSet *set = [NSSet setWithArray:originalArr];
NSLog(@"result: %@", [set allObjects]);
// 方案四
NSArray *originalArr = @[@1, @2, @3, @1, @3];
NSArray *result = [originalArr valueForKeyPath:@"@distinctUnionOfObjects.self"];
復制代碼
三、軟技術篇 ###1.開發過程中, 你碰到那些技術難點?是怎么解決的?
- 其實這個題主要還是面試官看想了解你的真實項目經驗, 如果你回答的東西,根本就算不上是什么技術問題,而是基礎問題那估計也沒什么聊下去的必要了, 舉個例子,你回答的是“我在開發過程中總是發現UITableView這個控件寫起來比較麻煩, 而且還總是報數組越界的問題” 那么對不起, 你最多能找一個實習的工作。因為你能說出這個問題,我估計你上面的硬核面試題,一半兒都不知道咋回事!
- 其實這題也沒那么高大上,說一下你真實開發過程中遇到的難點,說的越高大上越好, 最好是把面試官說懵逼了,當然如果你有自信的話,最好別夸大,萬一你遇上一個恰好在這個領域人家更牛逼呢?
- 舉個例子“我在開發我們項目的時候, 涉及到圖像處理的問題,就比如說現在網絡上比較火的用SDWebImage下載超清大圖的時候出現的崩潰問題,因為decodeImageWithImage 這個方法用于對圖片進行壓縮并且緩存起來,以保證tableview/collectionview交互更加流暢,但是如果用此方法加載超清大圖的時候, 會適得其反,有可能導致上G的內存消耗, 解決辦法是對于高清圖片,應該放在圖片解壓之后,禁止緩存解壓后的數據。 代碼如下”
- SD4.X的解決辦法
[[SDImageCache sharedImageCache] setShouldDecompressImages:NO];
[[SDWebImageDownloader sharedDownloader] setShouldDecompressImages:NO];
- SD5.0 及以上的解決辦法
SDWebImageAvoidDecodeImage添加了這個枚舉,意思是在子線程程解壓縮圖片
[self.imageView sd_setImageWithURL:self.url placeholderImage:[UIImage imageNamed:@"logo"] options:SDWebImageAvoidDecodeImage];
復制代碼
更多詳情請查看 也必須看!
在舉個例子“由于在開發過程中,用到的所有圖片必須是原圖, 問題就出現了, 如果幾張圖片是超清的超大圖片,把這些圖片全部渲染到一個畫布中并且進行隨機形狀的超高清拼圖, 這個過程會出現一個奇怪的問題,就是繪制的結果是大概率會變成 一張純白色的沒有任何圖案的圖片,出現的原因是因為在App內部如果正在運行的內存達到一定的值得時候繪制圖像的上下文就會獲取一個空白的圖片,解決辦法因為是概率事件所以內部做了一個循環渲染的機制,在特定次數范圍內,如果出現繪制成功的話返回正常的圖片,如果沒有正確繪制,則做一個內部的提示語App內部沒有任何反應, 所以如果制作跟圖片相關的項目,特別涉及到自定義系統相冊的功能,最好優化好內存問題,因為內存優化不好,導致的問題有很多系統層級的BUG。而且很難找到問題的原因。
** 這就把面試官引向了一個優化內存的事情。比如優化內存的工具, 檢測內存泄漏,循環引用的工具等等。 下面會一一介紹。**
2.過程中, 你用過什么調試工具?
- instrument
Leaks(泄漏):一般的查看內存使用情況,檢查泄漏的內存,并提供了所有活動的分配和泄漏模塊的類對象分配統計信息以及內存地址歷史記錄;
- locations(內存分配):跟蹤過程的匿名虛擬內存和堆的對象提供類名和可選保留/釋放歷史;
Leaks(泄漏):一般的查看內存使用情況,檢查泄漏的內存,并提供了所有活動的分配和泄漏模塊的類對象分配統計信息以及內存地址歷史記錄;
- locations(內存分配):跟蹤過程的匿名虛擬內存和堆的對象提供類名和可選保留/釋放歷史;
復制代碼
iOS大廠面試資料:查看更多 來獲取一份詳細的大廠面試資料 為你的跳槽加薪多一份保障
結語
這是陸陸續續面試中總結出來的, 總之,有的問題回答的比較撿漏, 有的問題模棱兩可,有些面試官的反饋比較扎實,有些面試官說回答的泰國籠統不夠細致,反正這東西見仁見智吧。
作者:執筆續春秋
鏈接:https://juejin.cn/post/6854573212169142285