導(dǎo)語
block是一些簡短代碼片段的封裝.本質(zhì)是函數(shù)指針. 雖然block聲明的時候不帶星,而且默認(rèn)情況下存放在棧里,但是,他確實是OC對象,往下看會有示例說明
block類型
- _NSConcreteGlobalBlock 全局的靜態(tài) block怨咪,不會訪問任何外部變量
- _NSConcreteStackBlock 保存在棧中的 block沛申,當(dāng)函數(shù)返回時會被銷毀劫笙。但在ARC下,會被復(fù)制到堆上.
- _NSConcreteMallocBlock 保存在堆中的 block纫普,當(dāng)引用計數(shù)為 0 時會被銷毀匿辩。 所以在ARC下,只有_NSConcreteGlobalBlock和_NSConcreteMallocBlock這兩種類型
寫個例子來測試一下block類型
/** 在.h中定義和.m中定義對結(jié)果沒有影響,已驗證完畢*/
//.h中定義類型,聲明屬性htestBlock
typedef NSString *(^testBlock)(NSString *);
@property (nonatomic ,copy)testBlock htestBlock;
//.m中聲明屬性mtestBlock及變量_ctestBlock
@property (nonatomic ,copy)testBlock mtestBlock; @implementation ViewController
{
testBlock _ctestBlock;
}
//下面進(jìn)行測試
self.htestBlock = ^(NSString *str)
{
return str;
};
self.mtestBlock = ^(NSString *str)
{
return str;
};
_ctestBlock = ^(NSString *str)
{
return str;
};
NSLog(@"%@",self.htestBlock(@"h"));
NSLog(@"%@",self.mtestBlock(@"m"));
NSLog(@"%@",_ctestBlock(@"c"));
結(jié)果如上圖,你會發(fā)現(xiàn),全部都是GlobalBlock額,但傳聞GlobalBlock不會訪問任何外部變量,所以接著測試
NSString *name = @"daqianqian";
self.htestBlock = ^(NSString *str)
{
return name;
};
self.mtestBlock = ^(NSString *str)
{
return name;
};
_ctestBlock = ^(NSString *str)
{
return name;
};
NSLog(@"%@",self.htestBlock(@"h"));
NSLog(@"%@",self.mtestBlock(@"m"));
NSLog(@"%@",_ctestBlock(@"c"));
這下全部變成MallocBlock了
根據(jù)上面的結(jié)果,個人猜測,block聲明后,就是全局的靜態(tài)block,因為現(xiàn)在使用的是ARC,所以當(dāng)你訪問外部變量,會自動copy一份到堆上.
若理解不對請大家指正,感謝!
__block
再做個小測試
//聲明block及屬性myBlock
typedef NSString * (^Block) (int);
@property(nonatomic, copy)Block myBlock;
你想在在內(nèi)部更改外部變量,你會發(fā)現(xiàn),無法更改!
因為block如果要訪問外部變量,他會拷貝進(jìn)來一份外部變量,并且這個外部變量是只讀的
外部變量改變也并不影響block內(nèi)部拷貝的那一份變量
請接著看下面的例子
這個輸出結(jié)果如下,驗證了上面的判斷
如果不想讓block拷貝變量响禽,而是想讓內(nèi)部使用的變量和外部使用的變量指向同一地址的話,需要在變量前面加上__block關(guān)鍵字,則外部變量不再是只讀的胖笛,在block內(nèi)部也可以改變它的值
定義和使用block
- 獨(dú)立block,作為類的變量或?qū)傩允褂?br>
1- 實現(xiàn)界面反向傳值
2- 可以實現(xiàn)界面A做完某一操作后,界面B立即實現(xiàn)其他操作 - 內(nèi)聯(lián)block,作為方法的參數(shù)使用
1-配合disptch_queue,可以方便的實現(xiàn)簡單的多線程編程和異步編程
Demo1 - 使用獨(dú)立block,實現(xiàn)界面反向傳值
獨(dú)立block格式:
typedef 返回值類型 (^block名稱)(參數(shù)列表)
/** 在界面2聲明block,*/
//自定義格式
typedef void(^CCBlock)(NSString *);
//聲明屬性
@property (nonatomic, copy)CCBlock myCCBlock;
@implementation TwoViewController
{
//聲明變量
CCBlock myCCBlock2;
}
//在你需要的地方,寫好調(diào)用方法
if(self.myCCBlock)
{
self.myCCBlock(@"你好");
}
/** 在界面1聲明界面2的對象,并實現(xiàn)回調(diào)*/
TwoViewController *secondVC = [[TwoViewController alloc] init];
secondController.myCCBlock = ^(NSString *str)
{
//在此處你可取到傳來的"你好",可以做刷新界面等其他操作
};
就這樣,輕松實現(xiàn)了界面的反向傳值
接上面的Demo1,再做個小測驗
在界面2聲明一個數(shù)組,我們在界面2初始化的時候就調(diào)用block,并將值作為對象存在數(shù)組里,打印結(jié)果如下:
self.myCCBlock(@"lalala");
NSArray *array = [[NSArray alloc] initWithObjects:@"1",self.myCCBlock,@"2",nil];
NSLog(@"myCCBlock內(nèi)容為%@,數(shù)組內(nèi)容為為%@",self.myCCBlock,array);
這說明,block就是OC對象,因為它能存在數(shù)組里!
Demo2 - 使用內(nèi)聯(lián)block,實現(xiàn)
內(nèi)聯(lián)block格式:
(返回值 (^)(參數(shù)列表))此方法的參數(shù)名
/** 在界面2聲明內(nèi)聯(lián)block并調(diào)用*/
- (void)insideBlock: (NSString *)name successBlock:(void (^)(NSString *))success faileBlock:(void (^)(NSString *))fails;
- (void)insideBlock: (NSString *)name successBlock:(void (^)(NSString *))success faileBlock:(void (^)(NSString *))fails
{
//可以在這里做判斷,比如有一個BOOL值控制,為True就調(diào)用success(name),為False就調(diào)用faile(name)
success(name);
faile(name);
}
/** 在界面1聲明界面2對象,并實現(xiàn)回調(diào)*/
TwoViewController *secondController = [[TwoViewController alloc] init];
[secondController insideBlock:@"name" successBlock:^(NSString *response)
{
NSLog(@"內(nèi)聯(lián)函數(shù)成功的回調(diào)是%@",response);
}
faileBlock:^(NSString *response)
{
NSLog(@"內(nèi)聯(lián)函數(shù)失敗的回調(diào)是%@",response);
}];
就這樣,我們實現(xiàn)了將一段程序塊寫在函數(shù)里面
循環(huán)引用
還是做個測驗來分享
大家應(yīng)該知道,navigationController在pop掉界面的時候,這個界面會被銷毀,在界面上加上這句話,會發(fā)現(xiàn)pop時這句話會打印出來
- (void)dealloc
{
NSLog(@"被銷毀了");
}
若出現(xiàn)循環(huán)引用,會出現(xiàn)什么情況呢,就是A引用B,B引用A,引用計數(shù)都為2,調(diào)用dealloc的時候釋放一次,但結(jié)果是AB的引用計數(shù)仍為1,無法銷毀,占用內(nèi)存.
/** 在界面2.h聲明block類型及屬性,另外聲明一個屬性name*/
typedef void (^Block) (NSString *);
@property(nonatomic, copy)Block myBlock;
@property(nonatomic, strong)NSString *name;
/** 在界面2.m給屬性name賦值,并在點(diǎn)擊按鈕返回界面1時,調(diào)用block,注意此時沒傳值*/
- (void)viewDidLoad
{
[super viewDidLoad];
self.name = @"lele";
}
- (IBAction)onClick:(id)sender
{
if (self.myBlock)
{
self.myBlock(@"");
}
[self .navigationController popViewControllerAnimated:NO];
}
/** 在界面1聲明界面2對象,并在界面1實現(xiàn)"在界面2點(diǎn)擊按鈕調(diào)用block方法"后的回調(diào)*/
- (IBAction)onClick:(id)sender
{
TwoViewController *secondVC = [[TwoViewController alloc] init];
secondVC.myBlock = ^(NSString * str)
{
NSLog(@"%@",secondVC.name);
};
[self.navigationController pushViewController:secondVC animated:NO];
}
此時運(yùn)行程序你會發(fā)現(xiàn),點(diǎn)擊界面2的按鈕后,界面2pop掉,返回界面1,"le'le"被傳了過來,但卻沒有執(zhí)行delloc,沒有打印"被銷毀了"
原因
1- 在界面1中,執(zhí)行此句,VC的引用計數(shù)為1
TwoViewController *secondVC = [[TwoViewController alloc] init];
2- 在界面1中,執(zhí)行此句,block的引用計數(shù)為1
secondVC.myBlock = ^(NSString * str)
{
};
3- 在界面1中,執(zhí)行此句,VC的引用計數(shù)為2
NSLog(@"%@",secondVC.name);
4- 在界面2中,執(zhí)行此句,block的引用計數(shù)為2
self.myBlock(@"");
5- 界面pop時,引用計數(shù)只會減1,所以造成循環(huán)引用
解決辦法
手動將VC改為弱引用,使用"__weak typeof(原來VC類名) 自己再起個名字"方法.
- (IBAction)onClick:(id)sender
{
TwoViewController *secondVC = [[TwoViewController alloc] init];
__weak typeof(secondVC) vc;
secondVC.myBlock = ^(NSString * str)
{
NSLog(@"%@",vc.name);
};
[self.navigationController pushViewController:secondVC animated:NO];
}
打印結(jié)果如圖,因為被銷毀了,所以取不到他的屬性值name
補(bǔ)充
-
1-內(nèi)聯(lián)block普通
(返回值 (^)(參數(shù)列表))此方法的參數(shù)名
/** 在界面2聲明內(nèi)聯(lián)block并調(diào)用*/
- (void)insideBlock: (NSString *)name successBlock:(void (^)(NSString *))success faileBlock:(void (^)(NSString *))fails;
- (void)insideBlock: (NSString *)name successBlock:(void (^)(NSString *))success faileBlock:(void (^)(NSString *))fails
{
//可以在這里做判斷,比如有一個BOOL值控制,為True就調(diào)用success(name),為False就調(diào)用faile(name)
success(name);
faile(name);
}
/** 在界面1聲明界面2對象,并實現(xiàn)回調(diào)*/
TwoViewController *secondController = [[TwoViewController alloc] init];
[secondController insideBlock:@"name" successBlock:^(NSString *response)
{
NSLog(@"內(nèi)聯(lián)函數(shù)成功的回調(diào)是%@",response);
}
faileBlock:^(NSString *response)
{
NSLog(@"內(nèi)聯(lián)函數(shù)失敗的回調(diào)是%@",response);
}];
1.系統(tǒng)會先調(diào)用
TwoViewController *secondController = [[TwoViewController alloc] init];
2.然后調(diào)用
[secondController insideBlock:@"name"
successBlock:^(NSString *response)
3.走到這里去調(diào)用這個方法內(nèi)部
- (void)insideBlock: (NSString *)name
successBlock:(void (^)(NSString *))success
faileBlock:(void (^)(NSString *))fails
{
success(name);
fails(name);
}
如果這里有success(name);則去回調(diào)下圖中的第一個方框
如果這里有fails(name);則去回調(diào)下圖中的第二個方框
-
2-內(nèi)聯(lián)block嵌套
本例子使用了FMDB,需導(dǎo)入<FMDB.h>
// 1.block類_初始化
#import "TwoViewController.h"
#import "objc/runtime.h"
#import <FMDB.h>
static TwoViewController *_vcSingleton = nil;
@interface TwoViewController ()
{
FMDatabase *_db;
}
@end
@implementation TwoViewController
#pragma mark - 單例
+(instancetype)sharedDBSingleton
{
if (_vcSingleton == nil)
{
_vcSingleton = [[TwoViewController alloc] init];
[_vcSingleton initVCSingleton];
}
return _vcSingleton;
}
- (void)initVCSingleton
{
NSString *documentsPath = [NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) lastObject];
NSString *filePath = [documentsPath stringByAppendingPathComponent:@"model.sqlite"];
NSLog(@"地址是%@",filePath);
_db = [FMDatabase databaseWithPath:filePath];
NSLog(@"這個db是什么%@",_db);
[_db open];
// 初始化數(shù)據(jù)表
NSString *personSql = @"CREATE TABLE 'animal' ('animal_name' VARCHAR(255))";
[_db executeUpdate:personSql];
[_db close];
}
// 2.block類_block
#pragma mark - Block
- (void)insideBlock1: (NSString *)name successBlock:(BOOL (^)(FMDatabase *db,SInt32 lastVersion))success
{
SInt32 lastVersion = 5555;
[self inTransaction:^(FMDatabase *db, BOOL *shouldRollback)
{
if (success(db, lastVersion))
{
NSLog(@"這是什么");
}
else
{
NSLog(@"這又是什么");
}
}];
}
- (void)inTransaction:(void (^)(FMDatabase *db, BOOL *shouldRollback))block
{
BOOL shouldRollback = NO;
block(_db, &shouldRollback);
}
//3.其他類_調(diào)用block
TwoViewController *secondController = [TwoViewController sharedDBSingleton];
[secondController insideBlock1:@"test" successBlock:
^BOOL(FMDatabase *db, SInt32 lastVersion)
{
NSLog(@"傳來的db是什么%@,傳來的lastVersion是什么%d",db,(int)lastVersion);
return YES;
}];
-
1.先執(zhí)行
TwoViewController *secondController = [TwoViewController sharedDBSingleton];
在initVCSingleton中初始化了db為<FMDatabase: 0x60000008e920> -
2.調(diào)用
[secondController insideBlock1:@"test" successBlock:
^BOOL(FMDatabase *db, SInt32 lastVersion)
的時候,是在block類內(nèi)部調(diào)用,先執(zhí)行
SInt32 lastVersion = 5555; -
3.然后執(zhí)行了
[self inTransaction:^(FMDatabase *db, BOOL *shouldRollback)
方法內(nèi)部,因為block(_db, &shouldRollback);
所以執(zhí)行的是下面的回調(diào),傳進(jìn)來的兩個參數(shù)就是_db和shouldRollback = NO
下面的這個判斷,就會去其他類_調(diào)用block中調(diào)用,并傳過去_db和5555
可以看到下圖是返回YES
所以執(zhí)行下面這段