我是一只勤勞的小蜜蜂。閑暇之余,總結下一直以來自己面試過程中所遇到的一些高概率問題,分享給大家,共同成長。該貼后續將會持續更新~~
1.BAD_ACCESS在什么情況下出現?
①訪問一個僵尸對象,訪問僵尸對象的成員變量或者向其發消息;②死循環。
2.簡述下Objective-C中調用方法的過程(runtime)
Objective-C是動態語言,每個方法在運行時會被動態轉為消息發送,即:objc_msgSend(receiver, selector),整個過程介紹如下:
①objc在向一個對象發送消息時,runtime庫會根據對象的isa指針找到該對象實際所屬的類
②然后在該類中的方法列表以及其父類方法列表中尋找方法運行
③如果,在最頂層的父類(一般也就NSObject)中依然找不到相應的方法時,程序在運行時會掛掉并拋出異常unrecognized selector sent to XXX
④但是在這之前,objc的運行時會給出三次拯救程序崩潰的機會,這三次拯救程序奔潰的說明見問題《什么時候會報unrecognized selector的異常》中的說明
? 補充說明:Runtime 鑄就了Objective-C 是動態語言的特性,使得C語言具備了面向對象的特性,在程序運行期創建,檢查,修改類、對象及其對應的方法,這些操作都可以使用runtime中的對應方法實現。
3.什么是method swizzling(俗稱黑魔法)
簡單說就是進行方法交換。
①在Objective-C中調用一個方法,其實是向一個對象發送消息,查找消息的唯一依據是selector的名字。利用Objective-C的動態特性,可以實現在運行時偷換selector對應的方法實現,達到給方法掛鉤的目的
②每個類都有一個方法列表,存放著方法的名字和方法實現的映射關系,selector的本質其實就是方法名,IMP有點類似函數指針,指向具體的Method實現,通過selector就
![Uploading 4a6c4741-9186-4ba2-b354-cde08612df6c_550019.png . . .]可以找到對應的IMP
- 交換方法的幾種實現方式
- 利用 method_exchangeImplementations 交換兩個方法的實現
- 利用 class_replaceMethod 替換方法的實現
-
利用 method_setImplementation 來直接設置某個方法的IMP
示意圖.png
4.objc中向一個nil對象發送消息將會發生什么?
在Objective-C中向nil發送消息是完全有效的——只是在運行時不會有任何作用
①如果一個方法返回值是一個對象,那么發送給nil的消息將返回0(nil);
② 如果方法返回值為指針類型,其指針大小為小于或者等于sizeof(void*);
③ float,double,long double 或者long long的整型標量,發送給nil的消息將返回0;
④ 如果方法返回值為結構體,發送給nil的消息將返回0。結構體中各個字段的值將都是0;
⑤ 如果方法的返回值不是上述提到的幾種情況,那么發送給nil的消息的返回值將是未定義的。
? 具體原因分析
①objc是動態語言,每個方法在運行時會被動態轉為消息發送,即:objc_msgSend(receiver, selector);
② 為了方便理解這個內容,還是貼一個objc的源代碼:
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; // 類名
long version OBJC2_UNAVAILABLE; // 類的版本信息,默認為0
long info OBJC2_UNAVAILABLE; // 類信息,供運行期使用的一些位標識
long instance_size OBJC2_UNAVAILABLE; // 該類的實例變量大小
struct objc_ivar_list *ivars OBJC2_UNAVAILABLE; // 該類的成員變量鏈表
struct objc_method_list **methodLists OBJC2_UNAVAILABLE; // 方法定義的鏈表
// 方法緩存,對象接到一個消息會根據isa指針查找消息對象,
// 這時會在method Lists中遍歷,
// 如果cache了,常用的方法調用時就能夠提高調用的效率。
// 這個方法緩存只存在一份,不是每個類的實例對象都有一個方法緩存
// 子類會在自己的方法緩存中緩存父類的方法,父類在自己的方法緩存中也會緩存自己的方法,而不是說子類就不緩存父類方法了
struct objc_cache *cache OBJC2_UNAVAILABLE;
struct objc_protocol_list *protocols OBJC2_UNAVAILABLE; // 協議鏈表
#endif
} OBJC2_UNAVAILABLE;
分析:
①objc在向一個對象發送消息時,runtime庫會根據對象的isa指針找到該對象實際所屬的類,然后在該類中的方法列表以及其父類方法列表中尋找方法運行,然后再發送消息的時候,objc_msgSend方法不會返回值,所謂的返回內容都是具體調用時執行的。
② 如果向一個nil對象發送消息,首先在尋找對象的isa指針時就是0地址返回了,所以不會出現任何錯誤。
5.objc中向一個對象發送消息[obj foo]和objc_msgSend()函數之間有什么關系?
[obj foo];在objc動態編譯時,會被轉意為:
objc_msgSend(obj, @selector(foo));
6.什么時候會報unrecognized selector的異常?
當調用該對象上某個方法,而該對象上沒有實現這個方法的時候, 可以通過“消息轉發”進行解決,如果還是不行就會報unrecognized selector異常。objc是動態語言,每個方法在運行時會被動態轉為消息發送,即:objc_msgSend(receiver, selector),整個過程介紹如下:
①objc在向一個對象發送消息時,runtime庫會根據對象的isa指針找到該對象實際所屬的類;
②然后在該類中的方法列表以及其父類方法列表中尋找方法運行
③如果,在最頂層的父類中依然找不到相應的方法時,程序在運行時會掛掉并拋出異常unrecognized selector sent to XXX 。但是在這之前,objc的運行時會給出三次拯救程序崩潰的機會:
○ Method resolution
§ objc運行時會調用+resolveInstanceMethod:或者 +resolveClassMethod:,讓你有機會提供一個函數實現。
§ 如果你添加了函數并返回 YES,那運行時系統就會重新啟動一次消息發送的過程
§ 如果 resolve 方法返回 NO ,運行時就會移到下一步,消息轉發
○ Fast forwarding
§ 如果目標對象實現了-forwardingTargetForSelector:,Runtime 這時就會調用這個方法,給你把這個消息轉發給其他對象的機會
§ 只要這個方法返回的不是nil和self,整個消息發送的過程就會被重啟,當然發送的對象會變成你返回的那個對象。
§ 否則,就會繼續Normal Fowarding。
§ 這里叫Fast,只是為了區別下一步的轉發機制。因為這一步不會創建任何新的對象,但Normal forwarding轉發會創建一個NSInvocation對象,相對Normal forwarding轉發更快點,所以這里叫Fast forwarding
○ Normal forwarding
§ 這一步是Runtime最后一次給你挽救的機會。
§ 首先它會發送-methodSignatureForSelector:消息獲得函數的參數和返回值類型。
§ 如果-methodSignatureForSelector:返回nil,Runtime則會發出-doesNotRecognizeSelector:消息,程序這時也就掛掉了。
§ 如果返回了一個函數簽名,Runtime就會創建一個NSInvocation對象并發送-forwardInvocation:消息給目標對象
7.HTTP協議中POST方法和GET方法有那些區別?
①GET用于向服務器請求數據,POST用于提交數據;
②GET請求,請求參數拼接形式暴露在地址欄,而POST請求參數則放在請求體里面,因此GET請求不適合用于驗證密碼等操作;
③GET請求的URL有長度限制,POST請求不會有長度限制。
8.猜想runloop內部是如何實現的?
從字面意思看:運行循環、跑圈;
本質:內部就是do-while循環,在這個循環內部不斷地處理各種事件(任務),比如:Source、Timer、Observer;
? 每條線程都有唯一一個RunLoop對象與之對應,主線程的RunLoop默認已經啟動,子線程的RunLoop需要手動啟動;
? 每次RunLoop啟動時,只能指定其中一個 Mode,這個Mode被稱作 CurrentMode,如果需要切換Mode,只能退出Loop,再重新指定一個Mode進入,這樣做主要是為了隔離不同Mode中的Source、Timer、Observer,讓其互不影響;
9.蘋果是如何實現autoreleasepool的?
autoreleasepool以一個隊列數組的形式實現,主要通過下列三個函數完成。
objc_autoreleasepoolPush
objc_autoreleasepoolPop
objc_aurorelease
10.這個寫法會出什么問題: @property (copy) NSMutableArray *array;
因為copy策略拷貝出來的是一個不可變對象,然而卻把它當成可變對象使用,很容易造成程序奔潰。
這里還有一個問題,該屬性使用了同步鎖,會在創建時生成一些額外的代碼用于幫助編寫多線程程序,這會帶來性能問題,通過聲明nonatomic可以節省這些雖然很小但是不必要額外開銷,在iOS開發中應該使用nonatomic替代atomic。
11.如何讓自定義類可以用 copy 修飾符?如何重寫帶 copy 關鍵字的 setter?
若想令自己所寫的對象具有拷貝功能,則需實現NSCopying協議。如果自定義的對象分為可變版本與不可變版本,那么就要同時實現NSCopyiog與NSMutableCopying協議,不過一般沒什么必要,實現NSCopying協議就夠了
實現不可變版本拷貝
- (id)copyWithZone:(NSZone *)zone;
實現可變版本拷貝
- (id)mutableCopyWithZone:(NSZone *)zone;
重寫帶 copy 關鍵字的setter
- (void)setName:(NSString *)name
{
_name = [name copy];
}
12.+(void)load; +(void)initialize;有什么用處?
+(void)load;```
① 當類對象被引入項目時, runtime會向每一個類對象發送 load 消息;
②load 方法會在每一個類甚至分類被引入時僅調用一次,調用的順序:父類優先于子類, 子類優先于分類;
③由于load 方法會在類被import 時調用一次,而這時往往是改變類的行為的最佳時機,在這里可以使用例如method swizlling 來修改原有的方法;
④load 方法不會被類自動繼承。
+(void)initialize;```
也是在第一次使用這個類的時候會調用這個方法,也就是說 initialize也是懶加載。
? 總結:
① 在Objective-C中,runtime會自動調用每個類的這兩個方法;
② +load會在類初始加載時調用;
③+initialize會在第一次調用類的類方法或實例方法之前被調用;
④這兩個方法是可選的,且只有在實現了它們時才會被調用;
⑤兩者的共同點:兩個方法都只會被調用一次。
13.如何關閉默認的KVO的默認實現,KVO的實現原理?
所謂的“手動觸發”是區別于“自動觸發”:
自動觸發是指類似這種場景:在注冊 KVO 之前設置一個初始值,注冊之后,設置一個不一樣的值,就可以觸發了。
想知道如何手動觸發,必須知道自動觸發 KVO 的原理:
鍵值觀察通知依賴于 NSObject 的兩個方法: willChangeValueForKey: 和 didChangevlueForKey: 。在一個被觀察屬性發生改變之前, willChangeValueForKey: 一定會被調用,這就 會記錄舊的值。而當改變發生后, didChangeValueForKey: 會被調用,繼而 observeValueForKey:ofObject:change:context: 也會被調用。如果可以手動實現這些調用,就可以實現“手動觸發”了。
當你觀察一個對象時,一個新的類會被動態創建。這個類繼承自該對象的原本的類,并重寫了被觀察屬性的 setter 方法。重寫的 setter 方法會負責在調用原 setter 方法之前和之后,通知所有觀察對象:值的更改。最后通過 isa 混寫(isa-swizzling) 把這個對象的 isa 指針 ( isa 指針告訴 Runtime 系統這個對象的類是什么 ) 指向這個新創建的子類,對象就神奇的變成了新創建的子類的實例。如下所示:
14.若一個類有實例變量NSString *_foo,調用setValue:forKey:時,是以foo還是_foo作為key?
? 都可以
15.KVC和KVO的keyPath一定是屬性么?
? 可以是成員變量
16.直接調用_objc_msgForward函數將會發生什么?
_objc_msgForward是 IMP 類型,用于消息轉發的:當向一個對象發送一條消息,但它并沒有實現的時候,_objc_msgForward會嘗試做消息轉發。
直接調用_objc_msgForward是非常危險的事,如果用不好會直接導致程序Crash,但是如果用得好,能做很多非常酷的事。
一旦調用_objc_msgForward,將跳過查找 IMP 的過程,直接觸發“消息轉發”,如果調用了_objc_msgForward,即使這個對象確實已經實現了這個方法,你也會告訴objc_msgSend:“我沒有在這個對象里找到這個方法的實現”。
17.斷點續傳如何實現的?
斷點續傳的理解可以分為兩部分:一部分是斷點,一部分是續傳。斷點的由來是在下載過程中,將一個下載文件分成了多個部分,同時進行多個部分一起的下載,當 某個時間點,任務被暫停了,此時下載暫停的位置就是斷點了。續傳就是當一個未完成的下載任務再次開始時,會從上次的斷點繼續傳送。
使用多線程斷點續傳下載的時候,將下載或上傳任務(一個文件或一個壓縮包)人為的劃分為幾個部分,每一個部分采用一個線程進行上傳或下載,多個線程并發可以占用服務器端更多資源,從而加快下載速度。
在下載(或上傳)過程中,如果網絡故障、電量不足等原因導致下載中斷,這就需要使用到斷點續傳功能。下次啟動時,可以從記錄位置(已經下載的部分)開始,繼續下載以后未下載的部分,避免重復部分的下載。斷點續傳實質就是能記錄上一次已下載完成的位置。
斷點續傳的過程:
①斷點續傳需要在下載過程中記錄每條線程的下載進度;
②每次下載開始之前先讀取數據庫,查詢是否有未完成的記錄,有就繼續下載,沒有則創建新記錄插入數據庫;
③在每次向文件中寫入數據之后,在數據庫中更新下載進度;
④下載完成之后刪除數據庫中下載記錄。
18.當 TableView 的 Cell 改變時,如何讓這些改變以動畫的形式呈現?
@interface ViewController ()
@property (nonatomic, strong) NSIndexPath *index;
@end
@implementation ViewController
static NSString *ID = @"cell";
- (void)viewDidLoad {
[super viewDidLoad];
}
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:ID];
cell.textLabel.text = [NSString stringWithFormat:@"%ld",(long)indexPath.row];
return cell;
}
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
{
return 10;
}
- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath
{
if(self.index == indexPath){
return 120;
}
return 60;
}
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
{
self.index = indexPath;
[tableView deselectRowAtIndexPath:indexPath animated:TRUE];
//重點是這2句代碼實現的功能
[tableView beginUpdates];
[tableView endUpdates];
}
19.如何把一個包含自定義對象的數組序列化到磁盤?
//自定義對象只需要實現NSCoding協議即可
- (void)viewDidLoad
{
[super viewDidLoad];
User *user = [User new];
Account *account = [Account new];
NSArray *userArray = @[user, account];
// 存到磁盤
NSData * tempArchive = [NSKeyedArchiver archivedDataWithRootObject: userArray];
}
//代理方法
- (instancetype)initWithCoder:(NSCoder *)coder
{
self = [super initWithCoder:coder];
if (self) {
self.user = [aDecoder decodeObjectForKey:@"user"];
self.account = [aDecoder decodeObjectForKey:@"account"];
}
return self;
}
// 代理方法
-(void)encodeWithCoder:(NSCoder *)aCoder{
[aCoder encodeObject:self.user forKey:@"user"];
[aCoder encodeObject:self.account forKey:@"account"];
}
20.內存管理的幾條原則時什么?按照默認法則.那些關鍵字生成的對象需要手動釋放?在和property結合的時候怎樣有效的避免內存泄露?
①誰申請,誰釋放;
②遵循Cocoa Touch的使用原則;
③內存管理主要要避免“過早釋放”和“內存泄漏”,對于“過早釋放”需要注意@property設置特性時,一定要用對特性關鍵字,對于“內存泄漏”,一定要申請了要負責釋放,要細心。
④關鍵字alloc 或new 生成的對象需要手動釋放;
⑤設置正確的property屬性,對于retain需要在合適的地方釋放。
21.Object C中創建線程的方法是什么?如果在主線程中執行代碼,方法是什么?如果想延時執行代碼、方法又是什么?
線程創建有三種方法:使用NSThread創建、使用GCD的dispatch、使用子類化的NSOperation,然后將其加入NSOperationQueue;在主線程執行代碼,方法是performSelectorOnMainThread,如果想延時執行代碼可以用performSelector:onThread:withObject:waitUntilDone:
22.IBOutlet連出來的視圖屬性為什么可以被設置成weak?
因為父控件的subViews數組已經對它有一個強引用。
23.XIB中User Defined Runtime Attributes如何使用?
①User Defined Runtime Attributes是一個不被看重但功能非常強大的的特性,它能夠通過KVC的方式配置一些你在interface builder中不能配置的屬性。
② 當你希望在IB中作盡可能多得事情,這個特性能夠幫助你編寫更加輕量級的viewcontroller。
24.請簡述UITableView的復用機制。
①每次創建cell的時候通過dequeueReusableCellWithIdentifier:方法創建cell,它先到緩存池中找指定標識的cell,如果沒有就直接返回nil。
②如果沒有找到指定標識的cell,那么會通過initWithStyle:reuseIdentifier:創建一個cell。
③ 當cell離開界面就會被放到緩存池中,以供下次復用
25.如何高性能的給UIImageView 加個圓角?
? 不好的解決方案
○ 使用下面的方式會強制Core Animation提前渲染屏幕的離屏繪制, 而離屏繪制就會給性能帶來負面影響,會有卡頓的現象出現 。
self.view.layer.cornerRadius = 5;
self.view.layer.masksToBounds = YES;
? 正確的解決方案:使用繪圖技術。
- (UIImage *)circleImage
{
NO代表透明
UIGraphicsBeginImageContextWithOptions(self.size, NO, 0.0);
獲得上下文
CGContextRef ctx = UIGraphicsGetCurrentContext();
添加一個圓
CGRect rect = CGRectMake(0, 0, self.size.width, self.size.height);
CGContextAddEllipseInRect(ctx, rect);
裁剪
CGContextClip(ctx);
將圖片畫上去
[self drawInRect:rect];
UIImage *image = UIGraphicsGetImageFromCurrentImageContext();
關閉上下文
UIGraphicsEndImageContext();
return image;
}
? 還有一種方案:使用了貝塞爾曲線"切割"個這個圖片, 給UIImageView 添加了的圓角,其實也是通過繪圖技術來實現的
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];
26.使用drawRect有什么影響?
? drawRect方法依賴Core Graphics框架來進行自定義的繪制
? 缺點:它處理touch事件時每次按鈕被點擊后,都會用setNeddsDisplay進行強制重繪;而且不止一次,每次單點事件觸發兩次執行。這樣的話從性能的角度來說,對CPU和內存來說都是欠佳的。特別是如果在我們的界面上有多個這樣的UIButton實例,那就會很糟糕了
? 這個方法的調用機制也是非常特別. 當你調用 setNeedsDisplay 方法時, UIKit 將會把當前圖層標記為dirty,但還是會顯示原來的內容,直到下一次的視圖渲染周期,才會將標記為 dirty 的圖層重新建立Core Graphics上下文,然后將內存中的數據恢復出來, 再使用 CGContextRef 進行繪制。
27.如何渲染UILabel的文字?
通過NSAttributedString/NSMutableAttributedString(富文本)
28.控制器的生命周期
? 就是問的view的生命周期,下面已經按方法執行順序進行了排序
自定義控制器view,這個方法只有實現了才會執行
- (void)loadView
{
self.view = [[UIView alloc] init];
self.view.backgroundColor = [UIColor orangeColor];
}
view是懶加載,只要view加載完畢就調用這個方法
- (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已經完成子控件的布局
- (void)viewDidLayoutSubviews
{
[super viewDidLayoutSubviews];
NSLog(@"%s",__func__);
}
view已經出現
- (void)viewDidAppear:(BOOL)animated
{
[super viewDidAppear:animated];
NSLog(@"%s",__func__);
}
view即將消失
- (void)viewWillDisappear:(BOOL)animated
{
[super viewWillDisappear:animated];
NSLog(@"%s",__func__);
}
view已經消失
- (void)viewDidDisappear:(BOOL)animated
{
[super viewDidDisappear:animated];
NSLog(@"%s",__func__);
}
收到內存警告
- (void)didReceiveMemoryWarning
{
[super didReceiveMemoryWarning];
NSLog(@"%s",__func__);
}
方法已過期,即將銷毀view
- (void)viewWillUnload
{
}
方法已過期,已經銷毀view
- (void)viewDidUnload
{
}
29.iOS本地數據存儲都有幾種方式?
①.NSkeyedArchiver:采用歸檔的形式來保存數據,該數據對象需要遵守NSCoding協議,并且該對象對應的類必須提供encodeWithCoder:和initWithCoder:方法.前一個方法告訴系統怎么對對象進行編碼,而后一個方法則是告訴系統怎么對對象進行解碼。
②.NSUserDefaults:用來保存應用程序設置和屬性,用戶保存的數據.用戶再次打開程序或者開機后這些數據仍然存在.NSUserDefaults可以存儲的數據類型包括:NSData,NSString,NSNumber,NSDate,NSArray.NSDictionary,其他類型的數據需要先行轉換。
③.Write寫入方式:永久保存在磁盤中.具體:a.獲得文件保存的路徑.b.生成該路徑下的文件,c,往文件中寫入數據.d.從文件中讀出數據。
④.SQLite:采用SQLite數據庫來存儲數據,SQLite作為一種輕量級數據庫.具體:a.添加SQLite相關的庫以及頭文件,b.使用數據庫存數數據:打開數據庫,編寫數據庫語句,執行,關閉數據庫.另:寫入數據庫,字符串可以采用char方式,而從數據庫中取出char類型,當char類型有表示中文字符時,會出現亂碼,這是因為數據庫默認使用ascII編碼方式,所以想要正確從數據庫中取出中文,需要使用NSString來接受從數據庫取出的字符串。
⑤.CoreData:原理是對SQLite的封裝,開發者不需要接觸sql語句,就可以對數據庫進行操作。
30.簡述應用程序按HOME鍵進入后臺時的生命周期,以及從后臺進入前臺時的生命周期.
//前者:
- (void)applicationWillResignActive:(UIApplication *)application
- (void)applicationDidEnterBackground:(UIApplication *)application
//后者:
- (void)applicationWillEnterForeground:(UIApplication *)application
- (void)applicationDidBecomeActive:(UIApplication *)application
補充各個程序運行狀態時代理的回調:
- (BOOL)application:(UIApplication *)application willFinishLaunchingWithOptions:(NSDictionary *)launchOptions
//告訴代理進程啟動但還沒進入狀態保存
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
//告訴代理啟動基本完成程序準備開始運行
- (void)applicationWillResignActive:(UIApplication *)application
//當應用程序將要入非活動狀態執行,在此期間,應用程序不接收消息或事件,比如來電話了
- (void)applicationDidBecomeActive:(UIApplication *)application
//當應用程序入活動狀態執行,這個剛好跟上面那個方法相反
- (void)applicationDidEnterBackground:(UIApplication *)application
//當程序被推送到后臺的時候調用。所以要設置后臺繼續運行,則在這個函數里面設置即可
- (void)applicationWillEnterForeground:(UIApplication *)application
//當程序從后臺將要重新回到前臺時候調用,這個剛好跟上面的那個方法相反。
- (void)applicationWillTerminate:(UIApplication *)application
//當程序將要退出是被調用,通常是用來保存數據和一些退出前的清理工作。這個需要要設置UIApplicationExitsOnSuspend的鍵值。
- (void)applicationDidFinishLaunching:(UIApplication*)application
//當程序載入后執行
在上面8個方法對應的方法中鍵入NSLog打印。
現在啟動程序看看執行的順序:
//啟動程序
lifeCycle[40428:11303] willFinishLaunchingWithOptions
lifeCycle[40428:11303] didFinishLaunchingWithOptions
lifeCycle[40428:11303] applicationDidBecomeActive
//按下home鍵
lifeCycle[40428:11303] applicationWillResignActive
lifeCycle[40428:11303] applicationDidEnterBackground
//雙擊home鍵,再打開程序
lifeCycle[40428:11303] applicationWillEnterForeground
lifeCycle[40428:11303] applicationDidBecomeActive