iOS中幾種通信方式

Block

1.block是帶有局部變量的匿名函數(shù)。

 ^(int event){
 
        DLog(@"event is %d",event);
    };

Block語法的BN范式:

^ 返回值類型 參數(shù)列表 表達式 (返回值類型和參數(shù)列表都是可以省略的)

省略返回值類型

^ (int count) {return conut +1;}

省略參數(shù)列表

^ void {printf("Blocks\n");}

2.聲明Block類型的變量:

int (^blk) (int);

Block類型的變量與一般C語言變量作用相同:

  • 賦值

  • 傳參

  • 返回值

使用Block語法將Block賦值給Block類型的變量(Block也指由Block語法生成的值)。

int (^blk) (int) = ^ (int count) {return count +1;};

也可以將Block類型的變量賦值給Block類型的變量。

int (^blk1) (int) = blk;
int (^blk2) (int);
blk2 = blk1;

因為Block類型的變量的記述方式比較復(fù)雜,所以我們一般使用typedef來重命名一下:

typedef int (^blk_t) (int);

Block是OC對象

捕獲變量

block可以捕獲在block之前聲明的變量,但是僅僅是copy了一份,在block中修改變量,并不會影響原始變量的值。也就是說block不能修改變量的值,除非使用__block修飾符。

捕獲變量,就是將變量保存到Block的結(jié)構(gòu)體實例中(copy了一份)。

C語言中,一個變量是可以直接被block捕獲并修改的:

  • 靜態(tài)變量

賦值給捕獲變量的話,編譯器會報錯,使用變量的話不會報錯

使用C語言的數(shù)組時要使用其指針,因為block捕獲變量的方法并不能直接捕獲C語言的數(shù)組

const char *text = @"hellow";
void (blk){
   printf("%c\n,text[2]");
};

Block的存儲位置

Block的類:

  • _NSConcreteStackBlock (棧)
  • _NSConcreteGlobalBlock (數(shù)據(jù)區(qū))
  • _NSConcreteMallocBlock (堆)

聲明在數(shù)據(jù)區(qū)的block,不能捕獲變量。只要block不捕獲變量,就可以將其聲明在數(shù)據(jù)區(qū)。

聲明在棧上的block,如果超出了作用域,那么block會廢棄。所以使用的時候,將其copy到堆上面,即使超出了作用域,block還可以繼續(xù)存在。如

  • block作為函數(shù)返回值返回時
  • 作為傳遞的參數(shù)時
  • Cocoa框架中的某些方法,比如數(shù)組的塊枚舉遍歷
  • GCD
  • 調(diào)用copy方法
  • 作為屬性或者賦值給 _strong id類型

dispose函數(shù)負(fù)責(zé)廢棄堆上的block。

將block從棧上復(fù)制到堆上是相當(dāng)消耗CPU的

不論block聲明在哪里,都可以copy,所以在不確定時調(diào)用copy來確保block被復(fù)制到堆上

__block變量

__block變量一開始也是被聲明到棧上的,會隨著block被copy到堆上。并且符合內(nèi)存管理原則,如果有block持有它,引用計數(shù)就會增加,反之減少。

因為__block變了在被copy到堆上之后,之前的變量會改變指針,也指向堆上的變量地址,所以不論是block里,還是block外的,修改的都是同一個變量。這也就是為什么我們要用__block修飾符,因為我們希望在block中修改這個變量。

只有copy到堆上的block才會將捕獲的變量變?yōu)開stong類型,這樣會強引用變量,使其不會過早地被釋放。如果沒有調(diào)用copy方法的話,是沒有效果的。

循環(huán)引用

參考另一篇文章中講到的block:ARC中的Block


block使用前要賦值,不然調(diào)用的話會產(chǎn)生野指針,程序崩潰。

Delegate

實現(xiàn)代理主要有3個部分:

(1)協(xié)議:指定代理的內(nèi)容,即要做什么

(2)委托者:根據(jù)協(xié)議,讓代理者實現(xiàn)功能

(3)代理者:根據(jù)協(xié)議,實現(xiàn)功能

協(xié)議:協(xié)議一般是方法列表,不過也可以定義屬性

協(xié)議也是可以被繼承的,A類的子類也可以實現(xiàn)協(xié)議內(nèi)容。

協(xié)議可以多繼承(對象不可以),但協(xié)議只聲明方法,不實現(xiàn)方法,方法由代理者去實現(xiàn)。

協(xié)議有兩個修飾符@optional(可選)和@required(必須,不實現(xiàn)會崩潰)。一般都會判斷代理者是否現(xiàn)實這個方法(能否相應(yīng)這個方法)。同理,block使用前也需要判斷block是否存在,已經(jīng)被賦值,不然的話也會崩潰。

if ([self.delegate respondsToSelector:@selector(userLoginWithUsername:password:)]) {
    [self.delegate userLoginWithUsername:self.username.text password:self.password.text];
}
代理的原理

實際上委托方是用一個id類型的指針弱引用了代理方,其實是向id這個指針指向?qū)ο蟀l(fā)送消息。

設(shè)置delegate屬性就是設(shè)置id指針指向代理者。

像上面的self.delegate就是代理者,如果它沒有實現(xiàn)userLoginWithUsername方法,會崩潰(因為userLoginWithUsername就是它自己的方法)。

代理的內(nèi)存管理

delegate屬性使用weak,如果使用strong的話,會出現(xiàn)循環(huán)引用(委托者通過delegate強引用代理者,代理者又強引用創(chuàng)建了委托者)。

一個對象被釋放后,weak會自動將指針指向nil,而assign則不會。在iOS中,向nil發(fā)送消息時不會導(dǎo)致崩潰的,所以assign就會導(dǎo)致野指針的錯誤unrecognized selector sent to instance。

所以修飾delegate的話,使用weak,不要使用assign。

非正式協(xié)議

非正式協(xié)議一般都是以NSObject的Category的方式存在的。由于是對NSObject進行的Category,所以所有基于NSObject的子類,都接受了所定義的非正式協(xié)議。對于@Protocol來說編譯器會在編譯期檢查語法錯誤,而非正式協(xié)議則不會檢查是否實現(xiàn)。

單例對象最好不要用delegate。單例對象由于始終都只是同一個對象,如果使用delegate,就會造成我們上面說的delegate屬性被重新賦值的問題,最終只能有一個對象可以正常響應(yīng)代理方法。

NSNotification

我感覺適用于跨層和跨頁面專遞消息。

使用了觀察著模式:

(1)在NSNotificationCenter中注冊觀察者,關(guān)注某個事件

[[NSNotificationCenter defaultCenter] addObserver:self
                                          selector:@selector(execute:)
                                             name:@"NOTIFICATION_NAME"
                                            object:nil];

(2)發(fā)送通知

[[NSNotificationCenter defaultCenter] postNotificationName:@"NOTIFICATION_NAME" object:nil];

(3)收到通知后執(zhí)行相應(yīng)的方法

- (void)execute:(NSNotification *)notification {
     //do something when received notification
     //notification.name is @"NOTIFICATION_NAME"
     if(notification.object && [notification.object isKindOfClass:[Test class]]){
         //do something
     }
}

(4)如果不需要通知了,要移除

[[NSNotificationCenter defaultCenter] removeObserver:self name:@"NOTIFICATION_NAME" object:nil];

KVC、KVO

KVC

key-value-code: 間接訪問對象的屬性,而不是通過調(diào)用存取方法,非對象類型的變量將被自動封裝或者解封成對象,很多情況下會簡化程序代碼;

直接修改屬性的值:

[laughingSir setValue:@"laughing 哥" forKey:@"name"];

缺點:編譯器無法檢測出錯誤;效率低;必須先解析字符串,然后再設(shè)置或者訪問對象的實例變量。

使用場景:

(1)apple 官網(wǎng)的一個例子,當(dāng)我們需要統(tǒng)計很多People的時候,每一行是一個人的實例,并且有2列屬性,name, age, 這時候我們可以會這樣做,

- (id)tableView:(NSTableView *)tableview 
      objectValueForTableColumn:(id)column row:(NSInteger)row { 
  
    People *people = [peoleArray objectAtIndex:row]; 
    if ([[column identifier] isEqualToString:@"name"]) { 
        return [people name]; 
    } 
    if ([[column identifier] isEqualToString:@"age"]) { 
        return [people age]; 
    } 
    // And so on. 
} 

同樣我們也可以用KVC,幫助我們化簡這些if, 因為name, age其實都是property, 我們可以直接通過key來訪問,所以整理過后是

People *people = [peopleArray objectAtIndex:row]; 
return [people valueForKey:[column identifier]]; 

(2)解析服務(wù)端返回的json字符串的時候,會用到KVC

[self setValuesForKeysWithDictionary:jsonObject];

不過現(xiàn)在解析json都用MJ的庫了。

(3)修改數(shù)組中元素的屬性,如把一個數(shù)組里的People的名字的首字母大寫,并且把新的名字存入新的數(shù)組。

  NSArray *newName = [self.people valueForKeyPath:@"name.capitalizedString"];

    Person *one = self.people[0];

    Person *two = self.people[1];

    one.name = newName[0];

    two.name = newName[1];

為什么用valueForKeyPath, 不用valueForKey, 因為valueForKeyPath可以傳遞關(guān)系,例如這里是每個People的name property的String的capitalizedString property, 而valueForKey不能傳遞這樣的關(guān)系,所以對于dict里面的dict, 我們也只能用valueForKeyPath。

KVC原理

當(dāng)通過KVC調(diào)用對象時,比如:[self valueForKey:@”someKey”]時,程序會自動試圖通過下面幾種不同的方式解析這個調(diào)用

  • 首先查找對象是否帶有 someKey 這個方法

  • 會繼續(xù)查找對象是否帶有someKey這個實例變量(iVar)

  • 程序會繼續(xù)試圖調(diào)用 -(id) valueForUndefinedKey:這個方法

  • 程序會拋出一個NSUndefinedKeyException異常錯誤。

補充:KVC查找方法的時候,不僅僅會查找someKey這個方法,還會查找getsomeKey這個方法,前面加一個get,或者_someKey以_getsomeKey這幾種形式。同時,查找實例變量的時候也會不僅僅查找someKey這個變量,也會查找_someKey這個變量是否存在。設(shè)計valueForUndefinedKey:方法的主要目的是當(dāng)你使用-(id)valueForKey方法從對象中請求值時,對象能夠在錯誤發(fā)生前,有最后的機會響應(yīng)這個請求。

KVO

key-value-observer :監(jiān)聽property的變化。

(1)添加觀察者:

[self.yiTian addObserver:self forKeyPath:@"narcotics" options:NSKeyValueObservingOptionNew |NSKeyValueObservingOptionOld context:nil];

(2)截獲變化:

-(void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSString *,id> *)change context:(void *)context{
    
    if([keyPath isEqualToString:@"narcotics"]){
        
        NSNumber *narcoticsN = [change objectForKey:@"new"];//修改之后的最新值
        NSInteger narcotics = [narcoticsN integerValue];
        if (narcotics>0) {
            if (self.delegate!=nil&&[self.delegate respondsToSelector:@selector(reportYitian:)]) {
                [self.delegate reportYitian:narcotics];
            }
        }
    }
}

(3)移除觀察者:

- (void)dealloc{
    //移除觀察者
    [self.yiTian removeObserver:self forKeyPath:@"narcotics"];
}

--

NSNotification、Block、Delegate和KVO的區(qū)別。

代理是一種回調(diào)機制,且是一對一的關(guān)系,通知是一對多的關(guān)系,一個對向所有的觀察者提供變更通知;

效率:Delegate比NSNOtification高;

Delegate和Block一般是一對一的通信;

Delegate需要定義協(xié)議方法,代理對象實現(xiàn)協(xié)議方法,并且需要建立代理關(guān)系才可以實現(xiàn)通信;

Block:Block更加簡潔,不需要定義繁瑣的協(xié)議方法,但通信事件比較多的話,建議使用Delegate;

引用文章:
iOS面試必看,最全梳理
iOS KVC & KVO

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容