CGRectContainsPoint 用法

CGRectContainsPoint 用法

UILongPressGestureRecognizer -> longPressed
长按手势重心 判断是否被view 包含

CGPoint point = [longPressed locationInView:self]; // 计算相对坐标(触摸手势的重心)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
判断给定的点是否被一个CGRect包含,可以用CGRectContainsPoint函数
BOOL contains = CGRectContainsPoint(CGRect rect, CGPoint point);
判断一个CGRect是否包含再另一个CGRect里面,常用与测试给定的对象之间是否又重叠
BOOL contains = CGRectContainsRect(CGRect rect1, CGRect rect2);
判断两个结构体是否有交错.可以用CGRectIntersectsRect
BOOL contains = CGRectIntersectsRect(CGRect rect1, CGRect rect2);
float float_ = CGRectGetMaxX(CGRect rect);返回矩形右边缘的坐标
CGRectGetMaxY返回矩形顶部的坐标
CGRectGetMidX返回矩形中心X的坐标
CGRectGetMidY返回矩形中心Y的坐标
CGRectGetMinX返回矩形左边缘的坐标
CGRectGetMinY返回矩形底部的坐标
CGRectContainsPoint 看参数说明,一个点是否包含在矩形中,所以参数为一个点一个矩形

IOS 常用正则

在开发过程中,有时需要对用户输入的类型做判断,最常见是在注册页面即用户名和密码,直接上代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
#pragma - mark 只能为中文
-(BOOL)onlyInputChineseCharacters:(NSString*)string{
NSString *inputString = @"[\u4e00-\u9fa5]+";
NSPredicate *predicate = [NSPredicate predicateWithFormat:@"SELF MATCHES %@",inputString];
BOOL inputString = [predicate evaluateWithObject:string];
return inputString;
}
#pragma - mark 只能为数字
- (BOOL)onlyInputTheNumber:(NSString*)string{
NSString *numString =@"[0-9]*";
NSPredicate *predicate = [NSPredicate predicateWithFormat:@"SELF MATCHES %@",numString];
BOOL inputString = [predicate evaluateWithObject:string];
return inputString;
}
#pragma - mark 只能为小写
- (BOOL)onlyInputLowercaseLetter:(NSString*)string{
NSString *regex =@"[a-z]*";
NSPredicate *predicate = [NSPredicate predicateWithFormat:@"SELF MATCHES %@",regex];
BOOL inputString = [predicate evaluateWithObject:string];
return inputString;
}
#pragma - mark 只能为大写
- (BOOL)onlyInputACapital:(NSString*)string{
NSString *regex =@"[A-Z]*";
NSPredicate *predicate = [NSPredicate predicateWithFormat:@"SELF MATCHES %@",regex];
BOOL inputString = [predicate evaluateWithObject:string];
return inputString;
}
#pragma - mark 允许大小写
- (BOOL)InputCapitalAndLowercaseLetter:(NSString*)string{
NSString *regex =@"[a-zA-Z]*";
NSPredicate *predicate = [NSPredicate predicateWithFormat:@"SELF MATCHES %@",regex];
BOOL inputString = [predicate evaluateWithObject:string];
return inputString;
}
#pragma - mark 允许含大小写或数字(不限字数)
- (BOOL)inputLettersOrNumbers:(NSString*)string{
NSString *regex =@"[a-zA-Z0-9]*";
NSPredicate *predicate = [NSPredicate predicateWithFormat:@"SELF MATCHES %@",regex];
BOOL inputString = [predicate evaluateWithObject:string];
return inputString;
}
#pragma - mark 允许含大小写或数字(限字数)
-(BOOL)inputNumberOrLetters:(NSString*)name {
NSString *userNameRegex = @"^[A-Za-z0-9]{6,20}+$";
NSPredicate *userNamePredicate = [NSPredicate predicateWithFormat:@"SELF MATCHES %@",userNameRegex];
BOOL inputString = [userNamePredicate evaluateWithObject:name];
return inputString;
}
#pragma - mark 允许汉字或数字(不限字数)
- (BOOL)inputChineseOrNumbers:(NSString*)string{
NSString *regex =@"[\u4e00-\u9fa5]+[0-9]*";
NSPredicate *predicate = [NSPredicate predicateWithFormat:@"SELF MATCHES %@",regex];
BOOL inputString = [predicate evaluateWithObject:string];
return inputString;
}
#pragma - mark 允许汉字或数字(限字数)
- (BOOL)inputChineseOrNumbersLimit:(NSString*)string{
NSString *regex =@"[\u4e00-\u9fa5][0-9]{6,20}+$";
NSPredicate *predicate = [NSPredicate predicateWithFormat:@"SELF MATCHES %@",regex];
BOOL inputString = [predicate evaluateWithObject:string];
return inputString;
}
#pragma - mark 允许汉字,大小写或数字(不限字数)
- (BOOL)inputChineseOrLettersAndNumbersNum:(NSString*)string{
NSString *regex =@"[\u4e00-\u9fa5]+[A-Za-z0-9]*";
NSPredicate *predicate = [NSPredicate predicateWithFormat:@"SELF MATCHES %@",regex];
BOOL inputString = [predicate evaluateWithObject:string];
return inputString;
}
#pragma - mark 允许汉字,大小写或数字(限字数)
- (BOOL)inputChineseOrLettersNumberslimit:(NSString*)string{
NSString *regex =@"[\u4e00-\u9fa5]+[A-Za-z0-9]{6,20}+$";
NSPredicate *predicate = [NSPredicate predicateWithFormat:@"SELF MATCHES %@",regex];
BOOL inputString = [predicate evaluateWithObject:string];
return inputString;
}

转:http://blog.csdn.net/h643342713/article/details/53966446

Masonry 使用方法

一个约束优先级解说
http://www.cnblogs.com/siasyl/p/6775055.html


1
2
3
4
5
6
7
8
9
10
11
如果想要约束变换之后实现动画效果,则需要执行如下操作
// 通知需要更新约束,但是不立即执行
[self setNeedsUpdateConstraints];
// 立即更新约束,以执行动态变换
// update constraints now so we can animate the change
[self updateConstraintsIfNeeded];
// 执行动画效果, 设置动画时间
[UIView animateWithDuration:0.4 animations:^{
[self layoutIfNeeded];
}];


Masonry 动画 更新或者重置动画
然后用UIView 动画

1
2
3
4
5
6
7
UIView.animate(withDuration: 0.5, animations: {
self.controlBar.layoutIfNeeded()
self.topbar.layoutIfNeeded()
}) { (bool) in
self.topbar.isHidden = isHidden
self.controlBar.isHidden = isHidden
}

Masonry的更新约束 mas_updateConstraints 更新约束 但是约束的相对实例不能变
mas_makeConstraints 删除以前的约束用新的约束


要求:

当键盘挡住输入框时,输入框自动向上弹到键盘上方。

实现:

这里需要使用到Masonry的另外一个方法mas_updateConstraints。这个方法用于更新控件约束。

具体的实现方式可以下载Demo来看,这里只贴出键盘弹出时的处理代码:

swift

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
NotificationCenter.default.addObserver(self, selector: #selector(self.keyboardChangeFrame(_:)), name: NSNotification.Name.UIKeyboardWillChangeFrame, object: nil)
NotificationCenter.default.addObserver(self, selector: #selector(self.keyboardHid(_:)), name: NSNotification.Name.UIKeyboardWillHide, object: nil)
}
func keyboardChangeFrame(_ sender:Notification){
var keyHeight:CGFloat = 246
var info = (sender as NSNotification).userInfo //as? NSDictionary
let keyboardSize = (info?[UIKeyboardFrameBeginUserInfoKey] as AnyObject).cgRectValue //UIKeyboardFrameEndUserInfoKey UIKeyboardFrameBeginUserInfoKey
let keyboardDuration = (info?[UIKeyboardAnimationDurationUserInfoKey] as AnyObject).doubleValue ?? 0
if keyboardSize != nil{
keyHeight = (keyboardSize!.height)
}
self.searchAssociative.mas_updateConstraints { (make) in
make?.bottom.mas_equalTo()(self.view)?.offset()(-keyHeight)
}
UIView.animate(withDuration: keyboardDuration) {
self.view.layoutIfNeeded()
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
- (void)keyboardWillChangeFrameNotification:(NSNotification *)notification {
// 获取键盘基本信息(动画时长与键盘高度)
NSDictionary *userInfo = [notification userInfo];
CGRect rect = [userInfo[UIKeyboardFrameBeginUserInfoKey] CGRectValue];
CGFloat keyboardHeight = CGRectGetHeight(rect);
CGFloat keyboardDuration = [userInfo[UIKeyboardAnimationDurationUserInfoKey] doubleValue];
// 修改下边距约束
[_textField mas_updateConstraints:^(MASConstraintMaker *make) {
make.bottom.mas_equalTo(-keyboardHeight);
}];
// 更新约束
[UIView animateWithDuration:keyboardDuration animations:^{
[self.view layoutIfNeeded];
}];
}

由于业务需要,有时候需要动态加载Cell,考虑一些方案后采取把Cell高度设置为0.01的方法。但是约束为-10 的话会有错误信息(据说ios7 会崩溃) ,解决方法是降低优先级

1
2
3
4
5
self.titleLabel.mas_makeConstraints({ (make) in
make?.top.mas_equalTo()(self.imageView.mas_bottom)?.offset()(interval_W_10)?.priority()(499)
make?.left.right().mas_equalTo()(self.imageView)
make?.bottom.mas_equalTo()(self)?.offset()(-interval_W_10)?.priority()(499)
})

From: http://www.jianshu.com/p/a24dd8638d28

个人喜欢用纯代码写东西,其中用到最多的就是Masonry,我整理一些使用过程中一些点,方便以后使用.(基本的语法就不说了)

首先说几点:

  1. 我一般将数值类型的约束用mas_equalTo,而相对于某个控件,或者某个控件的某个约束,我会使用equalTo,如:
    make.size.mas_equalTo(CGSizeMake(100, 100));
    make.center.equalTo(weakSelf.view);
  2. setNeedsLayout:告知页面需要更新,但是不会立刻开始更新。执行后会立刻调用layoutSubviews。
    layoutIfNeeded:告知页面布局立刻更新。所以一般都会和setNeedsLayout一起使用。如果希望立刻生成新的frame需要调用此方法,利用这点一般布局动画可以在更新布局后直接使用这个方法让动画生效。
    layoutSubviews:系统重写布局
    setNeedsUpdateConstraints:告知需要更新约束,但是不会立刻开始
    updateConstraintsIfNeeded:告知立刻更新约束
    updateConstraints:系统更新约束
  3. - (void)updateViewConstraints ViewController的View在更新视图布局时,会先调用ViewController的updateViewConstraints 方法。我们可以通过重写这个方法去更新当前View的内部布局,而不用再继承这个View去重写-updateConstraints方法。我们在重写这个方法时,务必要调用 super 或者 调用当前View的 -updateConstraints 方法。

    // 防止block中的循环引用
    __weak typeof(self) weakSelf = self;
    UIView view = [UIView new];
    view.backgroundColor = [UIColor brownColor];
    [self.view addSubview:view];
    //使用mas_makeConstraints添加约束
    [view mas_makeConstraints:^(MASConstraintMaker
    make) {

    // 添加大小约束(make就是要添加约束的控件view)
    make.size.mas_equalTo(CGSizeMake(200, 200));

    // 添加居中约束(居中方式与self相同)
    make.center.equalTo(weakSelf.view);
    }];

    UIView* blackView = [UIView new];
    blackView.backgroundColor = [UIColor blackColor];
    [self.view addSubview:blackView];

    [blackView mas_makeConstraints:^(MASConstraintMaker *make) {
    //添加约束大小
    make.size.mas_equalTo(CGSizeMake(100, 100));
    //在 左,上 添加约束 (左、上约束都是20)
    make.left.and.top.mas_equalTo(20);
    }];

    UIView* grayView = [UIView new];
    grayView.backgroundColor = [UIColor lightGrayColor];
    [self.view addSubview:grayView];

    [grayView mas_makeConstraints:^(MASConstraintMaker *make) {
    // 大小、上边距约束与黑色view相同
    make.size.and.top.equalTo(blackView);
    // 添加右边距约束(这里的间距是有方向性的,左、上边距约束为正数,右、下边距约束为负数)
    make.right.mas_equalTo(-20);
    }];

    • (void)dealloc {
      [[NSNotificationCenter defaultCenter] removeObserver:self];
      }

    • (void)viewDidLoad {
      [super viewDidLoad];
      // Do any additional setup after loading the view.
      __weak typeof(self) weakSelf = self;
      _textField = [UITextField new];
      _textField.backgroundColor = [UIColor redColor];
      [self.view addSubview:_textField];

      [_textField mas_makeConstraints:^(MASConstraintMaker *make) {
      //left,right,centerx,y 不能共存只能有其二
      make.left.mas_equalTo(20);
      // make.right.mas_equalTo(-60);
      make.centerX.equalTo(weakSelf.view);
      make.height.mas_equalTo(40);
      make.bottom.mas_equalTo(0);
      }];

      // 注册键盘通知
      [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(keyboardWillChangeFrameNotification:) name:UIKeyboardWillChangeFrameNotification object:nil];
      [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(keyboardWillHideNotification:) name:UIKeyboardWillHideNotification object:nil];
      }

    • (void)keyboardWillChangeFrameNotification:(NSNotification *)notification {

      // 获取键盘基本信息(动画时长与键盘高度)
      NSDictionary userInfo = [notification userInfo];
      CGRect rect = [userInfo[UIKeyboardFrameBeginUserInfoKey] CGRectValue];
      CGFloat keyboardHeight = CGRectGetHeight(rect);
      CGFloat keyboardDuration = [userInfo[UIKeyboardAnimationDurationUserInfoKey] doubleValue];
      // 修改下边距约束
      [_textField mas_updateConstraints:^(MASConstraintMaker
      make) {

      make.bottom.mas_equalTo(-keyboardHeight);
      

      }];

      // 更新约束
      [UIView animateWithDuration:keyboardDuration animations:^{
      [self.view layoutIfNeeded];
      }];
      }

    • (void)keyboardWillHideNotification:(NSNotification *)notification {

      // 获得键盘动画时长
      NSDictionary *userInfo = [notification userInfo];
      CGFloat keyboardDuration = [userInfo[UIKeyboardAnimationDurationUserInfoKey] doubleValue];

      // 修改为以前的约束(距下边距0)
      [_textField mas_updateConstraints:^(MASConstraintMaker *make) {

      make.bottom.mas_equalTo(0);
      

      }];

      // 更新约束
      [UIView animateWithDuration:keyboardDuration animations:^{

      [self.view layoutIfNeeded];
      

      }];
      }

    • (void)touchesBegan:(NSSet )touches withEvent:(UIEvent )event {
      [super touchesBegan:touches withEvent:event];
      [self.view endEditing:YES];
      }

方法一:

array 的 mas_distributeViewsAlongAxis withFixedSpacing 变化的是控件 长度或宽度

定义一个存放三个控件的数组NSArray *array;
array = @[greenView,redView,blueView];

注意:

数组里面的元素不能小于2个,要不会报错 views to distribute need to bigger than one

直接调用下面的方法:

- (void)getHorizontalone
{
//方法一,array 的 mas_distributeViewsAlongAxis
/**
 *  多个控件固定间隔的等间隔排列,变化的是控件的长度或者宽度值
 *
 *  @param axisType        轴线方向
 *  @param fixedSpacing    间隔大小
 *  @param leadSpacing     头部间隔
 *  @param tailSpacing     尾部间隔
 */
//    MASAxisTypeHorizontal  水平
//    MASAxisTypeVertical    垂直

[arrayList mas_distributeViewsAlongAxis:MASAxisTypeHorizontal
                       withFixedSpacing:20
                            leadSpacing:5
                            tailSpacing:5];
[arrayList mas_makeConstraints:^(MASConstraintMaker *make) {
    make.top.mas_equalTo(60);
    make.height.mas_equalTo(100);
}];

}

方法二:

array de mas_distributeViewsAlongAxis withFixedItemLength 控件size不变,变化的是间隙

- (void)getVertical
{
/**
 *  多个固定大小的控件的等间隔排列,变化的是间隔的空隙
 *
 *  @param axisType        轴线方向
 *  @param fixedItemLength 每个控件的固定长度或者宽度值
 *  @param leadSpacing     头部间隔
 *  @param tailSpacing     尾部间隔
 */
[arrayList mas_distributeViewsAlongAxis:MASAxisTypeVertical
                    withFixedItemLength:60
                            leadSpacing:40
                            tailSpacing:10];
[arrayList mas_makeConstraints:^(MASConstraintMaker *make) {
    //        make.top.mas_equalTo(100);
    //        make.height.mas_equalTo(100);
    make.left.mas_equalTo(20);
    make.right.mas_equalTo(-20);
}];

}

以上俩方法都在NSArray+MASAdditions

方法三:直接设置multiplier实现等间距

 for (NSUInteger i = 0; i < 4; i++) {
    UIView *itemView = [self getItemViewWithIndex:i];
    [_containerView addSubview:itemView];

    [itemView mas_makeConstraints:^(MASConstraintMaker *make) {
        make.width.and.height.equalTo(@(ITEM_SIZE));
        make.centerY.equalTo(_containerView.mas_centerY);
        make.centerX.equalTo(_containerView.mas_right).multipliedBy(((CGFloat)i + 1) / ((CGFloat)ITEM_COUNT + 1));
    }];
}

方法四: 利用透明等宽度的SpaceView实现等间距

  UIView *lastSpaceView       = [UIView new];
lastSpaceView.backgroundColor = [UIColor greenColor];
[_containerView1 addSubview:lastSpaceView];

[lastSpaceView mas_makeConstraints:^(MASConstraintMaker *make) {
    make.left.and.top.and.bottom.equalTo(_containerView1);
}];

for (NSUInteger i = 0; i < ITEM_COUNT; i++) {
    UIView *itemView = [self getItemViewWithIndex:i];
    [_containerView1 addSubview:itemView];

    [itemView mas_makeConstraints:^(MASConstraintMaker *make) {
        make.height.and.width.equalTo(@(ITEM_SIZE));
        make.left.equalTo(lastSpaceView.mas_right);
        make.centerY.equalTo(_containerView1.mas_centerY);
    }];

    UIView *spaceView         = [UIView new];
    spaceView.backgroundColor = [UIColor greenColor];
    [_containerView1 addSubview:spaceView];

    [spaceView mas_makeConstraints:^(MASConstraintMaker *make) {
        make.left.equalTo(itemView.mas_right).with.priorityHigh(); // 降低优先级,防止宽度不够出现约束冲突
        make.top.and.bottom.equalTo(_containerView1);
        make.width.equalTo(lastSpaceView.mas_width);
    }];

    lastSpaceView = spaceView;
}

[lastSpaceView mas_makeConstraints:^(MASConstraintMaker *make) {
    make.right.equalTo(_containerView1.mas_right);
}];

和面方法4一样,利用spaceView来实现

  UIView* bgView       = [[UIView alloc]init];
bgView.backgroundColor = [UIColor yellowColor];
[self.view addSubview:bgView];

[bgView mas_makeConstraints:^(MASConstraintMaker *make) {
    make.left.and.right.mas_equalTo(0);
    make.top.mas_equalTo(@100);
    make.height.mas_equalTo(@100);
}];

listText = @[@"北京",@"地大吴波啊",@"你大爷",@"我们的爱哎哎"];
UIView *lastSpaceView = nil;
for(int i = 0 ; i < listText.count;  i ++)
{
    UILabel* label = [UILabel new];
    label.text     = listText[i];
    label.backgroundColor = RANDOMCOLOR;
    [bgView addSubview:label];

    UIView* lineView         = [UIView new];
    lineView.backgroundColor = [UIColor redColor];
    [bgView addSubview:lineView];

    [label mas_makeConstraints:^(MASConstraintMaker *make) {
        make.top.bottom.mas_equalTo(0);
        if (lastSpaceView)
        {
            NSLog(@"存在 lastView");
            make.left.equalTo(lastSpaceView.mas_right).mas_offset(@20);
        }else
        {
            NSLog(@"不存在存在 lastView");
            make.left.equalTo(bgView.mas_left);
        }
        make.height.equalTo(bgView);
    }];

    lastSpaceView = label;

    [lineView mas_makeConstraints:^(MASConstraintMaker *make) {
        make.top.and.bottom.mas_equalTo(0);
        make.width.mas_equalTo(1);
        make.left.mas_equalTo(label.mas_right).mas_offset(@10);
    }];
}

UIView* bgView = [UIView new];
bgView.backgroundColor = [UIColor purpleColor];
[self.view addSubview:bgView];

UILabel* titleLab        = [UILabel new];
titleLab.backgroundColor = [UIColor redColor];
titleLab.textAlignment   = NSTextAlignmentCenter;
titleLab.font            = [UIFont systemFontOfSize:15.f];
titleLab.text            = @"曹操——《短歌行》";
[bgView addSubview:titleLab];

UILabel* contentLab        = [UILabel new];
contentLab.numberOfLines   = 0 ;
contentLab.textAlignment   = NSTextAlignmentCenter;
contentLab.backgroundColor = [UIColor brownColor];
contentLab.font            = [UIFont systemFontOfSize:13.f];
contentLab.text            = @" 对酒当歌,人生几何? 譬如朝露,去日苦多。\n 慨当以慷,忧思难忘。 何以解忧?唯有杜康。\n 青青子衿,悠悠我心。 但为君故,沉吟至今。\n 呦呦鹿鸣,食野之苹。 我有嘉宾,鼓瑟吹笙。\n 明明如月,何时可掇? 忧从中来,不可断绝。\n 越陌度阡,枉用相存。 契阔谈宴,心念旧恩。\n 月明星稀,乌鹊南飞。 绕树三匝,何枝可依?\n 山不厌高,海不厌深。 周公吐哺,天下归心。";

[bgView addSubview:contentLab];
//思路: 父视图的上间距等于title的上间距,父视图的下间距等于content的下间距
__weak typeof(self) weakSelf = self;
[bgView mas_makeConstraints:^(MASConstraintMaker *make) {
    make.left.mas_offset(@30);
    make.right.mas_offset(@-30);
    make.centerY.equalTo(weakSelf.view);
}];

[titleLab mas_makeConstraints:^(MASConstraintMaker *make) {
    make.left.top.right.mas_equalTo(@0);
}];

[contentLab mas_makeConstraints:^(MASConstraintMaker *make) {
    make.left.right.mas_equalTo(@0);
    make.top.equalTo(titleLab.mas_bottom).mas_offset(@10);
    make.bottom.equalTo(bgView);
}];
以后慢慢更新,记录方便以后使用

文/栋飞

//一些扒的别人的记录

自适应布局允许将宽度或高度设置为固定值.如果你想要给视图一个最小或最大值,你可以这样:

//width >= 200 && width <= 400 make.width.greaterThanOrEqualTo(@200); make.width.lessThanOrEqualTo(@400)

约束的优先级

.priority允许你指定一个精确的优先级,数值越大优先级越高.最高1000.
.priorityHigh等价于 UILayoutPriorityDefaultHigh .优先级值为 750.
.priorityMedium介于高优先级和低优先级之间,优先级值在 250~750之间.
.priorityLow等价于 UILayoutPriorityDefaultLow , 优先级值为 250.

优先级可以在约束的尾部添加:

make.left.greaterThanOrEqualTo(label.mas_left).with.priorityLow();
make.top.equalTo(label.mas_top).with.priority(600);

center 中心

//使 centerX和 centerY = button1
make.center.equalTo(button1)

//使 centerX = superview.centerX - 5, centerY = superview.centerY + 10 make.center.equalTo(superview).centerOffset(CGPointMake(-5, 10))

指定宽度为父视图的 1/4.

make.width.equalTo(superview).multipliedBy(0.25);


Masonry框架初次使用遇到的坑 frame为0
From: http://www.jianshu.com/p/ad9c075a7547

实现自适应布局的一个非常方便的方法就是使用Masonry框架,然而使用Masonry布局的时候,并不能立刻反应到frame的改变上,比如:

UIView *parent = [[UIView alloc] initWithFrame:CGRectMake(0, 0, 100, 100)];
UIImageView *child = [UIView alloc] init];
[parent addSubview:child];
[child mas_makeConstraints:^(MASConstraintMaker *make) {
        make.size.mas_equalTo(CGSizeMake(20,20));
        make.top.left.mas_equalTo(50);
    }];
NSLog(@"%@",redView);

打印结果:
** <UIImageView: 0x7fb222605550; frame = (0 0; 0 0); layer = <CALayer: 0x7fb22260b3a0>>**

可以发现,虽然使用Masonry进行布局和约束,但是子视图childframe仍然为(0, 0, 0 ,0).

而这时候如果有需求要设置child的形状为圆形,就得知道它的frame,像下面这样写肯定不会设置成功的:

[child mas_makeConstraints:^(MASConstraintMaker *make) {
        make.size.mas_equalTo(CGSizeMake(20,20));
        make.top.left.mas_equalTo(50);
    }];
 child.layer.cornerRadius = child.bounds.size.width/2;
 child.layer.masksToBounds = YES; //设置头像为圆形

因为这时候的frame还是0。我想会不会是因为block中的处理是放在另一个线程中异步进行的,block还没执行完就已经走到了下面使用frame的代码,(一阵狂喜,好聪明。。。),所以马上把代码改写:

[child mas_makeConstraints:^(MASConstraintMaker *make) {
        make.size.mas_equalTo(CGSizeMake(20,20));
        make.top.left.mas_equalTo(50);
        child.layer.cornerRadius = child.bounds.size.width/2;
        child.layer.masksToBounds = YES; //设置头像为圆形
    }];

然而并没有什么卵用。。。

没办法只能问谷歌了,然后找到了Masonry约束下获取frame的方法:

使用masonry的实质还是调用了ios7以后的autolayout,如果要更新frame,需要调用layoutIfNeeded函数进行布局,然后所约束的控件才会按照约束条件,生成当前布局相应的framebounds。这样就可以利用这两个属性来进行图片圆角剪裁。而调用layoutIfNeeded的目的是让系统调用layoutSubviews方法,我们也可以直接在这个方法里获取frame,因为这时候开始layout subviews,Masonry已经计算出了真实的frame。

下面附上关于autolayout更新几个方法的区别:

setNeedsLayout:告知页面需要更新,但是不会立刻开始更新。执行后会立刻调用layoutSubviews。

layoutIfNeeded:告知页面布局立刻更新。所以一般都会和setNeedsLayout一起使用。如果希望立刻生成新的frame需要调用此方法,利用这点,动画可以在更新布局后直接使用这个方法让动画生效。

layoutSubviews:系统重写布局

setNeedsUpdateConstraints:告知需要更新约束,但是不会立刻开始

updateConstraintsIfNeeded:告知立刻更新约束

updateConstraints:系统更新约束


From: http://www.cnblogs.com/gfxxbk/p/5827301.html
1、Masonry概述

  • 目前最流行的Autolayout第三方框架

  用优雅的代码方式编写Autolayout

  省去了苹果官方恶心的Autolayout代码

  大大提高了开发效率

2、常用方法

  • 这个方法只会添加新的约束

    1
    2
    3
    [blueView mas_makeConstraints:^(MASConstraintMaker *make) {
    }];
  • 这个方法会将以前的所有约束删掉,添加新的约束

    1
    2
    3
    [blueView mas_remakeConstraints:^(MASConstraintMaker *make) {
    }];
  • 这个方法将会覆盖以前的某些特定的约束

    1
    2
    3
    4
    [blueView mas_updateConstraints:^(MASConstraintMaker *make) {
    }];

3、约束类型

  • 尺寸:

  width(宽)\height(高)\size(大小)

// 宽度约束
make.width.mas_equalTo(100);
// 高度约束
make.height.mas_equalTo(100);

//  大小约束(与上面两句等价)
make.size.mas_equalTo(CGSizeMake(100, 100));
  • 边界:

  left\leading(左边界)\right\trailing(右边界)\top(顶部边界)\bottom(底部边界)  

// 左边(leading类似)
 make.left.mas_equalTo(self.view).offset(50); 
// 右边(trailing类似)
 make.right.equalTo(self.view).offset(-20);
 // 顶部
 make.top.equalTo(self.view).offset(20);
 // 底部
 make.bottom.mas_equalTo(self.view).offset(-50);
  • 中心点:

  center\centerX\centerY

    // 居中(水平+垂直)
    // 尺寸是父控件的一半
    [blueView mas_makeConstraints:^(MASConstraintMaker *make) {
        make.size.mas_equalTo(self.view).multipliedBy(0.5);
        make.center.mas_equalTo(self.view); // 与下面两句代码等价
//        make.centerX.mas_equalTo(self.view);
//        make.centerY.mas_equalTo(self.view);
    }];
  • 内边距实现边界约束:

  edges

// UIEdgeInsets 内边距

make.edges.mas_equalTo(self.view).insets(UIEdgeInsetsMake(50, 50, 50, 50));

4、mas_前缀修饰与不修饰的区别    

  • mas_equalTo和equalTo

  默认情况下:

   mas_equalTo有自动包装功能,比如自动将20包装为@20

   equalTo没有自动包装功能

  mas_equalTo的功能强于 > equalTo,可以一直使用mas_equalTo

  • mas_width和width

  默认情况下:

   width是make对象的一个属性,用来添加宽度约束用的,表示对宽度进行约束

   mas_width是一个属性值,用来当做equalTo的参数,表示某个控件的宽度属性

  mas_height、mas_centerX以此类推

  • 消除区别办法

  如果添加了下面的宏,那么 mas_equalTo 和 equalTo 就没有区别

  #define MAS_SHORTHAND_GLOBALS // 注意:这个宏一定要添加到#import “Masonry.h”前面

  如果添加了下面的宏,mas_width也可以写成width

  #define MAS_SHORTHAND

//define this constant if you want to use Masonry without the 'mas_' prefix
#define MAS_SHORTHAND

//define this constant if you want to enable auto-boxing for default syntax
#define MAS_SHORTHAND_GLOBALS

#import "Masonry.h"

- (void)viewDidLoad {
    [super viewDidLoad];

    // 蓝色控件
    UIView *blueView = [[UIView alloc] init];
    blueView.backgroundColor = [UIColor blueColor];
    [self.view addSubview:blueView];

    // 红色控件
    UIView *redView = [[UIView alloc] init];
    redView.backgroundColor = [UIColor redColor];
    [self.view addSubview:redView];

    // 添加约束
    CGFloat margin = 20;
    CGFloat height = 50;
    [blueView makeConstraints:^(MASConstraintMaker *make) {
        make.left.equalTo(self.view.left).offset(margin);
        make.right.equalTo(redView.left).offset(-margin);
        make.bottom.equalTo(self.view.bottom).offset(-margin);
        make.height.equalTo(height);
        make.top.equalTo(redView.top);
        make.bottom.equalTo(redView.bottom);
        make.width.equalTo(redView.width);
    }];

    [redView makeConstraints:^(MASConstraintMaker *make) {
        make.right.equalTo(self.view.right).offset(-margin);
    }];
} 

5、可有可无的用法

  以下方法都仅仅是为了提高可读性,可有可无

  • with

    • (MASConstraint*)with {
      return self;
      }

  使用情况示例代码

  // 尺寸限制:100x100
  // 位置:粘着父控件右下角,间距是20
  [blueView mas_makeConstraints:^(MASConstraintMaker *make) {
      // 宽度约束
      make.width.equalTo(@100);
      // 高度约束
      make.height.equalTo(@100);
      // 右边
      make.right.equalTo(self.view.mas_right).with.offset(-20);
      // 顶部
      make.top.equalTo(self.view.mas_top).with.offset(20);
  }];
  • and

    • (MASConstraint*)and {
      return self;
      }

  使用情况示例代码

// 尺寸限制:100x100
// 位置:粘着父控件右下角,间距是20

[blueView mas_makeConstraints:^(MASConstraintMaker *make) {
    // 宽度高度约束
    make.width.and.height.mas_equalTo(100);
    // 右边
    make.right.equalTo(self.view).offset(-20);
    // 顶部
    make.top.equalTo(self.view).offset(20);

}];

From: http://liuyanwei.jumppo.com/2015/06/14/ios-library-masonry.html

Masonry是一个轻量级的布局框架 拥有自己的描述语法 采用更优雅的链式语法封装自动布局 简洁明了 并具有高可读性 而且同时支持 iOS 和 Max OS X。 Masonry是一个用代码写iOS或os界面的库,可以代替Auto layout。 Masonry的github地址:https://github.com/SnapKit/Masonry

本章内容

    • Masonry配置
    • Masonry使用
    • Masonry实例

Masonry配置

    • 推荐使用pods方式引入类库,pod ‘Masonry’,若不知道pod如何使用,情况我的另一篇文章: 提高iOS开发效率的工具
    • 引入头文件 #import “Masonry.h”

Masonry使用讲解

  1. mas_makeConstraints 是给view添加约束,约束有几种,分别是边距,宽,高,左上右下距离,基准线。添加过约束后可以有修正,修正 有offset(位移)修正和multipliedBy(倍率)修正

  2. 语法一般是 make.equalTo or make.greaterThanOrEqualTo or make.lessThanOrEqualTo + 倍数和位移修正

  3. 注意点1: 使用 mas_makeConstraints方法的元素必须事先添加到父元素的中,例如[self.view addSubview:view];

  4. 注意点2: mas_equalTo 和 equalTo 区别:mas_equalTo 比equalTo多了类型转换操作,一般来说,大多数时候两个方法都是 通用的,但是对于数值元素使用mas_equalTo。对于对象或是多个属性的处理,使用equalTo。特别是多个属性时,必须使用equalTo,例如 make.left.and.right.equalTo(self.view);

  5. 注意点3: 注意到方法with和and,这连个方法其实没有做任何操作,方法只是返回对象本身,这这个方法的左右完全是为了方法写的时候的可读性 。make.left.and.right.equalTo(self.view);和make.left.right.equalTo(self.view);是完全一样的,但是明显的加了and方法的语句可读性 更好点。

Masonry初级使用例子

// exp1: 中心点与self.view相同,宽度为400*400
-(void)exp1{

    UIView *view = [UIView new];
    [view setBackgroundColor:[UIColor redColor]];
    [self.view addSubview:view];
    [view mas_makeConstraints:^(MASConstraintMaker *make) {

         make.center.equalTo(self.view);
         make.size.mas_equalTo(CGSizeMake(400,400));
    }];

}


//exp2: 上下左右边距都为10
-(void)exp2{

    UIView *view = [UIView new];
    [view setBackgroundColor:[UIColor redColor]];
    [self.view addSubview:view];
    [view mas_makeConstraints:^(MASConstraintMaker *make) {

        make.edges.equalTo(self.view).with.insets(UIEdgeInsetsMake(10, 10, 10, 10));

        //  make.left.equalTo(self.view).with.offset(10);
        //  make.right.equalTo(self.view).with.offset(-10);
        //  make.top.equalTo(self.view).with.offset(10);
        //  make.bottom.equalTo(self.view).with.offset(-10);
    }];

}

//exp3 让两个高度为150的view垂直居中且等宽且等间隔排列 间隔为10
-(void)exp3{

    UIView *view1 = [UIView new];
    [view1 setBackgroundColor:[UIColor redColor]];
    [self.view addSubview:view1];

    UIView *view2 = [UIView new];
    [view2 setBackgroundColor:[UIColor redColor]];
    [self.view addSubview:view2];

    [view1 mas_makeConstraints:^(MASConstraintMaker *make) {

        make.centerY.mas_equalTo(self.view.mas_centerY);
        make.height.mas_equalTo(150);
        make.width.mas_equalTo(view2.mas_width);
        make.left.mas_equalTo(self.view.mas_left).with.offset(10);
        make.right.mas_equalTo(view2.mas_left).offset(-10);

    }];
    [view2 mas_makeConstraints:^(MASConstraintMaker *make) {

        make.centerY.mas_equalTo(self.view.mas_centerY);
        make.height.mas_equalTo(150);
        make.width.mas_equalTo(view1.mas_width);
        make.left.mas_equalTo(view1.mas_right).with.offset(10);
        make.right.equalTo(self.view.mas_right).offset(-10);

    }];

}

Masonry例子-计算器布局

![][2]

   [2]: http://liuyanwei.jumppo.com/assets/uploads/masonry_1.png


//高级布局练习 iOS自带计算器布局
-(void)exp4{


    //申明区域,displayView是显示区域,keyboardView是键盘区域
    UIView *displayView = [UIView new];
    [displayView setBackgroundColor:[UIColor blackColor]];
    [self.view addSubview:displayView];

    UIView *keyboardView = [UIView new];
    [self.view addSubview:keyboardView];

    //先按1:3分割 displView(显示结果区域)和 keyboardView(键盘区域)
    [displayView mas_makeConstraints:^(MASConstraintMaker *make) {
        make.top.equalTo(self.view.mas_top);
        make.left.and.right.equalTo(self.view);
        make.height.equalTo(keyboardView).multipliedBy(0.3f);
    }];

    [keyboardView mas_makeConstraints:^(MASConstraintMaker *make) {
        make.top.equalTo(displayView.mas_bottom);
        make.bottom.equalTo(self.view.mas_bottom);
        make.left.and.right.equalTo(self.view);

    }];

    //设置显示位置的数字为0
    UILabel *displayNum = [[UILabel alloc]init];
    [displayView addSubview:displayNum];
    displayNum.text = @"0";
    displayNum.font = [UIFont fontWithName:@"HeiTi SC" size:70];
    displayNum.textColor = [UIColor whiteColor];
    displayNum.textAlignment = NSTextAlignmentRight;
    [displayNum mas_makeConstraints:^(MASConstraintMaker *make) {
        make.left.and.right.equalTo(displayView).with.offset(-10);
        make.bottom.equalTo(displayView).with.offset(-10);
    }];


    //定义键盘键名称,?号代表合并的单元格
    NSArray *keys = @[@"AC",@"+/-",@"%",@"÷"
                     ,@"7",@"8",@"9",@"x"
                     ,@"4",@"5",@"6",@"-"
                     ,@"1",@"2",@"3",@"+"
                     ,@"0",@"?",@".",@"="];


    int indexOfKeys = 0;
    for (NSString *key in keys){
        //循环所有键
        indexOfKeys++;
        int rowNum = indexOfKeys %4 ==0? indexOfKeys/4:indexOfKeys/4 +1;
        int colNum = indexOfKeys %4 ==0? 4 :indexOfKeys %4;
        NSLog(@"index is:%d and row:%d,col:%d",indexOfKeys,rowNum,colNum);

        //键样式
        UIButton *keyView = [UIButton buttonWithType:UIButtonTypeCustom];
        [keyboardView addSubview:keyView];
        [keyView setTitleColor:[UIColor blackColor] forState:UIControlStateNormal];
        [keyView setTitle:key forState:UIControlStateNormal];
        [keyView.layer setBorderWidth:1];
        [keyView.layer setBorderColor:[[UIColor blackColor]CGColor]];
        [keyView.titleLabel setFont:[UIFont fontWithName:@"Arial-BoldItalicMT" size:30]];

        //键约束
        [keyView mas_makeConstraints:^(MASConstraintMaker *make) {

            //处理 0 合并单元格
            if([key isEqualToString:@"0"] || [key isEqualToString:@"?"] ){

                if([key isEqualToString:@"0"]){
                    [keyView mas_makeConstraints:^(MASConstraintMaker *make) {
                        make.height.equalTo(keyboardView.mas_height).with.multipliedBy(.2f);
                        make.width.equalTo(keyboardView.mas_width).multipliedBy(.5);
                        make.left.equalTo(keyboardView.mas_left);
                        make.baseline.equalTo(keyboardView.mas_baseline).with.multipliedBy(.9f);
                    }];
                }if([key isEqualToString:@"?"]){
                    [keyView removeFromSuperview];
                }

            }
            //正常的单元格
            else{
                make.width.equalTo(keyboardView.mas_width).with.multipliedBy(.25f);
                make.height.equalTo(keyboardView.mas_height).with.multipliedBy(.2f);

                //按照行和列添加约束,这里添加行约束
                switch (rowNum) {
                    case 1:
                    {
                        make.baseline.equalTo(keyboardView.mas_baseline).with.multipliedBy(.1f);
                        keyView.backgroundColor = [UIColor colorWithRed:205 green:205 blue:205 alpha:1];

                    }
                        break;
                    case 2:
                    {
                        make.baseline.equalTo(keyboardView.mas_baseline).with.multipliedBy(.3f);
                    }
                        break;
                    case 3:
                    {
                        make.baseline.equalTo(keyboardView.mas_baseline).with.multipliedBy(.5f);
                    }
                        break;
                    case 4:
                    {
                        make.baseline.equalTo(keyboardView.mas_baseline).with.multipliedBy(.7f);
                    }
                        break;
                    case 5:
                    {
                        make.baseline.equalTo(keyboardView.mas_baseline).with.multipliedBy(.9f);
                    }
                        break;
                    default:
                        break;
                }
                //按照行和列添加约束,这里添加列约束
                switch (colNum) {
                    case 1:
                    {
                        make.left.equalTo(keyboardView.mas_left);

                    }
                        break;
                    case 2:
                    {
                        make.right.equalTo(keyboardView.mas_centerX);

                    }
                        break;
                    case 3:
                    {
                        make.left.equalTo(keyboardView.mas_centerX);
                    }
                        break;
                    case 4:
                    {
                        make.right.equalTo(keyboardView.mas_right);
                        [keyView setBackgroundColor:[UIColor colorWithRed:243 green:127 blue:38 alpha:1]];
                    }
                        break;
                    default:
                        break;
                }
            }
        }];
    }

}

本例子使用的baseline去控制高度位置,这似乎不是太准,如果想要精准控制高度位置,可以使用一行一行添加的方法,每次当前行的top去equelTo上一行的bottom。 给个提示:

for(遍历所有行)
    for(遍历所以列)
    //当前行约束根据上一行去设置
    ......
  • 下一个例子中,使用上面类似的方法

Masonry高级使用例子2

根据设计图,使用masonry布局:

代码下载

步骤1
 -(void)createUI{

    UIView *titleView = [UIView new];
    titleView.backgroundColor = [UIColor redColor];
    UIView *caredView = [UIView new];
    [self.view addSubview:caredView];
    UIView *brifeView = [UIView new];
    [self.view addSubview:brifeView];

    //self.view
    self.view.backgroundColor = [UIColor colorWithWhite:0.965 alpha:1.000];

    //thrm
    UIImageView *plantThrm = [[UIImageView alloc]initWithImage:[UIImage imageNamed:@"defalutPlantReferenceIcon"]];
    [self.view addSubview:plantThrm];
    [plantThrm mas_makeConstraints:^(MASConstraintMaker *make) {
        make.left.and.top.equalTo(self.view).with.offset(10);
    }];

    //title
       [self.view addSubview:titleView];
       UIImageView *bgTitleView = [[UIImageView alloc]initWithImage:[UIImage imageNamed:@"bg-plant-reference-title"]];
    [titleView addSubview:bgTitleView];
    [titleView mas_makeConstraints:^(MASConstraintMaker *make) {

        make.right.equalTo(self.view.mas_right);
        make.left.equalTo(plantThrm.mas_right).with.offset(20);
        make.centerY.equalTo(plantThrm.mas_centerY);
   }];

    [bgTitleView mas_makeConstraints:^(MASConstraintMaker *make) {
        make.edges.equalTo(titleView);
    }];

    UILabel *title = [[UILabel alloc]init];
    title.textColor =  [UIColor whiteColor];
    title.font = [UIFont fontWithName:@"Heiti SC" size:26];
    title.text = _reference.name;
    [titleView addSubview:title];
    [title mas_makeConstraints:^(MASConstraintMaker *make) {
        make.left.equalTo(titleView.mas_left).offset(10);
        make.width.equalTo(titleView.mas_width);
        make.centerY.equalTo(titleView.mas_centerY);
    }];

    //植物养护
    UILabel *caredTitle = [[UILabel alloc]init];
    caredTitle.textColor =  [UIColor colorWithRed:0.172 green:0.171 blue:0.219 alpha:1.000];
    caredTitle.font = [UIFont fontWithName:@"Heiti SC" size:10];
    caredTitle.text = @"植物养护";
    [self.view addSubview:caredTitle];
    [caredTitle mas_makeConstraints:^(MASConstraintMaker *make) {
        make.top.equalTo(plantThrm.mas_bottom).with.offset(20);
        make.left.and.right.equalTo(self.view).with.offset(10);
        make.height.mas_equalTo(10);
    }];



    //将图层的边框设置为圆脚
    caredView.layer.cornerRadius = 5;
    caredView.layer.masksToBounds = YES;
    //给图层添加一个有色边框
    caredView.layer.borderWidth = 1;
    caredView.layer.borderColor = [[UIColor colorWithWhite:0.521 alpha:1.000] CGColor];
    caredView.backgroundColor = [UIColor whiteColor];


    [caredView mas_makeConstraints:^(MASConstraintMaker *make) {
        make.top.equalTo(caredTitle.mas_bottom).with.offset(5);
        make.left.equalTo(self.view.mas_left).with.offset(10);
        make.right.equalTo(self.view.mas_right).with.offset(-10);
        make.height.equalTo(brifeView);
    }];


    //植物简介
    UILabel *brifeTitle = [[UILabel alloc]init];
    brifeTitle.textColor =  [UIColor colorWithRed:0.172 green:0.171 blue:0.219 alpha:1.000];
    brifeTitle.font = [UIFont fontWithName:@"Heiti SC" size:10];
    brifeTitle.text = @"植物简介";
    [self.view addSubview:brifeTitle];
    [brifeTitle mas_makeConstraints:^(MASConstraintMaker *make) {
        make.top.equalTo(caredView.mas_bottom).with.offset(20);
        make.left.and.right.equalTo(self.view).with.offset(10);
        make.height.mas_equalTo(10);
    }];


    //将图层的边框设置为圆脚
    brifeView.layer.cornerRadius = 5;
    brifeView.layer.masksToBounds = YES;
    //给图层添加一个有色边框
    brifeView.layer.borderWidth = 1;
    brifeView.layer.borderColor = [[UIColor colorWithWhite:0.521 alpha:1.000] CGColor];
    brifeView.backgroundColor = [UIColor whiteColor];


    [brifeView mas_makeConstraints:^(MASConstraintMaker *make) {
        make.top.equalTo(brifeTitle.mas_bottom).with.offset(5);
        make.left.equalTo(self.view.mas_left).with.offset(10);
        make.right.equalTo(self.view.mas_right).with.offset(-10);
        make.bottom.equalTo(self.view.mas_bottom).with.offset(-10);
        make.height.equalTo(caredView);
    }];


}

完成之后如下图 步骤1

步骤2,在上面的基础上,增加植物养护部分ui构造的代码,思想是,先构造出四行,然后根据每行单独构造出行样式。
//把块拆分为四行
-(void)createIndexUIWithView:(UIView *)view{

    //拆分四行
    UIView *row1 = [UIView new];
    UIView *row2 = [UIView new];
    UIView *row3 = [UIView new];
    UIView *row4 = [UIView new];
    [view addSubview:row1];
    [view addSubview:row2];
    [view addSubview:row3];
    [view addSubview:row4];

    [row1 mas_makeConstraints:^(MASConstraintMaker *make) {
        make.right.and.left.equalTo(view);
        make.height.equalTo(view.mas_height).multipliedBy(0.25);
        make.top.equalTo(view.mas_top);
    }];
    [row2 mas_makeConstraints:^(MASConstraintMaker *make) {
        make.right.and.left.equalTo(view);
        make.top.equalTo(row1.mas_bottom);
        make.height.equalTo(view.mas_height).multipliedBy(0.25);
    }];
    [row3 mas_makeConstraints:^(MASConstraintMaker *make) {
        make.right.equalTo(view.mas_right);
        make.top.equalTo(row2.mas_bottom);
        make.height.equalTo(view.mas_height).multipliedBy(0.25);
        make.left.equalTo(view.mas_left);
    }];
    [row4 mas_makeConstraints:^(MASConstraintMaker *make) {
        make.right.and.left.equalTo(view);
        make.top.equalTo(row3.mas_bottom);
        make.height.equalTo(view.mas_height).multipliedBy(0.25);
    }];

    [self createIndexRowUI:PlantReferenceWaterIndex withUIView:row1];
    [self createIndexRowUI:PlantReferenceSumIndex withUIView:row2];
    [self createIndexRowUI:PlantReferenceTemperatureIndex withUIView:row3];
    [self createIndexRowUI:PlantReferenceElectrolyteIndex withUIView:row4];
}


//构造每行的UI
-(void)createIndexRowUI:(PlantReferenceIndex) index withUIView:(UIView *)view{

    //index标题
    UILabel *indexTitle = [UILabel new];

    indexTitle.font = [UIFont fontWithName:@"HeiTi SC" size:14];
    indexTitle.textColor = [UIColor colorWithWhite:0.326 alpha:1.000];

    [view addSubview:indexTitle];
    [indexTitle mas_makeConstraints:^(MASConstraintMaker *make) {
        make.left.equalTo(view.mas_left).with.offset(20);
        make.centerY.equalTo(view.mas_centerY);
    }];


    switch (index) {


        case PlantReferenceWaterIndex:
        {
            indexTitle.text = @"水分";
            UIImageView * current;
            for(int i=1;i<=5;i++){
                if(i<_reference.waterIndex){
                    current = [[UIImageView alloc]initWithImage:[UIImage imageNamed:@"icon_water_light"]];
                }else{
                    current = [[UIImageView alloc]initWithImage:[UIImage imageNamed:@"icon_water_dark"]];
                }
                [view addSubview:current];

                //间距12%,左边留空30%
                [current mas_makeConstraints:^(MASConstraintMaker *make) {
                    make.left.equalTo(view.mas_right).with.multipliedBy(0.12*(i-1) +0.3);
                    make.centerY.equalTo(view.mas_centerY);
                }];
            }

        }
              break;
        case PlantReferenceSumIndex:
        {
            indexTitle.text = @"光照";
            UIImageView * current;
            for(int i=1;i<=5;i++){
                if(i<_reference.temperatureIndex){
                    current = [[UIImageView alloc]initWithImage:[UIImage imageNamed:@"icon_summer_light"]];
                }else{
                    current = [[UIImageView alloc]initWithImage:[UIImage imageNamed:@"icon_summer_dark"]];
                }
                [view addSubview:current];

                //间距12%,左边留空30%
                [current mas_makeConstraints:^(MASConstraintMaker *make) {
                    make.left.equalTo(view.mas_right).with.multipliedBy(0.12*(i-1) +0.3);
                    make.centerY.equalTo(view.mas_centerY);
                }];
            }

        }
              break;
        case PlantReferenceTemperatureIndex:
        {
            indexTitle.text = @"温度";
            UIImageView * current;
            for(int i=1;i<=5;i++){
                if(i<_reference.sumIndex){
                    current = [[UIImageView alloc]initWithImage:[UIImage imageNamed:@"icon_temperature_light"]];
                }else{
                    current = [[UIImageView alloc]initWithImage:[UIImage imageNamed:@"icon_temperature_dark"]];
                }
                [view addSubview:current];

                //间距12%,左边留空30%
                [current mas_makeConstraints:^(MASConstraintMaker *make) {
                    make.left.equalTo(view.mas_right).with.multipliedBy(0.12*(i-1) +0.3);
                    make.centerY.equalTo(view.mas_centerY);
                }];
            }

        }
              break;
        case PlantReferenceElectrolyteIndex:
        {
            indexTitle.text = @"肥料";
            UIImageView * current;
            for(int i=1;i<=5;i++){
                if(i<_reference.electrolyteIndex){
                    current = [[UIImageView alloc]initWithImage:[UIImage imageNamed:@"icon_electolyte_light"]];
                }else{
                    current = [[UIImageView alloc]initWithImage:[UIImage imageNamed:@"icon_electolyte_dark"]];
                }
                [view addSubview:current];

                //间距12%,左边留空30%
                [current mas_makeConstraints:^(MASConstraintMaker *make) {
                    make.left.equalTo(view.mas_right).with.multipliedBy(0.12*(i-1) +0.3);
                    make.centerY.equalTo(view.mas_centerY);
                }];
            }

        }
            break;

        default:
            break;
    }


}


//在步骤1createui的基础上,做了一些微调。
-(void)createUI{

    self.title = _reference.name;

    UIView *titleView = [UIView new];
    UIView *caredView = [UIView new];
    [self.view addSubview:caredView];
    UITextView *brifeView = [UITextView new];
    [self.view addSubview:brifeView];

    //self.view
    self.view.backgroundColor = [UIColor colorWithWhite:0.965 alpha:1.000];

    //thrm
    UIImageView *plantThrm = [[UIImageView alloc]initWithImage:[UIImage imageNamed:@"defalutPlantReferenceIcon"]];
    [self.view addSubview:plantThrm];
    [plantThrm mas_makeConstraints:^(MASConstraintMaker *make) {
        make.left.and.top.equalTo(self.view).with.offset(10);
    }];

    //title
       [self.view addSubview:titleView];
       UIImageView *bgTitleView = [[UIImageView alloc]initWithImage:[UIImage imageNamed:@"bg-plant-reference-title"]];
    [titleView addSubview:bgTitleView];
    [titleView mas_makeConstraints:^(MASConstraintMaker *make) {

        make.right.equalTo(self.view.mas_right);
        make.left.equalTo(plantThrm.mas_right).with.offset(20);
        make.centerY.equalTo(plantThrm.mas_centerY);
   }];

    [bgTitleView mas_makeConstraints:^(MASConstraintMaker *make) {
        make.edges.equalTo(titleView);
    }];

    UILabel *title = [[UILabel alloc]init];
    title.textColor =  [UIColor whiteColor];
    title.font = [UIFont fontWithName:@"Heiti SC" size:26];
    title.text = _reference.name;
    [titleView addSubview:title];
    [title mas_makeConstraints:^(MASConstraintMaker *make) {
        make.left.equalTo(titleView.mas_left).offset(10);
        make.width.equalTo(titleView.mas_width);
        make.centerY.equalTo(titleView.mas_centerY);
    }];

    //植物养护
    UILabel *caredTitle = [[UILabel alloc]init];
    caredTitle.textColor =  [UIColor colorWithRed:0.172 green:0.171 blue:0.219 alpha:1.000];
    caredTitle.font = [UIFont fontWithName:@"Heiti SC" size:10];
    caredTitle.text = @"植物养护";
    [self.view addSubview:caredTitle];
    [caredTitle mas_makeConstraints:^(MASConstraintMaker *make) {
        make.top.equalTo(plantThrm.mas_bottom).with.offset(20);
        make.left.and.right.equalTo(self.view).with.offset(10);
        make.height.mas_equalTo(10);
    }];
    //植物养护 数据
    [self createIndexUIWithView:caredView];





    //将图层的边框设置为圆脚
    caredView.layer.cornerRadius = 5;
    caredView.layer.masksToBounds = YES;
    //给图层添加一个有色边框
    caredView.layer.borderWidth = 1;
    caredView.layer.borderColor = [[UIColor colorWithWhite:0.521 alpha:1.000] CGColor];
    caredView.backgroundColor = [UIColor whiteColor];


    [caredView mas_makeConstraints:^(MASConstraintMaker *make) {
        make.top.equalTo(caredTitle.mas_bottom).with.offset(5);
        make.left.equalTo(self.view.mas_left).with.offset(10);
        make.right.equalTo(self.view.mas_right).with.offset(-10);
        make.height.equalTo(brifeView);
    }];


    //植物简介
    UILabel *brifeTitle = [[UILabel alloc]init];
    brifeTitle.textColor =  [UIColor colorWithRed:0.172 green:0.171 blue:0.219 alpha:1.000];
    brifeTitle.font = [UIFont fontWithName:@"Heiti SC" size:10];
    brifeTitle.text = @"植物简介";
    [self.view addSubview:brifeTitle];
    [brifeTitle mas_makeConstraints:^(MASConstraintMaker *make) {
        make.top.equalTo(caredView.mas_bottom).with.offset(20);
        make.left.and.right.equalTo(self.view).with.offset(10);
        make.height.mas_equalTo(10);
    }];



    //将图层的边框设置为圆脚
    brifeView.layer.cornerRadius = 5;
    brifeView.layer.masksToBounds = YES;
    //给图层添加一个有色边框
    brifeView.layer.borderWidth = 1;
    brifeView.layer.borderColor = [[UIColor colorWithWhite:0.447 alpha:1.000] CGColor];
    brifeView.backgroundColor = [UIColor whiteColor];

    //文字样式
//    brifeView.textColor = [UIColor colorWithWhite:0.352 alpha:1.000];
//    brifeView.font = [UIFont fontWithName:@"HeiTi SC" size:12];
    NSMutableParagraphStyle *paragraphStyle = [[NSMutableParagraphStyle alloc]init];
    paragraphStyle.lineHeightMultiple = 20.f;
    paragraphStyle.maximumLineHeight = 25.f;
    paragraphStyle.minimumLineHeight = 15.f;
    paragraphStyle.alignment = NSTextAlignmentJustified;
    NSDictionary *attributes = @{ NSFontAttributeName:[UIFont systemFontOfSize:12], NSParagraphStyleAttributeName:paragraphStyle, NSForegroundColorAttributeName:[UIColor colorWithWhite:0.447 alpha:1.000]};
    //植物简介数据
    //brifeView.text = _reference.brief;
    brifeView.attributedText = [[NSAttributedString alloc] initWithString: _reference.brief attributes:attributes];



    [brifeView mas_makeConstraints:^(MASConstraintMaker *make) {
        make.top.equalTo(brifeTitle.mas_bottom).with.offset(5);
        make.left.equalTo(self.view.mas_left).with.offset(10);
        make.right.equalTo(self.view.mas_right).with.offset(-10);
        make.bottom.equalTo(self.view.mas_bottom).with.offset(-10);
        make.height.equalTo(caredView);
    }];


}

}


采坑: 一个UIScrollView 上add 一些view
view 布局用masonry,
如下, 高度 和底部 有一个算多余约束 无报错, 但是UIScrollView, contentOffset 页面已跳转就会变为0,0

1
2
3
4
5
6
7
self.hotVideoListTab.mas_makeConstraints { (make) in
make?.left.mas_equalTo()(self.scrollView)
make?.top.mas_equalTo()(self.scrollView)
// make?.bottom.mas_equalTo()(self.scrollView)
make?.height.mas_equalTo()(self.scrollView)
make?.width.mas_equalTo()(stageWidth)
}

Objective-C编码规范

Objective-C-Coding-Guidelines-In-Chinese

Objective-C编码规范,内容来自苹果、谷歌的文档翻译,『博爱』的编码经验和对其它资料的总结。
详情可前往:开源项目『BABaseProject』

转载请注明出处。

##概要

Objective-C是一门面向对象的动态编程语言,主要用于编写iOS和Mac应用程序。关于Objective-C的编码规范,苹果和谷歌都已经有很好的总结:

本文主要整合了对上述文档的翻译、作者自己的编程经验和其他的相关资料,为公司总结出一份通用的编码规范。

##代码格式

###使用空格而不是制表符Tab

不要在工程里使用Tab键,使用空格来进行缩进。在Xcode > Preferences > Text Editing将Tab和自动缩进都设置为4个空格。(Google的标准是使用两个空格来缩进,但这里还是推荐使用Xcode默认的设置。

###每一行的最大长度

同样的,在Xcode > Preferences > Text Editing > Page guide at column:中将最大行长设置为80,过长的一行代码将会导致可读性问题。

###函数的书写

一个典型的Objective-C函数应该是这样的:

1
2
3
- (void)writeVideoFrameWithData:(NSData *)frameData timeStamp:(int)timeStamp {
...
}

-(void)之间应该有一个空格,第一个大括号{的位置在函数所在行的末尾,同样应该有一个空格。(我司的C语言规范要求是第一个大括号单独占一行,但考虑到OC较长的函数名和苹果SDK代码的风格,还是将大括号放在行末。

如果一个函数有特别多的参数或者名称很长,应该将其按照:来对齐分行显示:

1
2
3
4
5
6
7
8
9
10
11
-(id)initWithModel:(IPCModle)model
ConnectType:(IPCConnectType)connectType
Resolution:(IPCResolution)resolution
AuthName:(NSString *)authName
Password:(NSString *)password
MAC:(NSString *)mac
AzIp:(NSString *)az_ip
AzDns:(NSString *)az_dns
Token:(NSString *)token
Email:(NSString *)email
Delegate:(id<IPCConnectHandlerDelegate>)delegate;

在分行时,如果第一段名称过短,后续名称可以以Tab的长度(4个空格)为单位进行缩进:

1
2
3
4
5
6
- (void)short:(GTMFoo *)theFoo
longKeyword:(NSRect)theRect
evenLongerKeyword:(float)theInterval
error:(NSError **)theError {
...
}

###函数调用

函数调用的格式和书写差不多,可以按照函数的长短来选择写在一行或者分成多行:

1
2
3
4
5
6
7
8
9
10
11
12
13
//写在一行
[myObject doFooWith:arg1 name:arg2 error:arg3];
//分行写,按照':'对齐
[myObject doFooWith:arg1
name:arg2
error:arg3];
//第一段名称过短的话后续可以进行缩进
[myObj short:arg1
longKeyword:arg2
evenLongerKeyword:arg3
error:arg4];

以下写法是错误的:

1
2
3
4
5
6
7
8
9
10
//错误,要么写在一行,要么全部分行
[myObject doFooWith:arg1 name:arg2
error:arg3];
[myObject doFooWith:arg1
name:arg2 error:arg3];
//错误,按照':'来对齐,而不是关键字
[myObject doFooWith:arg1
name:arg2
error:arg3];

###@public和@private标记符

@public和@private标记符应该以一个空格来进行缩进:

1
2
3
4
5
6
7
@interface MyClass : NSObject {
@public
...
@private
...
}
@end

###协议(Protocols)

在书写协议的时候注意用<>括起来的协议和类型名之间是没有空格的,比如IPCConnectHandler()<IPCPreconnectorDelegate>,这个规则适用所有书写协议的地方,包括函数声明、类声明、实例变量等等:

1
2
3
4
5
6
7
@interface MyProtocoledClass : NSObject<NSWindowDelegate> {
@private
id<MyFancyDelegate> _delegate;
}
- (void)setDelegate:(id<MyFancyDelegate>)aDelegate;
@end

###闭包(Blocks)

根据block的长度,有不同的书写规则:

  • 较短的block可以写在一行内。
  • 如果分行显示的话,block的右括号}应该和调用block那行代码的第一个非空字符对齐。
  • block内的代码采用4个空格的缩进。
  • 如果block过于庞大,应该单独声明成一个变量来使用。
  • ^(之间,^{之间都没有空格,参数列表的右括号){之间有一个空格。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
//较短的block写在一行内
[operation setCompletionBlock:^{ [self onOperationDone]; }];
//分行书写的block,内部使用4空格缩进
[operation setCompletionBlock:^{
[self.delegate newDataAvailable];
}];
//使用C语言API调用的block遵循同样的书写规则
dispatch_async(_fileIOQueue, ^{
NSString* path = [self sessionFilePath];
if (path) {
// ...
}
});
//较长的block关键字可以缩进后在新行书写,注意block的右括号'}'和调用block那行代码的第一个非空字符对齐
[[SessionService sharedService]
loadWindowWithCompletionBlock:^(SessionWindow *window) {
if (window) {
[self windowDidLoad:window];
} else {
[self errorLoadingWindow];
}
}];
//较长的block参数列表同样可以缩进后在新行书写
[[SessionService sharedService]
loadWindowWithCompletionBlock:
^(SessionWindow *window) {
if (window) {
[self windowDidLoad:window];
} else {
[self errorLoadingWindow];
}
}];
//庞大的block应该单独定义成变量使用
void (^largeBlock)(void) = ^{
// ...
};
[_operationQueue addOperationWithBlock:largeBlock];
//在一个调用中使用多个block,注意到他们不是像函数那样通过':'对齐的,而是同时进行了4个空格的缩进
[myObject doSomethingWith:arg1
firstBlock:^(Foo *a) {
// ...
}
secondBlock:^(Bar *b) {
// ...
}];

###数据结构的语法糖

应该使用可读性更好的语法糖来构造NSArrayNSDictionary等数据结构,避免使用冗长的alloc,init方法。

如果构造代码写在一行,需要在括号两端留有一个空格,使得被构造的元素于与构造语法区分开来:

1
2
3
4
5
6
7
//正确,在语法糖的"[]"或者"{}"两端留有空格
NSArray *array = @[ [foo description], @"Another String", [bar description] ];
NSDictionary *dict = @{ NSForegroundColorAttributeName : [NSColor redColor] };
//不正确,不留有空格降低了可读性
NSArray* array = @[[foo description], [bar description]];
NSDictionary* dict = @{NSForegroundColorAttributeName: [NSColor redColor]};

如果构造代码不写在一行内,构造元素需要使用两个空格来进行缩进,右括号]或者}写在新的一行,并且与调用语法糖那行代码的第一个非空字符对齐:

1
2
3
4
5
6
7
8
9
10
11
NSArray *array = @[
@"This",
@"is",
@"an",
@"array"
];
NSDictionary *dictionary = @{
NSFontAttributeName : [NSFont fontWithName:@"Helvetica-Bold" size:12],
NSForegroundColorAttributeName : fontColor
};

构造字典时,字典的Key和Value与中间的冒号:都要留有一个空格,多行书写时,也可以将Value对齐:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
//正确,冒号':'前后留有一个空格
NSDictionary *option1 = @{
NSFontAttributeName : [NSFont fontWithName:@"Helvetica-Bold" size:12],
NSForegroundColorAttributeName : fontColor
};
//正确,按照Value来对齐
NSDictionary *option2 = @{
NSFontAttributeName : [NSFont fontWithName:@"Arial" size:12],
NSForegroundColorAttributeName : fontColor
};
//错误,冒号前应该有一个空格
NSDictionary *wrong = @{
AKey: @"b",
BLongerKey: @"c",
};
//错误,每一个元素要么单独成为一行,要么全部写在一行内
NSDictionary *alsoWrong= @{ AKey : @"a",
BLongerKey : @"b" };
//错误,在冒号前只能有一个空格,冒号后才可以考虑按照Value对齐
NSDictionary *stillWrong = @{
AKey : @"b",
BLongerKey : @"c",
};

##命名规范

###基本原则

####清晰

命名应该尽可能的清晰和简洁,但在Objective-C中,清晰比简洁更重要。由于Xcode强大的自动补全功能,我们不必担心名称过长的问题。

1
2
3
4
5
6
7
8
9
10
11
//清晰
insertObject:atIndex:
//不清晰,insert的对象类型和at的位置属性没有说明
insert:at:
//清晰
removeObjectAtIndex:
//不清晰,remove的对象类型没有说明,参数的作用没有说明
remove:

不要使用单词的简写,拼写出完整的单词:

1
2
3
4
5
6
7
//清晰
destinationSelection
setBackgroundColor:
//不清晰,不要使用简写
destSel
setBkgdColor:

然而,有部分单词简写在Objective-C编码过程中是非常常用的,以至于成为了一种规范,这些简写可以在代码中直接使用,下面列举了部分:

1
2
3
4
5
6
7
8
9
10
alloc == Allocate max == Maximum
alt == Alternate min == Minimum
app == Application msg == Message
calc == Calculate nib == Interface Builder archive
dealloc == Deallocate pboard == Pasteboard
func == Function rect == Rectangle
horiz == Horizontal Rep == Representation (used in class name such as NSBitmapImageRep).
info == Information temp == Temporary
init == Initialize vert == Vertical
int == Integer

命名方法或者函数时要避免歧义

1
2
3
4
5
//有歧义,是返回sendPort还是send一个Port?
sendPort
//有歧义,是返回一个名字属性的值还是display一个name的动作?
displayName

####一致性

整个工程的命名风格要保持一致性,最好和苹果SDK的代码保持统一。不同类中完成相似功能的方法应该叫一样的名字,比如我们总是用count来返回集合的个数,不能在A类中使用count而在B类中使用getNumber

###使用前缀

如果代码需要打包成Framework给别的工程使用,或者工程项目非常庞大,需要拆分成不同的模块,使用命名前缀是非常有用的。

  • 前缀由大写的字母缩写组成,比如Cocoa中NS前缀代表Founation框架中的类,IB则代表Interface Builder框架。

  • 可以在为类、协议、函数、常量以及typedef宏命名的时候使用前缀,但注意不要为成员变量或者方法使用前缀,因为他们本身就包含在类的命名空间内。

  • 命名前缀的时候不要和苹果SDK框架冲突。

###命名类和协议(Class&Protocol)

类名以大写字母开头,应该包含一个名词来表示它代表的对象类型,同时可以加上必要的前缀,比如NSString, NSDate, NSScanner, NSApplication等等。

而协议名称应该清晰地表示它所执行的行为,而且要和类名区别开来,所以通常使用ing词尾来命名一个协议,比如NSCopying,NSLocking

有些协议本身包含了很多不相关的功能,主要用来为某一特定类服务,这时候可以直接用类名来命名这个协议,比如NSObject协议,它包含了id对象在生存周期内的一系列方法。

###命名头文件(Headers)

源码的头文件名应该清晰地暗示它的功能和包含的内容:

  • 如果头文件内只定义了单个类或者协议,直接用类名或者协议名来命名头文件,比如NSLocale.h定义了NSLocale类。

  • 如果头文件内定义了一系列的类、协议、类别,使用其中最主要的类名来命名头文件,比如NSString.h定义了NSStringNSMutableString

  • 每一个Framework都应该有一个和框架同名的头文件,包含了框架中所有公共类头文件的引用,比如Foundation.h

  • Framework中有时候会实现在别的框架中类的类别扩展,这样的文件通常使用被扩展的框架名+Additions的方式来命名,比如NSBundleAdditions.h

###命名方法(Methods)

Objective-C的方法名通常都比较长,这是为了让程序有更好地可读性,按苹果的说法“好的方法名应当可以以一个句子的形式朗读出来”

方法一般以小写字母打头,每一个后续的单词首字母大写,方法名中不应该有标点符号(包括下划线),有两个例外:

  • 可以用一些通用的大写字母缩写打头方法,比如PDF,TIFF等。
  • 可以用带下划线的前缀来命名私有方法或者类别中的方法。

如果方法表示让对象执行一个动作,使用动词打头来命名,注意不要使用dodoes这种多余的关键字,动词本身的暗示就足够了:

1
2
3
//动词打头的方法表示让对象执行一个动作
- (void)invokeWithTarget:(id)target;
- (void)selectTabViewItem:(NSTabViewItem *)tabViewItem;

如果方法是为了获取对象的一个属性值,直接用属性名称来命名这个方法,注意不要添加get或者其他的动词前缀:

1
2
3
4
5
6
//正确,使用属性名来命名方法
- (NSSize)cellSize;
//错误,添加了多余的动词前缀
- (NSSize)calcCellSize;
- (NSSize)getCellSize;

对于有多个参数的方法,务必在每一个参数前都添加关键词,关键词应当清晰说明参数的作用:

1
2
3
4
5
6
7
8
9
10
11
//正确,保证每个参数都有关键词修饰
- (void)sendAction:(SEL)aSelector toObject:(id)anObject forAllCells:(BOOL)flag;
//错误,遗漏关键词
- (void)sendAction:(SEL)aSelector :(id)anObject :(BOOL)flag;
//正确
- (id)viewWithTag:(NSInteger)aTag;
//错误,关键词的作用不清晰
- (id)taggedView:(int)aTag;

不要用and来连接两个参数,通常and用来表示方法执行了两个相对独立的操作(从设计上来说,这时候应该拆分成两个独立的方法):

1
2
3
4
5
//错误,不要使用"and"来连接参数
- (int)runModalForDirectory:(NSString *)path andFile:(NSString *)name andTypes:(NSArray *)fileTypes;
//正确,使用"and"来表示两个相对独立的操作
- (BOOL)openFile:(NSString *)fullPath withApplication:(NSString *)appName andDeactivate:(BOOL)flag;

方法的参数命名也有一些需要注意的地方:

  • 和方法名类似,参数的第一个字母小写,后面的每一个单词首字母大写
  • 不要再方法名中使用类似pointer,ptr这样的字眼去表示指针,参数本身的类型足以说明
  • 不要使用只有一两个字母的参数名
  • 不要使用简写,拼出完整的单词

下面列举了一些常用参数名:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
...action:(SEL)aSelector
...alignment:(int)mode
...atIndex:(int)index
...content:(NSRect)aRect
...doubleValue:(double)aDouble
...floatValue:(float)aFloat
...font:(NSFont *)fontObj
...frame:(NSRect)frameRect
...intValue:(int)anInt
...keyEquivalent:(NSString *)charCode
...length:(int)numBytes
...point:(NSPoint)aPoint
...stringValue:(NSString *)aString
...tag:(int)anInt
...target:(id)anObject
...title:(NSString *)aString

###存取方法(Accessor Methods)

存取方法是指用来获取和设置类属性值的方法,属性的不同类型,对应着不同的存取方法规范:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
//属性是一个名词时的存取方法范式
- (type)noun;
- (void)setNoun:(type)aNoun;
//栗子
- (NSString *)title;
- (void)setTitle:(NSString *)aTitle;
//属性是一个形容词时存取方法的范式
- (BOOL)isAdjective;
- (void)setAdjective:(BOOL)flag;
//栗子
- (BOOL)isEditable;
- (void)setEditable:(BOOL)flag;
//属性是一个动词时存取方法的范式
- (BOOL)verbObject;
- (void)setVerbObject:(BOOL)flag;
//栗子
- (BOOL)showsAlpha;
- (void)setShowsAlpha:(BOOL)flag;

命名存取方法时不要将动词转化为被动形式来使用:

1
2
3
4
5
6
7
//正确
- (void)setAcceptsGlyphInfo:(BOOL)flag;
- (BOOL)acceptsGlyphInfo;
//错误,不要使用动词的被动形式
- (void)setGlyphInfoAccepted:(BOOL)flag;
- (BOOL)glyphInfoAccepted;

可以使用can,should,will等词来协助表达存取方法的意思,但不要使用do,和does

1
2
3
4
5
6
7
8
9
//正确
- (void)setCanHide:(BOOL)flag;
- (BOOL)canHide;
- (void)setShouldCloseDocument:(BOOL)flag;
- (BOOL)shouldCloseDocument;
//错误,不要使用"do"或者"does"
- (void)setDoesAcceptGlyphInfo:(BOOL)flag;
- (BOOL)doesAcceptGlyphInfo;

为什么Objective-C中不适用get前缀来表示属性获取方法?因为get在Objective-C中通常只用来表示从函数指针返回值的函数:

1
2
//三个参数都是作为函数的返回值来使用的,这样的函数名可以使用"get"前缀
- (void)getLineDash:(float *)pattern count:(int *)count phase:(float *)phase;

###命名委托(Delegate)

当特定的事件发生时,对象会触发它注册的委托方法。委托是Objective-C中常用的传递消息的方式。委托有它固定的命名范式。

一个委托方法的第一个参数是触发它的对象,第一个关键词是触发对象的类名,除非委托方法只有一个名为sender的参数:

1
2
3
4
5
6
//第一个关键词为触发委托的类名
- (BOOL)tableView:(NSTableView *)tableView shouldSelectRow:(int)row;
- (BOOL)application:(NSApplication *)sender openFile:(NSString *)filename;
//当只有一个"sender"参数时可以省略类名
- (BOOL)applicationOpenUntitledFile:(NSApplication *)sender;

根据委托方法触发的时机和目的,使用should,will,did等关键词

1
2
3
4
5
- (void)browserDidScroll:(NSBrowser *)sender;
- (NSUndoManager *)windowWillReturnUndoManager:(NSWindow *)window;、
- (BOOL)windowShouldClose:(id)sender;

###集合操作类方法(Collection Methods)

有些对象管理着一系列其它对象或者元素的集合,需要使用类似“增删查改”的方法来对集合进行操作,这些方法的命名范式一般为:

1
2
3
4
5
6
7
8
9
//集合操作范式
- (void)addElement:(elementType)anObj;
- (void)removeElement:(elementType)anObj;
- (NSArray *)elements;
//栗子
- (void)addLayoutManager:(NSLayoutManager *)obj;
- (void)removeLayoutManager:(NSLayoutManager *)obj;
- (NSArray *)layoutManagers;

注意,如果返回的集合是无序的,使用NSSet来代替NSArray。如果需要将元素插入到特定的位置,使用类似于这样的命名:

1
2
- (void)insertLayoutManager:(NSLayoutManager *)obj atIndex:(int)index;
- (void)removeLayoutManagerAtIndex:(int)index;

如果管理的集合元素中有指向管理对象的指针,要设置成weak类型以防止引用循环。

下面是SDK中NSWindow类的集合操作方法:

1
2
3
4
5
- (void)addChildWindow:(NSWindow *)childWin ordered:(NSWindowOrderingMode)place;
- (void)removeChildWindow:(NSWindow *)childWin;
- (NSArray *)childWindows;
- (NSWindow *)parentWindow;
- (void)setParentWindow:(NSWindow *)window;

###命名函数(Functions)

在很多场合仍然需要用到函数,比如说如果一个对象是一个单例,那么应该使用函数来代替类方法执行相关操作。

函数的命名和方法有一些不同,主要是:

  • 函数名称一般带有缩写前缀,表示方法所在的框架。
  • 前缀后的单词以“驼峰”表示法显示,第一个单词首字母大写。

函数名的第一个单词通常是一个动词,表示方法执行的操作:

1
2
NSHighlightRect
NSDeallocateObject

如果函数返回其参数的某个属性,省略动词:

1
2
unsigned int NSEventMaskFromType(NSEventType type)
float NSHeight(NSRect aRect)

如果函数通过指针参数来返回值,需要在函数名中使用Get

1
const char *NSGetSizeAndAlignment(const char *typePtr, unsigned int *sizep, unsigned int *alignp)

函数的返回类型是BOOL时的命名:

1
BOOL NSDecimalIsNotANumber(const NSDecimal *decimal)

###命名属性和实例变量(Properties&Instance Variables)

属性和对象的存取方法相关联,属性的第一个字母小写,后续单词首字母大写,不必添加前缀。属性按功能命名成名词或者动词:

1
2
3
4
5
//名词属性
@property (strong) NSString *title;
//动词属性
@property (assign) BOOL showsAlpha;

属性也可以命名成形容词,这时候通常会指定一个带有is前缀的get方法来提高可读性:

1
@property (assign, getter=isEditable) BOOL editable;

命名实例变量,在变量名前加上_前缀(有些有历史的代码会将_放在后面),其它和命名属性一样:

1
2
3
@implementation MyClass {
BOOL _showsTitle;
}

一般来说,类需要对使用者隐藏数据存储的细节,所以不要将实例方法定义成公共可访问的接口,可以使用@private@protected前缀。

按苹果的说法,不建议在除了initdealloc方法以外的地方直接访问实例变量,但很多人认为直接访问会让代码更加清晰可读,只在需要计算或者执行操作的时候才使用存取方法访问,我就是这种习惯,所以这里不作要求。

###命名常量(Constants)

如果要定义一组相关的常量,尽量使用枚举类型(enumerations),枚举类型的命名规则和函数的命名规则相同。
建议使用 NS_ENUMNS_OPTIONS 宏来定义枚举类型,参见官方的 Adopting Modern Objective-C 一文:

1
2
3
4
5
6
7
//定义一个枚举
typedef NS_ENUM(NSInteger, NSMatrixMode) {
NSRadioModeMatrix,
NSHighlightModeMatrix,
NSListModeMatrix,
NSTrackModeMatrix
};

定义bit map:

1
2
3
4
5
6
7
typedef NS_OPTIONS(NSUInteger, NSWindowMask) {
NSBorderlessWindowMask = 0,
NSTitledWindowMask = 1 << 0,
NSClosableWindowMask = 1 << 1,
NSMiniaturizableWindowMask = 1 << 2,
NSResizableWindowMask = 1 << 3
};

使用const定义浮点型或者单个的整数型常量,如果要定义一组相关的整数常量,应该优先使用枚举。常量的命名规范和函数相同:

1
const float NSLightGray;

不要使用#define宏来定义常量,如果是整型常量,尽量使用枚举,浮点型常量,使用const定义。#define通常用来给编译器决定是否编译某块代码,比如常用的:

1
#ifdef DEBUG

注意到一般由编译器定义的宏会在前后都有一个__,比如__MACH__

###命名通知(Notifications)

通知常用于在模块间传递消息,所以通知要尽可能地表示出发生的事件,通知的命名范式是:

[触发通知的类名] + [Did | Will] + [动作] + Notification

栗子:

1
2
3
4
NSApplicationDidBecomeActiveNotification
NSWindowDidMiniaturizeNotification
NSTextViewDidChangeSelectionNotification
NSColorPanelColorDidChangeNotification

##注释

读没有注释代码的痛苦你我都体会过,好的注释不仅能让人轻松读懂你的程序,还能提升代码的逼格。注意注释是为了让别人看懂,而不是仅仅你自己。

###文件注释

每一个文件都必须写文件注释,文件注释通常包含

  • 文件所在模块
  • 作者信息
  • 历史版本信息
  • 版权信息
  • 文件包含的内容,作用

一段良好文件注释的栗子:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
/*******************************************************************************
Copyright (C), 2011-2013, Andrew Min Chang
File name: AMCCommonLib.h
Author: Andrew Chang (Zhang Min)
E-mail: LaplaceZhang@126.com
Description:
This file provide some covenient tool in calling library tools. One can easily include
library headers he wants by declaring the corresponding macros.
I hope this file is not only a header, but also a useful Linux library note.
History:
2012-??-??: On about come date around middle of Year 2012, file created as "commonLib.h"
2012-08-20: Add shared memory library; add message queue.
2012-08-21: Add socket library (local)
2012-08-22: Add math library
2012-08-23: Add socket library (internet)
2012-08-24: Add daemon function
2012-10-10: Change file name as "AMCCommonLib.h"
2012-12-04: Add UDP support in AMC socket library
2013-01-07: Add basic data type such as "sint8_t"
2013-01-18: Add CFG_LIB_STR_NUM.
2013-01-22: Add CFG_LIB_TIMER.
2013-01-22: Remove CFG_LIB_DATA_TYPE because there is already AMCDataTypes.h
Copyright information:
This file was intended to be under GPL protocol. However, I may use this library
in my work as I am an employee. And my company may require me to keep it secret.
Therefore, this file is neither open source nor under GPL control.
********************************************************************************/

文件注释的格式通常不作要求,能清晰易读就可以了,但在整个工程中风格要统一。

###代码注释

好的代码应该是“自解释”(self-documenting)的,但仍然需要详细的注释来说明参数的意义、返回值、功能以及可能的副作用。

方法、函数、类、协议、类别的定义都需要注释,推荐采用Apple的标准注释风格,好处是可以在引用的地方alt+点击自动弹出注释,非常方便。

有很多可以自动生成注释格式的插件,推荐使用VVDocumenter

Screenshot

一些良好的注释:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
/**
* Create a new preconnector to replace the old one with given mac address.
* NOTICE: We DO NOT stop the old preconnector, so handle it by yourself.
*
* @param type Connect type the preconnector use.
* @param macAddress Preconnector's mac address.
*/
- (void)refreshConnectorWithConnectType:(IPCConnectType)type Mac:(NSString *)macAddress;
/**
* Stop current preconnecting when application is going to background.
*/
-(void)stopRunning;
/**
* Get the COPY of cloud device with a given mac address.
*
* @param macAddress Mac address of the device.
*
* @return Instance of IPCCloudDevice.
*/
-(IPCCloudDevice *)getCloudDeviceWithMac:(NSString *)macAddress;
// A delegate for NSApplication to handle notifications about app
// launch and shutdown. Owned by the main app controller.
@interface MyAppDelegate : NSObject {
...
}
@end

协议、委托的注释要明确说明其被触发的条件:

1
2
/** Delegate - Sent when failed to init connection, like p2p failed. */
-(void)initConnectionDidFailed:(IPCConnectHandler *)handler;

如果在注释中要引用参数名或者方法函数名,使用||将参数或者方法括起来以避免歧义:

1
2
3
// Sometimes we need |count| to be less than zero.
// Remember to call |StringWithoutSpaces("foo bar baz")|

定义在头文件里的接口方法、属性必须要有注释!

##编码风格

每个人都有自己的编码风格,这里总结了一些比较好的Cocoa编程风格和注意点。

###不要使用new方法

尽管很多时候能用new代替alloc init方法,但这可能会导致调试内存时出现不可预料的问题。Cocoa的规范就是使用alloc init方法,使用new会让一些读者困惑。

###Public API要尽量简洁

共有接口要设计的简洁,满足核心的功能需求就可以了。不要设计很少会被用到,但是参数极其复杂的API。如果要定义复杂的方法,使用类别或者类扩展。

####import和#include

#import是Cocoa中常用的引用头文件的方式,它能自动防止重复引用文件,什么时候使用#import,什么时候使用#include呢?

  • 当引用的是一个Objective-C或者Objective-C++的头文件时,使用#import
  • 当引用的是一个C或者C++的头文件时,使用#include,这时必须要保证被引用的文件提供了保护域(#define guard)。

栗子:

1
2
3
4
#import <Cocoa/Cocoa.h>
#include <CoreFoundation/CoreFoundation.h>
#import "GTMFoo.h"
#include "base/basictypes.h"

为什么不全部使用#import呢?主要是为了保证代码在不同平台间共享时不出现问题。

###引用框架的根头文件

上面提到过,每一个框架都会有一个和框架同名的头文件,它包含了框架内接口的所有引用,在使用框架的时候,应该直接引用这个根头文件,而不是其它子模块的头文件,即使是你只用到了其中的一小部分,编译器会自动完成优化的。

1
2
3
4
5
6
//正确,引用根头文件
#import <Foundation/Foundation.h>
//错误,不要单独引用框架内的其它头文件
#import <Foundation/NSArray.h>
#import <Foundation/NSString.h>

###BOOL的使用

BOOL在Objective-C中被定义为signed char类型,这意味着一个BOOL类型的变量不仅仅可以表示YES(1)和NO(0)两个值,所以永远不要将BOOL类型变量直接和YES比较:

1
2
3
4
5
6
7
8
9
//错误,无法确定|great|的值是否是YES(1),不要将BOOL值直接与YES比较
BOOL great = [foo isGreat];
if (great == YES)
// ...be great!
//正确
BOOL great = [foo isGreat];
if (great)
// ...be great!

同样的,也不要将其它类型的值作为BOOL来返回,这种情况下,BOOL变量只会取值的最后一个字节来赋值,这样很可能会取到0(NO)。但是,一些逻辑操作符比如&&,||,!的返回是可以直接赋给BOOL的:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
//错误,不要将其它类型转化为BOOL返回
- (BOOL)isBold {
return [self fontTraits] & NSFontBoldTrait;
}
- (BOOL)isValid {
return [self stringValue];
}
//正确
- (BOOL)isBold {
return ([self fontTraits] & NSFontBoldTrait) ? YES : NO;
}
//正确,逻辑操作符可以直接转化为BOOL
- (BOOL)isValid {
return [self stringValue] != nil;
}
- (BOOL)isEnabled {
return [self isValid] && [self isBold];
}

另外BOOL类型可以和_Bool,bool相互转化,但是不能Boolean转化。

###使用ARC

除非想要兼容一些古董级的机器和操作系统,我们没有理由放弃使用ARC。在最新版的Xcode(6.2)中,ARC是自动打开的,所以直接使用就好了。

###在init和dealloc中不要用存取方法访问实例变量

init``dealloc方法被执行时,类的运行时环境不是处于正常状态的,使用存取方法访问变量可能会导致不可预料的结果,因此应当在这两个方法内直接访问实例变量。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
//正确,直接访问实例变量
- (instancetype)init {
self = [super init];
if (self) {
_bar = [[NSMutableString alloc] init];
}
return self;
}
- (void)dealloc {
[_bar release];
[super dealloc];
}
//错误,不要通过存取方法访问
- (instancetype)init {
self = [super init];
if (self) {
self.bar = [NSMutableString string];
}
return self;
}
- (void)dealloc {
self.bar = nil;
[super dealloc];
}

###按照定义的顺序释放资源

在类或者Controller的生命周期结束时,往往需要做一些扫尾工作,比如释放资源,停止线程等,这些扫尾工作的释放顺序应当与它们的初始化或者定义的顺序保持一致。这样做是为了方便调试时寻找错误,也能防止遗漏。

###保证NSString在赋值时被复制

NSString非常常用,在它被传递或者赋值时应当保证是以复制(copy)的方式进行的,这样可以防止在不知情的情况下String的值被其它对象修改。

1
2
3
- (void)setFoo:(NSString *)aFoo {
_foo = [aFoo copy];
}

###使用NSNumber的语法糖

使用带有@符号的语法糖来生成NSNumber对象能使代码更简洁:

1
2
3
4
5
6
NSNumber *fortyTwo = @42;
NSNumber *piOverTwo = @(M_PI / 2);
enum {
kMyEnum = 2;
};
NSNumber *myEnum = @(kMyEnum);

###nil检查

因为在Objective-C中向nil对象发送命令是不会抛出异常或者导致崩溃的,只是完全的“什么都不干”,所以,只在程序中使用nil来做逻辑上的检查。

另外,不要使用诸如nil == Object或者Object == nil的形式来判断。

1
2
3
4
5
6
7
8
9
//正确,直接判断
if (!objc) {
...
}
//错误,不要使用nil == Object的形式
if (nil == objc) {
...
}

###属性的线程安全

定义一个属性时,编译器会自动生成线程安全的存取方法(Atomic),但这样会大大降低性能,特别是对于那些需要频繁存取的属性来说,是极大的浪费。所以如果定义的属性不需要线程保护,记得手动添加属性关键字nonatomic来取消编译器的优化。

###点分语法的使用

不要用点分语法来调用方法,只用来访问属性。这样是为了防止代码可读性问题。

1
2
3
4
5
6
7
8
//正确,使用点分语法访问属性
NSString *oldName = myObject.name;
myObject.name = @"Alice";
//错误,不要用点分语法调用方法
NSArray *array = [NSArray arrayWithObject:@"hello"];
NSUInteger numberOfItems = array.count;
array.release;

###Delegate要使用弱引用

一个类的Delegate对象通常还引用着类本身,这样很容易造成引用循环的问题,所以类的Delegate属性要设置为弱引用。

1
2
/** delegate */
@property (nonatomic, weak) id <IPCConnectHandlerDelegate> delegate;

Swift 3.0 去掉 C 风格循环后怎么办?

From: https://swiftcafe.io/2016/07/18/swift-loop/

Swift 3.0 版本将会去掉沿用已经的 C 风格循环语法, 又是向现代开发语言的一次迈进, 咱们就来看看没了 C 风格循环我们还有什么选择, 看过之后你会不会感觉 C 风格循环在 Swift 中确实有点多余呢?

C 风格循环

关于 C 风格循环, 不我们过多介绍了, 就是类似这样的语句:

let numberList = [1, 2, 3, 4, 5]

for var i = 0; i < numberList.count; i++ {

}

如今这样的语法在新版本的 Swift 中即将成为历史了, C 风格的循环语法可能是大家最熟悉的, 大家会不会觉得突然去掉这个语法有些不适应呢? 咱们再来看看 Swift 3 中的替代方案。

for .. in 语法

第一个替代方案, 我们可以使用 for .. in 这样的语法:

let numberList = [1, 2, 3, 4, 5]

var result = “”

for num in numberList {

result += “(num) “

}

这样就完成了对数组的遍历了, 但是还有另一个情况, 如果我们想知道每次遍历的索引怎么办呢, 还有一种方法:

for num in numberList.enumerate() {

result += “[(num.index)](num.element) “

}

我们可以使用这个集合类型的 enumerate 方法,将这个数组的索引和对应的元素都取了出来,然后我们在循环中就可以对索引项进行引用了, num.index 和 num.element 分别代表对应的索引和元素。

上面这个循环我们还可以再改写一下:

for (index, item) in numberList.enumerate() {

result += “[(index)](item) “

}

不难看出,其实循环中的每一项都是一个元组(Tuple),这个元组的第一项是当前的索引, 第二项是当前的数组元素。 那么我们就可以推理出, enumerate 函数其实就是对 numberList 数组做了一个变换,原来它是一个 Int 类型的数组,经过变换后,成为了 (Int, Int) 元组类型的数组。

是不是这么回事呢? 查看 enumerate 方法的文档后, 看到它的定义是这样的:

func enumerate() -> EnumerateSequence>

比我们想象的要复杂些, EnumerateSequence 是个什么鬼, 让我们再来看看它的文档定义:

The SequenceType returned by enumerate(). EnumerateSequence is a sequence of pairs (n, x), where ns are consecutive Ints starting at zero, and xs are the elements of a Base SequenceType

仔细看下, 其实跟我们理解的还是差不多的, 它只不过是对集合类的一个集成, 这个集合每一项是一个元组 (n, x) , n 代表索引, x 代表数组元素。

那么,我们还可以做点更有意思的事情:

for (index, item) in numberList.enumerate().reverse() {

result += “[(index)](item) “

}

调用 enumerate, 之后再调用 reverse 方法, 我们就可以对一个数组进行反向遍历。

for (index, item) in numberList.enumerate().reverse() {

result += “[(index)](item) “

}

我们还可以:

for (index, item) in numberList.enumerate().filter({ (index, item) in index % 2 == 0}) {

result += “[(index)](item) “

}

调用 filter 函数,过滤某些索引, 只遍历符合条件的那些元素。

当然, 我们还可以做的更多更多, 大家有兴趣可以看看 SequenceType 的文档,把你的新思路回复给大家。 http://swiftdoc.org/v2.2/protocol/SequenceType

区间(Range)循环

除了刚才咱们说的这些, Swift 还提供了更方便的循环语法, 叫做 Range 循环。 比如这样:

var rs = “”;

for i in 0…10 {

rs += “(i)”

}

print(rs)

这个语句会输出 0 到 10 之间的所有数字, 0…10 这个表示 Range 区间的范围。 当然,对于我们刚才的数组遍历来说, 一般数组索引都是数组长度减去 1, 用这个区间处理起来就会比较麻烦, 不过好在 Swift 给我们提供了另外一种 Range 方法:

for i in 0..<numberList.count {

rs += “(i)”

}

这次我们换成了 0..<numberList.count, 这种形式会排除闭区间最后那个数组,然后我们就可以在循环中用索引进行访问啦(注意符号 ..< 两边不要有空格)。

结尾

好了,今天跟大家分享的内容就这么多。C 风格的循环语句其实更多是我们的一个长期养成的习惯问题。 世界的一切都在进步发展,包括开发语言也是一样。 看了 Swift 提供的循环语法, 你对 C 风格循环还有没有存在的必要时什么看法呢。 早些拥抱趋势和变化总是好的。

如果你觉得这篇文章有帮助,还可以关注微信公众号 swift-cafe,会有更多我的原创内容分享给你~

本站文章均为原创内容,如需转载请注明出处,谢谢。


推荐关注微信公众平台
swift-cafe

UM集成坑点

/// 此笔记级一些集成UM遇到的坑
分享面板无法弹出 最终找到这个原因一坑

1
2
3
4
5
6
7
8
9
10
11
问题可能有下面的原因:
1. 创建Xcode项目会默认添加Main.storyboard作为Main Interface(General - Deployment Info),也就是项目的主Window
2. 如果没使用Main.storyboard而又另外在AppDelegate中创建了UIWindow对象,如
self.window = [[UIWindow alloc] initWithFrame:[UIScreen mainScreen].bounds]
如果项目中同时出现Main Interface以及代码创建UIWindow会导致分享面板无法正常弹出,解决方法是移除其一即可。如果移除了Main.storyboard,需要clean工程后再重新运行。
使用presentViewController或其他无法显示分享面板的情况,参考文档链接修改父窗口回调为self.view或其他指定视图,文档链接

UM 取消授权

1
2
3
UMSocialManager.default().cancelAuth(with: UMSocialPlatformType.QQ, completion: nil)
UMSocialManager.default().cancelAuth(with: UMSocialPlatformType.sina, completion: nil)
UMSocialManager.default().cancelAuth(with: UMSocialPlatformType.wechatSession, completion: nil)

下面这点算是QQ的坑点 , 统一id 问题

Unionid接口权限申请流程:目前只支持同一个开发者号码下的应用进行打通。如有需要,可以发邮件到connect@qq.com申请,提供应用类型、信息(APPID和APPKEY),附上营业执照图片、网站备案截图(若有网站应用需要提供该项)即可。打通后同一个QQ登录不同APP ID应用后返回的unionid一致。具体打通事宜后续工作人员会通过邮件确认,请在1~5个工作日留意邮件,以邮件回复为准。
http://wiki.connect.qq.com/%E5%BC%80%E5%8F%91%E8%80%85%E5%8F%8D%E9%A6%88

iOS中的设计模式——单例(Singleton)

swift

1
2
3
4
5
6
7
8
9
class NetTool: NSObject {
/// 实例创建
fileprivate static let instance = NetTool()
class var shared:NetTool{
return instance
}
}

From: http://ibloodline.com/articles/2016/09/19/singleton.html

设计模式

Comments

单例模式

单例模式(Singleton:保证一个类仅有一个实例,并提供一个访问它的全局访问点。

单例模式应该是设计模式中最简答的形式了。这一模式的意图是让类的一个对象成为系统中唯一的实例。

类图

Singleton

使用场景

  • 类只能有一个实例,而且必须从一个为人熟知的访问点对其进行访问,比如工厂方法
  • 这个唯一的实例只能通过子类化进行扩展,而且扩展的对象不会破坏客户端代码。

优点:

  1、提供了对唯一实例的受控访问。

  2、由于在系统内存中只存在一个对象,因此可以节约系统资源,对于一些需要频繁创建和销毁的对象单例模式无疑可以提高系统的性能。

  3.因为单例模式的类控制了实例化的过程,所以类可以更加灵活修改实例化过程。

缺点:

  1、由于单利模式中没有抽象层,因此单例类的扩展有很大的困难。

  2、单例类的职责过重,在一定程度上违背了“单一职责原则”。

使用方式

先看C++中的实现:

class Singlenton
{
public:
    static Singlenton *Instance();

protected:
    Singlenton();

private:
    static Singlenton *_instance;
};

Singlenton *Singlenton::_instance = NULL;

Singlenton *Singlenton::Instance()
{
    if (_instance == NULL) {
        _instance = new Singlenton;
    }
    return _instance;
}

OC下:

//Singleton.h
@interface Singleton : NSObject
+ (Singleton *)sharedInstance;
@end

//Singleton.m
@implementation Singleton
static Singleton * sharedSingleton = nil;
+ (Singleton *) sharedInstance {
    if (sharedSingleton == nil) {
        sharedSingleton = [[Singleton alloc] init];
    }
    return sharedSingleton;
}
@end

上面的实现是有问题的。首先,如果客户端使用不同的方式来初始化单例,则有可能出现多个实例的情况。另外,这样的实现也不是线程安全的。改进:

@implementation Singleton
static id sharedSingleton = nil;
+ (id)allocWithZone:(struct _NSZone *)zone {
    if (!sharedSingleton) {
        static dispatch_once_t onceToken;
        dispatch_once(&onceToken, ^{
            sharedSingleton = [super allocWithZone:zone];
        });
    }
    return sharedSingleton;
}
- (id)init {
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        sharedSingleton = [super init];
    });
    return sharedSingleton;
}
+ (instancetype)sharedInstance {
    return [[self alloc] init];
}
+ (id)copyWithZone:(struct _NSZone *)zone {
    return sharedSingleton;
}
+ (id)mutableCopyWithZone:(struct _NSZone *)zone {
    return sharedSingleton;
}
@end

当然对于懒癌患者来讲,每个单例都写这样的实现实在太不可接受了,我们把它抽取成宏:

// .h文件的实现
#define SingletonH(methodName) + (instancetype)shared##methodName;

// .m文件的实现
#if __has_feature(objc_arc) // 是ARC
#define SingletonM(methodName) \
static id _instace = nil; \
+ (id)allocWithZone:(struct _NSZone *)zone \
{ \
if (_instace == nil) { \
static dispatch_once_t onceToken; \
dispatch_once(&onceToken, ^{ \
_instace = [super allocWithZone:zone]; \
}); \
} \
return _instace; \
} \
\
- (id)init \
{ \
static dispatch_once_t onceToken; \
dispatch_once(&onceToken, ^{ \
_instace = [super init]; \
}); \
return _instace; \
} \
\
+ (instancetype)shared##methodName \
{ \
return [[self alloc] init]; \
} \
+ (id)copyWithZone:(struct _NSZone *)zone \
{ \
return _instace; \
} \
\
+ (id)mutableCopyWithZone:(struct _NSZone *)zone \
{ \
return _instace; \
}

#else // 不是ARC

#define SingletonM(methodName) \
static id _instace = nil; \
+ (id)allocWithZone:(struct _NSZone *)zone \
{ \
if (_instace == nil) { \
static dispatch_once_t onceToken; \
dispatch_once(&onceToken, ^{ \
_instace = [super allocWithZone:zone]; \
}); \
} \
return _instace; \
} \
\
- (id)init \
{ \
static dispatch_once_t onceToken; \
dispatch_once(&onceToken, ^{ \
_instace = [super init]; \
}); \
return _instace; \
} \
\
+ (instancetype)shared##methodName \
{ \
return [[self alloc] init]; \
} \
\
- (oneway void)release \
{ \
\
} \
\
- (id)retain \
{ \
return self; \
} \
\
- (NSUInteger)retainCount \
{ \
return 1; \
} \
+ (id)copyWithZone:(struct _NSZone *)zone \
{ \
return _instace; \
} \
\
+ (id)mutableCopyWithZone:(struct _NSZone *)zone \
{ \
return _instace; \
}

使用:

//SmartSingleton.h
@interface SmartSingleton : NSObject
SingletonH(SmartSingleton)
@end

//SmartSingleton.m
@implementation SmartSingleton
SingletonM(SmartSingleton)
@end

//客户端调用
Singleton *singleton = [Singleton sharedInstance];
NSLog(@"%@", singleton);

SmartSingleton *smartSingleton = [SmartSingleton sharedSmartSingleton];
NSLog(@"%@", smartSingleton);

Cocoa中的单例

Cocoa中最常见的单例类是UIApplication类。它提供了一个控制并协调应用程序的集中点。

每个应用程序有且只有一个UIApplication实例。它由UIApplicationMain函数在应用程序启动时创建为单例对象。之后,对同一UIApplication实例可以通过sharedUIApplication类方法进行访问。

UIApplication对象为应用程序处理许多内务管理任务(housekeeping task),包括传入的用户时间的最初路由,以及为UIControl分发动作消息给合适的目标对象。它还卫华应用程序中打开的所有UIWindow对象的列表。应用程序对象总是被分配一个UIApplicationDelegate对象。应用程序将把重要的运行时事件通知给它,比如iOS应用程序中的应用程序启动、内存不足警告、应用程序终止和后台进程执行。这让代理(delegate)有机会作出适当的响应。

NSUserDefaultNSFileManager等也是常见的单例实现。

总结

只要应用程序需要用集中式的类来协调其服务,这个类就应生成单一的实例。

代码

文章中的代码都可以从我的GitHub DesignPatterns找到。

iOS成员属性和成员变量的区别

From: http://www.jianshu.com/p/55f781f8c915

一、@property 和@synthesizer

在objective-c 1.0中,我们为interface同时声明了属性和底层实例变量,那时,属性是oc语言的一个新的机制,并且要求你必须声明与之对应的实例变量,例如:

@interface MyViewController :UIViewController
{
    UIButton *myButton;
}
@property (nonatomic, retain) UIButton *myButton;
@end

在objective-c 2.0中,@property它将自动创建一个以下划线开头的实例变量。因此,在这个版本中,我们不再为interface声明实例变量。变成我们常见的形式

@interface MyViewController :UIViewController
@property (nonatomic, retain) UIButton *myButton;
@end

在MyViewController.m文件中,编译器也会自动的生成一个实例变量_myButton。那么在.m文件中可以直接的使用_myButton实例变量,也可以通过属性self.myButton.都是一样的。

注意这里的self.myButton其实是调用的myButton属性的getter/setter方法。这与C++中点的使用是有区别的,C++中的点可以直接访问成员变量(也就是实例变量)。

例如在oc的.h文件中有如下代码

@interface MyViewController :UIViewController
{
    NSString *name;
}

.m文件中,self.name 这样的表达式是错误的。xcode会提示你使用->,改成self->name就可以了。因为oc中点表达式是表示调用方法,而上面的代码中没有name这个方法。所以在oc中点表达式其实就是调用对象的setter和getter方法的一种快捷方式。

你可能还见过这种写法

#import "ViewController.h"

@interface ViewController ()
@property (nonatomic, strong) UIButton *myButton;
@end

@implementation ViewController
@synthesize myButton;

@synthesize 语句只能被用在 @implementation 代码段中,@synthesize的作用就是让编译器为你自动生成setter与getter方法,@synthesize 还有一个作用,可以指定与属性对应的实例变量,例如@synthesize myButton = xxx;那么self.myButton其实是操作的实例变量xxx,而不是_myButton了。

如果.m文件中写了@synthesize myButton;那么生成的实例变量就是myButton;如果没写@synthesize myButton;那么生成的实例变量就是_myButton。所以跟以前的用法还是有点细微的区别。

二、类别中的属性property

类与类别中添加的属性要区分开来,因为类别中只能添加方法,不能添加实例变量。经常会在ios的代码中看到在类别中添加属性,这种情况下,是不会自动生成实例变量的。比如在:UINavigationController.h文件中会对UIViewController类进行扩展

@interface UIViewController (UINavigationControllerItem)
@property(nonatomic,readonly,retain) UINavigationItem *navigationItem;
@property(nonatomic) BOOL hidesBottomBarWhenPushed;
@property(nonatomic,readonly,retain) UINavigationController *navigationController;
@end

这里添加的属性,不会自动生成实例变量,这里添加的属性其实是添加的getter与setter方法。注意一点,匿名类别(匿名扩展)是可以添加实例变量的,非匿名类别是不能添加实例变量的,只能添加方法,或者属性(其实也是方法),常用的扩展是在.m文件中声明私有属性和方法。 Category理论上不能添加变量,但是可以使用rRuntime机制来弥补这种不足。

#import
static const void * externVariableKey =&externVariableKey;
@implementation NSObject (Category)
@dynamic variable;
- (id) variable
{
       return objc_getAssociatedObject(self, externVariableKey);
}
- (void)setVariable:(id) variable
{
    objc_setAssociatedObject(self, externVariableKey, variable, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}

三、@private、@protect、@public

@protected是受保护的,只能在本类及其子类中访问,在{}声明的变量默认是@protect
@private是私有的,只能在本类访问
@public公开的,可以被在任何地方访问。
在头文件.h中:

@interface ViewController : UIViewController
{
// 成员变量
        @public
            NSString* publicString;

        @protected
            NSString* protectedString;

        @private
            NSString* privateString;
}
//属性变量
@property (nonatomic,strong) NSArray *propertyString;
@end
  • 成员变量用于类内部,无需与外界接触的变量。
  • 根据成员变量的私有性,为了方便访问,所以就有了属性变量。属性变量是用于与其他对象交互的变量。(属性变量的好处就是允许让其他对象访问到该变量。当然,你可以设置只读或者可写等,设置方法也可自定义。)

一些建议:
1.如果只是单纯的private变量,最好声明在implementation里.
2.如果是类的public属性,就用property写在.h文件里
3.如果自己内部需要setter和getter来实现一些东西,就在.m文件的类目里用property来声明

.h中的interface的大括号{}之间的实例(成员)变量,.m中可以直接使用;
.h中的property(属性)变量,.m中需要使用self.propertyVariable的方式使用propertyVariable变量

四、成员变量和成员属性的关系

  1. 属性对成员变量扩充了存取方法 .
  2. 属性默认会生成带下划线的成员变量 .
  3. 但只声明了变量,是不会有属性的,可以通过以下代码证明
    在Person.h 头文件中

    @interface Person : NSObject {
    @private
    //name为私有成员变量
    NSString name;
    }
    // age 为成员属性
    @property (nonatomic ,copy) NSString
    age;

在viewController.m 中,通过RunTime机制获得对象的所有成员变量和成员属性。

Person *p = [Person new];
unsigned int count = 0; //count记录变量的数量

// 获取类的所有成员变量
Ivar *members = class_copyIvarList([Person class], &count);
for (int i = 0; i < count; i++) {
    Ivar ivar = members[i];
    // 取得变量名并转成字符串类型
    const char *memberName = ivar_getName(ivar);
    NSLog(@"变量名 = %s",memberName);
}
// 获取类的所有成员属性
objc_property_t *properties =class_copyPropertyList([Person class], &count);
for (int i = 0; i<count; i++)
{
    objc_property_t property = properties[i];
    const char* char_f =property_getName(property);
    NSString *propertyName = [NSString stringWithUTF8String:char_f];
    NSLog(@"属性名 = %@",propertyName);
}

打印结果为

2016-08-12 11:31:50.225 modifyPrivate[777:143231] 变量名 = name
2016-08-12 11:31:50.226 modifyPrivate[777:143231] 变量名 = _age
2016-08-12 11:31:50.226 modifyPrivate[777:143231] 属性名 = age

关于Block的定义,和作为参数的写法

关于Block的定义,和作为参数的写法
转载 : http://www.dahuangphone.com/dispbbs.asp?boardid=8&id=85&page=3&star=1

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
Block的定义
Block的格式: ^ + 返回值类型(可以省略) + (参数)(如果没有参数,可以省略) + {表达式}
Block变量格式: 返回值类型(不可省略, 最少void) + (^变量名称) + (参数) (不可省略, 至少()). 格式和函数指针很相似,只是把*改成了^.
int (^blockName)(int, NSString*)=^(int para1, NSString *para2){
return 1;
};
//int (^blockName)(int, NSString*)的意思是要定义一个名字为"blockName"的block,它据有两个参数,具有一个int类型返回值.
void (^blockName)()=^{
};//如果没有参数,可省略如上面写法, 如果没有返回值, 必须要写void
使用typedef: (Block声明比较复杂, 建议使用这种方式生命Block)
typedef int(^blockName1)(int,NSString*);
blockName1 bn=^(int para1, NSString *para2){
return 1;
};
无参数情况:
typedef int(^blockName1)();
blockName1 bn=^{
return 1;
};
.......................................................................
作为函数参数写法:
c函数:
参数
typedef int(^ABlock)();
void cFunc(void(^blockName)(), ABlock block){//两种写法都可以
}
返回值写法
int (^fun())(int){
return ^(int cout){ return cout;};
}
ABlock fun(){
return ^(int cout){ return cout;};
}
OC函数
参数
-(void)OCFunc:(void(^)())blockName andOtherBlock:(ABlock)block{ //注意第一种写法的特别之处, OC函数要求变量类型和形参名分开, 所以写法和C不同
}
@property 写法
@property(nonatomic,copy) int (^block)(int a); //使用c的方式, 不能使用OC函数形参的写法.
@property (nonatomic,copy) Block blockName;
以Block作为方法返回值的写法:
-(int (^)(int))blockBack{ //和c的写法是不同的, 需要注意
return ^(int cout){ return cout;};
}
block不同其它变量的原因在于它不是一个单一变量, 而是一个方法,
我们要传递的是一个代码块,并且这个代码块可以存在参数,
这个参数并不是在定义block的时候就赋予值, 而是我们在实际运行block的时候才赋予值.
因此对于有参数的block,当我们传递过去的时候, 它的需要接收方提供相应的参数才能运行,
这么做我们就可以在A类为B类将来会发生的事件提前做好处理的方法,即使我们还没有这些事件的具体参数.
某种意义上将这样就不需要两者之间的委托关系.
委托关系就是B类发生一个事件后,通知A类,让A类再针对这个事件进行一些处理
而使用block,则是A已经提前将这个事件的处理方法告诉了B类, 等时间发生的时候, B类无需通知A类, 直接运行实现设置好的处理方法(block)即可.
如果你在运行一个方法的时候又想告诉这个方法在某一特定情况你还要怎么做的话, 就可以使用Block.
GCD:
GCD主要使用block来代替委托模式,使程序变得简洁,同时运行效率也得到提高.
dispatch_async(dispatch_get_global_queue(0, 0), ^{
static int i=0;
while (i<20) {
dispatch_async(dispatch_get_main_queue(), ^{
_label.text=[NSString stringWithFormat:@"%d",i++]; //UI的更新必须要在主线程完成
});
[NSThread sleepForTimeInterval:1];
}
});
NSLog(@"s");
这个函数意思是更新label的显示.每秒钟更新一次, 如果我不实用异步更新直接使用一个方法如下:
-(void)runLabel{
static int i=0;
while (i<20) {
_label.text=[NSString stringWithFormat:@"%d",i++];
[NSThread sleepForTimeInterval:1];
}
}
这回导致整个程序无法响应其它事件.
如果使用NSThread 完成则需要委托, 非常繁琐.
[此贴子已经被作者于2013/6/27 12:18:57编辑过]
2楼 dahuangphone 发表于:2013/4/30 1
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
block的基础知识
block是一个特殊的OC对象, 它建立在栈上, 而不是堆上, 这么做一个是为性能考虑,还有就是方便访问局部变量.
默认情况下block使用到的局部变量都会被复制,而不是保留.
所以它无法改变局部变量的值.
如果在变量面前加上__block, 那么编译器回去不会复制变量, 而是去找变量的地址, 通过地址来访问变量, 实际上就是直接操作变量.
另外块是在栈上分配的, 所以一旦离开作用域, 就会释放, 因此如果你要把快用在别的地方, 必须要复制一份.
所以在属性定义一个快的时候需要使用copy: @property (nonatomic, copy) void (^onTextEntered)(NSString *enteredText);
块是不能保留的, retain对块没有意义.
用块遍历字典:
[dict enumerateKeysAndObjectsUsingBlock:^(id key, id obj, BOOL *stop) {
NSLog(@"%@,%@",key,obj);
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
ARC下Block何时会从栈自动被复制到推, 以及__block和__weak的使用问题
由于Block是默认建立在栈上, 所以如果离开方法作用域, Block就会被丢弃, 在非ARC情况下, 我们要返回一个Block ,需要 [Block copy];
在ARC下, 以下几种情况, Block会自动被从栈复制到堆:
1.被执行copy方法
2.作为方法返回值
3.将Block赋值给附有__strong修饰符的id类型的类或者Blcok类型成员变量时
4.在方法名中含有usingBlock的Cocoa框架方法或者GDC的API中传递的时候.
对于非ARC下, 为了防止循环引用, 我们使用__block来修饰在Block中实用的对象:
__block id blockSelf=self;
self.blk=^{
NSLog(@"%@",blockSelf); //在非ARC下对于栈上的_block对象, Block不会对其复制, 仅仅使用, 不会增加引用计数.
};
对于ARC下, 为了防止循环引用, 我们使用__weak来修饰在Block中实用的对象:
__weak id weakSelf=self;
self.blk=^{
NSLog(@"%@",weakSelf);
};
如果要在ARC下, 为了防止循环引用, 使用__block来修饰在Block中实用的对象,仍然会被retain, 所以需要多做一些设置
__block id blockSelf=self;
self.blk=^{
NSLog(@"%@",blockSelf);
self.blk=nil; //blk被释放, blk只有的blockSelf也就被释放了
};
blk(); //并且一定要运行一次, 否则不能被释放
这样就使blk断开了与blockSelf的持有关系.
这么多好处是可以自己控制对self的持有时间.
不过在最新的ios版本中, 这些会始终被已叹号形式提示存在循环引用问题.
这种书写方式不被推荐. 除非你要在block中修改__block的指针指向.
其实我们用使用__weak修饰符, 只是不能修改对象本身, 但是可以修改对象的属性.

关于AFNetworking3.0+的使用

From: http://www.jianshu.com/p/5e187c9d389b

  1. 为了让刚接触AFNetworking的开发者能够顺序上手,本文专门对AFNetworking的使用方法进行了归纳总结,同时方便自己以后的学习。
  2. 由于本人水平有限, 极大可能会出现错误,所以阅读本文过程中如发现异议,还请各位耐心指教,共同探讨学习
  3. 本文参考各位前辈的经验,感谢各位大神的分享

AFNetworking 1.0建立在NSURLConnection的基础API之上 ,AFNetworking 2.0开始使用NSURLConnection的基础API ,以及较新基于NSURLSession的API的选项。 AFNetworking 3.0现已完全基于NSURLSession的API,这降低了维护的负担,同时支持苹果增强关于NSURLSession提供的任何额外功能。由于Xcode 7中,NSURLConnection的API已经正式被苹果弃用。虽然该API将继续运行,但将没有新功能将被添加,并且苹果已经通知所有基于网络的功能,以充分使NSURLSession向前发展。

3.1、 NSURLConnection的介绍

关于NSURLConnection的使用,本文不做详细的介绍,具体参考简书某位大神的介绍http://www.jianshu.com/p/f291ee58c012

3.2、 NSURLSession的介绍

NSURLSession的优点:

1.后台上传和下载。当你的程序退出了也能进行网络操作,这对用户和APP来说都是个好消息,不用运行APP就可以下载和上传,这样更节约手机电量。

2.能够暂停和恢复网络操作。不需要使用NSOperation就可以实现暂停、继续、重启等操作。

3.可配置的容器。

4.可以子类化并且可以设置私有存储方式。可以修改数据的存储方式和存储位置。

5.改进了授权处理机制。

6.代理更强大。

7.通过文件系统上传和下载。

创建NSURLSession对象的方法
1.使用静态的sharedSession方法,该类使用共享的会话,该会话使用全局的Cache,Cookie和证书。
+ (NSURLSession *)sharedSession;  
2.通过sessionWithConfiguration:方法创建对象,也就是创建对应配置的会话,与NSURLSessionConfiguration合作使用。
(NSURLSession *)sessionWithConfiguration:(NSURLSessionConfiguration *)configuration;  
3.通过sessionWithConfiguration:delegate:delegateQueue方法创建对象,二三两种方式可以创建一个新会话并定制其会话类型。该方式中指定了session的委托和委托所处的队列。当不再需要连接时,可以调用Session的invalidateAndCancel直接关闭,或者调用finishTasksAndInvalidate等待当前Task结束后关闭。这时Delegate会收到URLSession:didBecomeInvalidWithError:这个事件。Delegate收到这个事件之后会被解引用。
+ (NSURLSession *)sessionWithConfiguration:(NSURLSessionConfiguration *)configuration delegate:(id <NSURLSessionDelegate>)delegate delegateQueue:(NSOperationQueue *)queue;
NSURLSessionTask支持的三种任务

加载数据/下载/上传
NSURLSessionTask类
NSURLSessionTask是一个抽象类,它有三个子类

1.NSURLSessionDataTask
2.NSURLSessionUploadTask
3.NSURLSessionDownloadTask

这三个类封装了应用程序的三个基本网络任务:获取数据,比如JSON或XML,以及上传和下载文件。
继承关系如下:

屏幕快照 2016-08-12 13.36.02.png

NSURLSessionConfiguration配置信息

NSURLSessionConfiguration用于配置会话的属性,可以通过该类配置会话的工作模式:

NSURLSessionConfiguration *config = [NSURLSessionConfiguration defaultSessionConfiguration];
// 超时时间
config.timeoutIntervalForRequest = 10;
// 是否允许使用蜂窝网络(后台传输不适用)
config.allowsCellularAccess = YES;
// 还有很多可以设置的属性
//关于配置信息的实例化方法大概有三种
/*
//默认会话模式(default):工作模式类似于原来的NSURLConnection,使用的是基于磁盘缓存的持久化策略,使用用户keychain中保存的证书进行认证授权。
+ (NSURLSessionConfiguration *)defaultSessionConfiguration;
//瞬时会话模式(ephemeral):该模式不使用磁盘保存任何数据。所有和会话相关的caches,证书,cookies等都被保存在RAM中,因此当程序使会话无效,这些缓存的数据就会被自动清空。
+ (NSURLSessionConfiguration *)ephemeralSessionConfiguration;
//后台会话模式(background):该模式在后台完成上传和下载,在创建Configuration对象的时候需要提供一个NSString类型的ID用于标识完成工作的后台会话。
+ (NSURLSessionConfiguration *)backgroundSessionConfigurationWithIdentifier:(NSString *)identifier
*/
NSURLSessionTask相关方法
//suspend可以让当前的任务暂停
- (void)suspend;
//resume方法不仅可以启动任务,还可以唤醒suspend状态的任务
- (void)resume;
//cancel方法可以取消当前的任务,你也可以向处于suspend状态的任务发送cancel消息,任务如果被取消便不能再恢复到之前的状态.
- (void)cancel;

3.2.1、NSURLSessionDataTask简单GET请求

如果请求的数据比较简单,也不需要对返回的数据做一些复杂的操作.那么我们可以使用带block

// 快捷方式获得session对象
NSURLSession *session = [NSURLSession sharedSession];
/*GET请求将参数拼接在 url 后面
    网络接口 和 参数 以 ? 分隔. 参数和参数之间以 & 符号分隔.注意删除最后一个 & 符号.
    如:http://127.0.0.1/login.php?username=zhangsan&password=zhang
*/
NSURL *url = [NSURL URLWithString:@"http://www.daka.com/login?username=daka&pwd=123"];
// GET请求直接根据url实例化网络任务
/*
     第一个参数:请求路径:内部会自动将路径包装成请求对象
     第二个参数:completionHandler回调(请求完成【成功|失败】的回调)
     data:响应体信息(期望的数据)
     response:响应头信息,主要是对服务器端的描述
     error:错误信息,如果请求失败,则error有值
*/
NSURLSessionTask *task = [session dataTaskWithURL:url completionHandler:^(NSData *data, NSURLResponse *response, NSError error) {
       // 默认是子线程.
        NSLog(@"%@",[[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding]);
}];
// 开启任务
[task resume];
3.2.2、NSURLSessionDataTask简单POST请求

POST和GET的区别就在于request,所以使用session的POST请求和GET过程是一样的,区别就在于对request的处理.

NSURL *url = [NSURL URLWithString:@"http://www.daka.com/login"];
//创建可变请求对象
NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:url];
//POST请求将参数添加在请求体中
//设置请求方法
request.HTTPMethod = @"POST";
//设置请求体
request.HTTPBody = [@"username=daka&pwd=123" dataUsingEncoding:NSUTF8StringEncoding];

NSURLSession *session = [NSURLSession sharedSession];
/*
     第一个参数:请求对象
     第二个参数:completionHandler回调(请求完成【成功|失败】的回调)
     data:响应体信息(期望的数据)
     response:响应头信息,主要是对服务器端的描述
     error:错误信息,如果请求失败,则error有值
*/
// 由于要先对request先行处理,我们通过request初始化task
NSURLSessionTask *task = [session dataTaskWithRequest:request
                                   completionHandler:^(NSData *data, NSURLResponse *response, NSError *error) 
{
       // 默认是子线程.
        NSLog(@"%@",[[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding]);
}];
[task resume];
3.2.3、NSURLSessionDataTask的NSURLSessionDataDelegate代理方法

NSURLSession提供了block方式处理返回数据的简便方式,但如果想要在接收数据过程中做进一步的处理,仍然可以调用相关的协议方法.NSURLSession的代理方法和NSURLConnection有些类似,都是分为接收响应、接收数据、请求完成几个阶段.

// 使用代理方法需要设置代理,但是session的delegate属性是只读的,要想设置代理只能通过这种方式创建session
NSURLSession *session = [NSURLSession sessionWithConfiguration:[NSURLSessionConfiguration defaultSessionConfiguration] delegate:self delegateQueue:[[NSOperationQueue alloc] init]];
// 创建任务(因为要使用代理方法,就不需要block方式的初始化了)
NSURLSessionDataTask *task = [session dataTaskWithRequest:[NSURLRequest requestWithURL:[NSURL URLWithString:@"http://www.daka.com/login?userName=daka&pwd=123"]]];
// 启动任务
[task resume];
//对应的代理方法如下:
// 1.接收到服务器的响应
- (void)URLSession:(NSURLSession *)session dataTask:(NSURLSessionDataTask *)dataTask didReceiveResponse:(NSURLResponse *)response completionHandler:(void (^)(NSURLSessionResponseDisposition))completionHandler { 
       // 允许处理服务器的响应,才会继续接收服务器返回的数据      
      completionHandler(NSURLSessionResponseAllow);
}
// 2.接收到服务器的数据(可能调用多次)
- (void)URLSession:(NSURLSession *)session dataTask:(NSURLSessionDataTask *)dataTask didReceiveData:(NSData *)data { 
       // 处理每次接收的数据
}
// 3.请求成功或者失败(如果失败,error有值)
- (void)URLSession:(NSURLSession *)session task:(NSURLSessionTask *)task didCompleteWithError:(NSError *)error 
{
       // 请求完成,成功或者失败的处理
}
Tips:
关键点在代码注释里面都有提及,重要的地方再强调一下:

如果要使用代理方法,需要设置代理,但从NSURLSession的头文件发现session的delegate属性是只读的.因此设置代理要通过session的初始化方法赋值:sessionWithConfiguration:delegate:delegateQueue:其中:
configuration参数(文章开始提到的)需要传递一个配置,我们暂且使用默认的配置[NSURLSessionConfiguration defaultSessionConfiguration]就好(后面会说下这个配置是干嘛用的);
delegateQueue参数表示协议方法将会在哪个队列(NSOperationQueue)里面执行.
NSURLSession在接收到响应的时候要先对响应做允许处理:completionHandler(NSURLSessionResponseAllow);,才会继续接收服务器返回的数据,进入后面的代理方法.值得一提的是,如果在接收响应的时候需要对返回的参数进行处理(如获取响应头信息等),那么这些处理应该放在前面允许操作的前面.
3.2.4、NSURLSessionDownloadTask简单下载

NSURLSessionDownloadTask同样提供了通过NSURL和NSURLRequest两种方式来初始化并通过block进行回调的方法.下面以NSURL初始化为例:

NSURLSession *session = [NSURLSession sharedSession];
NSURL *url = [NSURL URLWithString:@"http://www.daka.com/resources/image/icon.png"] ;
NSURLSessionDownloadTask *task = [session downloadTaskWithURL:url completionHandler:^(NSURL *location, NSURLResponse *response, NSError *error) {
    // location是沙盒中tmp文件夹下的一个临时url,文件下载后会存到这个位置,由于tmp中的文件随时可能被删除,所以我们需要自己需要把下载的文件挪到需要的地方
    NSString *path = [[NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES) lastObject] stringByAppendingPathComponent:response.suggestedFilename];
    [[NSFileManager defaultManager] moveItemAtURL:location toURL:[NSURL fileURLWithPath:path] error:nil];
}];
    // 启动任务
[task resume];
/*
Tips:
需要注意的就是需要将下载到tmp文件夹的文件转移到需要的目录.原因在代码中已经贴出.
response.suggestedFilename是从相应中取出文件在服务器上存储路径的最后部分,如数据在服务器的url为http://www.daka.com/resources/image/icon.png, 那么其suggestedFilename就是icon.png.
*/
3.2.5、NSURLSessionDownloadTask的NSURLSessionDownloadDelegate代理方法
// 每次写入调用(会调用多次)
- (void)URLSession:(NSURLSession *)session downloadTask:(NSURLSessionDownloadTask *)downloadTask didWriteData:(int64_t)bytesWritten totalBytesWritten:(int64_t)totalBytesWritten totalBytesExpectedToWrite:(int64_t)totalBytesExpectedToWrite 
{
      // 可在这里通过已写入的长度和总长度算出下载进度
      CGFloat progress = 1.0 * totalBytesWritten / totalBytesExpectedToWrite; NSLog(@"%f",progress);
}
// 下载完成调用
- (void)URLSession:(NSURLSession *)session downloadTask:(NSURLSessionDownloadTask *)downloadTask didFinishDownloadingToURL:(NSURL *)location 
{ 
      // location还是一个临时路径,需要自己挪到需要的路径(caches下面) NSString *filePath = [[NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES) lastObject] stringByAppendingPathComponent:downloadTask.response.suggestedFilename]; [[NSFileManager defaultManager] moveItemAtURL:location toURL:[NSURL fileURLWithPath:filePath] error:nil];
}
// 任务完成调用
- (void)URLSession:(NSURLSession *)session task:(NSURLSessionTask *)task didCompleteWithError:(NSError *)error 
{
}
3.2.6、NSURLSessionDownloadTask断点下载
// 使用这种方式取消下载可以得到将来用来恢复的数据,保存起来
[self.task cancelByProducingResumeData:^(NSData *resumeData) {
    self.resumeData = resumeData;
}];
// 由于下载失败导致的下载中断会进入此协议方法,也可以得到用来恢复的数据
- (void)URLSession:(NSURLSession *)session task:(NSURLSessionTask *)task didCompleteWithError:(NSError *)error
{
    // 保存恢复数据
    self.resumeData = error.userInfo[NSURLSessionDownloadTaskResumeData];
}
// 恢复下载时接过保存的恢复数据
self.task = [self.session downloadTaskWithResumeData:self.resumeData];
// 启动任务
[self.task resume];

//程序强制退出就无法断点下载了,具体方法见http://blog.csdn.net/qianlima210210/article/details/49303703

3.2.7、NSURLSessionUploadTask

在NSURLSession中,文件上传方式主要有以下两种:

//第一种方式
NSURLSessionUploadTask *task =
[[NSURLSession sharedSession] uploadTaskWithRequest:request
                                           fromFile:fileName
                                  completionHandler:^(NSData *data, NSURLResponse *response, NSError *error) {
}];
//第二种方式
[self.session uploadTaskWithRequest:request
                            fromData:body
                   completionHandler:^(NSData *data, NSURLResponse *response, NSError *error) {
 NSLog(@"-------%@", [NSJSONSerialization JSONObjectWithData:data options:kNilOptions error:nil]);
 }];
/*
处于安全性考虑,通常我们会使用POST方式进行文件上传,所以较多使用第二种方式.
但是,NSURLSession并没有为我们提供比NSURLConnection更方便的文件上传方式.方法中body处的参数需要填写request的请求体(http协议规定格式的大长串).

关于NSURLSessionConfiguration的使用可以参考http://www.jianshu.com/p/fafc67475c73
以及http://www.tuicool.com/articles/VBv2qe

由于苹果已经弃用NSURLConnection,所以在此暂时只介绍AFNetworking3.0及3.0+以上的使用方法

4.1、AFNetworking的导入

本人习惯使用cocoaPods 进行管理第三方类库,导入过程不做详细介绍,不会的可以上网查看教程

4.2、简单GET请求

AFHTTPSessionManager *manager = [AFHTTPSessionManager manager]; 
[manager GET:URL parameters:nil progress:^(NSProgress * _Nonnull downloadProgress) {

 } success:^(NSURLSessionDataTask * _Nonnull task, id _Nullable responseObject) {
             NSLog(@"这里打印请求成功要做的事");
 }failure:^(NSURLSessionDataTask * _Nullable task, NSError * _Nonnull error) { 
             NSLog(@"%@",error); //这里打印错误信息
}];

4.3、简单POST请求

AFHTTPSessionManager *manager = [AFHTTPSessionManager manager];
NSMutableDictionary *parameters = @{@"":@"",@"":@""};
[manager POST:URL parameters:parameters progress:^(NSProgress * _Nonnull uploadProgress) {

} success:^(NSURLSessionDataTask * _Nonnull task, id _Nullable responseObject) {

} failure:^(NSURLSessionDataTask * _Nullable task, NSError * _Nonnull error) {

}];

4.4、下载

- (void)downLoad{ 
    //1.创建管理者对象 
    AFHTTPSessionManager *manager = [AFHTTPSessionManager manager]; 
    //2.确定请求的URL地址
    NSURL *url = [NSURL URLWithString:@""];
    //3.创建请求对象 
    NSURLRequest *request = [NSURLRequest requestWithURL:url]; 
    //4.下载任务 
    NSURLSessionDownloadTask *task = [manager downloadTaskWithRequest:request progress:^(NSProgress * _Nonnull downloadProgress) {
           //打印下下载进度 
           NSLog(@"%lf",1.0 * downloadProgress.completedUnitCount / downloadProgress.totalUnitCount);
    } destination:^NSURL * _Nonnull(NSURL * _Nonnull targetPath, NSURLResponse * _Nonnull response) { 
           //下载地址 
           NSLog(@"默认下载地址:%@",targetPath);
           //设置下载路径,通过沙盒获取缓存地址,最后返回NSURL对象 
           NSString *filePath = [NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES)lastObject];
           return [NSURL URLWithString:filePath];
    } completionHandler:^(NSURLResponse * _Nonnull response, NSURL * _Nullable filePath, NSError * _Nullable error) {
           //下载完成调用的方法 WKNSLog(@"下载完成:"); 
           NSLog(@"%@--%@",response,filePath);
    }];
 //开始启动任务 
[task resume];
}

4.5、上传

//第一种方法是通过工程中的文件进行上传
- (void)upLoad1{

    //1。创建管理者对象
    AFHTTPSessionManager *manager = [AFHTTPSessionManager manager];

    //2.上传文件
    NSDictionary *dict = @{@"username":@"1234"};

    NSString *urlString = @"22222";
    [manager POST:urlString parameters:dict constructingBodyWithBlock:^(id<AFMultipartFormData>  _Nonnull formData) {
        //上传文件参数
        UIImage *iamge = [UIImage imageNamed:@"123.png"];
        NSData *data = UIImagePNGRepresentation(iamge);
        //这个就是参数
        [formData appendPartWithFileData:data name:@"file" fileName:@"123.png" mimeType:@"image/png"];

    } progress:^(NSProgress * _Nonnull uploadProgress) {

        //打印下上传进度
        WKNSLog(@"%lf",1.0 *uploadProgress.completedUnitCount / uploadProgress.totalUnitCount);
    } success:^(NSURLSessionDataTask * _Nonnull task, id  _Nullable responseObject) {

        //请求成功
        WKNSLog(@"请求成功:%@",responseObject);

    } failure:^(NSURLSessionDataTask * _Nullable task, NSError * _Nonnull error) {
        //请求失败
        WKNSLog(@"请求失败:%@",error);
    }];

}

//第二种是通过URL来获取路径,进入沙盒或者系统相册等等
- (void)upLoda2{
    //1.创建管理者对象
    AFHTTPSessionManager *manager = [AFHTTPSessionManager manager];
    //2.上传文件
    NSDictionary *dict = @{@"username":@"1234"};

    NSString *urlString = @"22222";
    [manager POST:urlString parameters:dict constructingBodyWithBlock:^(id<AFMultipartFormData>  _Nonnull formData) {

        [formData appendPartWithFileURL:[NSURL fileURLWithPath:@"文件地址"] name:@"file" fileName:@"1234.png" mimeType:@"application/octet-stream" error:nil];
    } progress:^(NSProgress * _Nonnull uploadProgress) {

        //打印下上传进度
        WKNSLog(@"%lf",1.0 *uploadProgress.completedUnitCount / uploadProgress.totalUnitCount);
    } success:^(NSURLSessionDataTask * _Nonnull task, id  _Nullable responseObject) {

        //请求成功
        WKNSLog(@"请求成功:%@",responseObject);

    } failure:^(NSURLSessionDataTask * _Nullable task, NSError * _Nonnull error) {

        //请求失败
        WKNSLog(@"请求失败:%@",error);
    }];
}

4.6、 网络监听

- (void)AFNetworkStatus{ 
       //1.创建网络监测者
       AFNetworkReachabilityManager *manager = [AFNetworkReachabilityManager sharedManager];
       /*枚举里面四个状态 分别对应 未知 无网络 数据 WiFi 
       typedef NS_ENUM(NSInteger, AFNetworkReachabilityStatus) {          
               AFNetworkReachabilityStatusUnknown = -1, 未知          

               AFNetworkReachabilityStatusNotReachable = 0, 无网络 

               AFNetworkReachabilityStatusReachableViaWWAN = 1, 蜂窝数据网络 

               AFNetworkReachabilityStatusReachableViaWiFi = 2, WiFi
       }; 
       */ 
      [manager setReachabilityStatusChangeBlock:^(AFNetworkReachabilityStatus status) { 
               //这里是监测到网络改变的block 可以写成switch方便 
              //在里面可以随便写事件 
              switch (status)
             { 
                 case AFNetworkReachabilityStatusUnknown: 
                      NSLog(@"未知网络状态"); 
                      break;
                 case AFNetworkReachabilityStatusNotReachable: 
                      NSLog(@"无网络");
                      break; 
                 case AFNetworkReachabilityStatusReachableViaWWAN:             
                      NSLog(@"蜂窝数据网"); 
                      break;
                 case AFNetworkReachabilityStatusReachableViaWiFi: 
                      NSLog(@"WiFi网络");
                      break; 
                 default:
                      break;
            }
      }] ;
}

4.7、 关于请求、返回格式设置

所有的网络请求,均有manager发起

4.7.1、请求格式(requestSerializer)

需要注意的是,默认提交请求的数据是二进制的,返回格式是JSON
如果提交数据是JSON的,需要将请求格式设置为AFJSONRequestSerializer

 //AFHTTPRequestSerializer            二进制格式(默认请求格式)
    manager.requestSerializer = [AFHTTPRequestSerializer serializer];
//AFJSONRequestSerializer            JSON
    manager.requestSerializer = [AFJSONRequestSerializer serializer];
 //AFPropertyListRequestSerializer    PList(是一种特殊的XML,解析起来相对容易)
    manager.requestSerializer = [AFPropertyListRequestSerializer serializer];
4.7.2、 返回格式
 //AFHTTPResponseSerializer           二进制格式
   manager.responseSerializer = [AFHTTPResponseSerializer serializer];
 //AFJSONResponseSerializer           JSON(默认情况下返回json,所以有时后返回的不是json,就要重新设置返回格式)
   manager.responseSerializer = [AFJSONResponseSerializer serializer];
 //AFXMLParserResponseSerializer       XML,只能返回XMLParser,还需要自己通过代理方法解析(下面将介绍用NSXMLParser解析xml)
   manager.responseSerializer = [AFXMLParserResponseSerializer serializer];
 //AFXMLDocumentResponseSerializer (Mac OS X)
 //AFPropertyListResponseSerializer   PList
 //AFImageResponseSerializer          Image
 //AFCompoundResponseSerializer       组合

 //通过acceptableContentTypes可以添加接收的类型,如果没有设置,出错情况下会提示,具体参考http://www.jianshu.com/p/212a128c9a33,可以在AFURLResponseSerialization.m源代码中添加接收的类型
 /*
 - (instancetype)init {
      self = [super init];
      if (!self) {
      return nil;
  }
      self.acceptableContentTypes = [NSSet setWithObjects:@"application/json", @"text/json", @"text/javascript", nil];
      return self; 
 }
*/
 manager.responseSerializer.acceptableContentTypes = [NSSet setWithObject:@"application/xml"];

关于该部分的理解,可以参考http://www.jianshu.com/p/3aa19c12000a

    AFHTTPSessionManager *manager = [AFHTTPSessionManager manager];
    manager.responseSerializer = [AFXMLParserResponseSerializer serializer];
    manager.responseSerializer.acceptableContentTypes = [NSSet setWithObject:@"application/xml"];
    manager.requestSerializer = [AFJSONRequestSerializer serializer];
   //afn默认发起的是异步请求
   [manager GET:@"http://localhost/sources/videos.xml" parameters:nil progress:nil success:^(NSURLSessionDataTask * _Nonnull task, id  _Nullable responseObject) {
        NSLog(@"%@",[NSThread currentThread]);//打印结果是主线程,也就是说,异步请求之后,自动返回主线程
//        NSLog(@"%@",responseObject);
        if ([responseObject isKindOfClass:[NSXMLParser class]]) {
            NSXMLParser *parser = responseObject;
            parser.delegate = self;
            dispatch_async(dispatch_get_global_queue(0, 0), ^{
                [parser parse];
            });
        }

    } failure:^(NSURLSessionDataTask * _Nullable task, NSError * _Nonnull error) {
        NSLog(@"%@",error.localizedDescription);
    }];

-(void)parserDidStartDocument:(NSXMLParser *)parser{
    NSLog(@"打开文档,准备开始解析");
}
-(void)parser:(NSXMLParser *)parser didStartElement:(NSString *)elementName namespaceURI:(NSString *)namespaceURI qualifiedName:(NSString *)qName attributes:(NSDictionary<NSString *,NSString *> *)attributeDict{
    NSLog(@"startElement=%@ attributeDict=%@",elementName,attributeDict);
    if ([elementName isEqualToString:@"video"]) {
        self.currentVideo = [[XFSVideo alloc]init];
        self.currentVideo.videoId = attributeDict[@"videoId"];
    }
}
-(void)parser:(NSXMLParser *)parser foundCharacters:(NSString *)string{
    NSLog(@"foundCharacters=%@",string);
    [self.elementString appendString:string];
}
-(void)parser:(NSXMLParser *)parser didEndElement:(NSString *)elementName namespaceURI:(NSString *)namespaceURI qualifiedName:(NSString *)qName{
    NSLog(@"endelement=%@",elementName);
    NSLog(@"%@",[NSThread currentThread]);
    if ([elementName isEqualToString:@"video"]) {
        [self.videos addObject:self.currentVideo];
        self.currentVideo = nil;
    }else if (![elementName isEqualToString:@"videos"]){
        [self.currentVideo setValue:self.elementString forKeyPath:elementName];
        self.elementString = nil;
    }
}
-(void)parserDidEndDocument:(NSXMLParser *)parser{
    NSLog(@"%@",self.videos);
    NSLog(@"结束文档");
    dispatch_async(dispatch_get_main_queue(), ^{ 
    });
}