美文网首页ios开发
NSUndoManager的理解及使用

NSUndoManager的理解及使用

作者: _菩提本无树_ | 来源:发表于2020-06-26 01:04 被阅读0次

    以下内容均为个人总结理解,如有错误欢迎指出

    NSUndoManager总结

    NSUndoManager是苹果提供的可以撤销(undo)恢复(redo)的一套API,他的使用方法呢看文档理解起来很难,然后看网络上的内容也有很多的介绍,但是看的多了发现好多都是从一篇文章中衍生出来的.写的很好,但是可能有些点写的不详细,下面是个人总结,希望对诸位有帮助.

    本来想直接写个人总结的精髓,但是发现没有铺垫下不了笔,要是直接写结果估计就该骂我写的烂了,所以还是理一遍主要思路,详细的API简介可以参考下面的这篇文章
    ForeverGuard-NSUndoManager

    开始正文,NSUndoManager是UIResponder的公开的一个属性,有些人说是成员变量特意去看了一下API确认不是成员变量,他们还是有区别的具体点我,所以说UIResponder的子类都有这个东西,其他的可以在使用时自行摸索尝试.

    NSUndoManger内部有两个栈,undo栈(撤销)和redo栈(重写,恢复)

    在UIResponder中NSUndoManager是readonly只读属性,所以我们要使用需要自己初始化

    1.初始化一个NSUndoManager
    NSUndoManager * undoManager = [[NSUndoManager alloc]init];
    
    2.注册操作到undo栈中

    好了到这里其实已经到重点了,就是注册的时候到底注册的是什么呢?下面直接上代码分析

    undo注册的方法应该是一个反向操作,下面代码见分析

    #import "UndoManager.h"
    
    @implementation UndoManager
    {
        NSUndoManager * undoManager;
        //测试数组
        NSMutableArray * titleArr;
    }
    - (instancetype)init
    {
        self = [super init];
        if (self) {
            //初始话undoManager
            undoManager = [[NSUndoManager alloc]init];
            //初始话测试数组
            titleArr = [NSMutableArray new];
            
            //接下来就开始测试了
            //第一步,先看addTitleWithStr:这个方法里面的简述
            [self addTitleWithStr:@"栈1"];
            dispatch_async(dispatch_get_global_queue(0, 0), ^{
                sleep(2);
                [self addTitleWithStr:@"栈2"];
            });
            
            //第二步
            dispatch_async(dispatch_get_global_queue(0, 0), ^{
                sleep(4);
                //执行撤销操作,判断是否能撤销
                if ([self->undoManager canUndo]) {
                     //undo这个方法最终调用的是undo栈顶存入的方法也就是removeTitle:这个方法,下面请看removeTitle:方法里面的简述
                    [self->undoManager undo];
                }
                NSLog(@"titleArr:%@",self->titleArr);
                //执行恢复操作,判断是否能撤销
                if ([self->undoManager canRedo]) {
                    [self->undoManager redo];
                }
                NSLog(@"titleArr:%@",self->titleArr);
    
            });
            
            
        }
        return self;
    }
    
    - (void)addTitleWithStr:(NSString *)str{
       
        //执行的操作每一步都需要registerUndoWithTarget一次,将方法和参数都放到undo栈中,划重点->注册的是逆向操作删除,注册的是逆向操作删除,注册的是逆向操作删除
        //当执行undo(撤销)操作时我们需要从undo栈顶取出存入的操作并执行,我们这个方法是存入,所以相应的反操作就是删除
        [undoManager registerUndoWithTarget:self selector:@selector(removeTitle:) object:str];
        
        //从这里可以看到NSObject也可以注册到NSUndoManager,本类是NSObject类
        //然后将数据添加到数组,看到这从init方法继续看不要直接跳到removeTitle:方法
        [titleArr addObject:str];
        
    }
    
    - (void)removeTitle:(NSString *)str{
        
        //这个removeTitle:方法就是对应的撤销操作,不会调用removeTitle:这个方法,调用这个方法是[undoManager undo]
        
        //在这里我们还需要注册一次addTitleWithStr:,为什么呢?
        //原因是一个规则,就是当执行[undoManager undo]操作时,执行registerUndoWithTarget:方法时,注册的这个内容会存到redo的栈中.
        //所以我们接下来执行[undoManager redo]操作时会调用addTitleWithStr:这个方法,依次类推会一直循环下去
        
        //这里呢也证明了一点,执行[undoManager undo]操作时undo栈顶的会出栈
        //执行[undoManager redo]操作时redo栈顶的也会出栈
        [undoManager registerUndoWithTarget:self selector:@selector(addTitleWithStr:) object:str];
        
        [titleArr removeObject:str];
    
    }
    @end
    
    

    上面代码中我使用线程操作是为了告诉各位一件事,就是说undo栈和redo栈其实对添加进来的方法是有进一步的包装的,在一个runloop执行完毕时,把这一个runloop期间添加进栈的所有操作包到一起形成一个集合,执行操作时是对这个集合进行操作的.
    举个例子就是redo或undo栈就是一个数组A,然后数组A里面包含多个数组,每个数组里面放的是一个runloop周期内加进来的方法.每次执行undo或redo操作时,操作的是A数组里面的小数组里面的所有操作.当然如果你觉得在一个runloop周期内你的操作不能执行完,比如画板涂鸦绘画的过程很长绝对不是一个runloop能解决的,那么可以使用[undoManager beginUndoGrouping];[undoManager endUndoGrouping];这两个方法,在这两个方法之间所有注册的undo里面的都会放到小数组里面,下次撤销或恢复时都会一起执行的.

    还有部分理解没有写入,夜很深了,睡一觉醒来再接着码;

    将方法注册到undo栈的方式有三种,下面依次介绍

    (1).selector方式
    使用- (void)registerUndoWithTarget:(id)target selector:(SEL)selector object:(nullable id)anObject;方法,上面的代码就是使用的这种方式,这种方法有缺点就是参数只能携带一个.详情看上面的代码.
    (2).block方式
    使用- (void)registerUndoWithTarget:(id)target handler:(void (^)(id target))undoHandle;方法,这种形式就是说把需要进行逆操作的代码放到block块中执行,根据API中的表达可以得知,需要iOS9以后可以使用,方法并没有持有target,但是我们仍需注意循环引用的问题.

    - (void)addTitleWithStr:(NSString *)str{
       
         __weak typeof (self) weakSelf = self;
        [undoManager registerUndoWithTarget:self handler:^(id  _Nonnull target) {
            [weakSelf removeTitle:str];
        }];
        [titleArr addObject:str];    
    
    }
    
    - (void)removeTitle:(NSString *)str{
        
        __weak typeof(self) weakSelf = self;
        [undoManager registerUndoWithTarget:self handler:^(id  _Nonnull target) {
            [weakSelf addTitleWithStr:str];
        }];
        [titleArr removeObject:str];
    
    }
    

    (3).使用NSInvocation和NSUndoManager搭配使用,可以传递多个参数.
    NSInvocation使用详解

    代码传送

    学习最好的方式就是让自己当自己的老师.

    相关文章

      网友评论

        本文标题:NSUndoManager的理解及使用

        本文链接:https://www.haomeiwen.com/subject/mvvefktx.html