27、ViewController的didReceiveMemoryWarning是在什么時(shí)候調(diào)用的仇轻?默認(rèn)的操作是什么玩焰?
當(dāng)程序接到內(nèi)存警告時(shí)View Controller將會(huì)收到這個(gè)消息:didReceiveMemoryWarning
從iOS3.0開始欲主,不需要重載這個(gè)函數(shù)别凹,把釋放內(nèi)存的代碼放到viewDidUnload中去。
這個(gè)函數(shù)的默認(rèn)實(shí)現(xiàn)是:檢查controller是否可以安全地釋放它的view(這里加粗的view指的是controller的view屬性)洽糟,比如view本身沒有superview并且可以被很容易地重建(從nib或者loadView函數(shù))炉菲。
如果view可以被釋放,那么這個(gè)函數(shù)釋放view并調(diào)用viewDidUnload坤溃。
你可以重載這個(gè)函數(shù)來釋放controller中使用的其他內(nèi)存拍霜。但要記得調(diào)用這個(gè)函數(shù)的super實(shí)現(xiàn)來允許父類(一般是UIVIewController)釋放view。
如果你的ViewController保存著view的子view的引用薪介,那么祠饺,在早期的iOS版本中,你應(yīng)該在這個(gè)函數(shù)中來釋放這些引用汁政。而在iOS3.0或更高版本中道偷,你應(yīng)該在viewDidUnload中釋放這些引用.
在iOS4和iOS5系統(tǒng)中,當(dāng)內(nèi)存不足记劈,應(yīng)用受到MemoryWarning時(shí)勺鸦,系統(tǒng)就會(huì)自動(dòng)調(diào)用當(dāng)前沒有在界面上得ViewController的viewDidUnload方法。通常情況下目木,未顯示在界面的ViewController是UINavigationController Push棧中未在棧頂?shù)腣iewController换途,以及UITabBarController中未顯示的子ViewController。這些ViewController都在MemoryWarning事件發(fā)生時(shí)刽射,讓系統(tǒng)自動(dòng)調(diào)用viewDidUnload 方法军拟。
在iOS6中,由于viewDidUnload 事件任何情況下都不會(huì)被觸發(fā)誓禁,所以蘋果在文檔中建議懈息,應(yīng)該將回收內(nèi)存的相關(guān)操作移到另一個(gè)函數(shù)didReceiveMemoryWarning中。但是如果僅僅寫成:(以下為錯(cuò)誤示例)
- (void)didReceiveMemoryWarning {
[super didReceiveMemoryWarning];
if(self.isViewLoaded&&!self.view.window)
{
self.view=nil;
}
}
在iOS6以后现横,不建議將view置為nil的原因如下:
1漓拾,UIView有一個(gè)CALayer成員變量,CALayer用于將自己畫到屏幕上戒祠,如下圖:
2骇两,CALayer是一個(gè)bitmap圖像的容器類,當(dāng)UIView調(diào)用自身的drawRect時(shí)姜盈,CALayer才會(huì)創(chuàng)建bitmap圖像類低千。
3,具體占內(nèi)存的其實(shí)是一個(gè)bitmap圖像類,CALayer只占48Bytes示血,UiView只占96Bytes棋傍。而一個(gè)iPad的全屏UIView的bitmap類會(huì)占到12MB的大小。
4难审,在iOS6瘫拣,當(dāng)系統(tǒng)發(fā)出MemoryWarning時(shí),系統(tǒng)會(huì)自動(dòng)回收bitmap類告喊,但是不回收UIView和CALayer類麸拄。這樣既能回收大部分內(nèi)存,又能在需要bitmap類時(shí)黔姜,通過調(diào)用UIView的drawRect:方法重建拢切。
內(nèi)存優(yōu)化:
蘋果系統(tǒng)對(duì)上面的內(nèi)存回收做了一個(gè)優(yōu)化:
1,當(dāng)一段內(nèi)存被分配時(shí)秆吵,它會(huì)被標(biāo)記成“In User”,以防止被重復(fù)使用淮椰。當(dāng)內(nèi)存被釋放時(shí),這段內(nèi)存被標(biāo)記為“Not in use”,這樣有新的內(nèi)存申請(qǐng)時(shí),這塊內(nèi)存就可能被分配給其他變量悲关。
2,CALayer包括的具體的bitmap內(nèi)容的私有成員變量類型為CABackingStore黔牵,當(dāng)收到MemoryWarning時(shí),CABackingStore類型的內(nèi)存會(huì)被標(biāo)記為Volatile類型爷肝,表示這塊內(nèi)存可能再次被原變量使用猾浦。
這樣,有了上面優(yōu)化后灯抛,當(dāng)收到MemoryWarning時(shí)金赦,雖然所有的CALayer所包含的bitmap內(nèi)存被標(biāo)記成volatile了,但是只要這塊內(nèi)存沒有被復(fù)用对嚼,當(dāng)需要重建bitmap內(nèi)存時(shí)夹抗,可以直接被復(fù)用,避免了再次調(diào)用UIView的drawRect:方法纵竖。
簡(jiǎn)單說:對(duì)于iOS6漠烧,不需要做任何以前viewDidUnload的事情,更不需要把以前viewDidUnload的代碼移到didReceiveMemoryWarning方法中靡砌。
30. frame和bounds有什么不同已脓?
31.ViewController生命周期
按照?qǐng)?zhí)行順序排列
- initWithCoder:通過nib文件初始化時(shí)觸發(fā)
- awakeFromNib:nib文件被加載的時(shí)候,會(huì)發(fā)送一個(gè)awakeFromNib的消息到nib文件中的每個(gè)對(duì)象
- loadView:開始加載視圖控制器自帶的view
- viewDidLoad:視圖控制器的view被加載完成
- viewWillAppear:視圖控制器的view將要顯示在window上
- updateViewConstraints:視圖控制器的view開始更新AutoLayout約束
- viewWillLayoutSubviews:視圖控制器的view將要更新內(nèi)容視圖的位置
- viewDidLayoutSubviews:視圖控制器的view已經(jīng)更新視圖的位置
- viewDidAppear:視圖控制器的view已經(jīng)展現(xiàn)到window上
- viewWillDisappear:視圖控制器的view將要從window上消失
- viewDidDisappear:視圖控制器的view已經(jīng)從window上消失
為什么不建議重載loadView通殃?
永遠(yuǎn)不要主動(dòng)調(diào)用這個(gè)函數(shù)度液。view controller會(huì)在view的property被請(qǐng)求并且當(dāng)前view值為nil時(shí)調(diào)用這個(gè)函數(shù)。如果你手動(dòng)創(chuàng)建view,你應(yīng)該重載這個(gè)函數(shù),且不要在重載的時(shí)候調(diào)用[super loadview]堕担。如果你用IB創(chuàng)建view并初始化view controller已慢,那就意味著你使用initWithNibName:bundle:方法,這時(shí)霹购,你不應(yīng)該重載loadView函數(shù)佑惠。
這個(gè)方法系統(tǒng)的默認(rèn)實(shí)現(xiàn)是這樣:
1;尋找有關(guān)可用的nib文件的信息,根據(jù)這個(gè)信息來加載nib文件 ? ? ? //所以齐疙,nib的加載過程是在loadview中完成的哦兢仰。
2;如果沒有有關(guān)nib文件的信息,默認(rèn)創(chuàng)建一個(gè)空白的UIView對(duì)象剂碴,然后把對(duì)象成賦值給view controller的主view。
所以轻专,如果你決定重載這個(gè)函數(shù)時(shí)忆矛,你也應(yīng)該完成這些步驟:
把子類的view賦給view屬性(property)(你create的view必須是唯一的實(shí)例,并且不被其他任何controller共享)请垛,而且你重載的這個(gè)函數(shù)不應(yīng)該調(diào)用super催训,這個(gè)也是為了保持主view與controller的單一映射關(guān)系。
一宗收、結(jié)構(gòu)
按結(jié)構(gòu)可以對(duì)ios的所有ViewController分成兩類:
1漫拭、主要用于展示內(nèi)容的ViewController,這種ViewController主要用于為用戶展示內(nèi)容混稽,并與用戶交互采驻,如UITableViewController,UIViewController匈勋。
2礼旅、用于控制和顯示其他ViewController的ViewController。這種ViewController一般都是一個(gè)ViewController的容器洽洁。如UINavigationController痘系,UITabbarController。它們都有一個(gè)屬性:viewControllers饿自。其中UINavigationController表示一種Stack式結(jié)構(gòu)汰翠,push一個(gè)ViewController或pop一次,因此后一個(gè)ViewController一般會(huì)依賴前一個(gè)ViewController昭雌。而UITabbarController表示一個(gè)Array結(jié)構(gòu)复唤,各個(gè)ViewController是并列的。
第一種ViewController會(huì)經(jīng)常被繼承烛卧,用來顯示不同的數(shù)據(jù)給用戶苟穆。而第二種很少被繼承,除非你真的需要自定義它。
二雳旅、Controller和View的生命周期
這里指的View是指Controller的View跟磨。它作為Controler的屬性,生命周期在Controller的生命周期內(nèi)攒盈。就是說你的Controller不能在view釋放前就釋放了抵拘。
圖2 ViewController生命周期
當(dāng)你alloc并init了一個(gè)ViewController時(shí),這個(gè)ViewController應(yīng)該是還沒有創(chuàng)建view的型豁。ViewController的view是使用了lazyInit方式創(chuàng)建僵蛛,就是說你調(diào)用的view屬性的getter:[self view]。在getter里會(huì)先判斷view是否創(chuàng)建迎变,如果沒有創(chuàng)建充尉,那么會(huì)調(diào)用loadView來創(chuàng)建view。loadView完成時(shí)會(huì)繼續(xù)調(diào)用viewDidLoad衣形。loadView和viewDidLoad的一個(gè)區(qū)別就是:loadView時(shí)還沒有view驼侠。而viewDidLoad時(shí)view以及創(chuàng)建好了。
當(dāng)view被添加其他view中之前時(shí)谆吴,會(huì)調(diào)用viewWillAppear倒源,而之后會(huì)調(diào)用viewDidAppear。
當(dāng)view從其他view中移出之前時(shí)句狼,會(huì)調(diào)用viewWillDisAppear笋熬,而之后會(huì)調(diào)用viewDidDisappear。
當(dāng)view不在使用腻菇,而且是disappeared胳螟,受到內(nèi)存警告時(shí),那么viewController會(huì)將view釋放并將其指向nil筹吐。
三旺隙、代碼組織(如何設(shè)計(jì)良好的viewcontroller)
ViewController生命周期中有那么多函數(shù),一個(gè)重要問題就是什么代碼該寫在什么地方骏令。
1蔬捷、init里不要出現(xiàn)創(chuàng)建view的代碼。良好的設(shè)計(jì)榔袋,在init里應(yīng)該只有相關(guān)數(shù)據(jù)的初始化周拐,而且這些數(shù)據(jù)都是比較關(guān)鍵的數(shù)據(jù)。init里不要掉self.view凰兑,否則會(huì)導(dǎo)致viewcontroller創(chuàng)建view妥粟。(因?yàn)関iew是lazyinit的)。
2吏够、loadView中只初始化view勾给,一般用于創(chuàng)建比較關(guān)鍵的view如tableViewController的tabView滩报,UINavigationController的navgationBar,不可掉用view的getter(在掉super loadView前)播急,最好也不要初始化一些非關(guān)鍵的view脓钾。如果你是從nib文件中創(chuàng)建的viewController在這里一定要首先調(diào)用super的loadView方法,但建議不要重載這個(gè)方法桩警。
3可训、viewDidLoad 這時(shí)候view已經(jīng)有了,最適合創(chuàng)建一些附加的view和控件了捶枢。有一點(diǎn)需要注意的是握截,viewDidLoad會(huì)調(diào)用多次(viewcontroller可能多次載入view,參見圖2)烂叔。
4谨胞、viewWillAppear 這個(gè)一般在view被添加到superview之前,切換動(dòng)畫之前調(diào)用蒜鸡。在這里可以進(jìn)行一些顯示前的處理胯努。比如鍵盤彈出,一些特殊的過程動(dòng)畫(比如狀態(tài)條和navigationbar顏色)术瓮。
5、viewDidAppear 一般用于顯示后贰健,在切換動(dòng)畫后胞四,如果有需要的操作,可以在這里加入相關(guān)代碼伶椿。
6辜伟、viewDidUnload 這時(shí)候viewController的view已經(jīng)是nil了。由于這一般發(fā)生在內(nèi)存警告時(shí)脊另,所以在這里你應(yīng)該將那些不在顯示的view釋放了导狡。比如你在viewcontroller的view上加了一個(gè)label,而且這個(gè)label是viewcontroller的屬性偎痛,那么你要把這個(gè)屬性設(shè)置成nil旱捧,以免占用不必要的內(nèi)存,而這個(gè)label在viewDidLoad時(shí)會(huì)重新創(chuàng)建踩麦。
ViewController是iOS開發(fā)中MVC模式中的C枚赡,ViewController是view的controller,ViewController的職責(zé)主要包括管理內(nèi)部各個(gè)view的加載顯示和卸載谓谦,同時(shí)負(fù)責(zé)與其他ViewController的通信和協(xié)調(diào)贫橙。在ios中,有兩類ViewController反粥,一類是顯示內(nèi)容的卢肃,比如UIViewController疲迂、UITableViewController等,同時(shí)還可以自定義繼承自UIViewController的ViewController莫湘;另一類是ViewController容器尤蒿,UINavigationViewController和UITabBarController等,UINavigationController是以Stack的形式來存儲(chǔ)和管理ViewController逊脯,UITabBarController是以Array的形式來管理ViewController优质。和Android中Activity一樣,IOS開發(fā)中军洼,ViewController也有自己的生命周期(Lifecycle)巩螃。
首先來看看View的加載過程,如下圖:
從圖中可以看到匕争,在view加載過程中首先會(huì)調(diào)用loadView方法避乏,在這個(gè)方法中主要完成一些關(guān)鍵view的初始化工作,比如UINavigationViewController和UITabBarController等容器類的ViewController甘桑;接下來就是加載view拍皮,加載成功后,會(huì)接著調(diào)用viewDidLoad方法跑杭,這里要記住的一點(diǎn)是铆帽,在loadView之前,是沒有view的德谅,也就是說爹橱,在這之前,view還沒有被初始化窄做。完成viewDidLoad方法后愧驱,ViewController里面就成功的加載view了,如上圖右下角所示椭盏。
在Controller中創(chuàng)建view有兩種方式组砚,一種是通過代碼創(chuàng)建、一種是通過Storyboard或Interface Builder來創(chuàng)建掏颊,后者可以比較直觀的配置view的外觀和屬性糟红,Storyboard配合IOS6后推出的AutoLayout,應(yīng)該是Apple之后主推的一種UI定制解決方案乌叶,后期我會(huì)專門介紹一篇使用AutoLayout進(jìn)行UI制作的文章改化。言歸正傳,通過IB或Storyboard創(chuàng)建view枉昏,在Controller中創(chuàng)建view后陈肛,會(huì)在Controller中對(duì)view進(jìn)行一些操作,會(huì)出現(xiàn)如下代碼:
@interface MyViewController()
@property (nonatomic) IBOutlet id myButton;
@property (nonatomic) IBOutlet id myTextField;
- (IBAction)myAction:(id)sender;
@end
這里用IBOutlet標(biāo)記了一個(gè)UIButton和一個(gè)UITextField兄裂,用IBAction來標(biāo)記UIButton的響應(yīng)事件句旱,IBOutlet和IBAction都是一個(gè)整形常量阳藻,用來標(biāo)記控件,通過一張圖能比較清晰的看清他們之間的關(guān)系:
上圖中谈撒,MyViewController是繼承自UIViewController的一個(gè)自定義ViewController腥泥,它包含兩個(gè)View,一個(gè)是UIButton啃匿,一個(gè)是UITextField蛔外,從箭頭的指向性上就可以比較好的理解IBOutlet和IBAction了。IBOutlet是告訴Interface Builder溯乒,此實(shí)例變量被連接到nib文件中的view對(duì)象夹厌,IBOutlet本身不做任何操作,只是一個(gè)標(biāo)記作用裆悄。IBAction同樣是個(gè)標(biāo)記關(guān)鍵字矛纹,它只能標(biāo)記方法,它告訴IB用IBAction標(biāo)記的方法可以被某個(gè)控件觸發(fā)光稼。
通過編程的方式創(chuàng)建view或南,如下代碼:
- (void)loadView
{
CGRect applicationFrame = [[UIScreen mainScreen] applicationFrame];
UIView *contentView = [[UIView alloc] initWithFrame:applicationFrame];
contentView.backgroundColor = [UIColor blackColor];
self.view = contentView;
levelView = [[LevelView alloc] initWithFrame:applicationFrame viewController:self];
[self.view addSubview:levelView];
}
上述代碼首先得到屏幕的frame,然后根據(jù)該frame生成了一個(gè)contentView艾君,并指定當(dāng)前ViewController的root view為contentView采够,然后生成了一個(gè)LevelView的自定義View并將它通過addSubview:方法添加到當(dāng)前ViewController當(dāng)中,完成view的初始化加載冰垄。
關(guān)于loadView方法的重寫蹬癌,官方文檔中有一個(gè)明顯的注釋,原文如下:
Note: When overriding the loadView method to create your views programmatically, you should not call super. Doing so initiates the default view-loading behavior and usually just wastes CPU cycles. Your own implementation of the loadView method should do all the work that is needed to create a root view and subviews for your view controller.
意思是當(dāng)通過代碼方式去創(chuàng)建你自己的view時(shí)播演,在loadView方法中不應(yīng)該調(diào)用super冀瓦,如果調(diào)用[super loadView]會(huì)影響CPU性能伴奥。
接下來我們看看ViewController中的view是如何被卸載的:
從圖中可以看到写烤,當(dāng)系統(tǒng)發(fā)出內(nèi)存警告時(shí),會(huì)調(diào)用didReceiveMemoeryWarning方法拾徙,如果當(dāng)前有能被釋放的view洲炊,系統(tǒng)會(huì)調(diào)用viewWillUnload方法來釋放view,完成后調(diào)用viewDidUnload方法尼啡,至此暂衡,view就被卸載了。此時(shí)原本指向view的變量要被置為nil崖瞭,具體操作是在viewDidUnload方法中調(diào)用self.myButton = nil;
小結(jié)一下:
loadView和viewDidLoad的區(qū)別就是狂巢,loadView時(shí)view還沒有生成,viewDidLoad時(shí)书聚,view已經(jīng)生成了唧领,loadView只會(huì)被調(diào)用一次藻雌,而viewDidLoad可能會(huì)被調(diào)用多次(View可能會(huì)被多次加載),當(dāng)view被添加到其他view中之前斩个,會(huì)調(diào)用viewWillAppear胯杭,之后會(huì)調(diào)用viewDidAppear。當(dāng)view從其他view中移除之前受啥,調(diào)用viewWillDisAppear做个,移除之后會(huì)調(diào)用viewDidDisappear。當(dāng)view不再使用時(shí)滚局,受到內(nèi)存警告時(shí)居暖,ViewController會(huì)將view釋放并將其指向?yàn)閚il。
ViewController的生命周期中各方法執(zhí)行流程如下:
init—>loadView—>viewDidLoad—>viewWillApper—>viewDidApper—>viewWillDisapper—>viewDidDisapper—>viewWillUnload->viewDidUnload—>dealloc
33. OC中是如何實(shí)現(xiàn)線程同步的核畴?
@synchronized: 添加同步鎖
NSLock:加鎖
NSCondition:加條件鎖
dispatch_async(dispatch_get_main_queue(), ^{}); :異步主線程
NSOperationQueue:添加線程依賴
NSOperationQueue:設(shè)置最大并發(fā)數(shù)為1
36. 編程中膝但,保存數(shù)據(jù)有哪幾種方式?
NSKeyedArchiver:
1.1關(guān)于數(shù)據(jù)的持久化存儲(chǔ)的幾種方式
說到NSKeyedArchiver,也就先要了解下iOS開發(fā)中關(guān)于數(shù)據(jù)持久化存儲(chǔ)的幾種方式:1.屬性列表 2.對(duì)象歸檔 3.數(shù)據(jù)庫存儲(chǔ)(SQLite) 4.Apple提供的CoreData存儲(chǔ)工具谤草,關(guān)于以上存儲(chǔ)方式的使用場(chǎng)景和各自的優(yōu)缺點(diǎn)跟束,我在此就不再贅述了,今天主要談一談關(guān)于第二類存儲(chǔ)方式-對(duì)象歸檔的使用方法和特點(diǎn)丑孩。
1.2什么是對(duì)象歸檔
歸檔是一種很常用的文件儲(chǔ)存方法冀宴,幾乎任何類型的對(duì)象都能夠被歸檔儲(chǔ)存(實(shí)際上是一種文件保存的形式)。
蘋果提供了NSKeyedArchiver和NSKeyedUnarchiver兩個(gè)類以供我們把對(duì)象序列化和反序列化温学,在存儲(chǔ)之前使用NSKeyedArchiver進(jìn)行序列化操作略贮,并且寫入本地文件,在使用之前使用NSKeyedUnarchiver進(jìn)行反序列化的操作仗岖,以供提取使用逃延!
1.3什么場(chǎng)景下會(huì)使用到對(duì)象歸檔
在實(shí)際的開發(fā)過程中,我們會(huì)使用各種數(shù)據(jù)存儲(chǔ)的方式轧拄。
如果是簡(jiǎn)單的進(jìn)行一些系統(tǒng)提供的類型揽祥,例如NSArray,NSDictionary,NSString,BOOL,NSInteger,NSfloat等基本數(shù)據(jù)類型或者對(duì)象,我們可以選擇系統(tǒng)提供的NSUserDefault這個(gè)單例檩电,使用簡(jiǎn)單方便拄丰,但是僅僅只能對(duì)以上這些特定的數(shù)據(jù)格式進(jìn)行存儲(chǔ),是否有些局限性俐末?而且屬性屬性列表這種方式又是否安全呢料按?可能這些這些條件NSUserDefault都無法滿足!
對(duì)于一些規(guī)律性的卓箫,量級(jí)比較大的數(shù)據(jù)载矿,又有規(guī)律可循的數(shù)據(jù),我們可以選擇建表或者使用Apple提供的CoreData進(jìn)行持久化的存儲(chǔ)烹卒!
那么如果數(shù)據(jù)的量級(jí)不是很大闷盔,沒有必要?jiǎng)佑脭?shù)據(jù)庫或者是CoreData這種大規(guī)模的殺傷性武器的時(shí)候魂挂,而且又對(duì)數(shù)據(jù)的安全性和持久性有那么些要求的時(shí)候,我們最好去選擇對(duì)象序列化這種中等殺傷性工具了馁筐!
1.4對(duì)象歸檔的使用方法
使用歸檔的方法對(duì)系統(tǒng)提供的基本類型和基本對(duì)象進(jìn)行歸檔的操作涂召,在這里不再闡述了,如果明白了對(duì)自定義對(duì)象的歸檔和解檔敏沉,那么系統(tǒng)的基本數(shù)據(jù)類型和基本對(duì)象的歸檔和解檔也就相對(duì)很easy了果正!
具體的使用方法我就借用目前我正在開發(fā)維護(hù)的代碼進(jìn)行以下說明:
目前我們產(chǎn)品的需求是在用戶登錄之后,就持久化存儲(chǔ)用戶的登陸相關(guān)信息盟迟,在后續(xù)使用中不需要再次登陸秋泳。當(dāng)用戶沒有退出登錄,卸載程序后攒菠,重新從App Store中現(xiàn)在app后迫皱,同樣保持登陸狀態(tài)。
如果僅僅是登陸之后的登陸狀態(tài)辖众,使用NSUserDefault完全可以實(shí)現(xiàn)卓起,只是安全性不是太好而已,但是當(dāng)用戶在登陸狀態(tài)卸載并且重新安裝app后凹炸,仍然要保持登陸狀態(tài)的話戏阅,那這個(gè)問題就值得思考下了!
基于以上需求啤它,也就是說我們要把用戶的登陸信息存儲(chǔ)在一個(gè)地方奕筐,這個(gè)地方要滿足的條件是1.持久化存儲(chǔ),用戶登錄后变骡,再次打開app不需要重新進(jìn)行登錄????? 2.用戶在登錄的狀態(tài)下卸載app离赫,再次重新安裝后,仍然保持卸載前的登陸狀態(tài)塌碌,也就是要完美重現(xiàn)卸載前的狀態(tài)渊胸!
基于以上的條件,我們自然而然地聯(lián)想到蘋果的sandBox機(jī)制誊爹,關(guān)于蘋果的sandBox機(jī)制蹬刷,我們不再詳述瓢捉,最關(guān)鍵的一點(diǎn)是:在sandBox中的Document目錄下存儲(chǔ)的文件频丘,會(huì)根據(jù)用戶的appleID同步到apple的服務(wù)端,也就是說如果再次安裝app的時(shí)候泡态,此app中的沙盒(sandBox)的Document目錄下的文件會(huì)被再次還原(用戶的app購買信息是和用戶的appleID綁定的)搂漠,那么需求就被完美的滿足了,具體的代碼實(shí)現(xiàn)以及注意事項(xiàng)請(qǐng)繼續(xù)向下閱讀:
@interface IHomeSession : NSObject
@property (nonatomic, strong) NSString *sessionId;? ? ? ? ????? //會(huì)話Id
@property (nonatomic, strong) NSDate?? *lastSessionDate;? ? //記錄上一次請(qǐng)求時(shí)間
@property (nonatomic, strong) NSString *token;? ? ? ? ? ? ?????? //ut值
@property (nonatomic, strong) NSString *alias;? ? ? ? ? ? ??????? //設(shè)備別名
@property (nonatomic, strong) NSString *username;? ? ? ? ??? //用戶名
//序列化對(duì)象的單例
+ (IHomeSession *)sharedMemory;
//保存
- (void)save;
//獲取ssesionId
- (NSString *)getSessionId;
//重置
- (void)reset;
@end
以上是.h文件
具體屬性可以根據(jù)自己的需求進(jìn)行添加
提供獲取序列化單例的方法某弦,方便在項(xiàng)目全局進(jìn)行獲取和使用
提供保存(save),重置(reset)桐汤,獲取sessionId(getSessionId)的api接口以供使用而克,也可以根據(jù)自己的需求添加api
最重要的一點(diǎn),是當(dāng)前類需要遵循NSCoding協(xié)議怔毛,NSCoding協(xié)議中有兩個(gè)方法员萍,都是requred方法,遵循該協(xié)議后拣度,必須實(shí)現(xiàn)碎绎。
以下是.m文件中NSCoding協(xié)議的具體實(shí)現(xiàn)
#pragma mark - NSCoding
- (void)encodeWithCoder:(NSCoder *)aCoder
{
[aCoder encodeObject:_sessionId forKey:@"_sessionId"];
[aCoder encodeObject:_lastSessionDate forKey:@"_lastSessionDate"];
[aCoder encodeObject:_token forKey:@"_token"];
[aCoder encodeObject:_username forKey:@"_username"];
[aCoder encodeObject:_alias forKey:@"_alias"];
}
通過以上編碼的方法,對(duì)當(dāng)前類中的屬性進(jìn)行逐一的鍵值編碼抗果!
- (id)initWithCoder:(NSCoder *)aDecoder
{
if (self = [super init])
{
_sessionId = [aDecoder decodeObjectForKey:@"_sessionId"];
_alias = [aDecoder decodeObjectForKey:@"_alias"];
_lastSessionDate = [aDecoder decodeObjectForKey:@"_lastSessionDate"];
_token = [aDecoder decodeObjectForKey:@"_token"];
_username = [aDecoder decodeObjectForKey:@"_username"];
}
return self;
}
通過以上解碼的方法筋帖,對(duì)當(dāng)前類中的屬性,根據(jù)鍵進(jìn)行逐一的逆向編碼冤馏,并返回一個(gè)當(dāng)前類的實(shí)例日麸!
實(shí)現(xiàn)了以上的協(xié)議方法后,我們就可對(duì)當(dāng)前的類對(duì)象進(jìn)行歸檔和解檔的操作了:
首先我們規(guī)定一個(gè)歸檔文件在沙盒中的存儲(chǔ)路徑逮光,寫在一個(gè)類方法中代箭,方便取用,為了滿足app重新安裝后仍然可以獲取到最后一次登陸信息的需求涕刚,我們把文件存儲(chǔ)在沙盒中的第一個(gè)文件夾(Document)中梢卸,這樣可以在程序重新安裝后自動(dòng)回復(fù),原理我在需求分析上已經(jīng)做了闡述副女!
+ (NSString *)path
{
NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
NSString *documentDir = [paths objectAtIndex:0];
NSString *dstPath = [documentDir stringByAppendingPathComponent:@"user.data"];
return dstPath;
}
歸檔的方法蛤高,我們集成在save的接口中:
- (void)save
{
[NSKeyedArchiver archiveRootObject:self toFile:[IHomeSession path]];
}
解檔的方法我們集成在單例的獲取中,一定要先查找對(duì)應(yīng)路徑下的文件是否存在碑幅,如果存在進(jìn)行解檔操作戴陡,不存在的話重新生成一個(gè)單例,這樣會(huì)增強(qiáng)程序的健壯性沟涨,防止誤取單例恤批,造成程序崩潰喜庞!
+ (IHomeSession *)sharedMemory
{
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
if ([[NSFileManager defaultManager] fileExistsAtPath:[IHomeSession path]]) {
instance = [NSKeyedUnarchiver unarchiveObjectWithData:
[NSData dataWithContentsOfFile:[IHomeSession path]]];
}
else
{
instance = [[IHomeSession alloc] init];
}
});
return instance;
}
重置的接口中延都,我們需要?jiǎng)h除本地文件晰房,同時(shí)將單例中的各種屬性恢復(fù)到初始狀態(tài)殊者,然后將初始狀態(tài)下的對(duì)象保存歸檔!
- (void)reset
{
[[NSFileManager defaultManager] removeItemAtPath:[IHomeSession path] error:nil];
instance = [[IHomeSession alloc] init];
instance.sessionId = nil;
instance.lastSessionDate = nil;
instance.token = @"default";
instance.username = nil;
instance.alias = nil;
[instance save];
}
例2:
@implementation person
#pragma mark 寫入文件
-(void)encodeWithCoder:(NSCoder *)encoder{
[super encodeWithCoder:encoder];//不要忘了這個(gè)
[encoder encodeInt:self.age forKey:@"age"];
[encoder encodeObject:self.name forKey:@"name"];
[encoder encodeFloat:self.height forKey:@"height"];
}
#pragma mark 從文件中讀取
-(id)initWithCoder:(NSCoder *)decoder{
self = [super initWithCoder:decoder];//不要忘了這個(gè)
self.age = [decoder decodeIntForKey:@"age"];
self.name = [decoder decodeObjectForKey:@"name"];
self.height = [decoder decodeFloatForKey:@"height"];
return self;
}
//創(chuàng)建
-(void)createPerson{
person *p = [[[person alloc] init] autorelease];
p.age = 20;
p.name = @"Rio";
p.height =1.75f;
//獲得Document的路徑
NSString *documents = [NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) lastObject];
NSString *path = [documents stringByAppendingPathComponent:@"person.archiver"];//拓展名可以自己隨便取
[NSKeyedArchiver archiveRootObject:p toFile:path];
}
//讀取
-(void)readPerson{
NSString *documents = [NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) lastObject];
NSString *path = [documents stringByAppendingPathComponent:@"person.archiver"];
person *person1 = [NSKeyedUnarchiver unarchiveObjectWithFile:path];
NSLog(@"%@",person1);
}
特點(diǎn):
可以存儲(chǔ)自定義模型對(duì)象
NSKeyedArchiver歸檔相對(duì)較plist存儲(chǔ)而言,它可以直接存儲(chǔ)自定義模型對(duì)象海蔽,而plist文件需要將模型轉(zhuǎn)為字典才可以存儲(chǔ)自定義對(duì)象模型簸搞;
2.歸檔不能存儲(chǔ)大批量數(shù)據(jù)(相比較Sqlite而言),存儲(chǔ)數(shù)據(jù)到文件是將所有的數(shù)據(jù)一下子存儲(chǔ)到文件中准潭,從文件中讀取數(shù)據(jù)也是一下子讀取所有的數(shù)據(jù)趁俊;
缺點(diǎn):
假如你的文件中有100個(gè)對(duì)象了,然后你想在利用歸檔添加一個(gè)對(duì)象刑然,你需要先把所有的數(shù)據(jù)解檔出來寺擂,然后再加入你想添加的那個(gè)對(duì)象,同理泼掠,你想刪除一個(gè)文件中的一個(gè)對(duì)象也是怔软,需要解檔出所有的對(duì)象,然后將其刪除择镇。性能低這樣處理
4.1 基本使用:需要?dú)w檔的模型類必須要遵守NSCoding協(xié)議逝她,然后模型實(shí)現(xiàn)類中必須實(shí)現(xiàn)兩個(gè)方法:1>encodeWithCoder ->歸檔癣疟;2> initWithCoder:? - >解檔
4.2 使用注意:
如果父類也遵守了NSCoding協(xié)議,請(qǐng)注意:
應(yīng)該在encodeWithCoder:方法中加上一句[superencodeWithCode:encode];// 確保繼承的實(shí)例變量也能被編碼,即也能被歸檔應(yīng)該在initWithCoder:方法中加上一句self = [superinitWithCoder:decoder];// 確保繼承的實(shí)例變量也能被解碼案淋,即也能被恢復(fù)
plist文件讀與寫:
通過代碼來創(chuàng)建plist文件妓湘,代碼如下:
//建立文件管理
NSFileManager *fm = [NSFileManager defaultManager];
//找到Documents文件所在的路徑
NSArray *path = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUSErDomainMask, YES);
//取得第一個(gè)Documents文件夾的路徑
NSString *filePath = [path objectAtIndex:0];
//把TestPlist文件加入
NSString *plistPath = [filePath stringByAppendingPathComponent:@"test.plist"];
//開始創(chuàng)建文件
[fm createFileAtPath:plistPath contents:nil attributes:nil];
//刪除文件
[fm removeItemAtPath:plistPath error:nil];
在寫入數(shù)據(jù)之前员咽,需要把要寫入的數(shù)據(jù)先寫入一個(gè)字典中,創(chuàng)建一個(gè)dictionary:
//創(chuàng)建一個(gè)字典
NSDictionary *dic = [NSDictionary dictionaryWithObjectsAndKeys:@"zhangsan",@"1",@"lisi",@"2", nil];
//把數(shù)據(jù)寫入plist文件
[dic writeToFile:plistPath atomically:YES];
讀取plist中的數(shù)據(jù),形式如下:
//讀取plist文件装蓬,首先需要把plist文件讀取到字典中
NSDictionary *dic2 = [NSDictionary dictionaryWithContentsOfFile:plistPath];
//打印數(shù)據(jù)
NSLog(@"key1 is %@",[dic2 valueForKey:@"1"]);
NSLog(@"dic is %@",dic2);
關(guān)于plist中的array讀寫,代碼如下:
//把TestPlist文件加入
NSString *plistPaths = [filePath stringByAppendingPathComponent:@"tests.plist"];
//開始創(chuàng)建文件
[fm createFileAtPath:plistPaths contents:nil attributes:nil];
//創(chuàng)建一個(gè)數(shù)組
NSArray *arr = [[NSArray alloc] initWithObjects:@"1",@"2",@"3",@"4", nil];
//寫入
[arr writeToFile:plistPaths atomically:YES];
//讀取
NSArray *arr1 = [NSArray arrayWithContentsOfFile:plistPaths];
//打印
NSLog(@"arr1is %@",arr1);
偏好設(shè)置:
//1.獲取NSUserDefaults對(duì)象
NSUserDefaults *defaults=[NSUserDefaults standardUserDefaults];
//2保存數(shù)據(jù)(如果設(shè)置數(shù)據(jù)之后沒有同步, 會(huì)在將來某一時(shí)間點(diǎn)自動(dòng)將數(shù)據(jù)保存到Preferences文件夾下面)
[defaults setObject:@"yangyong" forKey:@"name"];
[defaults setInteger:23 forKey:@"age"];
[defaults setDouble:1.73f forKey:@"height"];
[defaults setObject:@"man" forKey:@"gender"];
//3.強(qiáng)制讓數(shù)據(jù)立刻保存
[defaults synchronize];
(1)偏好設(shè)置是專門用來保存應(yīng)用程序的配置信息的, 一般情況不要在偏好設(shè)置中保存其他數(shù)據(jù)屡久。如果利用系統(tǒng)的偏好設(shè)置來存儲(chǔ)數(shù)據(jù), 默認(rèn)就是存儲(chǔ)在Preferences文件夾下面的糙及,偏好設(shè)置會(huì)將所有的數(shù)據(jù)都保存到同一個(gè)文件中。
(2)使用偏好設(shè)置對(duì)數(shù)據(jù)進(jìn)行保存之后, 它保存到系統(tǒng)的時(shí)間是不確定的,會(huì)在將來某一時(shí)間點(diǎn)自動(dòng)將數(shù)據(jù)保存到Preferences文件夾下面聪蘸,如果需要即刻將數(shù)據(jù)存儲(chǔ),可以使用[defaults synchronize];
設(shè)置數(shù)據(jù)時(shí),synchornize方法強(qiáng)制寫入
UserDefaults設(shè)置數(shù)據(jù)時(shí)壤短,不是立即寫入,而是根據(jù)時(shí)間戳定時(shí)地把緩存中的數(shù)據(jù)寫入本地磁盤躲雅。所以調(diào)用了set方法之后數(shù)據(jù)有可能還沒有寫入磁盤應(yīng)用程序就終止了。出現(xiàn)以上問題钮科,可以通過調(diào)用synchornize方法強(qiáng)制寫入
(3)注意點(diǎn):所有的信息都寫在一個(gè)文件中,對(duì)比簡(jiǎn)單的plist可以保存和讀取基本的數(shù)據(jù)類型赃承。
好處:
1.存儲(chǔ)數(shù)據(jù)不需要關(guān)心文件名稱
2.快速存儲(chǔ)鍵值對(duì)
底層實(shí)現(xiàn):
它其實(shí)就是一個(gè)字典
Write寫入方式:永久保存在磁盤中。具體方法為:
第一步:獲得文件即將保存的路徑:
NSArray *documentPaths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask,YES);//使用C函數(shù)NSSearchPathForDirectoriesInDomains來獲得沙盒中目錄的全路徑抓于。該函數(shù)有三個(gè)參數(shù),目錄類型呕缭、User domain mask睬愤、布爾值砂豌。其中布爾值表示是否需要通過~擴(kuò)展路徑。而且第一個(gè)參數(shù)是不變的筐摘,即為NSSearchPathDirectory 柳畔。在iOS中后兩個(gè)參數(shù)也是不變的确沸,即為:NSUserDomainMask 和 YES观谦。
NSString *ourDocumentPath =[documentPaths objectAtIndex:0];
還有一種方法是使用NSHomeDirectory函數(shù)獲得sandbox的路徑盾剩。具體的用法為:
NSString *sandboxPath = NSHomeDirectory();
// Once you have the full sandbox path, you can create a path from it告私,但是不能在sandbox的本文件層上寫文件也不能創(chuàng)建目錄根悼,而應(yīng)該是此基礎(chǔ)上創(chuàng)建一個(gè)新的可寫的目錄酷麦,例如Documents,Library或者temp母廷。
NSString *documentPath = [sandboxPath stringByAppendingPathComponent:@"Documents"];//將Documents添加到sandbox路徑上馆揉,具體原因前面分析了勤讽!
這兩者的區(qū)別就是:使用NSSearchPathForDirectoriesInDomains比在NSHomeDirectory后面添加Document更加安全巢墅。因?yàn)樵撐募夸浛赡茉谖磥戆l(fā)送的系統(tǒng)上發(fā)生改變芹彬。
第二步:生成在該路徑下的文件:
NSString *FileName=[documentDirectory stringByAppendingPathComponent:fileName];//fileName就是保存文件的文件名
第三步:往文件中寫入數(shù)據(jù):
[data writeToFile:FileName atomically:YES];//將NSData類型對(duì)象data寫入文件,文件名為FileName
最后:從文件中讀出數(shù)據(jù):
NSData data=[NSData dataWithContentsOfFile:FileName options:0 error:NULL];//從FileName中讀取出數(shù)據(jù)
Sqlite:
生成路徑
+(NSString*)path{
NSArray*documentArr =NSSearchPathForDirectoriesInDomains(NSDocumentDirectory,NSUserDomainMask,YES);NSString*documentPath = [documentArr firstObject];// crylown.db 為數(shù)據(jù)庫的名字NSString*path = [NSString stringWithFormat:@"%@/crylown.db",documentPath];returnpath;}
創(chuàng)建/打開數(shù)據(jù)庫
sqlite3 *database;
int databaseResult = sqlite3_open([[selfpath] UTF8String], &database);
if(databaseResult != SQLITE_OK) {
NSLog(@"創(chuàng)建/打開數(shù)據(jù)庫失敗,%d",databaseResult);
}
創(chuàng)建表
char*error;//? ? 建表格式: create table if not exists 表名 (列名 類型,....)? ? 注: 如需生成默認(rèn)增加的id: id integer primary key autoincrement
const char*createSQL ="create table if not exists list(id integer primary key autoincrement,name char,sex char)";
int tableResult = sqlite3_exec(database, createSQL, NULL, NULL, &error);
if(tableResult != SQLITE_OK) {? ?
?? ? NSLog(@"創(chuàng)建表失敗:%s",error);?
?? }
添加數(shù)據(jù)
// 對(duì)SQL語句執(zhí)行預(yù)編譯intsqlite3_prepare(sqlite3 *db,constchar*sql,intbyte,sqlite3_stmt **stmt,constchar**tail)
1.db代表打開的數(shù)據(jù)庫連接
2.sql代表的sql語句
3.byte代表SQL語句的最大長(zhǎng)度
4.傳出參數(shù),指向預(yù)編譯SQL語句產(chǎn)生的sqlite3_stmt
5.指向SQL語句中未使用的部分
int sqlite3_prapare_v2()版本,代表該函數(shù)的最新版本。
//? 添加//? sql語句格式: insert into 表名 (列名)values(值)
constchar*insertSQL ="insert into haha (name,sex)values('iosRunner','male')";
int insertResult = sqlite3_prepare_v2(database, insertSQL,-1, &stmt,nil);
if(insertResult != SQLITE_OK) {
NSLog(@"添加失敗,%d",insertResult);? ? ??
? }else{//? ? ? ??
? 執(zhí)行sql語句sqlite3_step(stmt);? ? ?
?? }
查找數(shù)據(jù)
//返回sqlite3_stmt(預(yù)編譯SQL語句產(chǎn)生的結(jié)果)const char* sqlite3_colum_int/text...(sqlite3_stmt *,intN)
根據(jù)結(jié)果返回的值的類型不同選擇int/text等,N代表列名在表中的位置。
//? ? 查找//? sql語句格式: select 列名from表名 where 列名 = 參數(shù)? ? ? 注:前面的列名為查詢結(jié)果里所需要看到的 列名,后面的 列名 = 參數(shù) 用于判斷刪除哪條數(shù)據(jù)
const char*searchSQL ="select id,name,sex from haha where name = 'puyun2'";
intsearchResult = sqlite3_prepare_v2(database, searchSQL, -1, &stmt,nil);
if(searchResult !=SQLITE_OK) {
NSLog(@"查詢失敗,%d",searchResult);? ? ? ?
?}else{
while(sqlite3_step(stmt) ==SQLITE_ROW) { ? ? ? ? ?// 查詢的結(jié)果可能不止一條,直到 sqlite3_step(stmt) !=SQLITE_ROW,查詢結(jié)束。
int idWord = sqlite3_column_int(stmt,0);
char *nameWord = (char*) sqlite3_column_text(stmt,1);
char*sexWord = (char*)sqlite3_column_text(stmt,2);
NSLog(@"%d,%s,%s",idWord,nameWord,sexWord);? ? ? ??
? ? }? ? ? ?
?}
修改數(shù)據(jù)
// 修改? ? ? // sql語句格式: update 表名set列名 = 新參數(shù) where 列名 = 參數(shù)? 注:前面的 列名 = 新參數(shù) 是修改的值, 后面的 列名 = 參數(shù) 用于判斷刪除哪條數(shù)據(jù)
const char*changeSQL ="update haha set name = 'buhao' where name = 'iosRunner'";
int updateResult = sqlite3_prepare_v2(database, changeSQL, -1, &stmt,nil);
if(updateResult !=SQLITE_OK) {
NSLog(@"修改失敗,%d",updateResult);? ? ?
?? }else{? ? ? ? ? ??
sqlite3_step(stmt);? ??
? ? }
刪除數(shù)據(jù)
//? ? ? ? 刪除//? ? ? ? sql語句格式: deletefrom表名 where 列名 = 參數(shù)? ? 注:后面的 列名 = 參數(shù) 用于判斷刪除哪條數(shù)據(jù)
const char*deleteSQL ="delete from haha where name = 'iosRunner'";
intdeleteResult = sqlite3_prepare_v2(database, deleteSQL, -1, &stmt,nil);
if(deleteResult !=SQLITE_OK) {
NSLog(@"刪除失敗,%d",deleteResult);? ? ??
? }else{? ? ? ??
? ? sqlite3_step(stmt);?
?? ? ? }
結(jié)束處理
//? ? ? ? 銷毀stmt,回收資源
sqlite3_finalize(stmt);
// ? ?關(guān)閉數(shù)據(jù)庫
sqlite3_close(database);
注意:寫入數(shù)據(jù)庫,字符串可以采用char方式侧蘸,而從數(shù)據(jù)庫中取出char類型晌坤,當(dāng)char類型有表示中文字符時(shí)商乎,會(huì)出現(xiàn)亂碼。這是因?yàn)閿?shù)據(jù)庫默認(rèn)使用ascII編碼方式祭阀。所以要想正確從數(shù)據(jù)庫中取出中文鹉戚,需要用NSString來接收從數(shù)據(jù)庫取出的字符串鲜戒。
NSfileManager:
/**
獲取Documents路徑
@return 返回Documents路徑
*/
- (NSString *)getDocumentsPath {
NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
NSString *path = [paths objectAtIndex:0];
return path;
}
/**
創(chuàng)建文件夾
@param folderName 文件夾的名字
*/
- (void)createDirectoryWithFolderName:(NSString *)folderName {
NSString *documentsPath =[self getDocumentsPath];
NSFileManager *fileManager = [NSFileManager defaultManager];
NSString *iOSDirectory = [documentsPath stringByAppendingPathComponent:folderName];
BOOL isSuccess = [fileManager createDirectoryAtPath:iOSDirectory withIntermediateDirectories:YES attributes:nil error:nil];
if (isSuccess) {
[self remindMessage:CreateFolderSuccess];
} else {
[self remindMessage:CreateFolderFail];
}
}
/**
創(chuàng)建文件
@param name 文件的名字
*/
- (void)createFileWithName:(NSString *)name {
NSString *documentsPath =[self getDocumentsPath];
NSFileManager *fileManager = [NSFileManager defaultManager];
NSString *iOSPath = [documentsPath stringByAppendingPathComponent:name];
BOOL isSuccess = [fileManager createFileAtPath:iOSPath contents:nil attributes:nil];
if (isSuccess) {
[self remindMessage:CreateFileSuccess];
} else {
[self remindMessage:CreateFileFail];
}
}
/**
往文件中寫內(nèi)容
@param name 文件的名字
@param content 要寫入的內(nèi)容
*/
- (void)writeFileWithName:(NSString *)name withContent:(NSString *)content {
NSString *documentsPath = [self getDocumentsPath];
NSString *iOSPath = [documentsPath stringByAppendingPathComponent:name];
BOOL isSuccess = [content writeToFile:iOSPath atomically:YES encoding:NSUTF8StringEncoding error:nil];
if (isSuccess) {
[self remindMessage:WriteFileSuccess];
} else {
[self remindMessage:WriteFileFail];
}
}
/**
讀取文件內(nèi)容
@param name 需要讀取的文件的名字
@return 返回讀取的內(nèi)容
*/
- (NSString *)readFileContentWithName:(NSString *)name {
NSString *documentsPath =[self getDocumentsPath];
NSString *iOSPath = [documentsPath stringByAppendingPathComponent:name];
NSString *content = [NSString stringWithContentsOfFile:iOSPath encoding:NSUTF8StringEncoding error:nil];
return content;
}
/**
判斷文件是否存在
@param filePath 文件路徑
@return 返回BOOL值
*/
- (BOOL)isExistAtPath:(NSString *)filePath {
NSFileManager *fileManager = [NSFileManager defaultManager];
BOOL isExist = [fileManager fileExistsAtPath:filePath];
if (!isExist) {
[self remindMessage:NoFileExist];
}
return isExist;
}
/**
判斷文件是否存在,如果不存在,則拷貝
@param fileName 文件的名字
*/
- (void)isExistFileWithName:(NSString *)fileName {
NSFileManager *fileManager = [NSFileManager defaultManager];
NSString *filePath = [[self getDocumentsPath] stringByAppendingPathComponent:fileName];
if(![fileManager fileExistsAtPath:filePath]) { //如果不存在
NSLog(@"xxx.txt is not exist");
NSString *nameStr = [NSString stringWithFormat:@"/%@",fileName];
NSString *dataPath = [[[NSBundle mainBundle] bundlePath] stringByAppendingString:nameStr];//獲取程序包中相應(yīng)文件的路徑
NSError *error;
if ([fileManager copyItemAtPath:dataPath toPath:filePath error:&error]) { //拷貝
[self remindMessage:CopyFileSuccess];
} else {
[self remindMessage:CopyFileFail];
}
}
}
/**
計(jì)算文件大小
@param filePath 文件路徑
@return 返回文件大小
*/
- (unsigned long long)fileSizeAtPath:(NSString *)filePath {
NSFileManager *fileManager = [NSFileManager defaultManager];
BOOL isExist = [fileManager fileExistsAtPath:filePath];
if (isExist) {
unsigned long long fileSize = [[fileManager attributesOfItemAtPath:filePath error:nil] fileSize];
return fileSize;
} else {
[self remindMessage:NoFileExist];
return 0;
}
}
/**
計(jì)算整個(gè)文件夾中所有文件大小
@param folderPath 文件夾路徑
@return 返回文件夾中所有文件大小
*/
- (unsigned long long)folderSizeAtPath:(NSString*)folderPath {
NSFileManager *fileManager = [NSFileManager defaultManager];
BOOL isExist = [fileManager fileExistsAtPath:folderPath];
if (isExist) {
NSEnumerator *childFileEnumerator = [[fileManager subpathsAtPath:folderPath] objectEnumerator];
unsigned long long folderSize = 0;
NSString *fileName = @"";
while ((fileName = [childFileEnumerator nextObject]) != nil) {
NSString *fileAbsolutePath = [folderPath stringByAppendingPathComponent:fileName];
folderSize += [self fileSizeAtPath:fileAbsolutePath];
}
return folderSize / (1024.0 * 1024.0);
} else {
[self remindMessage:NoFileExist];
return 0;
}
}
/**
刪除文件
@param name 需要?jiǎng)h除的文件名字
*/
- (void)deleteFileWithName:(NSString *)name {
NSString *documentsPath =[self getDocumentsPath];
NSFileManager *fileManager = [NSFileManager defaultManager];
NSString *iOSPath = [documentsPath stringByAppendingPathComponent:name];
BOOL isSuccess = [fileManager removeItemAtPath:iOSPath error:nil];
if (isSuccess) {
[self remindMessage:DeleteFileSuccess];
} else {
[self remindMessage:DeleteFileFail];
}
}
/**
移動(dòng)文件
@param name 需要移動(dòng)的文件名字
*/
- (void)moveFileWithName:(NSString *)name {
NSString *documentsPath =[self getDocumentsPath];
NSFileManager *fileManager = [NSFileManager defaultManager];
NSString *filePath = [documentsPath stringByAppendingPathComponent:name];
NSString *moveToPath = [documentsPath stringByAppendingPathComponent:name];
BOOL isSuccess = [fileManager moveItemAtPath:filePath toPath:moveToPath error:nil];
if (isSuccess) {
[self remindMessage:MoveFileSuccess];
} else {
[self remindMessage:MoveFileFail];
}
}
/**
文件重命名
@param name1 需要重命名的文件名字
@param name2 作為重命名的文件名字
*/
- (void)renameFileName:(NSString *)name1 willChangeFileName:(NSString *)name2 {
//通過移動(dòng)該文件對(duì)文件重命名
NSString *documentsPath = [self getDocumentsPath];
NSFileManager *fileManager = [NSFileManager defaultManager];
NSString *filePath = [documentsPath stringByAppendingPathComponent:name1];
NSString *moveToPath = [documentsPath stringByAppendingPathComponent:name2];
BOOL isSuccess = [fileManager moveItemAtPath:filePath toPath:moveToPath error:nil];
if (isSuccess) {
[self remindMessage:RenameFileSuccess];
} else {
[self remindMessage:RenameFileFail];
}
}
38. OC中創(chuàng)建線程的方法是什么抹凳?如果在主線程中執(zhí)行代碼,方法是什么黔攒?如果想延時(shí)執(zhí)行代碼觉阅,方法又是什么伤溉?
方法一:
NSThread *t = [[NSThread alloc] initWithTarget:self selector:@selector(mutableThread) object:nil];
方法二:
[NSThread detachNewThreadSelector:@selector(mutableThread) toTarget:self withObject:nil];
方法三:
[self performSelectorInBackground:@selector(mutableThread) withObject:nil];
方法四:多線程blog創(chuàng)建
NSOperationQueue *operationQueue = [[NSOperationQueue alloc] init];
//會(huì)開啟一個(gè)多線程
[operationQueue addOperationWithBlock:^{
for(int i = 0; i < 50 ;i++)
{
NSLog(@"多線程:%d",i);
}
}];
方法五:
//相當(dāng)于是一個(gè)線程池
NSOperationQueue *operationQueue = [[NSOperationQueue alloc] init];
operationQueue.maxConcurrentOperationCount = 1;//設(shè)置并發(fā)數(shù)
//創(chuàng)建線程
NSInvocationOperation *opertion1 = [[NSInvocationOperation alloc] initWithTarget:self selector:@selector(thread1) object:nil];
//設(shè)置線程的優(yōu)先級(jí)
[opertion1 setQueuePriority:NSOperationQueuePriorityVeryLow];
//創(chuàng)建另一個(gè)線程
NSInvocationOperation *opertion2 = [[NSInvocationOperation alloc] initWithTarget:self selector:@selector(thread2) object:nil];
[opertion2 setQueuePriority:NSOperationQueuePriorityHigh];
//NSOperation就是一個(gè)操作單元,用來執(zhí)行方法,是一個(gè)抽象類,必須子類化或者使用系統(tǒng)創(chuàng)建好的子類(NSInvocationOperation or NSBlockOperation)
// //NSOperation是最小的操作單元;只能夠執(zhí)行一次;
// //NSInvocationOperation第一步:創(chuàng)建
NSInvocationOperation *invocation = [[NSInvocationOperation alloc] initWithTarget:self selector:@selector(banZhuanPlus) object:nil];
// //第二步:(不設(shè)置的話不添加到隊(duì)列)在主線程中執(zhí)行
// [invocation start];
//NSBlockOperation第一步:創(chuàng)建
NSBlockOperation *block = [NSBlockOperation blockOperationWithBlock:^{
[self banZhuanPlus];
}];
// //第二步:執(zhí)行(在主線程中執(zhí)行)
// [block start];//如果添加到隊(duì)列就不要start了琢蛤,如果不添加沸毁,當(dāng)前線程就是在主線程中執(zhí)行,如果添加嘲碧,就不是在主線程了
// 這個(gè)隊(duì)列會(huì)自動(dòng)幫咱們創(chuàng)建一個(gè)輔助的線程,這個(gè)時(shí)候當(dāng)前線程就不是主線程了
//這個(gè)隊(duì)列里面只能夠添加NSOperation以及子類的對(duì)象;
NSOperationQueue *queue = [[NSOperationQueue alloc] init];
[queue setMaxConcurrentOperationCount:2];//設(shè)置最大并行數(shù);
[queue addOperation:block];//只要把操作隊(duì)列添加到隊(duì)列中就會(huì)執(zhí)行;
[queue addOperation:invocation];
方法六:
dispatch_queue_t queue = dispatch_queue_create("test",NULL);
dispatch_async(queue,^{
for(int i=0;i<50;i++)
{
NSLog(@"多線程:%d",i);
});
創(chuàng)建線程的方法:
- [NSThread detachNewThreadSelector:nil toTarget:nil withObject:nil]
- [self performSelectorInBackground:nil withObject:nil];
- [[NSThread alloc] initWithTarget:nil selector:nil object:nil];
- dispatch_async(dispatch_get_global_queue(0, 0), ^{});
- dispatch_sync(dispatch_get_global_queue(0, 0), ^{});
- [[NSOperationQueue new] addOperation:nil];
主線程中執(zhí)行代碼的方法:
-[self performSelectorOnMainThread:@selector() withObject:nil waitUntilDone:NO];
- dispatch_async(dispatch_get_main_queue(), ^{});//還有異步
-[[NSOperationQueue mainQueue] addOperationWithBlock:^{
// UI更新代碼
}];
延遲執(zhí)行代碼
NSTimer啟動(dòng)定時(shí)器
sleep(2)? 睡兩秒鐘
+ (void)sleepUntilDate:(NSDate *)date;
+ (void)sleepForTimeInterval:(NSTimeInterval)ti;
[NSThread sleepUntilDate:[NSDate dateWithTimeIntervalSinceNow:10]];
[NSThread sleepForTimeInterval:1.0f];
1.performSelector方法
[self performSelector:@selector(delayMethod) withObject:nil afterDelay:1.0f];
此方式要求必須在主線程中執(zhí)行草戈,否則無效星持。
是一種非阻塞的執(zhí)行方式酒来,
暫時(shí)未找到取消執(zhí)行的方法噩翠。
2.定時(shí)器:NSTimer
[NSTimer scheduledTimerWithTimeInterval:1.0f target:self selector:@selector(delayMethod) userInfo:nil repeats:NO];
此方式要求必須在主線程中執(zhí)行惕它,否則無效缤沦。
是一種非阻塞的執(zhí)行方式恕汇,
可以通過NSTimer類的- (void)invalidate;取消執(zhí)行。
3. sleep方式
[NSThread sleepForTimeInterval:1.0f]; [self delayMethod];
此方式在主線程和子線程中均可執(zhí)行。
是一種阻塞的執(zhí)行方式纲缓,建方放到子線程中纫塌,以免卡住界面
沒有找到取消執(zhí)行的方法诊县。
GCD延遲執(zhí)行
double delayInSeconds = 1.0;
__block ViewController* bself = self;
dispatch_time_t popTime = dispatch_time(DISPATCH_TIME_NOW, (int64_t)(delayInSeconds * NSEC_PER_SEC));
dispatch_after(popTime, dispatch_get_main_queue(), ^(void){
[bself test1]; });
此方式在可以在參數(shù)中選擇執(zhí)行的線程。
是一種非阻塞的執(zhí)行方式措左,
沒有找到取消執(zhí)行的方法。
39. iOS中有哪些多線程方案避除?
常用的有三種: NSThread NSOperationQueue GCD怎披。
1、NSThread 是這三種范式里面相對(duì)輕量級(jí)的瓶摆,但也是使用起來最負(fù)責(zé)的凉逛,
你需要自己管理thread的生命周期,線程之間的同步群井。線程共享同一應(yīng)用程序的部分內(nèi)存空間状飞,
它們擁有對(duì)數(shù)據(jù)相同的訪問權(quán)限。你得協(xié)調(diào)多個(gè)線程對(duì)同一數(shù)據(jù)的訪問,
一般做法是在訪問之前加鎖诬辈,這會(huì)導(dǎo)致一定的性能開銷酵使。
2、NSOperationQueue 以面向?qū)ο蟮姆绞椒庋b了用戶需要執(zhí)行的操作焙糟,
我們只要聚焦于我們需要做的事情口渔,而不必太操心線程的管理,同步等事情穿撮,
因?yàn)镹SOperation已經(jīng)為我們封裝了這些事情缺脉。
NSOperation 是一個(gè)抽象基類,我們必須使用它的子類悦穿。
3攻礼、 GCD: iOS4 才開始支持,它提供了一些新的特性栗柒,以及運(yùn)行庫來支持多核并行編程秘蛔,
它的關(guān)注點(diǎn)更高:如何在多個(gè)cpu上提升效率。
總結(jié):
- NSThread是早期的多線程解決方案傍衡,實(shí)際上是把C語言的PThread線程管理代碼封裝成OC代碼深员。
- GCD是取代NSThread的多線程技術(shù),C語法+block蛙埂。功能強(qiáng)大倦畅。
- NSOperationQueue是把GCD封裝為OC語法,額外比GCD增加了幾項(xiàng)新功能绣的。
* 最大線程并發(fā)數(shù)
* 取消隊(duì)列中的任務(wù)
* 暫停隊(duì)列中的任務(wù)
* 可以調(diào)整隊(duì)列中的任務(wù)執(zhí)行順序叠赐,通過優(yōu)先級(jí)
* 線程依賴
* NSOperationQueue支持KVO。 這就意味著你可以觀察任務(wù)的狀態(tài)屬性屡江。
但是NSOperationQueue的執(zhí)行效率沒有GCD高芭概,所以一半情況下,我們使用GCD來完成多線程操作惩嘉。