BAT面試的準備—iOS篇

本文中的全部面試題都來自面試BAT的學長或者同窗給個人一手記錄,我將這些題目羅列並給予解答,一方面當作筆記,另外一方面造福你們

iOS網絡層設計git

一、網絡層和業務層的對接設計github

  • 使用哪一種交互模式來和業務層對接 : 使用Delegate爲主,目的是爲了(1)減小代碼的分散度(2)減小業務層和網絡層的耦合,網絡層對於業務層應該是抽象的,隱藏了實現細節的 (3)只採用一種是限制了靈活性,方便進行維護面試

  • 在網絡層不要濫用block :(1)block會延長對象的生命期,delegate則不會
    (2)block適合於在每次回調的任務都不同的狀況下,若是同樣則應使用delegate,蘋果內部的網絡層封裝爲delegate(離散型),AF的網絡層封裝爲block(集約型)算法

  • 使用一個reformer對象來封裝數據轉化邏輯,從而節省了業務層進行字典轉模型這樣相似的繁瑣操做,同時爲了解決直接使用字典的可讀性差的問題,採用KPropertyStudentID這樣的const變量來做爲字典的key。數據庫

  • 使用離散型(delegate)的方式作網絡層封裝須要使用到繼承,使用一個BaseAPIManager做爲父類,來處理全部須要集約化的操做(例如一些公用信息),而後讓不少子類來作離散化的操做編程

二、網絡層的安全防範windows

  • 防止競爭對手使用本身的API,爲本身的API設計一個簽名,服務端給出一個密鑰,在每次使用API的時候進行一個hash算法的操做,將hash出來的值和服務端hash出來的值進行一個對比,若是同樣,則代表是本身在使用API緩存

  • 防止中間人攻擊,使用較爲安全的HTTPS協議,防止運營商在請求中加入廣告安全

MVC模式和MVVM模式的區別微信

一、MVC模式存在Controller中代碼臃腫的問題
之因此會出現MVC模式,是由於發如今開發中會有不少代碼能夠進行復用,同時事實也正是如此,MVC三個沒款中,Model和View的代碼確實能夠由於MVC模式而進行復用,在github上也有不少開源的項目中封裝了不少View,咱們能夠很方便得使用這些view,model類做爲一個數據轉化邏輯的類也能夠在同一個項目中進行屢次複用,可是Controller卻很難在一個項目中進行復用,因此咱們在寫代碼的時候儘可能在Controller中只寫那些沒法複用的代碼,例如將view添加到controller上,將model的數據傳給view等等,可是實際上很難作到這一點,每每有不少代碼咱們都不知道放在哪裏,到了最後便放在了controller裏面,致使controller變得十分臃腫。

二、對MVC模式中的Controller進行瘦身
咱們能夠從下面幾點對Controller進行瘦身:

  • 將添加view到controller上的代碼進行抽取到自定義的UIView中去封裝
  • 將網絡請求抽取出來進行封裝
  • 將數據獲取和數據的轉化邏輯抽取出來進行封裝
  • 構造MVVM模式中的viewModel,其中封裝了數據的轉化邏輯

三、MVVM模式的認知

  • MVVM模式存在雙向綁定技術,也就是說viewModel變化,那麼model也跟着變化
  • 雙向綁定會致使難以發現bug出現的位置
  • 在大型項目中雙向綁定會致使內存消耗過大

四、總結
應該結合MVC和MVVM的各自的優勢去讓Controller進行瘦身,而不該該盲目地去追求新技術,亦或是過於保守,不肯意向前發展。

iOS中如何設置圓角

一、常規的設置方式帶來的性能損耗
使用cornerRadius屬性設置圓角是不會產生離屏渲染的,同時也不會真正在UI上產生圓角,這時候咱們須要將masksToBounds設置爲YES,纔可以產生在UI上的圓角效果,可是同時,這樣也會致使離屏渲染。產生離屏渲染對於性能上有很大的消耗,將會下降FPS幀數,緣由是由於離屏渲染須要將圖像的處理放在屏幕以外的內存緩存區進行處理,處理結束以後才把獲得的圖像放到主屏幕上。在這個過程當中產生最大消耗的是兩次上下文的交互,將處理放到屏幕以外的緩存區,而後把獲得的圖像放到主屏幕上。

二、使用不產生離屏渲染的方式來創造圓角
使用貝塞爾曲線UIBezierPath和Core Graphics框架畫出一個圓角

UIImageView *imageView = [[UIImageView alloc]initWithFrame:CGRectMake(100, 100, 100, 100)]; imageView.image = [UIImage imageNamed:@"1"]; //開始對imageView進行畫圖 UIGraphicsBeginImageContextWithOptions(imageView.bounds.size, NO, [UIScreen mainScreen].scale); //使用貝塞爾曲線畫出一個圓形圖 [[UIBezierPath bezierPathWithRoundedRect:imageView.bounds cornerRadius:imageView.frame.size.width] addClip]; [imageView drawRect:imageView.bounds]; imageView.image = UIGraphicsGetImageFromCurrentImageContext(); //結束畫圖 UIGraphicsEndImageContext(); [self.view addSubview:imageView];

三、總結

  • 若是屏幕中沒有不少的圓角的話,那麼就採用常規的方式設置便可
  • 若是屏幕中存在了大量的圓角的話,那麼須要對圓角進行優化,防止離屏渲染

微信中點擊頭像放大動畫的思路

建立一個背景和新的UIImageView,UIImageView是位於背景之上的,先把背景的透明度改成0,而後進行動畫,動畫的效果是將新的UIImageView從原始的位置(這個位置是原來的UIImageView在新的背景上對應的frame)變化到放大的位置,而後監聽背景的點擊事件,點擊的時候進行透明度和frame的相反變化便可。具體過程我封裝好了上傳到Github了,點擊這裏查看。

線上項目出現bug怎麼解決

這裏將會涉及到JSPatch這個框架的使用,這個框架的做用就是對bug進行熱修復..後續更新

iOS開發中有哪些狀況會產生循環引用

一、block
二、delegate
三、NSTimer
解決辦法:使用一個NSTimer的Catagory,而後重寫初始化方法,在實現中利用block,從而在調用的時候可使用weakSelf在block執行任務

autoreleasePool(加入到autoreleasePool中的對象)在何時釋放?


Runloop和AutoreleasePool
  • RunLoop啓動的時候建立autoreleasePool
  • RunLoop結束的時候銷燬autoreleasePool
  • 當RunLoop進行休眠的時候,將會將以前的autoreasePool銷燬,同時建立新的autoreleasePool

iOS中的深淺複製

請查看這篇文章,講得很深刻:iOS剖析深淺複製

iOS中的屬性修飾符

列舉的順序就是修飾符在聲明的時候的順序

一、原子性修飾符

  • nonatomic:通常對於屬性都採用nonatomic來修飾,若是須要保證線程安全,則手動添加代碼進行保護
  • atomic:默認是atomic,使用該修飾符,系統會對屬性進行原子性的保護操做,保證線程安全,可是會有性能損耗

二、讀寫權限修飾符

  • readonly:只讀
  • readwrite:默認爲readwrite修飾

三、內存管理修飾符

  • strong:強引用,主要有任何strong類型的指針指向對象,那麼就不會被ARC銷燬
  • copy:主要用於NSString、NSArray、NSDictionary以及block,前者是由於他們都有可變類型,後者是由於在MRC中,使用copy可以將block從棧區拷貝到堆區,在ARC中使用strong和copy效果同樣,可是寫上copy彷彿在時刻提醒着咱們編譯器幫咱們進行了copy操做
  • weak:弱引用:表示定義了一種非擁有關係,若是屬性所指向的對象被銷燬了,那麼屬性值也會被清空,設置爲nil指針
  • assign:對於基本數據類型的修飾,只會單純進行賦值操做
  • unsafe_unretained:由unsafe和unretained組成,unretained和weak類似,unsafe表示他是不安全的,可能引發野指針的出現,致使crash
  • retain:ARC中引入了strong和weak,retain效果和strong等同

四、讀寫方法名修飾符

  • setter:修改setter方法的名字
  • getter:修改getter方法的名字

iOS中屬性內存管理修飾符中的那些CP

  • strong vs copy

    self.name = anotherName;

    例如上面的代碼,使用strong表示的是self.name和anotherName這兩個指針同時指向了一個對象,過程是self.name指向了anotherName指向的對象,而若是使用copy的話,self.name和anotherName這兩個指針同時指向了不一樣的對象,過程是copy會將anotherName所指向的對象拷貝一份出來(淺拷貝),而後讓self.name指向這個被拷貝出來的對象。

  • strong vs weak
    只要存在strong類型修飾的屬性(指針)指向了一個對象,那麼這個對象就不會被ARC銷燬,可是對於weak類型修飾的屬性(指針)指向了一個對象,若是這個對象被銷燬了,那麼這個屬性(指針)就會被自動設置爲nil。能夠說weak類型的指針是沒有約束做用的,只是簡單弱弱地表示了一下關係。
    這裏還須要分析在聲明控件到底應該使用strong仍是weak

    • 若是是使用storyboard:


      storyboard引用關係圖
    • 若是是使用純代碼:


      純代碼引用關係圖
    • 綜上,都應該使用weak去聲明控件,純代碼中若是使用了strong去聲明控件,那麼有一種狀況:若是將控件remove了,那麼controller中的view裏面的subviews所引用的那條線將會被切斷,可是strong屬性(指針)所引用的這條線依然存在,因爲採用的是強引用,因此控件將不會被ARC給銷燬,那麼就會一直佔用內存,直到控制器銷燬。

  • weak vs assign
    weak只能用於對象類型,assign能夠用於基本類型,weak比起assign有一點更好,若是weak修飾的屬性指向的一個對象被銷燬了,那麼這個屬性將會自動被設置爲nil指針,若是assign修飾的屬性指向的一個對象被銷燬了,那麼這個屬性不會被自動設置爲nil,同時他也不知道所指向的對象已經被銷燬了,這樣就引起了野指針。

  • weak vs unsafe_unretained
    若是weak修飾的屬性指向的一個對象被銷燬了,那麼這個屬性將會自動被設置爲nil指針,若是unsafe_unretained修飾的屬性指向的一個對象被銷燬了,那麼這個屬性不會被自動設置爲nil,同時他也不知道所指向的對象已經被銷燬了,這樣就引起了野指針。

  • assign vs unsafe_unretained
    assign能修飾基本類型,unsafe_unretained只能修飾對象類型

iOS中的多線程

一、pthread
基於C語言,不經常使用
二、NSThread
須要本身手管理線程的生命週期,偶爾使用,例如獲取當前線程

[NSThread currentThread];

三、GCD(Grand Central Dispatch)
GCD是蘋果開發出來的多核編程的解決方案,雖然是基於C語言的,可是採用了block進行封裝,使用起來也很方便,同時也很重要,推薦使用GCD進行多線程編程
四、NSOperation
是蘋果對於GCD的封裝,效率不及GCD

iOS中的GCD

  • 常規使用

主隊列:是一個特殊的串行隊列,在主線程中運行,用於刷新UI,是一個串行隊列

//串行隊列 dispatch_queue_t queue = dispatch_get_main_queue;

自定義建立隊列: 既能夠建立串行隊列也能夠建立並行隊列。

//串行隊列 dispatch_queue_t queue = dispatch_queue_create("nineteen", NULL); dispatch_queue_t queue = dispatch_queue_create("nineteen", DISPATCH_QUEUE_SERIAL); //並行隊列 dispatch_queue_t queue = dispatch_queue_create("nineteen", DISPATCH_QUEUE_CONCURRENT);

全局並行隊列:系統提供的並行隊列

//並行隊列 dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0); dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0); dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_LOW, 0); dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_BACKGROUND, 0);
  • 其餘使用

循環執行任務:dispatch_apply相似一個for循環,併發地執行每一項。全部任務結束後,dispatch_apply纔會返回,會阻塞當前線程(相似同步執行)。

dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0); /* *count: 循環執行次數 *queue: 隊列,能夠是串行隊列或者是並行隊列(使用串行隊列可能致使死鎖) *block: 任務 */ dispatch_apply(count, queue, ^(size_t i) { NSLog(@"%zu %@", i, [NSThread currentThread]); });

隊列組:隊列組將不少隊列添加到一個組裏,當組裏全部任務都執行完後,它會經過一個方法通知咱們。基本流程是首先建立一個隊列組,而後把任務添加到組中,最後等待隊列組的執行結果。

//建立隊列組 dispatch_group_t group = dispatch_group_create(); //建立隊列 dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0); //並行隊列執行3次循環 (隊列組只能用異步方法執行) dispatch_group_async(group, queue, ^{ for (NSInteger i = 0; i < 3; i++) { NSLog(@"group-01 - %@", [NSThread currentThread]); } }); //主隊列執行5次循環 dispatch_group_async(group, dispatch_get_main_queue(), ^{ for (NSInteger i = 0; i < 5; i++) { NSLog(@"group-02 - %@", [NSThread currentThread]); } }); //都完成後會自動通知 dispatch_group_notify(group, dispatch_get_main_queue(), ^{ NSLog(@"完成 - %@", [NSThread currentThread]); });

實現單例模式

static dispatch_once_t onceToken; dispatch_once(&onceToken, ^{ //dispatch_once中的代碼只執行一次,經常使用來實現單例 });

GCD延遲操做

//建立隊列 dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0); //設置延時,單位秒 double delay = 3; dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(delay * NSEC_PER_SEC)), queue, ^{ //3秒後須要執行的任務 });

GCD中的死鎖場景

五個案例瞭解GCD的死鎖
一、
案例:

NSLog(@"1"); // 任務1 dispatch_sync(dispatch_get_main_queue(), ^{ NSLog(@"2"); // 任務2 }); NSLog(@"3"); // 任務3

結果:

1

二、
案例:

NSLog(@"1"); // 任務1 dispatch_sync(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0), ^{ NSLog(@"2"); // 任務2 }); NSLog(@"3"); // 任務3

結果:

1 2 3

三、
案例:

dispatch_queue_t queue = dispatch_queue_create("com.demo.serialQueue", DISPATCH_QUEUE_SERIAL); NSLog(@"1"); // 任務1 dispatch_async(queue, ^{ NSLog(@"2"); // 任務2 dispatch_sync(queue, ^{ NSLog(@"3"); // 任務3 }); NSLog(@"4"); // 任務4 }); NSLog(@"5"); // 任務5

結果:

1 5 2 // 5和2的順序不必定

四、
案例:

NSLog(@"1"); // 任務1 dispatch_async(dispatch_get_global_queue(0, 0), ^{ NSLog(@"2"); // 任務2 dispatch_sync(dispatch_get_main_queue(), ^{ NSLog(@"3"); // 任務3 }); NSLog(@"4"); // 任務4 }); NSLog(@"5"); // 任務5

結果:

1 2 5 3 4 // 5和2的順序不必定

五、
案例:

dispatch_async(dispatch_get_global_queue(0, 0), ^{ NSLog(@"1"); // 任務1 dispatch_sync(dispatch_get_main_queue(), ^{ NSLog(@"2"); // 任務2 }); NSLog(@"3"); // 任務3 }); NSLog(@"4"); // 任務4 while (1) { } NSLog(@"5"); // 任務5

結果:

1 4 // 1和4的順序不必定

iOS中的遞歸鎖

若是加鎖操做處於一個循環或者遞歸中,在第一次加鎖尚未解鎖的時候,就進行了第二次加鎖,因此就形成死鎖現象,這時候應該使用遞歸鎖來防止死鎖的發生。

iOS中的ARC是怎麼解決內存管理問題的

ARC會自動處理對象的聲明週期,編譯的時候在合適的地方插入內存管理代碼

ARC中autorelease的使用場景

  • 函數返回對象的時候:函數對象做爲返回值過了做用域的時候應該被銷燬,可是這時候可能尚未被賦值(被強引用),因此須要將該對象添加到自動釋放池中延長生命週期。
  • _weak修飾屬性的時候:_weak修飾的屬性所指向的對象可能沒有一個強引用來引用他,可能會被銷燬,這時候就須要對其使用autorelease方法保證他不被銷燬
  • id 的指針或對象的指針

iOS中的RunLoop

通常主線程會自動運行RunLoop,咱們通常狀況下不會去管。在其餘子線程中,若是須要咱們須要去管理。使用RunLoop後,能夠把線程想象成進入了一個循環;若是沒有這個循環,子線程完成任務後,這個線程就結束了。因此若是須要一個線程處理各類事件而不讓它結束,就須要運行RunLoop。

SDWebImage是怎麼使用RunLoop的

- (void)start{ ... self.connection = [[NSURLConnection alloc] initWithRequest:self.request delegate:self startImmediately:NO]; ... if(self.connection){ ... CFRunLoopRun( ) ... } }
- (void)cancelInternalAndStop { if (self.isFinished) return; [self cancelInternal]; CFRunLoopStop(CFRunLoopGetCurrent()); }

在建立self.connection成功後,執行了CFRunLoopRun(),開啓了runloop。在failed或finished的時候會調用CFRunLoopStop中止runloop。若是不開啓runloop的話,在執行完start ()後任務就完成了,NSURLConnection的代理就不會執行了。runloop至關於子線程的循環,能夠靈活控制子線程的生命週期。

AFNetworking是怎麼使用RunLoop的

AFNetworking解決這個問題採用了另外一種方法:單獨起一個global thread,內置一個runloop,全部的connection都由這個runloop發起,回調也都由它接收。這是個不錯的想法,既不佔用主線程,又不耗CPU資源:

iOS中的響應鏈

  • 經過官方文檔提供的圖來看看事件響應
    兩種方式,因爲設置不一樣,但大體過程是同樣的

事件響應
  • 具體的響應過程
    1. 發生了觸摸事件後,系統會將該事件加入到UIApplication管理的一個隊列中
    2. UIApplication從隊列中取出最前面的事件,而後將事件傳遞下去,處理的順序大體爲UIApplication->AppDelegate->UIWindow->UIViewController->superView->subViews
    3. 一般來講UIApplication會將事件先交給keyWindow來處理,keyWindow會找到一個最合適的視圖來處理這個事件,處理的第一步就從這裏開始了
    4. keyWindow是這樣來找到合適視圖的:調用hitTest:withEvent方法去尋找可以處理觸摸事件的視圖,hitTest:withEvent方法會遞歸地檢查view以及view的子類是否包含了觸摸點,像這樣一直遞歸下去,找到離用戶最近同時包含了觸摸點的一個view,而後將觸摸事件傳遞給這個view。在這個過程當中是經過PointInside:withEvent:方法來判斷是否包含了觸摸點的,若是包含了,就返回YES,若是沒有包含就返回NO,而後hitTest:withEvent這個方法就返回nil,將再也不對該視圖的子視圖進行判斷。

NSRunloop、runloop、autoreleasePool、thread

  • NSRunloop:NSRunloop是一個消息循環,它會檢測輸入元和定時源,而後作回調處理。NSRunloop封裝了windows中的消息處理,將SendMessage、PostMessage、GetMessage等細節封裝了起來。關於NSRunloop須要着重瞭解這幾點內容:

    1. NSRunloop用來監聽耗時的異步事件,例如網絡回調
    2. NSRunloop解決了CPU空轉問題,當沒有任何事件須要處理的時候,NSRunloop會把線程調整爲休眠狀態,從而消除CPU的週期輪詢。
    3. 每個線程都有一個NSRunloop,主線程是默認運行的,其餘線程默認是沒有運行的,須要在NSRunloop中添加一個事件,而後去啓動這個線程的runloop。
  • runloop:新建iOS項目的時候會看到在main方法中會手動建立一個autoreleasePool,程序開始時建立,結束時銷燬,若是隻是從表面上來看的話,那麼這樣和內存泄露是沒有什麼區別的。其實,對於每個runloop,系統會隱式地建立一個autoreleasePool,這樣全部的autoreleasePool構成一個棧式的結構,在每個runloop結束的時候,當前棧頂的autoreleasePool就會被彈出,同時銷燬,其中的全部對象也一樣被銷燬。這裏所指的runloop不是NSRunloop,這裏的runloop多是一個UI事件,一個timer等等,具體來講指的是從接受到消息,處處理完這個消息的一個完整過程。

  • autoreleasePool和thread
    thread是不會自動建立autoreleasePool的

drawRect的做用

  • drawRect方法的目的是進行UIView的繪製,使用的時候將繪製的具體內容寫在drawRect方法裏面
  • 蘋果不建議直接使用drawRect方法,而是調用setNeedsDisplay方法,系統接着會自動調用drawRect進行UIView的繪製

layoutSubviews的做用

  • layoutSubviews的做用是對子視圖進行從新佈局
  • 蘋果不建議直接使用該方法,而是經過調用setLayoutSubviews,讓系統去自動調用layoutSubviews方法進行佈局
  • 下面列出在何時會出發layoutSubviews方法
    1. 直接調用setLayoutSubviews。(這個在上面蘋果官方文檔裏有說明)
    2. addSubview的時候。
    3. 當view的frame發生改變的時候。
    4. 滑動UIScrollView的時候。
    5. 旋轉Screen會觸發父UIView上的layoutSubviews事件。
    6. 改變一個UIView大小的時候也會觸發父UIView上的layoutSubviews事件。

自定義控件

  • 自定義控件分爲兩種,一種是經過xib,另外一種是經過純代碼的方式
  • 自定義控件須要在兩個方法中進行重寫,達到開發者想要的效果
    1. initWithFrame:通常來講是重寫這個方法而不是init方法,由於init方法最終也會調用到initWithFrame方法,這個方法主要是對控件以及控件的子控件進行一些初始化的設置(若是是經過xib的話則是重寫awakFromNib方法)
    2. layoutSubviews:這個方法是描述子控件如何佈局,也就是賦予子控件在自定義控件中的位置關係,因此說對於子控件的frame的設置代碼不該該放在initWithFrame中,而是應該放在layoutSubviews這個方法裏面

數據持久化的幾種方式的對比

  • Plist文件(屬性列表):
    plist文件是將某些特定的類,經過XML文件的方式保存在目錄中,這些類包括(若是存在對應的可變類也包括可變類)
    NSArray
    NSDictionary
    NSData
    NSString
    NSNumber
    NSDate

  • Preference(偏好設置):

    1. 偏好設置是專門用來保存應用程序的配置信息的
    2. 若是沒有調用synchronize方法,系統會根據I/O狀況不定時刻地保存到文件中。因此若是須要當即寫入文件的就必須調用synchronize方法。
    3. 偏好設置會將全部數據保存到同一個文件中。即preference目錄下的一個以此應用包名來命名的plist文件。
  • NSKeyedArchiver(歸檔):
    歸檔在iOS中是另外一種形式的序列化,只要遵循了NSCoding協議的對象均可以經過它實現序列化

  • SQLite 3
    以前的全部存儲方法,都是覆蓋存儲。若是想要增長一條數據就必須把整個文件讀出來,而後修改數據後再把整個內容覆蓋寫入文件。因此它們都不適合存儲大量的內容,而SQLite 3卻能更好進行大量內容的讀寫操做。
  • CoreData
    蘋果封裝的本地數據庫,通常用於規劃應用中的對象

app的狀態

  • Not running:app還沒運行
  • Inactive:app運行在foreground但沒有接收事件
  • Active:app運行在foreground和正在接收事件
  • Background:運行在background和正在執行代碼
  • Suspended:運行在background但沒有執行代碼

UIView和CALayer的區別

  • UIView能夠響應事件,而CALayer不行
  • 一個 Layer 的 frame 是由它的 anchorPoint,position,bounds,和 transform 共同決定的,而一個 View 的 frame 只是簡單的返回 Layer的 frame,一樣 View 的 center和 bounds 也是返回 Layer 的一些屬性。
  • UIView主要是對顯示內容的管理而 CALayer 主要側重顯示內容的繪製。
  • 在作 iOS 動畫的時候,修改非 RootLayer的屬性(譬如位置、背景色等)會默認產生隱式動畫,而修改UIView則不會。

KVO的實現原理

  • KVO是什麼:
    KVO提供一種機制,指定一個被觀察對象(例如A類),當對象某個屬性(例如A中的字符串name)發生更改時,對象會得到通知,並做出相應處理
  • KVO的原理:
    1. NSKVONotifying_A:
      在這個過程,被觀察對象的 isa 指針從指向原來的A類,被KVO機制修改成指向系統新建立的子類 NSKVONotifying_A類,來實現當前類屬性值改變的監聽;因此當咱們從應用層面上看來,徹底沒有意識到有新的類出現,這是系統「隱瞞」了對KVO的底層實現過程,讓咱們誤覺得仍是原來的類。
    2. 子類setter方法剖析:KVO的鍵值觀察通知依賴於 NSObject 的兩個方法:willChangeValueForKey:和 didChangevlueForKey:,在存取數值的先後分別調用2個方法:被觀察屬性發生改變以前,willChangeValueForKey:被調用,通知系統該 keyPath 的屬性值即將變動;當改變發生後, didChangeValueForKey: 被調用,通知系統該 keyPath 的屬性值已經變動;以後, observeValueForKey:ofObject:change:context: 也會被調用。



文/NtZheng(簡書做者) 原文連接:http://www.jianshu.com/p/be6197d00de9 著做權歸做者全部,轉載請聯繫做者得到受權,並標註「簡書做者」。
相關文章
相關標籤/搜索