1. 程式人生 > >iOS之Block程式碼塊的定義及使用

iOS之Block程式碼塊的定義及使用

不會使用Block的iOS程式設計師,不是一個合格的程式設計師

Block沒有你想象中的那麼難,不要害怕,不要畏懼,勇敢嘗試

Block進階:

Block其實就是一個程式碼塊,把你想要執行的程式碼封裝在這個程式碼塊裡,等到需要的時候再去呼叫。

個人覺得Block優勢如下:第一可以使程式碼看起來更簡單明瞭,第二可以取代以前的delegate使程式碼的邏輯看起來更清晰。

Block程式碼塊和普通函式都是一段程式碼,兩者有什麼區別?


Block程式碼:是一個函式物件,是在程式執行過程中產生的;

普通函式:是一段固定程式碼,產生於編譯期;

借一張圖表達基本定義:

完整定義如下:
returnType (^blockName)(parameterTypes) = ^returnType(parameters) {...};

注1: Block的宣告與賦值只是儲存了一段程式碼段,必須呼叫才能執行內部程式碼
注2: ^被稱作"脫字元"

void (^myBlock1)(void);  //無返回值,無引數
void (^myBlock2)(NSObject, int); //無返回值,有引數
NSString* (^myBlock3)(NSString* name, int age); //有返回值和引數,並且在引數型別後面加入了引數名(僅為可讀性)


Block變數的宣告



Block變數的宣告格式為: 返回值型別(^Block名字)(引數列表);

注3:形參變數名稱可以省略,只留有變數型別即可

// 宣告一個無返回值,引數為兩個字串物件,叫做aBlock的Block
void(^aBlock)(NSString *x, NSString *y);

// 形參變數名稱可以省略,只留有變數型別即可
void(^aBlock)(NSString *, NSString *);

巧記Block格式

很多人覺得Block格式定義很難記,其實我們可以通過與 java 函式方法對比加強記憶

int     getResult   (String a, String b)  // Java的方法宣告

int  (^MyBlockName) (String a, String b)  // iOS的block宣告

block的定義

/*定義屬性,block屬性可以用strong修飾,也可以用copy修飾 */
 @property (nonatomic, strong) void(^myBlock)();                   //無參無返回值
 @property (nonatomic, strong) void(^myBlock1)(NSString *);        //帶引數無返回值
 @property (nonatomic, strong) NSString *(^myBlock2)(NSString *);  //帶引數與返回值

 //定義變數
 void(^myBlock)() = nil;                   //無參無返回值
 void(^myBlock1)(NSString *) = nil;        //帶引數無返回值
 NSString *(^myBlock2)(NSString *) = nil;  //帶引數與返回值

block被當做方法的引數
格式:(block型別)引數名稱
 - (void)test:(void(^)())testBlock                    //無參無返回值
 - (void)test1:(void(^)(NSString *))testBlock         //帶引數無返回值
 - (void)test2:(NSString *(^)(NSString *))testBlock   //帶引數與返回值


使用typedef定義block
 typedef void(^myBlock)();                            //以後就可以使用myBlock定義無參無返回值的block
 typedef void(^myBlock1)(NSString *);                 //使用myBlock1定義引數型別為NSString的block
 typedef NSString *(^myBlock2)(NSString *);           //使用myBlock2定義引數型別為NSString,返回值也為NSString的block
 //定義屬性
 @property (nonatomic, strong) myBlock testBlock;
 //定義變數
 myBlock testBlock = nil;
 //當做引數
 - (void)test:(myBlock)testBlock;

以下兩者等價:

- (void) testAnimations:(void       (^)    (void) ) animations;  // 無引數

- (void) testAnimations:(void       (^)    ()            ) animations;  // 無引數



block的賦值
格式:block = ^返回值型別 (引數列表) {函式主體}

注4: 通常情況下,可以省略返回值型別,因為編譯器可以從儲存程式碼塊的變數中確定返回值的型別。

沒有引數沒有返回值
myBlock testBlock = ^void(){
     NSLog(@"test");
 };

沒有返回值,void可以省略
myBlock testBlock1 = ^(){
     NSLog(@"test1");
 };

沒有引數,小括號也可以省略
myBlock testBlock2 = ^{
     NSLog(@"test2");
 };

有引數沒有返回值
myBlock1 testBlock = ^void(NSString *str) {
      NSLog(str);
}

省略void
myBlock1 testBlock = ^(NSString *str) {
      NSLog(str);
}

有引數有返回值
myBlock2 testBlock = ^NSString *(NSString *str) {
     NSLog(str)
     return @"hi";
}

有返回值時也可以省略返回值型別
 myBlock2 testBlock2 = ^(NSString *str) {
     NSLog(str)
     return @"hi";
}



宣告Block變數的同時進行賦值
int(^myBlock)(int) = ^(int num){
    return num * 7;
};

// 如果沒有引數列表,在賦值時引數列表可以省略
void(^aVoidBlock)() = ^{
    NSLog(@"I am a aVoidBlock");
};

注5:如果沒有引數,= 左邊用括號表示,= 右邊引數可以省略

Block變數的呼叫

// 呼叫後控制檯輸出"Li Lei love Han Meimei"
aBlock(@"Li Lei",@"Han Meimei");

// 呼叫後控制檯輸出"result = 63"
NSLog(@"result = %d", myBlock(9));

// 呼叫後控制檯輸出"I am a aVoidBlock"
aVoidBlock();

Block作為OC函式引數
// 1.定義一個形參為Block的OC函式
- (void)useBlockForOC:(int(^)(int, int))aBlock
{
    NSLog(@"result = %d", aBlock(300,200));
}

// 2.宣告並賦值定義一個Block變數
int(^addBlock)(int, int) = ^(int x, int y){
    return x+y;
};

// 3.以Block作為函式引數,把Block像物件一樣傳遞
[self useBlockForOC:addBlock];

// 將第2點和第3點合併一起,以內聯定義的Block作為函式引數
[self useBlockForOC:^(int x, int y){
    return x+y;
}];


注意:當block作為方法引數時,定義(形參)和使用(實參)格式不一樣。

    // 定義block作為引數
    + (RACSignal *)createSignal:(RACDisposable *  (^) (id<RACSubscriber> subscriber)  )didSubscribe {
        return [RACDynamicSignal createSignal:didSubscribe];
    }
    
    // 使用block作為引數
    RACSignal *signal = 

    [RACSignal createSignal:^RACDisposable *   ( id<RACSubscriber> subscriber) {

        [subscriber sendNext:letterSubject];
        [subscriber sendNext:numberSubject];
        [subscriber sendCompleted];
        return nil;  // 如果不為空,返回型別是 RACDisposable *
    }];

Block在定義時並不會執行內部的程式碼,只有在呼叫時候才會執行。

使用示例:

// 在AAViewController.h定義

@property (nonatomic, copy) void (^successBlock)(NSInteger count);

// 在AAViewController.m賦值

if (self.successBlock && !_willUpdate){
    self.successBlock([self.cards count]);
}


在BViewController.m中呼叫:

AAViewController *aa=[[[AAViewController alloc] init];
// 回撥要如何處理
aa.successBlock=^(NSInteger count) {
    if (count==0) {
	// 處理程式碼     
     }
};
[aa httpRequest];


利用typedef為Block進行重新命名

我們可以使用typedef為block進行一次重新命名,方法跟為函式指標進行重新命名是一樣的:
typedef int (^Sum) (int,  int);
這樣我們就利用typedef定義了一個block,這個block的名字就是Sum,需要傳入兩個引數。當我們需要使用時,就可以這樣做了:
Sum mysum = ^(int a, int b) {
 
                n = 2;
 
                return (a + b)*n;
            };
這樣就完整的定義好了一個block了,接下來的使用如下:
#import <Foundation/Foundation.h>
 
typedef int (^Sum) (int, int);
 
int main(int argc, const char * argv[])
{
    __block int n = 1;
 
    @autoreleasepool {
 
            Sum mysum = ^(int a, int b) {
 
                n = 2;
 
                return (a + b)*n;
            };
 
            NSLog(@"(3 + 5) * %i = %d", n, mysum(3, 5));
 
    }
    return 0;
}

Block在記憶體中的位置

根據Block在記憶體中的位置分為三種類型NSGlobalBlock,NSStackBlock, NSMallocBlock。

NSGlobalBlock:類似函式,位於text段;
NSStackBlock:位於棧記憶體,函式返回後Block將無效;
NSMallocBlock:位於堆記憶體。

示例1:

BlkSum blk1 = ^ long (int a, int b) {
  return a + b;
};
NSLog(@"blk1 = %@", blk1);// 列印結果:blk1 = <__NSGlobalBlock__: 0x47d0>



示例2:

int base = 100;
BlkSum blk2 = ^ long (int a, int b) {
  return base + a + b;
};
NSLog(@"blk2 = %@", blk2); // 列印結果:blk2 = <__NSStackBlock__: 0xbfffddf8>


示例3:

BlkSum blk3 = [[blk2 copy] autorelease];
NSLog(@"blk3 = %@", blk3); // 列印結果:blk3 = <__NSMallocBlock__: 0x902fda0>


blk1和blk2的區別在於:

blk1沒有使用Block以外的任何外部變數,Block不需要建立區域性變數值的快照,這使blk1與一般函式沒有任何區別

blk2與blk1唯一不同是的使用了局部變數base,

注意1:在定義(注意“定義”,不是“執行”)blk2時,區域性變數base當前值被copy到棧上,作為常量供Block使用。執行下面程式碼,結果是203,而不是204。

int base = 100;
  base += 100;
  BlkSum sum = ^ long (int a, int b) {
    return base + a + b;
  };
  base++;
  printf("%ld",sum(1,2));

在Block內變數base是隻讀的,如果想在Block內改變base的值,在定義base時要用 __block修飾:__block int base = 100;
  __block int base = 100;
  base += 100;
  BlkSum sum = ^ long (int a, int b) {
    base += 10;
    return base + a + b;
  };
  base++;
  printf("%ld\n",sum(1,2));
  printf("%d\n",base);

輸出將是214,211。

注意2:Block中使用__block修飾的變數時,將取變數此刻執行時的值,而不是定義時的快照。這個例子中,執行sum(1,2)時,base將取base++之後的值,也就是201,再執行Blockbase+=10; base+a+b,執行結果是214。執行完Block時,base已經變成211了。

區域性自動變數:在Block中只讀。Block定義時copy變數的值,在Block中作為常量使用,所以即使變數的值在Block外改變,也不影響他在Block中的值。

int base = 100;
BlkSum sum = ^ long (int a, int b) {
  // base++; 編譯錯誤,只讀
  return base + a + b;
};
base = 0;
printf("%ld\n",sum(1,2)); // 這裡輸出是103,而不是3


static變數、全域性變數。如果把上個例子的base改成全域性的、或static。Block就可以對他進行讀寫了。因為全域性變數或靜態變數在記憶體中的地址是固定的,Block在讀取該變數值的時候是直接從其所在記憶體讀出,獲取到的是最新值,而不是在定義時copy的常量

static修飾變數,效果與_ _block一樣

static int base = 100;
BlkSum sum = ^ long (int a, int b) {
  base++;
  return base + a + b;
};
base = 0;
printf("%d\n", base);
printf("%ld\n",sum(1,2)); // 這裡輸出是3,而不是103
printf("%d\n", base);

輸出結果是:

0     

4     

1

表明Block外部對base的更新會影響Block中的base的取值,同樣Block對base的更新也會影響Block外部的base值。


Block變數,被__block修飾的變數稱作Block變數基本型別的Block變數等效於全域性變數、或靜態變數

retain cycle

retain cycle問題的根源在於Block和obj可能會互相強引用,互相retain對方,這樣就導致了retain cycle,最後這個Block和obj就變成了孤島,誰也釋放不了誰。比如:
@implementation TsetBlock

-(id)init{

   if (self = [superinit]) {
       self.testStr [email protected]"中國";
        self.block = ^(NSString *name, NSString *str){
           NSLog(@"arr:%@",self.testStr); // 編譯警告:Capturing 'self' strongly in this block is likely to lead to a retain cycle
       };
   }
   returnself;
}
@end

網上大部分帖子都表述為"block裡面引用了self導致迴圈引用",其實這種說法是不嚴謹的,不一定要顯式地出現"self"字眼才會引起迴圈引用。我們改一下程式碼,不通過屬性self.testStr去訪問String變數,而是通過例項變數_testStr去訪問,如下:
@implementation TsetBlock

-(id)init{

   if (self = [superinit]) {
       self.testStr [email protected]"中國";
        self.block = ^(NSString *name,NSString *str){
           NSLog(@"arr:%@", _testStr); // 同樣出現: Capturing 'self' strongly in this block is likely to lead to a retain cycle
       };
   }
   returnself;
}
@end
可以發現:
即使在你的block程式碼中沒有顯式地出現"self",也會出現迴圈引用!只要你在block裡用到了self所擁有的東西!

要分兩種環境去解決:在ARC下不用__block ,而是用 __weak 為了避免出現迴圈引用

1.ARC:用__week

__weaktypeof (self)  weakSelf = self; 或者

__weak someClass *weakSelf = self;


2.MRC:用__block ,__block修飾的變數在Block copy時是不會retain的,所以,也可以做到破解迴圈引用。
__block someClass *blockSelf = self;

使用如下程式碼解決迴圈引用

weakify(self);
success:^(AFHTTPRequestOperation *operation, id responseObject) {
    strongify(self);
    if (!self__weak_) return ;
       //...................

}

1、weakify(self); 建立一個指向self的弱引用
2、strongify(self); 當加上修飾符strong時,當別處把“self”釋放掉,但呼叫該“self”的block如果仍然沒有執行結束,那麼系統就會等待block執行完成後再釋放,對該“self”在block中的使用起到了保護作用。當block執行結束後會自動釋放掉。
3、if (!self__weak_) return ; 進行判斷,如果在執行strongify(self)之前“self已經被釋放掉了,則此時self=nil,所以直接return即可”



Block的copy、retain、release操作

對Block不管是retain、copy、release都不會改變引用計數retainCount,retainCount始終是1;

NSGlobalBlock:retain、copy、release操作都無效;
NSStackBlock:retain、release操作無效,必須注意的是,NSStackBlock在函式返回後,Block記憶體將被回收。即使retain也沒用。容易犯的錯誤是[[mutableAarry addObject:stackBlock],在函數出棧後,從mutableAarry中取到的stackBlock已經被回收,變成了野指標。正確的做法是先將stackBlock copy到堆上,然後加入陣列:[mutableAarry addObject:[[stackBlock copy] autorelease]]。支援copy,copy之後生成新的NSMallocBlock型別物件。
NSMallocBlock支援retain、release,雖然retainCount始終是1,但記憶體管理器中仍然會增加、減少計數。copy之後不會生成新的物件,只是增加了一次引用,類似retain;
儘量不要對Block使用retain操作。

幾個應用例項:

1、程式碼用在字串陣列排序

NSArray *stringArray = [NSArray arrayWithObjects:@"abc 1", @"abc 21", @"abc 12",@"abc 13",@"abc 05",nil];  
NSComparator sortBlock = ^(id string1, id string2)  
{  
    return [string1 compare:string2];  
};  
NSArray *sortArray = [stringArray sortedArrayUsingComparator:sortBlock];  
NSLog(@"sortArray:%@", sortArray);  

執行結果:sortArray:(

   "abc 05",

   "abc 1",

   "abc 12",

   "abc 13",

   "abc 21"

)

2、程式碼塊的遞迴呼叫

程式碼塊想要遞迴呼叫,程式碼塊變數必須是全域性變數或者是靜態變數,這樣在程式啟動的時候程式碼塊變數就初始化了,可以遞迴呼叫

  1. staticvoid (^ const blocks)(int) = ^(int i)  
  2. {  
  3.     if (i > 0) {  
  4.         NSLog(@"num:%d", i);  
  5.         blocks(i - 1);  
  6.     }  
  7. };  
  8. blocks(3);  
執行列印結果:

num:3

num:2

num:1


參考資料源自網際網路

相關推薦

iOSBlock程式碼定義使用

不會使用Block的iOS程式設計師,不是一個合格的程式設計師 Block沒有你想象中的那麼難,不要害怕,不要畏懼,勇敢嘗試 Block進階: Block其實就是一個程式碼塊,把你想要執行的程式碼封裝在這個程式碼塊裡,等到需要的時候再去呼叫。 個人覺得

IOS學習block程式碼

前言:    block這個名詞對於做一般開發者來說可能會覺得稀奇古怪而非常陌生,但是對於iOS工程師來說,在整個開發中到處都是它的影子,今天一大早覺得在假期學習一下它,對明年的iOS開發工作做個準備,突然有點找回當年即將畢業時的最後一個春節假期的感覺,今天基於最近使用的角

iOS block程式碼強引用問題

block程式碼塊在iOS開發中經常見到,例如AFN資料請求就是block,值得注意的是在block中不恰當的操作程式碼,會導致資源不被釋放,導致記憶體洩漏,例如下面的資料請求: __weak typeof(self) weakSelf = self; [

iOSblock基礎傳值

最近碰到不少關於block的相關問題,在此做一些總結。 1.基本形式 返回值型別(^block)(形參列表)= ^(形參列表列表){程式碼段;}; eg: int(^block)(int,int) = ^(int i, int j){return i +

Objective C程式 block程式碼 和property協議Foundation框架

block程式碼塊 和property協議  1.block型別是一個c級別的語法和執行機制,他與標準c函式類似,不同之處在於,它除了有可執行程式碼以外,還包含了與堆。棧記憶體繫結的變數,因此block 物件包含著一組狀態資料,這些資料在程式執行是用於對行為產生影響,blo

shell腳本函數的定義使用

shell之函數的使用函數Function的使用 定義函數1) 函數名稱() { ... ...}2) function 函數名稱 { ... ...}調用函數 函數名稱 也可以通過位置變量的方式給函數傳遞參數 例子: 編寫腳本,實現目錄管理功能,要求使用函數 #!/bin/bash#createDir()

Shellfunction函式的定義呼叫

文章目錄 `function`函式的定義及呼叫 `function`函式的定義 `function`函式的呼叫【位置傳參】 函式使用return返回值【位置傳參】 函式的呼叫【陣列傳參】

IOS xib和程式碼定義UIView

https://www.jianshu.com/p/1bcc29653085 總結的比較好     iOS開發中,我們常常將一塊View封裝起來,以便於統一管理內部的子控制元件。 下面就來說說自定義View的封裝以及它的多種實現方式 自定義UIView(控制元件)的封裝 什麼是

java普通程式碼、構造、靜態

程式碼塊 定義:{}中定義的一段程式碼 根據程式碼塊出現的位置以及關鍵字不同,分為下面四類程式碼塊 普通程式碼塊 定義在方法中的程式碼塊,解決方法中變數重新命名的問題,瞭解即可,沒什麼意義。 public class 程式碼塊 { public static void ma

ios xcode 設定程式碼

在Xcode中自定義自己的快速程式碼,很簡單,用起來也很方便 首先選擇自己想要的自定義的程式碼塊 拖拽到紅框的區域鬆手,會彈出設定視窗 這是我的填寫 這裡有另外一個技巧,可以給自己想要改變的地方設定成預留字(預留字是我自己隨口胡鄒的名詞) 總之,如果你想把b

Java中普通程式碼,構造程式碼,靜態程式碼區別程式碼示例

//執行順序:(優先順序從高到低。)靜態程式碼塊>mian方法>構造程式碼塊>構造方法。 其中靜態程式碼塊只執行一次。構造程式碼塊在每次建立物件是都會執行。 1 普通程式碼塊 //普通程式碼塊:在方法或語句中出現的{}就稱為普通程式碼塊。普通程式碼塊

Java碼農進階路~程式碼&面向物件特徵繼承

一 程式碼塊1.區域性程式碼塊(方法)書寫位置:方法中作用:限制作用域2.構造程式碼塊書寫位置:類中 方法外程式碼的執行順序:①系統呼叫②只要建立物件 就會呼叫③構造程式碼塊 在 構造方法 之前被呼叫作用:當你有方法 需要每一個物件都呼叫的時候,可以將這個方法在構造程式碼塊中

Idea小技巧 摺疊程式碼

檢視程式碼的時候,當一個類很大的時候。當我們需要檢視XSD檔案的時候,如果需要知道這個schema下面的所有一級元素的時候。這時候因為龐大的類檔案,或者很長的xsd檔案定義就會喪失檢視的興趣。針對這個問題Idea是有相應的方案,那就是程式碼塊摺疊。 下面就來看

iOS-#ifdef DEBUG程式碼介紹

iOS-#ifdef DEBUG巨集定義介紹 一.#ifdef DEBUG程式碼塊 #ifdef DEBUG // Debug 模式的程式碼... #else // Release 模式的程式碼... #endif 二.#DEBUG定義

【風宇衝】Unity3D教程寶典 C#程式碼註釋規範文件生成

原為地址:http://blog.sina.com.cn/lsy835375 C#程式碼註釋規範及文件生成 在使用c#進行Unity3D遊戲開發中,良好的註釋和文件能讓開發更有效率,條理更清晰。 本講分為兩個部分: 一:編寫註釋 二: 生成文件     

IOS 通過 storyboard 自定義控制器以及 loadView 方法簡述

通過 UIStoryboard 物件,就能載入 storyboard 檔案。 注意:必須要有 storyboard,建立 UIStoryboard 物件才有意義,alloc init 建立 UIStoryboard 物件沒有意義。 兩個方法的解析: (1)

iOS 使用純程式碼定義UITableViewCell實現一個簡單的微博介面佈局

一、實現效果 二、使用純程式碼自定義一個UITableViewCell的步驟 1.新建一個繼承自UITableViewCell的類 2.重寫initWithStyle:reuseIdentifier:方法 新增所有需要顯示的子控制元件(不需要設定子控制元件的資料和fram

swift基礎學習傳值[屬性傳值、代理、block程式碼、等]

傳值:在此之前我們先定義兩個檢視控制器ViewController ViewController01 1.屬性傳值正向、這裡以字串做例子、其他型別類似:ViewController->>

IOS 程式碼block的宣告、建立、傳參的基本使用

Block 是iOS在4.0之後新增的程式語法,在iOS SDK 4.0之後,block應用幾乎無處不在。 在其他語言中也有類似的概念稱做閉包(closure),比如object C的好兄弟Swift 中閉包(swift 閉包詳解)的使用跟 OC的block一樣

[ios開發基礎]程式碼 ——block

iOS4引入了一個新特性,支援程式碼塊的使用, 這將從根本上改變你的程式設計方式。程式碼塊是對C語言的一個擴充套件,因此在Objective-C中完全支援。如果你學過Ruby,Python或Lisp程式設計 語言,那麼你肯定知道程式碼塊的強大之處。簡單的說,你可以通