block传递事件 ios

2019-06-25 18:15:27 CC1991_ 阅读数 1596
  • Block的3种类型

    帮助学员深入理解在iOS开发中容易忽略的语法重点 提高开发效率和面试成功率 增加开发效率

    2人学习 张益珲
    免费试看

在iOS开发中传值是一个非常经典的方法,有六种传值方式:属性传值、代理传值、Block传值、方法传值、单例传值、通知传值。本章就来分享一下通过Block完成两个不同界面间的传值操作。

首先再来了解一下Block,简单一点说,Block就是一段匿名的代码块,是具有某种功能的代码块。那么接下来通过实际应用场景,来直观的演示一下用Block传值的操作,具体如下所示。

实例场景是在控制器A里面点击按钮进入到控制器B中,控制器B里面是一个单元格界面,每一个列表对应的三个参数,需要选中其中想要的列表然后返回并传值到控制器A里面,这就是整个使用场景的描述,接下来是具体实现的代码步骤。

1、控制器A.m文件

控制器A里面按钮点击事件的写法如下:

- (void)popoutBtnClick {

//跳转到控制器B

TeaMineBluetoothController *histoyVC = [TeaMineBluetoothController new];

            [self.navigationController pushViewController:histoyVC animated:YES];

            histoyVC.Complate = ^(NSString *temp, NSString *time, NSString *water) {

//Block传的三个参数给控制器A赋值的地方

                _centigradeDegree = [temp floatValue];

                _timeDegree = [time floatValue];

                _waterDegree = [water floatValue];

            };

}

2、控制器B.h文件

控制器B.h文件里面,需要声明Block函数,需要传三个参数值,具体如下所示:

#import "BaseViewController.h"

@interface TeaMineBluetoothController : BaseViewController

@property (nonatomic, copy) void(^Complate)(NSString *temp, NSString *time, NSString *water);

@end

3、控制器B.m文件

控制器B.m文件里面,主要是在单元格的点击事件里面给Block里面的参数赋值,具体步骤如下所示:

- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath {

    [tableView deselectRowAtIndexPath:indexPath animated:YES];

    if (_selectedIndexPath && _selectedIndexPath.row == indexPath.row) {

        _selectedIndexPath = nil; //点击了已经选中的列表项,取消选中

    }else {

        _selectedIndexPath = indexPath;

        NSDictionary *dic = _dateSource[indexPath.row];

        NSString *tempValue = dic[@"Temp"];

        NSString *timeValue = dic[@"Time"];

        NSString *waterValue = dic[@"Water"];

        NSString *title = NSLocalizedString(@"Title", nil);

        NSString *bluetooth = NSLocalizedString(@"Choose Success!", nil);

        NSString *confirm = NSLocalizedString(@"Confirm", nil);

        UIAlertController *alert = [UIAlertController alertControllerWithTitle:title                                                                     message:bluetooth preferredStyle:UIAlertControllerStyleAlert];

        UIAlertAction* cancelAction = [UIAlertAction actionWithTitle:confirm style:UIAlertActionStyleDefault

                                                             handler:^(UIAlertAction * action) {

                                                                //Block的赋值地方

                                                                 if (_Complate) {

                                                                     _Complate(tempValue, timeValue, waterValue); //直接给Block里面的三个参数赋值

                                                                 }

                                                                 [self.navigationController popViewControllerAnimated:YES];

                                                             }];

        [alert addAction:cancelAction];

        [self presentViewController:alert animated:YES completion:nil];

        [self.tableView reloadData];//数据加载完成之后刷新tableview

    }

}

这就是通过Block进行的一个简单传值操作,记录一下方便以后使用,不喜勿喷,大牛请绕行。

 

以上就是本章全部内容,欢迎关注三掌柜的微信公众号、微博,欢迎关注!

三掌柜的微信公众号:

三掌柜的新浪微博:

 

2017-03-08 09:40:44 GorLong 阅读数 1016
  • Block的3种类型

    帮助学员深入理解在iOS开发中容易忽略的语法重点 提高开发效率和面试成功率 增加开发效率

    2人学习 张益珲
    免费试看

        在iOS开发中UIAlertView是我们最常用的控件,使用它的方法也非常简单,但是如果在一个页面需要多个UIAlertView,我们可以用tag值标记,然后在UIAlertView的代理方法里面来判断点击的是那个UIAlertView,从而进行相应的逻辑处理,那么这样一来会有两个弊端!

       第一:代码不整洁,不美观.

       第二:逻辑分离,不利于以后维护.

    为了避免以上两点,我们写一个UIAlertView分类,用block来代替他的代理方法,


     首先.h文件

#import <UIKit/UIKit.h>

@interface UIAlertView (Block)

- (void)handlerClickedButton:(void (^)(NSInteger btnIndex))aBlock;

@end

    然后.m文件的实现:


#import "UIAlertView+Block.h"
#import <objc/runtime.h>

@implementation UIAlertView (Block)

- (void)handlerClickedButton:(void (^)(NSInteger btnIndex))aBlock{
    self.delegate = self;
    objc_setAssociatedObject(self, @"UIAlertView_key_clicked", aBlock, OBJC_ASSOCIATION_COPY);
}

- (void)alertView:(UIAlertView *)alertView clickedButtonAtIndex:(NSInteger)buttonIndex{
    void (^block)(NSInteger btnIndex) = objc_getAssociatedObject(self, @"UIAlertView_key_clicked");
    if (block) block(buttonIndex);
}

@end
    最后外部调用

   UIAlertView *alert = [[UIAlertView alloc] initWithTitle:@"温馨提示"message:@"欢迎光临"delegate:nil cancelButtonTitle:@"关闭"otherButtonTitles:@"开启",nil];
   [alert handlerClickedButton:^(NSInteger btnIndex) {
         if (btnIndex == 0) {
           //逻辑处理                         
         }
         else {
           //逻辑处理
         }
      }];
   [alert show];
  

     这样是不是感觉清爽多了,逻辑梳理起来也顺畅了,不用在去找他的代理方法了!


     注:该文中涉及到 runtime的知识点,我会专门写一篇文件专门讲解!


 

2014-09-18 11:21:50 a15950711997 阅读数 4399
  • Block的3种类型

    帮助学员深入理解在iOS开发中容易忽略的语法重点 提高开发效率和面试成功率 增加开发效率

    2人学习 张益珲
    免费试看

ios手势是ios开发不可或缺的一部分,但对于ios的手势及事件相应机理大家并不知道,所以今天给大家讲一讲ios的事件相应机理

首先,ios的事件传递是依靠“事件链”传递的,当传递到链的某一个链环上的对象处理了这个事件,事件就停止传递。那么事件链是神马东西?

事件链是由一组UIResponder(能够响应时间的基类)组成的对象,当然有事件头和事件尾,事件从头开始传递的,挨个询问是否处理当前事件。处理的话,就停止传递。

对于一个APP的“事件链”是如何建立的,建立需要什么东西。

我们先说app的结构,一般的app是先生成一个APPdelegate 和一个application,然后加window,在加rootview,在加其他的view如下图

\

图1

从图中可以看出,app的生成次序,先是appdegate 和UIapplication,然后是window ,下来是各种view,而事件链也是在这种结构的基础上链接起来的,形成了一个树形结构。

从图中可以看出红色的标号和箭头,这个是事件链的顺序。那么事件链是如何产生的呢,有以下两种情况。

1.addsubview,将一个普通的view B加到 A上 那么B的nextresponder(下一个响应者) 就被赋值为A。

2.如果一个veiw不是普通的view,它是某个viewcontroller的根veiw,例如图中的A,A的nextresponder在A从初始化完后就赋值为viewControllerA。然后当A被addsubview到rootview的时候,Controller A的nextresponder被复制为rootview。

事件链的构建不是统一在某一个地方一次构建,而是当产生addsubview时或者在viewController 初始化view时就会触发。触发的仅仅是两个对象,superview和 subview,或者是superview和subview的viewcontroller

既然事件链是一个树形结构,那么事件也就不能view的兄弟之间传递。

补充,UIApplication的nextresponder是APPdelete

讲完事件链,那么下来事件是如何传递的。

首先,当用户触摸时,UIKit先创建一个事件对象,加到事件队列中去,然后UIAPPlication从中取出来,然后来确定事件链,然后依次询问处理。

如何确定事件链。

有个hittesting过程。如下

首先先找到window ,

1.调用hitTest:withEvent:,hitTest:withEvent:先调用pointInside:withEvent:来判断事件是否发生在自己的区域内

2.如果返回YES,那么就从window的每个subview开始调用1过程。然后一层一层调用下去找到最顶部的view。

当然了hitTest:withEvent:在除了pointInside:withEvent:同时,还会参考view的userinteractionenabled和view的隐藏属性及alpha值,当view隐藏,或者userinteractionenabled为NO,或者alpha小于0.01, hitTest:withEvent:会返回NO。比如说view A 和view B,如下图

\

图2

A和B的superview 首先会调hitTest:withEvent:在B上,发现B隐藏,或者userinteractionenabled为NO,或者alpha小于0.01,那么它就放弃了B,转而检查A。

以上就是确定事件链,然后传递就很简单了。但是有了手势以后,稍微复杂了点。

首先,我们每个手势,其实是一系列的UIEvent。

1,每次传递其中有一个,他会从事件链的最前面开始传递,先讲event传递给事件链顶部的View的手势们。然后看有没有识别的,如果没有就继续到下一个响应者的手势们,如果立即识别,那么就停止传递。如果传递了整个链,还没有发现有识别的,怎么办呢。别以为事件就丢掉了,事件会从回到链的最前端,然后开始找第一个实现touch(touchbegin,touchend,等等)的view进行识别,然后停止本次传递。

2.从时间中再取出一个,继续进行一过程。最终到没有事件位置。

以上就算事件的传递和识别的整个过程。

对于手势的参数设置及特殊viewdui时间传递处理待续。

2017-01-05 11:38:47 qq_30070947 阅读数 1130
  • Block的3种类型

    帮助学员深入理解在iOS开发中容易忽略的语法重点 提高开发效率和面试成功率 增加开发效率

    2人学习 张益珲
    免费试看

此文章仅做个人笔记
block是一个数据类型, 多用于参数传递, 代替代理方法, (有多个参数需要传递或者多个代理方法需要实现还是推荐使用代理方法), 少用于当做返回值传递。
block是一个OC对象, 它的功能是保存代码片段, 预先准备好代码, 并在需要的时候执行。
block分为三类:
1.无参数,无返回值

//定义无参无返回值的Block
   void (^printBlock)() = ^(){
      printf("no number");
         };
    printBlock();
2.有参数,无返回值
//定义一个有参数,没有返回值的Block
void (^printNumBlock)(int) = ^(int num){
    printf("int number is %d",num);
};
3.有参数,有返回值
//定义名为myBlock的代码块,返回值类型为int
    int (^myBlock)(int) = ^(int num){
        return num*mutiplier;
    }
    //使用定义的myBlock
    int newMutiplier = myBlock(3);

对于block,我们常用来进行反向传值
需求:有一个页面V1, 点击一个按钮push到页面V2,在V2中输入多个文本存放到dictionary中,点击V2的确定按钮回到V1,并把dictionary传给V1页面。
V2.h中的代码

#import <UIKit/UIKit.h>

@interface V1ViewController : UIViewController
/* 定义了一个addApplyDetail的block。
 * 这个block必须带一个可变字典类型的参数
 * 无返回值
 */
typedef void(^addApplyDetail)(NSMutableDictionary *dict);
//用上面定义的addApplyDetail 声明一个block ,声明的合格block必须遵守声明的要求
@property (nonatomic,copy) addApplyDetail applyDetail;
@end

V2.m中的代码
1.将输入的多个文本存放到字典当中

- (void)textFieldDidEndEditing:(UITextField *)textField {
    NSString *text = textField.text;
   NSString *textIndexPath = [NSString stringWithFormat:@"%ld",(long)textField.tag];
    [self.textDict setObject:text forKey:textIndexPath];

}

2.点击确定按钮传值

- (void)SureBtnClick {
    [self.view resignFirstResponder];
    self.applyDetail(self.textDict);
    [self.navigationController popViewControllerAnimated:YES];
}

V1中的代码

- (void)AddBtnClick {
    ApplyDetailViewController *applyDetail = [[ApplyDetailViewController alloc]init];

    applyDetail.applyDetail = ^(NSMutableDictionary *dict){
        [self.applyArray addObject:dict];
        [self.tableView reloadData];
    };
    [self.navigationController pushViewController:applyDetail animated:YES];
}
2014-07-24 19:56:45 fengsh998 阅读数 61222
  • Block的3种类型

    帮助学员深入理解在iOS开发中容易忽略的语法重点 提高开发效率和面试成功率 增加开发效率

    2人学习 张益珲
    免费试看

作者:fengsh998
原文地址:http://blog.csdn.net/fengsh998/article/details/38090205
转载请注明出处
如果觉得文章对你有所帮助,请通过留言或关注微信公众帐号fengsh998来支持我,谢谢!


本文不讲block如何声明及使用,只讲block在使用过程中暂时遇到及带来的隐性危险。

主要基于两点进行演示:

1.block 的循环引用(retain cycle)

2.去除block产生的告警时,需注意问题。


有一次,朋友问我当一个对象中的block块中的访问自己的属性会不会造成循环引用,我哈绰绰的就回了一句,不会。兄弟,看完这个,希望你能理解我为什么会说不会循环引用。别废话,演示开始。


下面是我专们写了一个类来演示:

头文件.h

//
//  BlockDemo.h
//  blockDemo
//
//  Created by apple on 14-7-24.
//  Copyright (c) 2014年 fengsh. All rights reserved.
/*
 -fno-objc-arc
 
 由于Block是默认建立在栈上, 所以如果离开方法作用域, Block就会被丢弃,
 在非ARC情况下, 我们要返回一个Block ,需要 [Block copy];
 
 在ARC下, 以下几种情况, Block会自动被从栈复制到堆:
 
 1.被执行copy方法
 2.作为方法返回值
 3.将Block赋值给附有__strong修饰符的id类型的类或者Blcok类型成员变量时
 4.在方法名中含有usingBlock的Cocoa框架方法或者GDC的API中传递的时候.
 */

#import <Foundation/Foundation.h>

@class BlockDemo;

typedef void(^executeFinishedBlock)(void);
typedef void(^executeFinishedBlockParam)(BlockDemo *);

@interface BlockDemo : NSObject
{
    executeFinishedBlock finishblock;
    executeFinishedBlockParam finishblockparam;
}

/**
 *  执行结果
 */
@property (nonatomic,assign) NSInteger resultCode;

/**
 *  每次调用都产生一个新对象
 *
 *  @return
 */
+ (BlockDemo *)blockdemo;

/**
 *  不带参数的block
 *
 *  @param block
 */
- (void)setExecuteFinished:(executeFinishedBlock)block;

/**
 *  带参数的block
 *
 *  @param block
 */
- (void)setExecuteFinishedParam:(executeFinishedBlockParam)block;

- (void)executeTest;


@end

实现文件

//
//  BlockDemo.m
//  blockDemo
//
//  Created by apple on 14-7-24.
//  Copyright (c) 2014年 fengsh. All rights reserved.
//

#if __has_feature(objc_arc) && __clang_major__ >= 3
    #define OBJC_ARC_ENABLED 1
#endif // __has_feature(objc_arc)

#if OBJC_ARC_ENABLED
    #define OBJC_RETAIN(object)         (object)
    #define OBJC_COPY(object)           (object)
    #define OBJC_RELEASE(object)        object = nil
    #define OBJC_AUTORELEASE(object)    (object)
#else
    #define OBJC_RETAIN(object)           [object retain]
    #define OBJC_COPY(object)             [object copy]
    #define OBJC_RELEASE(object)          [object release], object = nil
    #define OBJC_AUTORELEASE(object)      [object autorelease]
#endif

#import "BlockDemo.h"

@implementation BlockDemo


+ (BlockDemo *)blockdemo
{
    return OBJC_AUTORELEASE([[BlockDemo alloc]init]);
}

- (id)init
{
    self = [super init];
    if (self) {
        NSLog(@"Object Constructor!");
    }
    return self;
}

- (void)dealloc
{
    NSLog(@"Object Destoryed!");
#if !__has_feature(objc_arc)
    [super dealloc];
#endif
}

- (void)setExecuteFinished:(executeFinishedBlock)block
{
    OBJC_RELEASE(finishblock);
    finishblock = OBJC_COPY(block); //在非ARC下这里不能使用retain
}

- (void)setExecuteFinishedParam:(executeFinishedBlockParam)block
{
    OBJC_RELEASE(finishblockparam);
    finishblockparam = OBJC_COPY(block); //在非ARC下这里不能使用retain
}

- (void)executeTest
{
    [self performSelector:@selector(executeCallBack) withObject:nil afterDelay:5];
}

- (void)executeCallBack
{
    _resultCode = 200;
    
    if (finishblock)
    {
        finishblock();
    }
    
    if (finishblockparam)
    {
        finishblockparam(self);
    }
}

@end

上面是因为考虑到在ARC 和非ARC中进行编译演示,所以我特意加了ARC预编译判断。主要是方便不要改动太多的代码来给大家演示。


在非ARC环境下


执行下在语句的测试:

- (IBAction)onTest:(id)sender
{
    BlockDemo *demo = [[[BlockDemo alloc]init]autorelease];
    
    [demo setExecuteFinished:^{
        if (demo.resultCode == 200) {
            NSLog(@"call back ok.");
        }
    }];
    
    [demo executeTest];
     
}

输出结果:

2014-07-24 19:08:04.852 blockDemo[25104:60b] Object Constructor!
2014-07-24 19:08:09.854 blockDemo[25104:60b] call back ok.

很显然。尽管demo 是局部变量,并autorelease但可以看出自然至终并没有得到释放,这是因为block中使用了 block内进行访问了自身的resultCode属性。相信很多朋友也都会解决这种循环引用问题。就是在变量前面加个__block,就像这样。

__block BlockDemo *demo = [[[BlockDemo alloc]init]autorelease];
在非ARC下,只虽一个__block关键词就可以。相对还是简单的。

好下面再来看一下在ARC模式下的block循环引用又是怎么样的。

在ARC模式下

执行下面语句:

- (IBAction)onTest:(id)sender
{
    BlockDemo *demo = [[BlockDemo alloc]init];
    [demo setExecuteFinished:^{
        if (demo.resultCode == 200) {
            NSLog(@"call back ok.");
        }
    }];
    
    [demo executeTest];
     
}

执行输出结果:

2014-07-24 19:20:33.997 blockDemo[25215:60b] Object Constructor!
2014-07-24 19:20:39.000 blockDemo[25215:60b] call back ok.
同样会被引入循环。

相信看到这里的人,大多都要喷了,这哪个不知道呀,还知道怎么解决呢,非ARC中加了个__block,当然的在ARC中加一个__weak就搞定了。嗯,确实是这样,但别急,接着往下看,绝对有收获。在这里先自己默认想一下,你是如何加这个__weak的。

对于第一个问是点block 的循环引用(retain cycle)到这里暂告结束。下面讲第二点。因为block告警在非ARC 中暂未发现因写法引入(如果你知道,麻烦告诉我怎么弄产生告警,我好研究一下。)

下面讲在ARC模式下去除因写法产生的告警时需要注意的问题。

像上面的写法其实在ARC中会产生(Capturing 'demo' strongly in this block is likely to lead to a retain cycle)告警。如下图:


在ARC中,编译器智能化了,直接提示这样写会产生循环引用。因此很多爱去除告警的朋友就会想法去掉,好,咱再来看去掉时需注意的问题。

情况一:

- (IBAction)onTest:(id)sender
{
    __weak BlockDemo *demo = [[BlockDemo alloc]init];
    [demo setExecuteFinished:^{
        if (demo.resultCode == 200) {
            NSLog(@"call back ok.");
        }
    }];
    [demo executeTest];
}
直接在前面加一个__weak,但这样真的没有告警了吗?如果有,哪么恭喜欢你,说明编译器还帮你大忙。见下图



这时还会告警,说这是一个WEAK变量,就马上会被release。因此就不会执行block中的内容。大家可以运行一下看

输出结果为:

2014-07-24 19:38:02.453 blockDemo[25305:60b] Object Constructor!
2014-07-24 19:38:02.454 blockDemo[25305:60b] Object Destoryed!
很显然,马上被release了,所以block 中的代码根本就不执行。

谢天谢地,幸好编译器提前告诉了我们有这个隐性危险。相信大家为解决告警,又会得到一个比较圆满的解决方案,见下:

- (IBAction)onTest:(id)sender
{
    BlockDemo *demo = [[BlockDemo alloc]init];
    
    __weak typeof(BlockDemo) *weakDemo = demo;
    
    [demo setExecuteFinished:^{
        if (weakDemo.resultCode == 200) {
            NSLog(@"call back ok.");
        }
    }];
    [demo executeTest];
}

这样写,即去除了告警又保证了block的运行。这才是我们最终想要的结果。
输出为:

2014-07-24 19:40:33.204 blockDemo[25328:60b] Object Constructor!
2014-07-24 19:40:38.206 blockDemo[25328:60b] call back ok.
2014-07-24 19:40:38.207 blockDemo[25328:60b] Object Destoryed!

但大家别得意。有提示,相信大家都能处理,并得到个好的解决方法。哪么下面大来再来看一下这个写法,让你真心甘拜下风。。。。。

- (IBAction)onTest:(id)sender
{
    __weak BlockDemo *demo = [BlockDemo blockdemo]; //这里才是重点,前面是[[BlockDemo alloc]init];会有告警。
    
    [demo setExecuteFinished:^{
        if (demo.resultCode == 200) {
            NSLog(@"call back ok.");
        }
    }];
    [demo executeTest];
}


其实只是把init放到了类方法中进行书写而已,但会有什么不同。
+ (BlockDemo *)blockdemo
{
    return OBJC_AUTORELEASE([[BlockDemo alloc]init]);
}
不同点见下图:真心看不到作何告警,是不是。但这存在什么风险,风险就是运行的时候,block根本就没有run。因为对象早就释放了。


直接输出:

2014-07-24 19:47:53.033 blockDemo[25395:60b] Object Constructor!
2014-07-24 19:47:53.035 blockDemo[25395:60b] Object Destoryed!

因此,写这个主要用来告戒一些喜欢用BLOCK但又想当然的朋友,有一些朋友喜欢去除告警,但只是盲目的加上__weak 或__block关键语,往往可能存在一些重大的安全隐患。就像演示中block根本不走。如果到了发布时,为了去告警而这样简单的处理了,并没有进行测试就打包。哪么将死得很惨。。。。。


好,到了尾声,来说说为什么朋友问我block会不会引行死循环,我说不会的理由。

见码:

- (IBAction)onTest:(id)sender
{
    BlockDemo *demo = [BlockDemo blockdemo];//[[BlockDemo alloc]init];
    
    [demo setExecuteFinishedParam:^(BlockDemo * ademo) {
        if (ademo.resultCode == 200) {
            NSLog(@"call back ok.");
        }
    }];
    
    [demo executeTest];
}

不管是在外面init,还是在里面,且没有加__block 及__weak。为什么,因为我个人常常在使用自己写的block时,如果是回调,比较喜欢把自身当作参数传到block中。这样期实是编译器给我们做了弱引用。因此不会产生循环引用。

由于我一直都这样写block,所以朋友一问起,我就说不会循环引用了,因为压根他碰到的就是前面讲述的哪种访问方式,而我回答的是我的这种使用方式。正因为口头描述,与实际回复真是差之千里。。。哈哈。为了验证我朋友的这个,我特意写了个这篇文章,希望对大家有所帮助。最后,谢谢大家花时间阅读。












IOS 返回传值--Block

阅读数 1161