iOS面試旗開得勝之答案篇

如下問題的答案是以前寫的一篇文章 iOS面試旗開得勝之問題篇 如今把問題的答案整理了一份出來給你們。但願對你們有所幫助。若是整理的答案有問題,請聯繫我。shavekevin@gmail.comjava

一、屬性readwrite,readonly,assign,retain,copy,nonatomic 各自什麼做用,他們在那種狀況下用?ios

  • readwrite:默認的屬性,可讀可寫,生成setter和getter方法。git

  • readonly:只讀,只生成getter方法,也就是說不能修改變量。github

  • assign:用於聲明基本數據類型(int、float)僅設置變量,是賦值屬性。面試

  • retain:持有屬性,setter方法將傳入的參數先保留,再賦值,傳入的參數 引用計數retaincount 會加1sql

    在堆上開闢一塊空間,用指針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,避免控件被過早釋放。

二、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的緣由。

三、內存管理的幾條原則時什麼?按照默認法則.哪些關鍵字生成的對象須要手動釋放?在和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的對象不須要手動釋放,會直接進入自動釋放池。

四、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一下。
複製代碼
  • 觀察者模式

    觀察者模式本質上時一種發佈-訂閱模型,用以消除具備不一樣行爲的對象之間的耦合,經過這一模式,不一樣對象能夠協同工做,同時它們也能夠被複用於其餘地方Observer從Subject訂閱通知,ConcreteObserver實現重現ObServer並將其重載其update方法。一旦SubJect的實例須要通知Observer任何新的變動,Subject會發送update消息來通知存儲在其內部類中所註冊的Observer、在ConcreteObserverupdate方法的實際實現中,Subject的內部狀態可被取得並進行後續處理。

    通知

    在Cocoa Touch框架中NSNotificationCenterNSNotification對象實現了一對多的模型。經過NSNotificationCenter可讓對象之間進行通信,即使這些對象之間並不認識。

    KVO

    KVO是Cocoa提供的一種稱爲鍵值觀察的機制,對象能夠經過它獲得其餘對象特定屬性的變動通知。而這個機制是基於NSKeyValueObserving非正式些,Cocoa經過這個協議爲全部遵循協議的對象提供了一種自動化的屬性監聽的功能。

    雖然通知KVO均可以對觀察者進行實現,可是他們之間仍是略有不一樣的,由上面的例子咱們能夠看出通知是由一箇中心對象爲全部觀察者提供變動通知,主要是廣義上關注程序事件,而KVO則是被觀察的對象直接想觀察者發送通知,主要是綁定於特定對象屬性的值。

  • 單例模式

    單例設計模式確保對於一個給定的類只有一個實例存在,這個實例有一個全局惟一的訪問點。它一般採用懶加載的方式在第一次用到實例的時候再去建立它。

    注意:蘋果大量使用了此模式。例如:[NSUserDefaults standardUserDefaults], [UIApplication sharedApplication], [UIScreen mainScreen], [NSFileManager defaultManager],全部的這些方法都返回一個單例對象。

    有一些狀況下,只有一個實例顯得很是合理。舉例來講,你不須要有多個Logger的實例,除非你想去寫多個日誌文件。或者一個全局的配置處理類:實現線程安全的方式訪問共享實例是容易的,好比一個配置文件,有好多個類同時修改這個文件。

  • 工廠模式

    正式的解釋是:在基類中定義建立對象的一個接口,讓子類決定實例化哪一個類。工廠方法讓一個類的實例化延遲到子類中進行。工廠方法要解決的問題是對象的建立時機,它提供了一種擴展的策略,很好地符合了開放封閉原則。工廠方法也叫作虛構造器(Virtual Constructor)。

    經過工廠方法建立工廠對象,而後在工廠類中定義建立基類的子類對象的方法並經過外部傳入的條件判斷去建立哪個子類對象,不過因爲OC是運行時語言,因此工廠類雖然提供了建立子類對象的方法,可是在編譯時期並不能肯定對象類型,編譯時期建立的子類對象類型是基類類型,真正的類型在運行時期由子類來肯定,也即此時肯定爲子類類型。

    優勢

    極大地優化了代碼,若是須要100個子類對象,不用再一直調用alloc方法去建立,而是直接經過其工廠類的一句代碼便可實現,提升了對代碼的複用性。同時,也能夠將大量的操做放到工廠類中去處理,業務類中只負責去調用建立須要的對象便可。

    缺點

    由於它的實現條件之一必須存在繼承關係,因此模式中工廠類集中了全部的建立邏輯,造成一個龐大的全能類,當全部的類不是繼承自同一個父類的時候擴展比較困難。

五、淺複製和深複製的區別?

  • 淺拷貝

    淺拷貝就是對內存地址的複製,讓目標對象指針和源對象指向同一片內存空間。

    淺拷貝只是對對象的簡單拷貝,讓幾個對象共用一片內存,當內存銷燬的時候,指向這片內存的幾個指針須要從新定義纔可使用,要否則會成爲野指針。

    在 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;

}
複製代碼

六、什麼是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:方法。

    • 使用場景

      一、賦值:setValue:forkey。

      二、字典轉模型:KVC,使用setValuesForKeysWithDictionary:方法,該方法默認根據字典中每一個鍵值對,調用setValue:forKey方法

      缺點:字典中的鍵值對必須與模型中的鍵值對徹底對應,不然程序會崩潰

      三、取值:valueForKey

  • KVO

    Key-Value Observing (KVO) 創建在 KVC 之上,它可以觀察一個對象的 KVC key path 值的變化。說白了就是你關心的一個值改變了,你就會獲得通知。你就能夠在你想處理的地方處理這個值。

    一、爲對象添加一個觀察者(監聽器)

    二、設置監聽事件

    三、取消監聽

    • 底層實現原理
      • KVO是基於runtime機制實現的
      • 當某個類的屬性對象第一次被觀察時,系統就會在運行期動態地建立該類的一個派生類,在這個派生類中重寫基類中任何被觀察屬性的setter 方法。派生類在被重寫的setter方法內實現真正的通知機制
      • 若是原類爲Person,那麼生成的派生類名爲NSKVONotifying_Person
      • 每一個類對象中都有一個isa指針指向當前類,當一個類對象的第一次被觀察,那麼系統會偷偷將isa指針指向動態生成的派生類,從而在給被監控屬性賦值時執行的是派生類的setter方法
      • 鍵值觀察通知依賴於NSObject 的兩個方法: willChangeValueForKey:didChangevlueForKey:;在一個被觀察屬性發生改變以前, willChangeValueForKey:必定會被調用,這就 會記錄舊的值。而當改變發生後,didChangeValueForKey:會被調用,繼而 observeValueForKey:ofObject:change:context: 也會被調用。
      • 補充:KVO的這套實現機制中蘋果還偷偷重寫了class方法,讓咱們誤認爲仍是使用的當前類,從而達到隱藏生成的派生類

  • 主要分爲三大步
    • 第一步:尋找該屬性有沒有setsetter方法?有,就直接賦值
    • 第二步:尋找有沒有該屬性的成員屬性?有,就直接賦值
    • 第三步:尋找有沒有該屬性帶下劃線的成員屬性?有,就直接賦值

七、通知和協議有哪些不一樣之處?

  • 通知

    須要有一個通知中心:NSNotificationCenter,自定義通知的話須要給一個名字,而後監聽。

    • 優勢: 通知的發送者和接受者都不須要知道對方。能夠指定接收通知的具體方法。通知名能夠是任何字符串。
    • 缺點: 較鍵值觀察(KVO)須要多點代碼,在刪掉前必須移除監聽者。
  • 協議

    經過setDelegate來設置代理對象,最典型的例子是經常使用的 TableView.

    • 優勢:支持它的類有詳盡和具體信息。
    • 缺點:該類必須支持委託。某一時間只能有一個委託鏈接到某一對象。

八、在iOS應用有哪些方式保存本地數據?他們都應用在哪些場景?

  • 沙盒

    • Documents:保存用戶產生的數據,iTunes同步設備的時候會備份該目錄。用戶產生的數據就是指用戶在使用當前app的時候保存的一些數據,好比保存app中的圖片、保存下載的文件等。
    • Library:這個目錄下有2個文件夾,一個是Caches、一個是Preferences,Caches主要保存緩存數據,好比SDWebImage把緩存的圖片就存放到該目錄下。當程序退出後,改目錄保存的文件一直存在。 PreferencesXcode6以前保存的是偏好設置,好比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主要保存的數據類型爲NSStringNSNumberNSDataNSArrayNSDictionary

    • 具體實現:

//把字典寫入到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上封裝好的類KeychainItemWrapperSFHFKeychainUtils

      • KeychainItemWrapper是蘋果封裝的類,封裝了操做KeyChain的基本操做,下載地址:github.com/baptistefet…
// 初始化一個保存用戶賬號的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,下載地址:github.com/ldandersen/… 這個庫是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。
  • 若是兩個應用的usernameserviceName參數同樣,那麼這兩個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;
      }
複製代碼

答案整理:少聰:github &&簡書

QQ技術交流羣:214541576

微信公衆號:shavekevin

熱愛生活,分享快樂。好記性不如爛筆頭。多寫,多記,多實踐,多思考。

相關文章
相關標籤/搜索