首页 > 代码库 > 块语法Block在MVC思维的妙用之多重M层代理传值
块语法Block在MVC思维的妙用之多重M层代理传值
注:以下代码均来自真实项目案例。
在项目开发中,经常避免一些与系统工具交互的功能需求。比如说开启蓝牙,开启相机,通讯录功能,还有数据加密等等。
由于这些功能的实现没有实例化的必要,并且又是许多项目都共用的功能,所以一般我们会作为类的静态方法去作为自己的工具类。
以下是一段将字典的键值对导入通讯录的静态方法代码。
假如说现在有一个这样的逻辑流程,C层按钮交互,将页面某个数据加密导入通讯录。
让我们以MVC的思维梳理一下整过流程。
在这整个事件中,有三个参与者。页面(C层),加密(M层),通讯录导入(M层)。
为了简写,我们把页面(C)定义为A,加密(M层)定义为B,通讯录导入(M层)定义为C。
结果也有三种,第一种是导入失败,第二种是能够导入成功但是通讯录中已经存在该联系人(需要提示A【用户】该次导入是否要覆盖掉已经存在的联系人),第三种是能够导入成功并且通讯录中没有该联系人。
方案也有三种,第一种是A将加密导入通讯录指令告知B,B执行加密命令后再将数据传递给C,C进行通讯录导入将结果告知B,B再告知C。 实现上为了简洁可以将该逻辑代码封装为B的一个方法,B调用C的静态方法将返回参数通过自己的方法告知A,B和C的两个方法返回参数为int类型123分别表示三种情况。
第二种方案跟第一种方案类似,主要是由于int类型的表述结果不够直观,可以通过回调代理去执行,如-(void)setAddressSuccess -(void)setAddresFaild -(void)setAddresExist 代理方式更为直观,便于开发人员自身检测以及维护人员维护。
第三种方案是A将处理三种结果的代码块作为Block参数送给B,B执行完加密之后将A交于自己的Block送给C,C在执行完导入操作之后直接执行Block,不需要将自己的结果告知任何人。
以下代码是第二种方案和第三种方案融合处理,主要为了便于看到各种方案的利与弊。
首先是C的静态方法
+ (BOOL)SetAddressBookWithInfo:(NSMutableDictionary *)info RePetBlock:(void (^)(void))block
{
//获取通讯录信息
ABAddressBookRef addressBook =ABAddressBookCreateWithOptions(nil,nil);
// ABRecordRef是一个属性的集合,相当于通讯录中联系人的对象
ABRecordRef person = ABPersonCreate();
//写入名字
NSString *firstName = [info objectForKey:@"firstName"];
// 保存到联系人对象中,每个属性都对应一个宏,例如:kABPersonFirstNameProperty
// 设置firstName属性
ABRecordSetValue(person, kABPersonFirstNameProperty, (__bridge CFTypeRef) firstName, NULL);
//写入图片
if(![[infoobjectForKey:@"image"]isKindOfClass:[NSNullclass]])
{
UIImage *image = [UIImageimageNamed:@"icon80-80.png"];
NSData *data = http://www.mamicode.com/UIImageJPEGRepresentation(image,1.0);
ABPersonSetImageData(person, (__bridgeCFDataRef) data, NULL);
}
//写入公司
if([info objectForKey:@"company"])
{
ABRecordSetValue(person,kABPersonOrganizationProperty, (__bridgeCFTypeRef) [info objectForKey:@"company"],NULL);
}
//写入公司地址
if([infoobjectForKey:@"companyAddress"])
{
ABMutableMultiValueRef multiAddress =ABMultiValueCreateMutable(kABMultiDictionaryPropertyType);
NSMutableDictionary *addressDictionary = [[NSMutableDictionaryalloc] init];
[addressDictionarysetObject:[info objectForKey:@"companyAddress"]forKey:(NSString *)kABPersonAddressStreetKey];
ABMultiValueAddValueAndLabel(multiAddress, (__bridgeCFTypeRef)(addressDictionary), kABWorkLabel, NULL);
//设置address
ABRecordSetValue(person, kABPersonAddressProperty, multiAddress, nil);
}
//写入电话
NSMutableArray *phones = [NSMutableArrayarrayWithCapacity:0];
NSMutableArray *phonesName = [NSMutableArrayarrayWithCapacity:0];
if(![[infoobjectForKey:[NSStringstringWithFormat:@"phone%d",i]]isKindOfClass:[NSNullclass]])
{
[phonesaddObject:[info objectForKey:[NSStringstringWithFormat:@"phone%d",0]]];
[phonesNameaddObject:[NSStringstringWithFormat:@"phone%d",0]];
}
if(phones.count !=0 || phonesName.count !=0)
{
// ABMultiValueRef类似是Objective-C中的NSMutableDictionary
ABMultiValueRef mv =ABMultiValueCreateMutable(kABMultiStringPropertyType);
//添加电话号码与其对应的名称内容
for (int i =0; i < [phones count]; i ++) {
ABMultiValueIdentifier mi = ABMultiValueAddValueAndLabel(mv, (__bridgeCFStringRef)[phones objectAtIndex:i], (__bridge CFStringRef)[phonesName objectAtIndex:i], &mi);
}
// 设置phone属性
ABRecordSetValue(person, kABPersonPhoneProperty, mv, NULL);
}
//写入邮箱
if([info objectForKey:@"email"])
{
ABMultiValueRef em = ABMultiValueCreateMutable(kABPersonEmailProperty);
ABMultiValueIdentifier ma = ABMultiValueAddValueAndLabel(em, (__bridge CFTypeRef)([info objectForKey:@"email"]), (__bridge CFStringRef)@"email", &ma);
ABRecordSetValue(person, kABPersonEmailProperty, em, NULL);
}
//
// //写入网址
// if([info objectForKey:@"url"])
// {
// NSMutableArray *y = [NSMutableArray arrayWithObjects:[info objectForKey:@"url"],@"http://www.ucardpro.com", nil];
// NSMutableArray *z = [NSMutableArray arrayWithObjects:@"个人主页",@"Ucard官网", nil];
// ABMultiValueRef url = ABMultiValueCreateMutable(kABPersonURLProperty);
// for (int i = 0; i < [phones count]; i ++) {
// ABMultiValueIdentifier ur = ABMultiValueAddValueAndLabel(url, (__bridge CFStringRef)[y objectAtIndex:i], (__bridge CFStringRef)[z objectAtIndex:i], &ur);
// }
// ABRecordSetValue(person, kABPersonURLProperty, url, NULL);
// }
//
// //写入日期
// NSDate *date = [NSDate date];
// NSDateFormatter *f = [[NSDateFormatter alloc] init];
// f.dateFormat = @"yyyy年MM月dd日 HH:mm:SS";
// NSString *s = [NSString stringWithFormat:@"名片导入时间:%@ 导入来自Ucard",[f stringFromDate:date]];
// NSLog(@"%@",s);
// ABRecordSetValue(person,kABPersonNoteProperty, (__bridge CFTypeRef)(s), NULL);
//写入前检查是否联系人已存在
if([ZxkSetAddressBookexamineAddressWithName:firstName] == NO)
{
block();
return NO;
}
//获取通讯录中所有的联系人
NSArray *array = (__bridgeNSArray *)ABAddressBookCopyArrayOfAllPeople(addressBook);
//遍历所有的联系人并删除
for (id objin array) {
ABRecordRef people = (__bridgeABRecordRef)obj;
NSString *first = (__bridgeNSString *)ABRecordCopyValue(people,kABPersonFirstNameProperty);
if ([first isEqualToString:firstName])
{
ABAddressBookRemoveRecord(addressBook, people,nil);
}
}
//将新建的联系人添加到通讯录中
ABAddressBookAddRecord(addressBook, person, NULL);
if (ABAddressBookGetAuthorizationStatus() ==kABAuthorizationStatusNotDetermined) {
dispatch_semaphore_t sema =dispatch_semaphore_create(0);
ABAddressBookRequestAccessWithCompletion(addressBook, ^(bool granted,CFErrorRef error) {
dispatch_semaphore_signal(sema);
});
dispatch_semaphore_wait(sema,DISPATCH_TIME_FOREVER);
}
elseif (ABAddressBookGetAuthorizationStatus() ==kABAuthorizationStatusAuthorized) {
// The user has previously given access, add the contact
if(ABAddressBookSave(addressBook,NULL))
{
return YES;
}
}
else {
// The user has previously denied access
// Send an alert telling user to change privacy setting in settings app
UIAlertView *alert = [[UIAlertViewalloc] initWithTitle:@"提示"message:@"导入失败,请在设置-隐私中检查是否有权限访问您的通讯录"delegate:selfcancelButtonTitle:@"确认"otherButtonTitles:nil];
[alertshow];
}
return NO;
}
以下是B的加密操作,部分核心代码有删减
//通讯录加密
-(void)encryptWithAccountList:(AccountList *)list RePetBlock:(void(^)(void))block
{
if([ZxkSetAddressBookSetAddressBookWithInfo:[dic mutableCopy]RePetBlock:block] == YES)
{
if(self.delegate && [self.delegaterespondsToSelector:@selector(setAddressSuccess:)])
{
[self.delegatesetAddressSuccess:self.accountList.remark];
}
}
}
最后是A的按钮交互代码
-(void)buttonClick
{
__blockNSArray *arr = self.p_tableArr;
__block int select =self.p_didSelect;
__blockAccountViewController *v = self;
__block UIAlertView *alt;
[self.encryptencryptWithAccountList:[self.p_tableArrobjectAtIndex:self.p_didSelect]RePetBlock:^
{
self.p_alertBlock = ^(NSInteger buttonIndex)
{
if(buttonIndex==1)
{
AccountList *list = [arr objectAtIndex:select];
[vsetAddressSuccess:list.remark];
}
};
alt = [[UIAlertViewalloc] initWithTitle:NSLocalizedString(@"tip",nil)message:NSLocalizedString(@"setaddressTips",nil) delegate:v cancelButtonTitle:NSLocalizedString(@"cancel",nil) otherButtonTitles:NSLocalizedString(@"ok",nil),nil];
[altshow];
}];
以下是B的结果回调代理 部分核心代码有删减
#pragma mark - AddressbookEncryptDelegate
- (void)setAddressSuccess:(NSString *)firstName
{
[self.navigationControllerpushViewController:self.p_peoplePickeranimated:YES];
}
以上实现既有代理也有Block,可以看到代理实现的过程较为繁杂,而Block实现更加的便捷直观,根据上一章节在Block中讲到的系统UIAlertView的集中代码用法,这一整个过程不需要任何的回调代理,也不需要任何的逻辑判断,实际上只需三个Block代码快就可以完成,第一个是处理UIAlertView交互的bLOCK,一个是处理三种结果情况的Block,一个是组合前两个Block之后的最终Block。
也许从实现来看,三种方法都可以达到目的,但是在MVC的思维中前两种实现方法都存在M层越界的情况。
在这个过程中,作为数据层,B的职责只是加密,C的职责只是导入,A作为Controller层主要是想拿到C的处理结果来做下一步的逻辑。第一种方案和第二种方案的实现中,由于C都是把自己的结果先交给B,B再交给A,所以这里存在A把自己与B无关的机密信息泄漏给了B,因为是B先知道这三种结果,A后知道。所以从理论来讲,这里可能会存在B操作不当没有正确的把C的结果告知A。当然,由于以上过程较为简单,一般不会出现这种情况。而由于C作为一个工具类的静态方法,无法做到代理告知,这也是一个问题。
Block语法在这个过程中的好处就是:当Controller的某一个命令需要多个Model层协调进行的时候,Controller只需要将自己对最终结果处理的逻辑代码写在这个命令传达之前,并且准确的交由真正那个能够给自己明确答复的Model层去直接执行命令,既能够集中自己的逻辑代码,也避免了其他M层作为传声筒传递结果的重复代码。
块语法Block在MVC思维的妙用之多重M层代理传值