源码阅读:Masonry(二)—— MASUtilities/MASLayoutConstraint

该文章阅读的 Masonry 的版本为1.1.0。

这个类做了一些系统上的兼容,以及提供了一些公共方法。

1.系统兼容

#if TARGET_OS_IPHONE || TARGET_OS_TV

    #import <UIKit/UIKit.h>
    #define MAS_VIEW UIView
    #define MAS_VIEW_CONTROLLER UIViewController
    #define MASEdgeInsets UIEdgeInsets

    typedef UILayoutPriority MASLayoutPriority;
    static const MASLayoutPriority MASLayoutPriorityRequired = UILayoutPriorityRequired;
    static const MASLayoutPriority MASLayoutPriorityDefaultHigh = UILayoutPriorityDefaultHigh;
    static const MASLayoutPriority MASLayoutPriorityDefaultMedium = 500;
    static const MASLayoutPriority MASLayoutPriorityDefaultLow = UILayoutPriorityDefaultLow;
    static const MASLayoutPriority MASLayoutPriorityFittingSizeLevel = UILayoutPriorityFittingSizeLevel;

#elif TARGET_OS_MAC

    #import <AppKit/AppKit.h>
    #define MAS_VIEW NSView
    #define MASEdgeInsets NSEdgeInsets

    typedef NSLayoutPriority MASLayoutPriority;
    static const MASLayoutPriority MASLayoutPriorityRequired = NSLayoutPriorityRequired;
    static const MASLayoutPriority MASLayoutPriorityDefaultHigh = NSLayoutPriorityDefaultHigh;
    static const MASLayoutPriority MASLayoutPriorityDragThatCanResizeWindow = NSLayoutPriorityDragThatCanResizeWindow;
    static const MASLayoutPriority MASLayoutPriorityDefaultMedium = 501;
    static const MASLayoutPriority MASLayoutPriorityWindowSizeStayPut = NSLayoutPriorityWindowSizeStayPut;
    static const MASLayoutPriority MASLayoutPriorityDragThatCannotResizeWindow = NSLayoutPriorityDragThatCannotResizeWindow;
    static const MASLayoutPriority MASLayoutPriorityDefaultLow = NSLayoutPriorityDefaultLow;
    static const MASLayoutPriority MASLayoutPriorityFittingSizeCompression = NSLayoutPriorityFittingSizeCompression;

#endif
复制代码

在这部分里,主要对三个方面做了兼容:

  • 不同系统不同头文件的引用。
  • 不同系统上不同类名的统一。
  • 不同系统上不同布局优先级的统一。

2.调试约束冲突

在设置约束的时候难免会出现约束冲突的情况,比如在下面的代码中,我即设置了 greenView 的左右边距,又设置了它的宽度:

UIView *redView = [UIView new];
redView.backgroundColor = [UIColor redColor];
[self.view addSubview:redView];
    
[redView mas_makeConstraints:^(MASConstraintMaker *make) {
    make.size.mas_equalTo(CGSizeMake(200.0, 100.0));
    make.top.equalTo(self.view).offset(200.0);
    make.centerX.equalTo(self.view);
}];
    
    
UIView *greenView = [UIView new];
greenView.backgroundColor = [UIColor greenColor];
[self.view addSubview:greenView];

[greenView mas_makeConstraints:^(MASConstraintMaker *make) {
    make.height.mas_equalTo(200.0);
    make.top.equalTo(redView.mas_bottom).offset(30.0);
    make.left.equalTo(self.view).offset(15.0);
    make.right.equalTo(self.view).offset(-15.0);
    make.width.mas_equalTo(750.0);
}];
复制代码

这时,如果运行的话,控制台就会打印一堆约束冲突的信息:

(
    <MASLayoutConstraint:0x6000000b8ea0 UIView:0x7fb48ed33dc0.left == UIView:0x7fb48ec03fd0.left + 15>,
    <MASLayoutConstraint:0x6000000b8f60 UIView:0x7fb48ed33dc0.right == UIView:0x7fb48ec03fd0.right - 15>,
    <MASLayoutConstraint:0x6000000b9080 UIView:0x7fb48ed33dc0.width == 750>,
    <NSLayoutConstraint:0x6040002819f0 UIView:0x7fb48ec03fd0.width == 375>
)
复制代码

虽然,信息中很明确的告诉你那些约束冲突了,但是 UIView:0x7fb48ed33dc0 是哪个视图,UIView:0x7fb48ec03fd0 又是哪个视图?一脸懵逼是不是?约束少的时候还能好找一些,一旦视图复杂起来,就很难查找了。

这个时候,Masonry 提供的这个宏就派上用场了:

#define MASAttachKeys(...)                                                        \
    {                                                                             \
        NSDictionary *keyPairs = NSDictionaryOfVariableBindings(__VA_ARGS__);     \
        for (id key in keyPairs.allKeys) {                                        \
            id obj = keyPairs[key];                                               \
            NSAssert([obj respondsToSelector:@selector(setMas_key:)],             \
                     @"Cannot attach mas_key to %@", obj);                        \
            [obj setMas_key:key];                                                 \
        }                                                                         \
    }
复制代码

在使用时,你只需要添加上这么一句代码:

MASAttachKeys(redView, greenView, self.view);
复制代码

再次运行,你就会发现控制带打印的内容变化了:

(
    <MASLayoutConstraint:0x6000000b0380 UIView:greenView.left == UIView:self.view.left + 15>,
    <MASLayoutConstraint:0x6000000b0440 UIView:greenView.right == UIView:self.view.right - 15>,
    <MASLayoutConstraint:0x6000000b0560 UIView:greenView.width == 750>,
    <NSLayoutConstraint:0x60000028b1d0 UIView:self.view.width == 375>
)
复制代码

原来的 UIView:0x7fb48ed33dc0 变成了 greenView, 而 UIView:0x7fb48ec03fd0 变成了 self.view,这样是不是就一目了然啊,再进行调试就方便了很多。

其实这个宏只做了两件事:一件是对系统提供的宏 NSDictionaryOfVariableBindings(...) 做了封装;另一件是利用 Masonry 提供的分类 View+MASAdditions 将用户设置的 key (例如:redView) 保存。

3.对象的哈希值

#define MAS_NSUINT_BIT (CHAR_BIT * sizeof(NSUInteger))
#define MAS_NSUINTROTATE(val, howmuch) ((((NSUInteger)val) << howmuch) | (((NSUInteger)val) >> (MAS_NSUINT_BIT - howmuch)))
复制代码

一眼看过去,全是不认的宏和函数,但是我们一点点的看:

  • CHAR_BIT:这个宏代表一个 char 对象所占的位数,因为一个 char 就是一个字节(byte),可也说它代表了一个字节所占的位数,一般是8位。
  • sizeof():sizeofC/C++ 中的一个操作符(operator),简单的说其作用就是返回一个对象或者类型所占的内存字节数。

到这,我们第一个宏就能看懂了:它计算的是当前设备上 NSUInteger 类型在内存中占用的位数。

看懂了第一个宏,第二个宏也就看懂了:将 val 按照 howmuch 值进行左右反转。例如:val 如果是 0110 0010howmuch4。计算就是 0010 0000 | 0000 0110,结果为 0010 0110

这两个宏的作用是:在自定义类 MASViewAttribute 中生成 hash 值。

4.数据装箱

当我们在使用 Masonry 愉快的布局时,经常会用到以下写法:

make.height.mas_equalTo(200.0);
复制代码
make.size.mas_equalTo(CGSizeMake(160.0, 90.0));
复制代码
make.edges.mas_equalTo(UIEdgeInsetsMake(0, 10, 20, 30));
复制代码

我们发现 mas_equalTo() 这个宏对参数并不挑剔,几乎是传啥数据类型都可以,这就要归功于下面我们将要看的内容了。


#define MASBoxValue(value) _MASBoxValue(@encode(__typeof__((value))), (value))
复制代码

首先从宏开始看起:MASBoxValue(value) 只需要传递一个参数,在宏内,获取了传入的 value 的 OC 内部类型字符串,然后和 value 一起传给了 _MASBoxValue 函数。


static inline id _MASBoxValue(const char *type, ...) {
    va_list v;
    va_start(v, type);
    id obj = nil;
    if (strcmp(type, @encode(id)) == 0) {
        id actual = va_arg(v, id);
        obj = actual;
    } else if (strcmp(type, @encode(CGPoint)) == 0) {
        CGPoint actual = (CGPoint)va_arg(v, CGPoint);
        obj = [NSValue value:&actual withObjCType:type];
    } else if (strcmp(type, @encode(CGSize)) == 0) {
        CGSize actual = (CGSize)va_arg(v, CGSize);
        obj = [NSValue value:&actual withObjCType:type];
    } else if (strcmp(type, @encode(MASEdgeInsets)) == 0) {
        MASEdgeInsets actual = (MASEdgeInsets)va_arg(v, MASEdgeInsets);
        obj = [NSValue value:&actual withObjCType:type];
    } else if (strcmp(type, @encode(double)) == 0) {
        double actual = (double)va_arg(v, double);
        obj = [NSNumber numberWithDouble:actual];
    } else if (strcmp(type, @encode(float)) == 0) {
        float actual = (float)va_arg(v, double);
        obj = [NSNumber numberWithFloat:actual];
    } else if (strcmp(type, @encode(int)) == 0) {
        int actual = (int)va_arg(v, int);
        obj = [NSNumber numberWithInt:actual];
    } else if (strcmp(type, @encode(long)) == 0) {
        long actual = (long)va_arg(v, long);
        obj = [NSNumber numberWithLong:actual];
    } else if (strcmp(type, @encode(long long)) == 0) {
        long long actual = (long long)va_arg(v, long long);
        obj = [NSNumber numberWithLongLong:actual];
    } else if (strcmp(type, @encode(short)) == 0) {
        short actual = (short)va_arg(v, int);
        obj = [NSNumber numberWithShort:actual];
    } else if (strcmp(type, @encode(char)) == 0) {
        char actual = (char)va_arg(v, int);
        obj = [NSNumber numberWithChar:actual];
    } else if (strcmp(type, @encode(bool)) == 0) {
        bool actual = (bool)va_arg(v, int);
        obj = [NSNumber numberWithBool:actual];
    } else if (strcmp(type, @encode(unsigned char)) == 0) {
        unsigned char actual = (unsigned char)va_arg(v, unsigned int);
        obj = [NSNumber numberWithUnsignedChar:actual];
    } else if (strcmp(type, @encode(unsigned int)) == 0) {
        unsigned int actual = (unsigned int)va_arg(v, unsigned int);
        obj = [NSNumber numberWithUnsignedInt:actual];
    } else if (strcmp(type, @encode(unsigned long)) == 0) {
        unsigned long actual = (unsigned long)va_arg(v, unsigned long);
        obj = [NSNumber numberWithUnsignedLong:actual];
    } else if (strcmp(type, @encode(unsigned long long)) == 0) {
        unsigned long long actual = (unsigned long long)va_arg(v, unsigned long long);
        obj = [NSNumber numberWithUnsignedLongLong:actual];
    } else if (strcmp(type, @encode(unsigned short)) == 0) {
        unsigned short actual = (unsigned short)va_arg(v, unsigned int);
        obj = [NSNumber numberWithUnsignedShort:actual];
    }
    va_end(v);
    return obj;
}
复制代码

这个函数的实现逻辑很简单:就是根据传入参数类型的不同,装箱成 NSValueNSNumber 类型的对象。

其中有一点需要注意的是:可变参数的实现。

我们平常在写代码的过程中,经常会接触到可变参数,最常用的莫过于 NSLog(),在使用时,我们可以传递很多参数。比方说下面的代码中,我们就传递了 4 个参数:

NSLog(@"%@%@%@", @"1", @"2", @"3");
复制代码

那我们想要自己实现可变参数的方法应该怎么做?这就要用到 C 语言提供的 API 了:va_listva_startva_argva_end

我们来写两个方法玩耍一下:

  1. 先写一个能打印所有传入参数的方法:
- (void)print:(NSString *)string, ... {
    
    if (!string) {
        return;
    }
    
    NSLog(@"%@", string);
    
    va_list args;
    va_start(args, string);
    
    NSString *printString;
    
    while ((printString = va_arg(args, NSString *))) {
        NSLog(@"%@", printString);
    }
    
    va_end(args);
}
复制代码

使用方法:

[self print:@"1", @"2", @"3", @"4", @"5", @"6", @"7", nil];
复制代码

打印结果:

2018-08-07 17:47:26.312814+0800 WTMasonryDemo[11100:994417] 1
2018-08-07 17:47:26.312972+0800 WTMasonryDemo[11100:994417] 2
2018-08-07 17:47:26.313080+0800 WTMasonryDemo[11100:994417] 3
2018-08-07 17:47:26.313170+0800 WTMasonryDemo[11100:994417] 4
2018-08-07 17:47:26.313255+0800 WTMasonryDemo[11100:994417] 5
2018-08-07 17:47:26.313354+0800 WTMasonryDemo[11100:994417] 6
2018-08-07 17:47:26.313460+0800 WTMasonryDemo[11100:994417] 7
复制代码
  1. 再写一个类似于 NSLog() 函数的方法:
- (void)log:(NSString *)string, ... {
    
    if (!string) {
        return;
    }
    
    va_list args;
    va_start(args, string);
    
    NSString *logString = [[NSString alloc] initWithFormat:string arguments:args];
    NSLog(@"%@", logString);
    
    va_end(args);
}
复制代码

使用方法:

[self log:@"123%@%@%@", @"4", @"5", @"6"];
复制代码

打印结果:

2018-08-07 17:50:23.797442+0800 WTMasonryDemo[11139:999893] 123456
复制代码

5.MASLayoutConstraint

这个类是 NSLayoutConstraint 的子类,它的作用是当我们使用上面提到的宏 MASAttachKeys 调试约束冲突时,通过在控制台打印出的内容,更容易区分那些约束是通过 Masonry 设置的。

该类只添加了一个属性:

@property (nonatomic, strong) id mas_key;
复制代码

这个属性是由于保存标识该约束对象的 key。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值