iOS面試旗開得勝之答案篇
以下問題的答案是之前寫的一篇文章 iOS面試旗開得勝之問題篇 現在把問題的答案整理了一份出來給大家。希望對大家有所幫助。
1、屬性readwrite,readonly,assign,retain,copy,nonatomic 各自什么作用,他們在那種情況下用?
-
readwrite:默認的屬性,可讀可寫,生成setter和getter方法。
-
readonly:只讀,只生成getter方法,也就是說不能修改變量。
-
assign:用于聲明基本數據類型(int、float)僅設置變量,是賦值屬性。
-
retain:持有屬性,setter方法將傳入的參數先保留,再賦值,傳入的參數 引用計數retaincount 會加1
在堆上開辟一塊空間,用指針a指向,然后將指針a賦值(assign)給指針b,等于是a和b同時指向這塊堆空間,當a不使用這塊堆空間的時候,是否要釋放這塊堆空間?答案是肯定要的,但是這件堆空間被釋放后,b就成了野指針。
如何避免這樣的問題? 這就引出了引用計數器,當a指針這塊堆空間的時候,引用計數器+1,當b也指向的時候,引用計數器變成了2,當a不再指向這塊堆空間時,release-1,引用計數器為1,當b也不指向這塊堆空間時,release-1,引用計數器為0,調用dealloc函數,空間被釋放
總結:當數據類型為int,float原生類型時,可以使用assign。如果是上面那種情況(對象)就是用retain。
-
copy:是賦值特性,setter方法將傳入對象賦值一份;需要完全一份新的變量時,直接從堆區拿。
當屬性是 NSString、NSArray、NSDictionary時,既可以用strong 修飾,也可以用copy修飾。當用strong修飾的NSString 指向一個NSMutableString時,如果在不知情的情況下這個NSMutableString的別的引用修改了值,就會出現:一個不可變的字符串卻被改變了的情況, 使用copy就不會出現這種情況。
-
nonatomic:非原子性,可以多線程訪問,效率高。
-
atomic:原子性,屬性安全級別的表示,同一時刻只有一個線程訪問,具有資源的獨占性,但是效率很低。
-
strong:強引用,引用計數+ 1,ARC下,一個對象如果沒有強引用,系統就會釋放這個對象。
-
weak:弱引用,不會使引用計數+1.當一個指向對象的強引用都被釋放時,這塊空間依舊會被釋放掉。
使用場景:在ARC下,如果使用XIB 或者SB 來創建控件,就使用 weak。純代碼創建控件時,用strong修飾,如果想用weak 修飾,就需要先創建控件,然后賦值給用weak修飾的對象。
查找了一些資料,發現主要原因是,controller需要擁有它自己的view(這個view是所以子控件的父view),因此viewcontroller對view就必須是強引用(strong reference),得用strong修飾view。對于lable,它的父view是view,view需要擁有label,但是controller是不需要擁有label的。如果用strong修飾,在view銷毀的情況下,label還仍然占有內存,因為controller還對它強引用;如果用wak修飾,在view銷毀的時label的內存也同時被銷毀,避免了僵尸指針出現。
用引用計數回答就是:因為Controller并不直接“擁有”控件,控件由它的父view“擁有”。使用weak關鍵字可以不增加控件引用計數,確保控件與父view有相同的生命周期。控件在被addSubview后,相當于控件引用計數+1;父view銷毀后,所有的子view引用計數-1,則可以確保父view銷毀時子view立即銷毀。weak的控件在removeFromSuperview后也會立即銷毀,而strong的控件不會,因為Controller還保有控件強引用。
總結歸納為:當控件的父view銷毀時,如果你還想繼續擁有這個控件,就用srtong;如果想保證控件和父view擁有相同的生命周期,就用weak。當然在大多數情況下用兩個都是可以的。
使用weak的時候需要特別注意的是:先將控件添加到superview上之后再賦值給self,避免控件被過早釋放。
2、Objective-C如何對內存管理的,說說你的看法以及你遇到的問題以及解決方法?
? Objective-C使用引用計數來管理內存,對象有個計數器,用以表示當前有多少個事物想令此對象繼續存活下去。
-
MRC 手動內存計數 (Reference Counted)
retain 遞增保留計數
release 遞減保留計數
對象被創建出來,對象的保留計數至少為1,若想令某對象繼續存活,則調用retain方法。要是不想令其繼續存活,就調用release或autorelease。當保留計數歸零時,對象就回收了(deallocated),系統會將其占用的內存標記為“可重用”。(拖對象保留計數為1 的時候 調用release或autorelease,不會讓計數為0,會直接釋放,因為這樣可以省一步操作)
?注意:對象創建出來之后,并不是說對象此時的保留計數必定是1.在alloc或initWith方法的實現代碼中,也許還有其他對象也保留了此對象,所以,其保留計數可能會大于1.絕不應該說保留計數一定是某個值,只能說你執行的操作是遞增了引用計數或遞減了引用計數。
autoreleasepool(自動釋放池)
調用autorelease時,對象的引用計數不會馬上遞減,而是先對象放進自動釋放池,通常是在下一次“事件循環”時遞減。
因為自動釋放池中的釋放操作要等到下一次事件循環時才會執行,所以NSLog語句在使用str對象前不需要手工保留。但是,假如要持有此對象的話(比如將其設置給實例變量),那就需要保留。
-
ARC 自動內存計數(Garbage Collection)
使用ARC時,引用計數實際還是要執行的,只不過保留與釋放操作現在是由ARC自動添加。
這種方式和java類似,在你的程序的執行過程中。始終有一個高人在背后準確地幫你收拾垃圾,你不用考慮它什么時候開始工作,怎樣工作。你只需要明白,我申請了一段內存空間,當我不再使用從而這段內存成為垃圾的時候,我就徹底的把它忘記掉,反正那個高人會幫我收拾垃圾。遺憾的是,那個高人需要消耗一定的資源。
ARC在調用這些方法時(retain、release、autorelease、dealloc),并不通過普通的Objective-C消息派發機制,而是直接調用其底層C語言版本。這樣做性能更好,因為保留及釋放操作需要頻繁執行,所以直接調用底層函數能節省很多CPU周期。比方說,ARC會調用與retain等價的底層函數objc_retain。這也是不能覆寫retain、release、autorelease的原因。
3、內存管理的幾條原則時什么?按照默認法則.哪些關鍵字生成的對象需要手動釋放?在和property結合的時候如何有效的避免內存泄露?
-
誰申請,誰釋放 遵循Cocoa Touch的使用原則; 內存管理主要要避免“過早釋放”和“內存泄漏”,對于“過早釋放”需要注意@property設置特性時,一定要用對特性關鍵字,對于“內存泄漏”,一定要申請了要負責釋放,要細心。 關鍵字alloc 或new 生成的對象需要手動釋放; 設置正確的property屬性,對于retain需要在合適的地方釋放,
-
使用new、alloc或copy方法創建一個對象時,該對象引用計數器為1。如果不需要使用該對象,可以向其發送release或autorelease消息,在其使用完畢時被銷毀。
? 如果通過其他方法獲取一個對象,則可以假設這個對象引用計數為1,并且被設置為autorelease,不需要對該對象進行清理,如果確實需要retain這個對象,則需要使用完畢后release。
? 如果retain了某個對象,需要release或autorelease該對象,保持retain方法和release方法使用次數相等。
? 使用new、alloc、copy關鍵字生成的對象和retain了的對象需要手動釋放。設置為autorelease的對象不需要手動釋放,會直接進入自動釋放池。
4、MVC設計模式是什么? 你還熟悉什么設計模式?他們和MVC有什么不同的地方?
-
MVC設計模式是什么
MVC全名是Model View Controller,是模型(model)-視圖(view)-控制器(controller)的縮寫,一種軟件設計典范,用一種業務邏輯、數據、界面顯示分離的方法組織代碼,將業務邏輯聚集到一個部件里面,在改進和個性化定制界面及用戶交互的同時,不需要重新編寫業務邏輯。MVC被獨特的發展起來用于映射傳統的輸入、處理和輸出功能在一個邏輯的圖形化用戶界面的結構中。
Model(模型)是應用程序中用于處理應用程序數據邏輯的部分。 通常模型對象負責在數據庫中存取數據。
View(視圖)是應用程序中處理數據顯示的部分。 通常視圖是依據模型數據創建的。
Controller(控制器)是應用程序中處理用戶交互的部分。 通常控制器負責從視圖讀取數據,控制用戶輸入,并向模型發送數據。
-
你還熟悉什么設計模式?
- 代理模式
解決什么場景:當一個類的某些功能需要被別人來實現,但是既不明確是些什么功能,又不明確誰來實現這些功能的時候,委托代理模式就可以派上用場。
在cocoa框架中的Delegate模式中,委托人往往是框架中的對象(視圖中的控件、表視圖神馬的),代理人往往是視圖控制器對象。
自定義一個delegate模式
@interface A:UIView id transparendValueDelegate; @property(nomatic, retain) id transparendValueDelegate; @end @implementation A @synthesize transparendValueDelegate -(void)Call { NSString* value = @"你好"; [transparendValueDelegate transparendValue: value]; } @end @interface B:UIView NSString* value; @end @implementation B -(void)transparendValue:(NSString*)fromValue { value = fromValue; NSLog(@"%@ ,我是B",value); } @end
使用時:
A* a = [[A alloc] init]; B* b = [[B alloc] init]; a. transparendValueDelegate = b;//設置A代理委托對象為B [a Call];
這樣就會輸出:
**你好,我是B** 委托模式關鍵就在于一個“**被”**字。這個B是很被動的,隨時就會被你A Call一下。
-
觀察者模式
觀察者模式本質上時一種發布-訂閱模型,用以消除具有不同行為的對象之間的耦合,通過這一模式,不同對象可以協同工作,同時它們也可以被復用于其他地方 Observe r從 Subject 訂閱通知, ConcreteObserver 實現重現 ObServer 并將其重載其 update 方法。一旦SubJect的實例需要通知 Observer 任何新的變更, Subject 會發送 update 消息來通知存儲在其內部類中所注冊的 Observer 、在 ConcreteObserver 的 update 方法的實際實現中, Subject 的內部狀態可被取得并進行后續處理。
通知
在Cocoa Touch框架中 NSNotificationCenter 和 NSNotification 對象實現了一對多的模型。通過 NSNotificationCenter 可以讓對象之間進行通訊,即便這些對象之間并不認識。
KVO
KVO是 Cocoa 提供的一種稱為鍵值觀察的機制,對象可以通過它得到其他對象特定屬性的變更通知。而這個機制是基于 NSKeyValueObserving 非正式些, Cocoa 通過這個協議為所有遵循協議的對象提供了一種自動化的屬性監聽的功能。
雖然 通知 和 KVO 都可以對觀察者進行實現,但是他們之間還是略有不同的,由上面的例子我們可以看出通知是由一個中心對象為所有觀察者提供變更通知,主要是廣義上關注程序事件,而 KVO 則是被觀察的對象直接想觀察者發送通知,主要是綁定于特定對象屬性的值。
-
單例模式
單例設計模式確保對于一個給定的類只有一個實例存在,這個實例有一個全局唯一的訪問點。它通常采用懶加載的方式在第一次用到實例的時候再去創建它。
注意:蘋果大量使用了此模式。例如:[NSUserDefaults standardUserDefaults], [UIApplication sharedApplication], [UIScreen mainScreen], [NSFileManager defaultManager],所有的這些方法都返回一個單例對象。
有一些情況下,只有一個實例顯得非常合理。舉例來說,你不需要有多個Logger的實例,除非你想去寫多個日志文件。或者一個全局的配置處理類:實現線程安全的方式訪問共享實例是容易的,比如一個配置文件,有好多個類同時修改這個文件。
-
工廠模式
正式的解釋是:在基類中定義創建對象的一個接口,讓子類決定實例化哪個類。工廠方法讓一個類的實例化延遲到子類中進行。工廠方法要解決的問題是對象的創建時機,它提供了一種擴展的策略,很好地符合了開放封閉原則。工廠方法也叫做虛構造器(Virtual Constructor)。
通過工廠方法創建工廠對象,然后在工廠類中定義創建基類的子類對象的方法并通過外部傳入的條件判斷去創建哪一個子類對象,不過由于OC是運行時語言,所以工廠類雖然提供了創建子類對象的方法,但是在編譯時期并不能確定對象類型,編譯時期創建的子類對象類型是基類類型,真正的類型在運行時期由子類來確定,也即此時確定為子類類型。
優點
極大地優化了代碼,如果需要100個子類對象,不用再一直調用alloc方法去創建,而是直接通過其工廠類的一句代碼即可實現,提高了對代碼的復用性。同時,也可以將大量的操作放到工廠類中去處理,業務類中只負責去調用創建需要的對象即可。
缺點
因為它的實現條件之一必須存在繼承關系,所以模式中工廠類集中了所有的創建邏輯,形成一個龐大的全能類,當所有的類不是繼承自同一個父類的時候擴展比較困難。
5、淺復制和深復制的區別?
-
淺拷貝
淺拷貝就是對內存地址的復制,讓目標對象指針和源對象指向同一片內存空間。
淺拷貝只是對對象的簡單拷貝,讓幾個對象共用一片內存,當內存銷毀的時候,指向這片內存的幾個指針需要重新定義才可以使用,要不然會成為野指針。
在 iOS 里面, 使用retain 關鍵字進行引用計數,就是一種更加保險的淺拷貝。他既讓幾個指針共用同一片內存空間,又可以在release 由于計數的存在,不會輕易的銷毀內存,達到更加簡單使用的目的。
-
深拷貝
深拷貝是指拷貝對象的具體內容,而內存地址是自主分配的,拷貝結束之后,兩個對象雖然存的值是相同的,但是內存地址不一樣,兩個對象也互不影響,互不干涉。
copy 與 retain 的區別:
copy 是創建一個新對象,retain 是創建一個指針,引用對象計數加一。 copy屬性標識兩個對象內容相同,新的對象retain count為1, 與舊有對象引用計數無關,舊有對象沒有變化。copy減少對象對上下文的依賴。
iOS提供了copy和mutableCopy方法,顧名思義,copy就是復制了一個imutable的對象,而mutableCopy就是復制了一個mutable的對象。以下將舉幾個例子來說明。 這里指的是NSString, NSNumber等等一類的對象。
NSString *string = @”dddd"; NSString *stringCopy = [string copy]; NSMutableString *stringDCopy = [string mutableCopy]; [stringMCopy appendString:@``"!!"``];
查看內存可以發現,string和stringCopy指向的是同一塊內存區域(weak reference),引用計數沒有發生改變。而stringMCopy則是我們所說的真正意義上的復制,系統為其分配了新內存,是兩個獨立的字符串內容是一樣的。
-
當然在 ios 中并不是所有的對象都支持copy,mutableCopy,遵守NSCopying協議的類可以發送copy消息,遵守NSMutableCopying協議的類才可以發送mutableCopy消息。
copy構造
- (id)copyWithZone:(NSZone *)zone{ MyObj *copy = [[[self class] allocWithZone :zone] init]; copy->name = [_name copy]; copy->imutableStr = [_imutableStr copy]; copy->age = age; return copy; }
mutableCopy構造
- (id)mutableCopyWithZone:(NSZone *)zone{ MyObj *copy = NSCopyObject(self, 0, zone); copy->name = [_name mutableCopy]; copy->age = age; return copy; }
6、什么是KVO和KVC?他們的使用場景是什么?
-
KVC
KVC,即是指 NSKeyValueCoding,一個非正式的 Protocol,提供一種機制來間接訪問對象的屬性。KVO 就是基于 KVC 實現的關鍵技術之一。
一個對象擁有某些屬性。比如說,一個 Person 對象有一個 name 和一個 address 屬性。以 KVC 說法,Person 對象分別有一個 value 對應他的 name 和 address 的 key。 key 只是一個字符串,它對應的值可以是任意類型的對象。從最基礎的層次上看,KVC 有兩個方法:一個是設置 key 的值,另一個是獲取 key 的值。
說白了就是通過指定的key獲得想要的值value。而不是通過調用Setter、Getter方法訪問。
- 注意
(1). key的值必須正確,如果拼寫錯誤,會出現異常
(2). 當key的值是沒有定義的,valueForUndefinedKey:這個方法會被調用,如果你自己寫了這個方法,key的值出錯就會調用到這里來
(3). 因為類key反復嵌套,所以有個keyPath的概念,keyPath就是用.號來把一個一個key鏈接起來,這樣就可以根據這個路徑訪問下去
(4). NSArray/NSSet等都支持KVC
-
底層原理
-
當一個對象調用setValue:forKey: 方法時,方法內部會做以下操作:
1.判斷有沒有指定key的set方法,如果有set方法,就會調用set方法,給該屬性賦值 2.如果沒有set方法,判斷有沒有跟key值相同且帶有下劃線的成員屬性( key).如果有,直接給該成員屬性進行賦值 3.如果沒有成員屬性 key,判斷有沒有跟key相同名稱的屬性.如果有,直接給該屬性進行賦值 4.如果都沒有,就會調用 valueforUndefinedKey 和setValue:forUndefinedKey:方法。
-
使用場景
1、賦值:setValue:forkey。
2、字典轉模型:KVC,使用setValuesForKeysWithDictionary:方法,該方法默認根據字典中每個鍵值對,調用setValue:forKey方法
缺點:字典中的鍵值對必須與模型中的鍵值對完全對應,否則程序會崩潰
3、取值:valueForKey
-
KVO
Key-Value Observing (KVO) 建立在 KVC 之上,它能夠觀察一個對象的 KVC key path 值的變化。說白了就是你關心的一個值改變了,你就會得到通知。你就可以在你想處理的地方處理這個值。
1、為對象添加一個觀察者(監聽器)
2、設置監聽事件
3、取消監聽
- 底層實現原理
- KVO是基于runtime機制實現的
- 當某個類的屬性對象 第一次被觀察 時,系統就會在運行期 動態 地創建 該類的一個派生類 ,在這個派生類中重寫基類中任何被觀察屬性的setter 方法。派生類在被重寫的setter方法內實現真正的 通知機制
- 如果原類為Person,那么生成的派生類名為 NSKVONotifying_Person
- 每個類對象中都有一個isa指針指向當前類,當一個類對象的第一次被觀察,那么系統會偷偷將isa指針指向動態生成的派生類,從而在給被監控屬性賦值時執行的是派生類的setter方法
- 鍵值觀察通知依賴于NSObject 的兩個方法: willChangeValueForKey: 和 didChangevlueForKey: ;在一個被觀察屬性發生改變之前, willChangeValueForKey: 一定會被調用,這就 會記錄舊的值。而當改變發生后, didChangeValueForKey: 會被調用,繼而 observeValueForKey:ofObject:change:context: 也會被調用。
- 補充:KVO的這套實現機制中蘋果還偷偷重寫了class方法,讓我們誤認為還是使用的當前類,從而達到隱藏生成的派生類
- 主要分為三大步
- 第一步:尋找該屬性有沒有setsetter方法?有,就直接賦值
- 第二步:尋找有沒有該屬性的成員屬性?有,就直接賦值
- 第三步:尋找有沒有該屬性帶下劃線的成員屬性?有,就直接賦值
7、通知和協議有哪些不同之處?
-
通知
需要有一個通知中心: NSNotificationCenter ,自定義通知的話需要給一個名字,然后監聽。
- 優點: 通知的發送者和接受者都不需要知道對方。可以指定接收通知的具體方法。通知名可以是任何字符串。
- 缺點: 較鍵值觀察(KVO)需要多點代碼,在刪掉前必須移除監聽者。
-
協議
通過setDelegate來設置代理對象,最典型的例子是常用的 TableView.
- 優點:支持它的類有詳盡和具體信息。
- 缺點:該類必須支持委托。某一時間只能有一個委托連接到某一對象。
8、在iOS應用有哪些方式保存本地數據?他們都應用在哪些場景?
-
沙盒
- Documents: 保存用戶產生的數據,iTunes同步設備的時候會備份該目錄。用戶產生的數據就是指用戶在使用當前 app 的時候保存的一些數據,比如保存 app 中的圖片、保存下載的文件等。
- Library: 這個目錄下有2個文件夾,一個是 Caches 、一個是 Preferences , Caches 主要保存緩存數據,比如 SDWebImage 把緩存的圖片就存放到該目錄下。當程序退出后,改目錄保存的文件一直存在。 Preferences 在 Xcode6 之前保存的是偏好設置,比如 NSUserDefaults 保存的文件。但是 Xcode6 以上就保存到 /Users/用戶名/Library/ Developer/CoreSimulator/Devices/模擬器UDID/data/Library/Preferences/ 文件夾下。
- tmp: 保存程序中的臨時數據,當程序退出后系統會自動刪除 tmp 中所有的文件。
-
NSUserDefaults
-
NSUserDefaults 是個單例對象,在整個程序的生命周期中都只有一個實例。
-
NSUserDefaults 保存的數據類型:NSNumber, 基本數據類型(int,NSInter,float,double,CGFlat......), NSString, NSData, NSArray, NSDictionary, NSURL。
-
NSUserDefaults:一般保存配置信息,比如用戶名、密碼、是否保存用戶名和密碼、是否離線下載等一些配置條件信息。
-
用法
-
NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults]; //保存值(key值同名的時候會覆蓋的) [defaults setObject:@"用戶名" forKey:kUsernameKey]; //立即保存 [defaults synchronize]; //取值 NSString *username = [defaults objectForKey:kUsernameKey];
- 保存的一些方法
//保存NSInteger [defaults setInteger:(NSInteger) forKey:(nonnull NSString *)]; //保存BOOL [defaults setBool:(BOOL) forKey:(nonnull NSString *)]; //保存NSURL [defaults setURL:(nullable NSURL *) forKey:(nonnull NSString *)]; //保存float [defaults setFloat:(float) forKey:(nonnull NSString *)]; //保存double [defaults setDouble:(double) forKey:(nonnull NSString *)];
- 取值方法
[defaults integerForKey:(nonnull NSString *)]; [defaults boolForKey:(nonnull NSString *)]; [defaults URLForKey:(nonnull NSString *)]; [defaults floatForKey:(nonnull NSString *)]; [defaults doubleForKey:(nonnull NSString *)];
- 刪除方法
[defaults removeObjectForKey:(nonnull NSString *)];
-
歸檔(序列化)
-
一般保存自定義的對象,但是只有遵守NSCoding的類才能只用歸檔。
-
準守NSCoding協議必須要實現兩個 require 方法
-
(void)encodeWithCoder:(NSCoder *)aCoder //歸檔會觸發
-
- (nullable instancetype)initWithCoder:(NSCoder *)aDecoder //解歸檔會觸發
-
Coding 類具體實現:
@interface Coding : NSObject<NSCoding> @property (nonatomic, copy) NSString *name; @property (nonatomic, assign) NSInteger age;
-
#import "Coding.h" #import <objc/runtime.h> @implementation Coding /** * 根據類動畫獲取類的所有屬性,不要忘記導入#import <objc/runtime.h> * * @param cls <#cls description#> * * @return <#return value description#> */ - (NSArray *)perperiesWithClass:(Class)cls { NSMutableArray *perperies = [NSMutableArray array]; unsigned int outCount; //動態獲取屬性 objc_property_t *properties = class_copyPropertyList(cls, &outCount); //遍歷person類的所有屬性 for (int i = 0; i < outCount; i++) { objc_property_t property = properties[i]; const char *name = property_getName(property); NSString *s = [[NSString alloc] initWithUTF8String:name]; [perperies addObject:s]; } return perperies; } /** * 歸檔會觸發 * * @param aCoder <#aCoder description#> */ - (void)encodeWithCoder:(NSCoder *)aCoder { for (NSString *perperty in [self perperiesWithClass:[self class]]) { [aCoder encodeObject:perperty forKey:perperty]; } } /** * 解歸檔會觸發 * * @param aDecoder <#aDecoder description#> * * @return <#return value description#> */ - (nullable instancetype)initWithCoder:(NSCoder *)aDecoder { if (self = [super init]) { for (NSString *perperty in [self perperiesWithClass:[self class]]) { [self setValue:[aDecoder decodeObjectForKey:perperty] forKey:perperty];; } } return self; } @end
- 歸檔具體使用
Coding *coding1 = [[Coding alloc] init]; coding1.name = @"小明"; coding1.age = 12; Coding *coding2 = [[Coding alloc] init]; coding1.name = @"小王"; coding1.age = 20; NSArray *array = @[coding1, coding2]; //保存對象轉化為二進制數據(一定是可變對象) NSMutableData *data = [NSMutableData data]; //1.初始化 NSKeyedArchiver *archivier = [[NSKeyedArchiver alloc] initForWritingWithMutableData:data]; //2.歸檔 [archivier encodeObject:array forKey:@"key"]; //3.完成歸檔 [archivier finishEncoding]; //4.保存 [[NSUserDefaults standardUserDefaults] setObject:data forKey:@"data"];
- 解歸檔的具體使用:
//1.獲取保存的數據 NSData *data = [[NSUserDefaults standardUserDefaults] objectForKey:@"data"]; //2.初始化解歸檔對象 NSKeyedUnarchiver *unarchiver = [[NSKeyedUnarchiver alloc] initForReadingWithData:data]; //3.解歸檔 NSArray *persons = [unarchiver decodeObjectForKey:@"key"]; //4.完成解歸檔 [unarchiver finishDecoding];
-
plist文件保存
-
一般在iOS用 plist 保存, plist 本身就是XML文件,名字后綴為 .plist 。
-
plist 主要保存的數據類型為 NSString 、 NSNumber 、 NSData 、 NSArray 、 NSDictionary 。
-
具體實現:
-
//把字典寫入到plist文件,比如文件path為:~/Documents/data.plist [dictionary writeToFile:path atomically:YES]; //把數組寫入到plist文件中 [array writeToFile:path atomically:YES];
- 讀取數據
NSDictionary *dictionary = [NSDictionary dictionaryWithContentsOfURL:[NSURL fileURLWithPath:(nonnull NSString *)]]; NSDictionary *dictionary = [NSDictionary dictionaryWithContentsOfFile:[[NSBundle mainBundle] pathForResource:(nullable NSString *) ofType:(nullable NSString *)]];
NSArray *array = [NSArray arrayWithContentsOfURL:[NSURL fileURLWithPath:(nonnull NSString *)]]; NSArray *array = [NSArray arrayWithContentsOfFile:[[NSBundle mainBundle] pathForResource:(nullable NSString *) ofType:(nullable NSString *)]];
-
數據庫
-
iOS用的 sqlite3 , 使用 sqlite3 需要配置庫文件 libsqlite3.tbd 或者導入 libsqlite3.0.tbd ,這兩個庫導入任何一個都可以
-
保存大量數據可以優先考慮用數據庫,sql語句對查詢操作有優化作用,所以從查詢速度或者插入效率都是很高的。
-
sqlite 使用步驟:
-
指定數據庫路徑。
-
創建 sqlite3 對象并且打開數據庫。
-
創建表。
-
對數據庫操作,包括增刪改查。
-
關閉數據庫。
?
-
具體實現:
-
數據庫路徑
``` //返回數據庫路徑,保存到Cache目錄下 -(NSString *)databasePath { NSString *path = NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES)[0];
return [path stringByAppendingPathComponent:@"contacts.db"]; } ```
-
創建 sqlite3 對象并且打開數據庫,如果數據庫打開成功,就創建表。
-
//數據庫對象 sqlite3 *contactDB; const char *path = [[self databasePath] UTF8String]; if (sqlite3_open(path, &contactDB) == SQLITE_OK) { char *errMsg; const char *sql_stmt = "CREATE TABLE IF NOT EXISTS CONTACTS(ID INTEGER PRIMARY KEY AUTOINCREMENT, NAME TEXT, ADDRESS TEXT,PHONE TEXT)"; //執行語句 if (sqlite3_exec(contactDB, sql_stmt, NULL, NULL, &errMsg) != SQLITE_OK) { //創建表失敗 } } else { //打開數據庫失敗 } sqlite3_close(contactDB);
-
代碼解釋:
- sqlite3_open: 打開指定路徑的數據庫,如果數據庫不存在,就會創建一個新的數據庫。
- SQLITE_OK 是一個常量,表示打開數據庫成功。
- contactDB 就是數據庫對象。
- sqlite3_exec 就是執行sql語句方法。
-
sqlite3_close 關閉數據庫,一般暫時不用數據庫的時候手動關閉,防止資源浪費。
- 保存數據到數據庫
//是一個抽象類型,是一個句柄,在使用過程中一般以它的指針進行操作 sqlite3_stmt *statement; //數據庫路徑 const char *path = [[self databasePath] UTF8String]; //使用的時候打開數據庫 if (sqlite3_open(path, &contactDB) == SQLITE_OK) { NSString *insertSQL = [NSString stringWithFormat:@"INSERT INTO CONTACTS (name,address,phone) VALUES(\"%@\",\"%@\",\"%@\")",name.text,address.text,phone.text]; const char *insert_stmt = [insertSQL UTF8String]; // 這個函數將sql文本轉換成一個準備語句(prepared statement)對象,同時返回這個對象的指針。這個接口需要一個數據庫連接指針以及一個要準備的包含SQL語句的文本。它實際上并不執行這個SQL語句,它僅僅為執行準備這個sql語句 sqlite3_prepare_v2(contactDB, insert_stmt, -1, &statement, NULL); //執行這個sql if (sqlite3_step(statement) == SQLITE_DONE) { //TODO:已存儲到數據庫; } else { //TODO:保存失敗 } //銷毀statement對象 sqlite3_finalize(statement); //關閉數據庫 sqlite3_close(contactDB); }
- 查詢操作
//數據庫路徑 const char *path = [[self databasePath] UTF8String]; //查詢結果集對象句柄 sqlite3_stmt *statement; //打開數據庫 if (sqlite3_open(path, &contactDB) == SQLITE_OK) { //查詢的sql語句 NSString *querySQL = [NSString stringWithFormat:@"SELECT address,phone from contacts where name=\"%@\"",name.text]; const char *query_stmt = [querySQL UTF8String]; //執行查詢sql語句 if (sqlite3_prepare_v2(contactDB, query_stmt, -1, &statement, NULL) == SQLITE_OK) { //遍歷每條數據 if (sqlite3_step(statement) == SQLITE_ROW) { //獲取每條數據的字段。 NSString *addressField = [[NSString alloc] initWithUTF8String:(const char *)sqlite3_column_text(statement, 0)]; address.text = addressField; NSString *phoneField = [[NSString alloc] initWithUTF8String:(const char *)sqlite3_column_text(statement, 1 )]; phone.text = phoneField; //TODO:已查到結果 } else { //TODO:未查到結果 } sqlite3_finalize(statement); } sqlite3_close(contactDB); }
-
CoreData
-
CoreData 提供了一種“對象-關系映射”的功能,能將OC對象轉化成數據,保存Sqlite中。
-
CoreData 的好處就是能夠合理管理內存,避免sql語句的麻煩(不用寫sql語句)。
-
CoreData 構成
-
NSManagedObjectContext: 被管理的數據上下文,主要作用:插入、查詢、刪除。
- NSManagedObjectModel: 數據庫所有的表結構和數據結構,包含各個實體的定義的信息。主要作用就是添加實體、實體屬性,建立屬性之間的關系。
- NSPersistentStoreCoordinator 持久化存儲助理對象,相當于數據庫的連接器。主要作用就是設置存儲的名字、位置、存儲方式。
- NSFetchRequest 相當于 select 語句。查詢封裝對象。
- NSEntityDescription 實體結構對象,相當于表格結構。
-
后綴為 xxx.xcdatamodeld 文件,編譯后為 xxx.momd 的文件。
-
保存數據
-
- (NSManagedObjectContext *)context { AppDelegate *app = [UIApplication sharedApplication].delegate; return app.managedObjectContext; }
//創建Person對象 /* insertNewObjectForEntityForName:就是創建的實體名字。 inManagedObjectContext:上下文,appDelegate里面已經創建完成。 */ Person *person = [NSEntityDescription insertNewObjectForEntityForName:@"Person" inManagedObjectContext:[self context]]; //賦值 [person setValue:@"小王" forKey:@"name"]; [person setValue:@(35) forKey:@"age"]; //保存 if (![[self context] save:nil]) { //TODO:保存失敗 }
- 查詢
//創建查詢對象 NSFetchRequest *request = [NSFetchRequest fetchRequestWithEntityName:@"Person"]; #if 0 //條件查詢 //NSPredicate *predicate = [NSPredicate predicateWithFormat:@"age<=35"]; //查詢名字帶有王的 NSPredicate *predicate = [NSPredicate predicateWithFormat:@"name like[cd]'*王*'"]; //設置查詢條件 request.predicate = predicate; #endif //排序 NSSortDescriptor *sort = [NSSortDescriptor sortDescriptorWithKey:@"age" ascending:NO]; //設置排序條件 request.sortDescriptors = @[sort]; //執行查詢 NSArray *objectArray = [[self context] executeFetchRequest:request error:nil]; //遍歷查詢結果 for (Person *p in objectArray) { NSLog(@"%@ - %@",[p valueForKey:@"name"],[p valueForKey:@"age"]); }
- 修改
//先查詢要修改的對象 NSFetchRequest *request = [NSFetchRequest fetchRequestWithEntityName:@"Person"]; //設置查詢條件 NSPredicate *predicate = [NSPredicate predicateWithFormat:@"name='小王' and age = 35"]; request.predicate = predicate; //執行查詢 NSArray *objectArray = [[self context] executeFetchRequest:request error:nil]; //遍歷要修改的對象 for (Person *p in objectArray) { //修改(修改內存數據,沒有同步數據庫) [p setValue:@(45) forKey:@"age"]; } //同步數據庫 [[self context] save:nil];
- 刪除
//查詢要刪除的數據 NSFetchRequest *request = [NSFetchRequest fetchRequestWithEntityName:@"Person"]; //設置查詢條件 NSPredicate *predicate = [NSPredicate predicateWithFormat:@"name='小王'"]; request.predicate = predicate; //執行查詢 NSArray *objectArray = [[self context] executeFetchRequest:request error:nil]; //遍歷刪除 for (Person *p in objectArray) { //刪除內存中的數據 [[self context] deleteObject:p]; } //同步數據庫 [[self context] save:nil];
-
當app更新版本,并且表結構有修改,需要版本升級和數據遷移操作,否則app就是崩掉。
- KeyChain
-
鑰匙串(英文: KeyChain)是蘋果公司Mac OS中的密碼管理系統。
-
一個鑰匙串可以包含多種類型的數據:密碼(包括網站,FTP服務器,SSH帳戶,網絡共享,無線網絡,群組軟件,加密磁盤鏡像等),私鑰,電子證書和加密筆記等。
-
iOS的KeyChain服務提供了一種安全的保存私密信息(密碼,序列號,證書等)的方式。每個iOS程序都有一個獨立的KeyChain存儲。從iOS 3.0開始,跨程序分享KeyChain變得可行。
-
當應用程序被刪除后,保存到KeyChain里面的數據不會被刪除,所以KeyChain是保存到沙盒范圍以外的地方。
-
KeyChain的所有數據也都是以key-value的形式存儲的,這和 NSDictionary 的存儲方式一樣。
-
相比于 NSUserDefaults 來說,KeyChain保存更為安全,而且KeyChain里面保存的數據不會因為app刪除而丟失。
-
基本使用
為了使用方便,我們使用github上封裝好的類 KeychainItemWrapper 和 SFHFKeychainUtils
- KeychainItemWrapper 是蘋果封裝的類,封裝了操作KeyChain的基本操作,下載地址: https://github.com/baptistefetet/KeychainItemWrapper
// 初始化一個保存用戶帳號的KeychainItemWrapper KeychainItemWrapper *wrapper = [[KeychainItemWrapper alloc] initWithIdentifier:@"Your Apple ID" accessGroup:@"YOUR_APP_ID.com.yourcompany.AppIdentifier"]; //保存帳號 [wrapper setObject:@"<帳號>" forKey:(id)kSecAttrAccount]; //保存密碼 [wrapper setObject:@"<帳號密碼>" forKey:(id)kSecValueData]; //從keychain里取出帳號密碼 NSString *password = [wrapper objectForKey:(id)kSecValueData]; //清空設置 [wrapper resetKeychainItem];
-
上面代碼的 setObject: forKey: 里參數 forKey 的值應該是 Security.framework 里頭文件 SecItem.h 里定義好的 key 。
- SFHFKeychainUtils 是另外一個第三方庫,這個類比 KeychainItemWrapper 要簡單很多,提供了更簡單的方法保存密碼到KeyChain,下載地址: https://github.com/ldandersen/scifihifi-iphone/tree/master/security。 這個庫是mrc,導入后可能會因為mrc會報錯。
-
SFHFKeychainUtils 就3個方法:
//獲取密碼密碼 +(NSString *) getPasswordForUsername: (NSString *) username andServiceName: (NSString *) serviceName error: (NSError **) error; //存儲密碼 +(BOOL) storeUsername: (NSString *) username andPassword: (NSString *) password forServiceName: (NSString *) serviceName updateExisting: (BOOL) updateExisting error: (NSError **) error; //刪除密碼 +(BOOL) deleteItemForUsername: (NSString *) username andServiceName: (NSString *) serviceName error: (NSError **) error;
-
參數說明
- username: 因為KeyChain保存也是以鍵值對存在,所以這個可以看作key,根據key取value.
-
forServiceName : 這個就是組的名字,可以理解為KeyChain保存是分組保存。一般要唯一哦,命名可以使用YOUR APP ID.com.yourcompany.AppIdentifier。
-
如果兩個應用的 username 、 serviceName 參數一樣,那么這兩個app會共用KeyChain里面的數據,也就是可以共享密碼。
-
KeyChain還有一個用途,就是替代UDID。UDID已經被廢除了,所以只能用UUID代替,所以我們可以把UUID用KeyChain保存。
-
//創建一個uuid NSString *uuidString = [self uuidString]; //31C75924-1D2E-4AF0-9C67-96D6929B1BD3 [SFHFKeychainUtils storeUsername:kKeyChainKey andPassword:uuidString forServiceName:kKeyChainGroupKey updateExisting:NO error:nil]; -(NSString *)uuidString { //創建一個uuid CFUUIDRef uuidRef = CFUUIDCreate(kCFAllocatorDefault); CFStringRef stringRef = CFUUIDCreateString(kCFAllocatorDefault, uuidRef); NSString *uuidString = (__bridge NSString *)(stringRef); CFRelease(uuidRef); return uuidString; }
來自:http://shavekevin.com/2017/12/17/mianshiqikaidesheng-daanpian/