1.什么是 ARC ? ( ARC 是為了解決什么問題誕生的 ? )####
自動引用計數 (
ARC
,Automatic Reference Counting
): 是指內存管理中對引用采取自動計數的技術,在Objective-C
中采用ARC
機制,讓編譯器來進行內存管理. 在新一代Apple LLVM
編譯器中設置ARC
為有效狀態,就無需再次鍵入retain
或release
代碼,這在降低程序崩潰、內存泄漏等風險的同時,很大程度上減少了開發程序的工作量.編譯器完全清楚目標對象,并能立刻釋放那些不再被使用的對象.
ARC 是為了解決什么問題誕生的:需追溯到 MRC 手動內存管理時代.MRC 內存管理存在的問題:
- 當我們要釋放一個堆內存時,首先要確定指向這個堆空間的指針都被
release
了( 避免提前釋放 ) - 釋放指針指向的堆空間,首先要確定哪些指針指向同一個堆,這些指針只能釋放一次(
MRC
下即誰創建,誰釋放,避免重復釋放 ) - 模塊化操作時,對象可能被多個模塊創建和使用,不能確定最后由誰去釋放.
- 多線程操作時,不確定哪個線程最后使用完畢
2. 請解釋以下 Keywords 的區別: assign 和 weak,block 和 weak?####
assign
和weak
:
-
assign
適用于基本數據類型( 如NSInteger
)和 C 數據類型(int
、float
、double
、char
等),另外還有id
,weak
適用于NSObject
對象,并且是一個弱引用.( 反正記住:前面不需要加‘ * ’的就用assign
) -
assign
也可以用來修飾對象,但為什么不用它修飾對象呢 ?因為assign
修飾的對象( 一般編譯的時候會產生警告:Assigning retained object to unsafe property:object will be released after assignment
)在釋放之后,指針的地址還是存在的,也就是說指針并沒有被置為nil
,造成野指針. 對象一般分配在堆上的某塊內存,如果在后續內存分配中,剛好分配了這塊內存,程序就會崩潰. - 為什么可以用
assign
修飾基本數據類型 ? 因為基礎數據類型一般分配在棧上,棧的內存會由系統自動處理,不會造成野指針錯誤. -
weak
修飾的對象在釋放之后,指針地址會被置為nil
. 這樣再向weak
修飾的屬性發送消息就不會導致野指針操作 crash.
weak
使用場景:- 在
ARC
下,在有可能出現循環引用的時候,往往要通過讓其中一端使用 weak 來解決,比如:delegate
代理屬性,通常就會聲明為weak
. - 自身已經對它進行一次強引用,沒有必要再強引用一次時也用
weak
.比如:自定義IBOutlet
控件屬性一般使用weak
,當然也可以使用strong
.
- 在
__block
和__weak
:
-
__block
不管是ARC
還是MRC
模式下都可以使用,可以修飾對象,還可以修飾基本數據類型. -
__weak
只能在ARC
模式下使用,也只能修飾對象(NSString
),不能修飾基本數據類型(int
). -
__block
對象可以在block
中被重新賦值,__weak
不可以. -
__block
對象在ARC
下可能會導致循環引用,非ARC
下會避免循環引用,__weak
只在ARC
下使用,可以避免循環引用. -
__unsafe_unretained
修飾符可以被視為iOS SDK 4.3
以前版本的__weak
的替代品,不過不會被自動置為nil
,所以盡可能不要使用這個修飾符.
參考文章:
正確使用Block避免Cycle Retain和Crash - Cooper's Blog
參考書籍:《 Objective-C 高級編程 iOS 與 OS X 多線程和內存管理 - 第2章 Blocks 》
3. __block 在 ARC 和非 ARC 下含義一樣嗎 ?####
不一樣,標記為
__block
的變量,在block
中使用,在ARC
下會retain
,在非ARC
下則不會retain
.
參考鏈接:iOS: ARC和非ARC下使用Block屬性的問題
4.使用 atomic 一定是線程安全的嗎 ?####
nonatomic
的內存管理語義是非原子的,非原子的操作本來就是線程不安全的,而atomic
的操作是原子的,但是并不意味著它是線程安全的,它會增加正確的幾率,能夠更好的避免線程的錯誤,但它仍然是線程不安全的.
當使用nonatomic
的時候,屬性的setter
和getter
操作是非原子的,所以當多個線程同時對某一屬性進行讀和寫的操作,屬性的最終結果是不能預測的.
當使用
atomic
時,雖然對屬性的讀和寫是原子的,但是仍然可能出現線程錯誤:當線程 A 進行寫操作,這時其他線程的讀或寫操作會因為該操作的進行而等待,當 A 線程的寫操作結束后,B 線程進行寫操作,然后當 A 線程進行讀操作時,卻獲得了在 B 線程中的值,這就破壞了線程安全,如果有線程 C 在 A 線程讀操作前release
了該屬性,那么還會導致程序崩潰.所以僅僅使用atomic
并不會使得線程安全,我們還需要為線程添加lock
來確保線程的安全.
注意:
atomic
所說的線程安全只是保證了getter
和setter
存取方法的線程安全,并不能保證整個對象是線程安全的。如下列所示:@property(atomic,strong)NSMutableArray *arr;
如果一個線程循環的讀數據,一個線程循環寫數據,那么肯定會產生內存問題,因為這和setter
、getter
沒有關系,如使用[ self.arrobjectAtIndex:index ]
就不是線程安全的. 好的解決方案就是加鎖
5.描述一個你遇到過的 retain cycle 例子####
typedef void ( ^blk_t )( void );
@interface MyObject : NSObject
{
blk_t blk;
id obj_;
}
@implementation MyObject
- ( id ) init
{
self = [ super init ];
blk_ = ^ { NSLog ( @" obj_ = %@ " ,** obj_ **); }
return self;
}
雖然 Block
內沒有使用self
也同樣截獲了 self
,引起了循環引用,即Block
語法內使用的obj_
實際上截獲了self
,對編譯器來說,obj_
只不過是對象用結構體的成員變量.相當于blk_ = ^ { NSLog( @" obj_ = %@ " , self -> obj_ ); }
為了避免循環引用,聲明附有 __weak
修飾符的變量并賦值 obj_
使用,也可安全的使用__unsafe__unretained
修飾符.
- ( id ) init
{
self = [ super init ];
id __weak obj = obj_;
blk_ = ^ { NSLog ( @" obj_ = %@ " , **obj** ); }
return self;
}
6. +(void)load; 和 +(void)initialize; 有什么用處 ?####
**Objective-C 中絕大多數類都繼承自 NSObject,該類有兩個方法,+load 和 +initialize,用來實現類的初始化操作. **
**
+load()
: **該方法是在類或者分類被添加到Objective-C runtime
時被調用的,而且只調用一次. 當包含類或分類的程序庫載入系統時,就會執行此方法,這通常就是指應用程序啟動的時候,load
方法務必實現的精簡一些,也就是要減少其所執行的操作,因為整個應用程序在執行load
方法時都會阻塞.
注意:
- 子類的
+load
方法會在它的所有父類的+load
方法之后執行,而分類的+load
方法會在它的主類的+load
方法之后執行,但是不同類的+load
方法的調用順序是不確定的. -
load
方法并不像普通的方法那樣,它并不遵循那套繼承規則,如果某個類本身沒有實現load
方法,那么不管其各級超類是否實現此方法,系統都不會調用. -
load
方法執行時,運行時系統處于脆弱狀態,在執行子類的load
方法之前,必定會執行所有超類的load
方法,而如果代碼還依賴其他程序庫,那么程序庫里相關類的load
方法也必定會先執行,然而由第一條知根據某個給定的程序庫,卻無法判斷出其中各個類的載入順序,因此在load
方法中使用其他類是不安全的. - 分類和其所屬的類里,都可能出現
load
方法,此時兩種實現代碼都會調用,類的實現要比分類的實現先執行.
+initialize()
:該方法是在類或它的子類收到第一條消息之前被調用的,這里的消息包括實例方法和類方法的調用,也就是說+initialize
方法是以懶加載的方式被調用的,如果程序一直沒有給某個類或它的子類發送消息,那么這個類的initialize
方法永遠不會被調用,這樣可以節省系統資源,避免浪費.
注意:
- 該方法會在程序首次用該類之前調用,且只調用一次. 它是由運行期系統來調用的,絕不應該通過代碼直接調用.
- 它是“惰性調用”,就是說只有當程序用到相關類時才會調用.也就是說應用程序無需先把每個類的
initialize
都執行一遍,這與load
方法不同,對于load
方法來說,應用程序必須阻塞并等著所有類的load
都執行完才能繼續. -
initialize
方法與其他消息一樣,如果某個類未實現它,而其超類實現了,那么就會運行超類的實現代碼.
要點: - 在加載階段,如果類實現了
load
方法,那么系統就會調用它. 分類里也可以定義此方法,類的load
方法要比分類中的先調用. 與其他方法不同,load
方法不參與覆寫機制. - 首次使用某個類之前,系統會向其發送
initialize
消息,由于此方法遵從普通的覆寫規則,所以通常應該在里面判斷當前要初始化的是哪個類. -
load
與initialize
方法都應該實現的精簡一些,這有助于保持應用程序的響應能力,也能減少引入“依賴環”的幾率. - 無法在編譯期設定的全局變量,可以放在
initialize
方法里初始化.
參考鏈接:
Objective-C +load vs +initialize - 雷純鋒的技術博客
Objective C類方法load和initialize的區別 - Ider - 博客園
參考書籍:Effective Objective-C 2.0 編寫高質量的 iOS 與 OS X 代碼的52個有效方法 (第51條)
7.為什么其他語言里叫函數調用,OC 中則是給對象發送消息(或者談下對 runtime 的理解).
首先,
OC
是一門動態語言,可以在運行的時候動態決定調用哪個方法實現,甚至添加、替換方法的具體實現,而這些都歸功于Objective-C
的運行時runtime
系統.
消息與函數之間的關鍵區別在于:使用消息結構的語言,其運行時所應執行的代碼有運行環境來決定,使用函數調用的語言,則有編譯器決定;
-
發送消息的含義
[receiver message] //向 receiver 對象發送 message 消息
會被編譯器轉化為:objc_msgSend (receiver, selector);
如果消息含有參數,轉化為objc_msgSend ( receiver, selector,arg1,arg2,... );
如果消息的接收者能夠找到對應的selector
,那么就相當于直接執行了接收者這個對象的特定方法;否則,消息要么被轉發,或是臨時向接收者動態添加這個selector
對應的實現內容,要么就干脆玩完崩潰掉.
可以看出[receiver message]
真的不是一個簡簡單單的方法調用,因為這只是在編譯階段確定了要向接收者發送message
這條消息,而receive
將要如何響應這條消息,那就要看運行時發生的情況來決定了.
-
關于runtime
Objc Runtime
使得 C 具有了面向對象能力,在程序運行時創建,檢查,修改類、對象和它們的方法,可以使用runtime
的一系列方法實現; 順便附上 OC 中一個類的數據結構 /usr/include/objc/runtime.h
struct objc_class {
//isa指針指向Meta Class,因為Objc的類的本身也是一個Object,為了處理這個關系,runtime就創造了Meta Class,當給類發送[NSObject alloc]這樣消息時,實際上是把這個消息發給了Class Object
Class isa OBJC_ISA_AVAILABILITY;
#if !__OBJC2__
// 父類
Class super_class OBJC2_UNAVAILABLE;
// 類名
const char *name OBJC2_UNAVAILABLE;
//類的版本號,默認為0
long version OBJC2_UNAVAILABLE;
// 類信息,供運行期使用的一些位標識
long info OBJC2_UNAVAILABLE;
// 該類的實例變量大小
long instance_size OBJC2_UNAVAILABLE;
// 該類的成員變量鏈表
struct objc_ivar_list *ivars OBJC2_UNAVAILABLE;
// 方法定義的鏈表
struct objc_method_list **methodLists OBJC2_UNAVAILABLE;
// 方法緩存,對象接到一個消息會根據isa指針查找消息對象,這時會在methodLists中遍歷,如果cache了,常用的方法調用時就能夠提高調用的效率。
struct objc_cache *cache OBJC2_UNAVAILABLE;
// 協議鏈表
struct objc_protocol_list *protocols OBJC2_UNAVAILABLE;
#endif
} OBJC2_UNAVAILABLE;
向object
發送消息時,Runtime
庫會根據object
的isa
指針找到這個實例object
所屬于的類,然后在類的方法列表以及父類方法列表尋找對應的方法運行,id
是一個objc_object
結構類型的指針,這個類型的對象能夠轉換成任何一種對象.
然后再來看看消息發送的函數:objc_msgSend
函數,看起來objc_msgSend
像是返回了數據,其實objc_msgSend
從不返回數據而是你的方法被調用后返回了數據.下面詳細敘述下消息發送步驟:
- 1)檢測這個
selector
是不是要忽略的,比如 Mac OS X 開發,有了垃圾回收就不理會retain
,release
這些函數了, - 2)檢測這個
target
是不是nil
對象,Objc
的特性是允許對一個nil
對象執行任何一個方法不會Crash
,因為會被忽略掉. - 3)如果上面兩個都過了,那就開始查找這個類的
IMP
,先從cache
里面找,完了找得到就跳到對應的函數去執行. - 4)如果
cache
找不到就找一下方法分發表,如果分發表找不到就到超類的分發表去找,一直找,直到找到NSObject
類為止,如果還找不到就要開始進入動態方法解析了.
參考文章:
刨根問底Objective-C Runtime
8.什么是 Method Swizzling ?####
Method Sswizzling
指的是改變一個已存在的選擇器對應的實現過程,它依賴于Objective-C
中方法的調用能夠在運行時改變,在運行時通過修改類的分發表dispatch table
中selector
對應的函數,來修改方法的實現.
舉個例子,假設我們想跟蹤在一個iOS應用中每個視圖控制器展現給用戶的次數:
我們可以給每個視圖控制器對應的
viewWillAppear:
實現方法中增加相應的跟蹤代碼,但是這樣做會產生大量重復的代碼;子類化可能是另一個選擇,但要求你將
UIViewController
、UITableViewController
、UINavigationController
以及所有其他視圖控制器類都子類化,這也會導致代碼重復;幸好,還有另一個方法,在分類中進行
method swizzling
,在這之前先來看看選擇器、方法和實現的區別;選擇器、方法及實現:在
Objective-C
中,盡管這些詞經常被放在一起來描述消息傳遞的過程,但選擇器、方法及實現分別代表運行時的不同方面;下面是蘋果Objective-C Runtime Reference
文檔中對它們的描述:選擇器(
typedef struct objc_selector *SEL
):選擇器用于表示一個方法在運行時的名字,一個方法的選擇器是一個注冊到(或映射到)Objective-C
運行時中的C
字符串,它是由編譯器生成并在類加載的時候被運行時系統自動映射方法(
typedef struct objc_method *Method
):一個代表類定義中一個方法的不明類型;實現(
typedef id (*IMP)(id, SEL, ...)
):這種數據類型是實現某個方法的函數開始位置的指針,函數使用的是基于當前CPU
架構的標準C
調用規約,第一個參數是指向self
的指針(也就是該類的某個實例的內存空間,或者對于類方法來說,是指向元類(metaclass
)的指針),第二個參數是方法的選擇器,后面跟的都是參數;
理解這些概念之間關系最好的方式是:一個類
Class
維護一張調度表dispatch table
,用于解析運行時發送的消息;調度表中的每個實體entry
都是一個方法Method
,其中key
值是一個唯一的名字——選擇器SEL
,它對應到一個實現IMP
——實際上就是指向標準C
函數的指針(注:個人覺得一個Method包含了方法名SEL和方法實現IMP,不知道這樣理解對不對)
-
在分類中進行Method Swizzing
#import <objc/runtime.h>
@implementation UIViewController (Tracking)
+ (void)load {
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
Class class = [self class];
// When swizzling a class method, use the following:
// Class class = object_getClass((id)self);
SEL originalSelector = @selector(viewWillAppear:);
SEL swizzledSelector = @selector(xxx_viewWillAppear:);
Method originalMethod = class_getInstanceMethod(class, originalSelector);
Method swizzledMethod = class_getInstanceMethod(class, swizzledSelector);
BOOL didAddMethod =
class_addMethod(class,
originalSelector,
method_getImplementation(swizzledMethod),
method_getTypeEncoding(swizzledMethod));
if (didAddMethod) {
class_replaceMethod(class,
swizzledSelector,
method_getImplementation(originalMethod),
method_getTypeEncoding(originalMethod));
} else {
method_exchangeImplementations(originalMethod, swizzledMethod);
}
});
}
#pragma mark - Method Swizzling
- (void)xxx_viewWillAppear:(BOOL)animated {
[self xxx_viewWillAppear:animated];
NSLog(@"viewWillAppear: %@", self);
}
//這段代碼看起來像是會導致一個死循環,但其實并沒有,在Swizzling的過程中,xxx_viewWillAppear:會被重新分配給UIViewController的-viewWillAppear:的原始實現
//反而,如果我們在這個方法中調用viewWillAppear:才會真的導致死循環,因為這個方法的實現會在運行時被swizzle到viewWillAppear:的選擇器
@end
現在,當UIViewController
或它子類的任何實例觸發viewWillAppear:
方法都會打印一條log
日志;Method Swizzling
就是改變類的調度表讓消息解析時從一個選擇器對應到另外一個的實現,同時將原始的方法實現混淆到一個新的選擇器;
-
根據上述代碼總結
Method Swizzling
的實現過程: - Swizzling應該在
+load
方法中實現:每個類的+load
和+initialize
這兩個方法都會被Objective-C
運行時系統自動調用,+load
是在一個類最開始加載時調用,+initialize
是在應用中第一次調用該類或它的實例的方法之前調用,這兩個方法都是可選的,只有實現了才會被執行;因為method swizzling
會影響全局,所以減少冒險情況就很重要;+load
能夠保證在類初始化的時候就會被加載,這為改變系統行為提供了一些統一性,但+initialize
并不能保證在什么時候被調用——事實上也有可能永遠也不會被調用,例如應用程序從未直接的給該類發送消息 - Swizzling應該在
dispatch_once
中實現:還是因為swizzling
會改變全局,我們需要在運行時采取所有可用的防范措施,保障原子性就是一個措施,它確保代碼即使在多線程環境下也只會被執行一次,GCD
中的diapatch_once
就提供這些保障,它應該被當做swizzling
的標準實踐
參考文章:
Objective-C Method Swizzling 的最佳實踐
Objective-C的hook方案(一): Method Swizzling
Method Swizzling
9. UIView 和 CALayer 是什么關系 ?####
首先應該明確:
-
CALayer
繼承自NSObject
,而UIView繼承自NSResponder
,所以UIView
是可以響應用戶的點擊事件的,而CALayer
更多的是做渲染和動畫效果. -
CALayer
和UIView
都可以在屏幕上展示,每個在頁面上展示的UIView
都是要在自己的根layer
上進行對自己的繪制,就好比layer
是畫板,而view
是畫筆 - 它們兩個在展示效率上并沒有很大差別,但是依然推薦子啊
tableViewCell
中使用drewRect:
而不是subViews
的形式進行cell
繪制.
10.如何高性能的給 UIImageView 加個圓角 ?(不準說 layer.cornerRadius ).####
圓角是一種很常見的視圖效果,相比于直角它更加柔和優美,易于接受;當然,設置圓角會帶來一定的性能損耗,如何提高性能是另一個需要重點討論的話題.
UIImageView 的圓角通過直接截取圖片實現,其它視圖的圓角可以通過 Core Graphics 畫出圓角矩形實現,在下文會講到;
-
如何設置圓角
通常label.layer.cornerRadius = x;
就可以設置圓角,但是cornerRadius
只會影響視圖的背景顏色和border
,對于內部還有子視圖的控件就會設置不成功,如UILabel
;對于內部還有子視圖的控件還需要設置label.layer.masksToBounds = true;
才能使cornerRadius
屬性生效,此時會造成離屏渲染,設置圓角時masksToBounds
的使用會降低手機每秒刷新的幀數,特別是在tableView
中設置圓角時幀率下降非常快,幀率下降直接造成界面的不流暢;
Off-Screen Rending
離屏渲染,指的是圖形處理器GPU(Graphic Processing Unit)
在當前屏幕緩沖區以外新開辟一個緩沖區進行渲染操作,視圖和圓角的大小對幀率并沒有什么影響,設置圓角的數量才是影響幀率的原因;離屏渲染耗時是發生在離屏這個動作上面,而不是渲染;為什么離屏這個操作耗時?原因主要有緩沖區和上下文切換,創建新的緩沖區代價都不算大,付出最大代價的是上下文切換,不管實在GPU
渲染過程中,還是進程切換,上下文切換都是一個相當耗時的操作;首先我要保存當前屏幕渲染環境,然后切換到一個新的繪制環境,申請繪制資源,初始化環境,然后開始一個繪制,繪制完畢后銷毀這個繪制環境,如需要切換到On-Screen Rendering
或者再開始一個新的離屏渲染,重復之前的操作;
-
針對上述設置圓角造成頻率下降的解決方案
- 方案一:不要在滾動視圖使用
cornerRadius
或者mask,如果你非要作死使用怎么辦?那么這樣也可以拯救你:
- 方案一:不要在滾動視圖使用
self.layer.shouldRasterize = YES;
self.layer.rasterizationScale = [UIScreen mainScreen].scale;
這樣大部分情況下可以馬上挽救你的幀數在55幀每秒以上,shouldRasterize = YES
會使視圖渲染內容被緩存起來,下次繪制的時候可以直接顯示緩存,當然要在視圖內容不改變的情況下;參考文章iOS圖片圓角設置的正確姿勢
- 方案二:預先生成圓角圖片并緩存起來,這個方法才是比較好的手段,預處理圓角圖片可以在后臺處理,處理完畢后緩存起來,再在主線程顯示,這就避免了不必要的離屏渲染了;
- 方案三:在圖片上面覆蓋一個鏤空圓形圖片同樣可以實現圓形頭像效果,這個也是極為高效的方法,缺點就是對視圖的背景有要求,單色背景效果就最為理想;
- 方案四:為普通的
UIView
添加圓角和為UIImageView
設置圓角的原理截然不同;為普通的
UIView
添加圓角的原理是手動畫出圓角,利用Core Graphics
自己畫出一個圓角矩形,再從當前的繪圖上下文中獲取圖片并返回,有了這個圖片后,我們創建一個UIImageView
并插入到視圖層級的底部(參考文章iOS 高效添加圓角效果實戰講解)-
為
UIImageView
添加圓角通過直接截取圖片實現,可以給UIImage
添加一個分類UIImage+Extension
,分類中增加一個返回圓形圖片的方法,擴展性強(參考文章iOS開發中設置圓角的幾種方法)#import <UIKit/UIKit.h> @interface UIImage (Extension) - (UIImage *)circleImage; @end
#import "UIImage+Extension.h" @implementation UIImage (Extension) - (UIImage *)circleImage { // 開始圖形上下文 UIGraphicsBeginImageContextWithOptions(self.size, NO, 0.0); // 獲得圖形上下文 CGContextRef ctx = UIGraphicsGetCurrentContext(); // 設置一個范圍 CGRect rect = CGRectMake(0, 0, self.size.width, self.size.height); // 根據一個rect創建一個橢圓 CGContextAddEllipseInRect(ctx, rect); // 裁剪 CGContextClip(ctx); // 將原照片畫到圖形上下文 [self drawInRect:rect]; // 從上下文上獲取剪裁后的照片 UIImage *newImage = UIGraphicsGetImageFromCurrentImageContext(); // 關閉上下文 UIGraphicsEndImageContext(); return newImage;
}
```
具體使用:
```
// 獲得的就是一個圓形的圖片
UIImage *placeHolder = [[UIImage imageNamed:@"defaultUserIcon"] circleImage];
```
參考文章:
圓角卡頓刨根問底
iOS 高效添加圓角效果實戰講解
iOS view圓角化的四種方法
開源高性能處理圓角
ZYCornerRadius 一句代碼,圓角風雨無阻
11.使用 drawRect 有什么影響 ?(可深可淺,至少得用過)####
drawRect
方法依賴Core Graphics
框架來進行自定義的繪制,但這種方法主要的缺點就是它處理touch
事件的方式:每次按鈕被點擊后,都會用setNeddsDisplay
進行強制重繪;而且不止一次,每次單點事件觸發兩次執行,這樣的話從性能的角度來說,對CPU
和內存來說都是欠佳的;特別是如果在我們的界面上有多個這樣的UIButton
實例.
參考文章:
內存惡鬼drawRect
iOS重繪機制drawRect
UIView的layoutSubviews和drawRect
12.ASIHttpRequest 或者 SDWebImage 里面給 UIImageView 加載圖片的邏輯是什么樣的 ?(把 UIImageView 放到 UITableViewCell 里面問更贊)####
13.麻煩你設計一個簡單的圖片內存緩存器( 移除策略是一定要說的 ).####
14.講講你用 Instrument 優化動畫性能的經歷吧(別問我什么是 Instrument )####
15. loadView 是干嘛用的 ?####
當你訪問一個
ViewController
的view
屬性時,如果此時view
的值是nil
,那么,ViewController
就會自動調用loadView
這個方法;這個方法就會加載或者創建一個view
對象,賦值給view
屬性;
loadView
默認做的事情是:如果此ViewController
存在一個對應的nib
文件,那么就加載這個nib
;否則,就創建一個UIView
對象;
如果你用Interface Builder
來創建界面,那么不應該重載這個方法;
如果你想自己創建view
對象,那么可以重載這個方法;此時你需要自己給view
屬性賦值;你自定義的方法不應該調用super
;如果你需要對view
做一些其他的定制操作,在viewDidLoad
里面去做;
根據上面的文檔可以知道,有兩種情況:
1、如果你用了
nib
文件,重載這個方法就沒有太大意義;因為loadView
的作用就是加載nib
;如果你重載了這個方法不調用super
,那么nib
文件就不會被加載;如果調用了super
,那么view
已經加載完了,你需要做的其他事情在viewDidLoad
里面做更合適;2、如果你沒有用
nib
,這個方法默認就是創建一個空的view
對象;如果你想自己控制view
對象的創建,例如創建一個特殊尺寸的view
,那么可以重載這個方法,自己創建一個UIView
對象,然后指定self.view = myView;
但這種情況也沒有必要調用super
,因為反正你也不需要在super
方法里面創建的view
對象;如果調用了super
,那么就是浪費了一些資源而已;
16. viewWillLayoutSubView 講一講####
frame
改變之前會調用viewWillLayoutSubviews
,如橫豎屏切換的時候,系統會響應一些函數,其中 包括viewWillLayoutSubviews
和viewDidLayoutSubviews
;
-
ARC
環境下單個viewController
的生命周期: -
initWithCoder:(NSCoder *)aDecoder:
(如果使用storyboard
或者xib
) -
loadView:
加載view
-
viewDidLoad:
view
加載完畢 -
viewWillAppear:
控制器的view
將要顯示 -
viewWillLayoutSubviews:
控制器的view
將要布局子控件 -
viewDidLayoutSubviews:
控制器的view
布局子控件完成
注:這期間系統可能會多次調用viewWillLayoutSubviews和viewDidLayoutSubviews這兩個方法
-
viewDidDisappear:
控制器的view
完全消失的時候