解决Xcode8 debug输出不想管信息

打开Xcode,如下图进行操作即可。

添加 key:OS_ACTIVITY_MODE value:disable

05/17/2017 14:48 下午 posted in  Xcode

get the iOS device CPU architecture in runtime

from:http://stackoverflow.com/questions/19859388/how-can-i-get-the-ios-device-cpu-architecture-in-runtime

#include <sys/types.h>
#include <sys/sysctl.h>
#include <mach/machine.h>

NSString *getCPUType(void)
{
    NSMutableString *cpu = [[NSMutableString alloc] init];
    size_t size;
    cpu_type_t type;
    cpu_subtype_t subtype;
    size = sizeof(type);
    sysctlbyname("hw.cputype", &type, &size, NULL, 0);

    size = sizeof(subtype);
    sysctlbyname("hw.cpusubtype", &subtype, &size, NULL, 0);

    // values for cputype and cpusubtype defined in mach/machine.h
    if (type == CPU_TYPE_X86)
    {
            [cpu appendString:@"x86 "];
             // check for subtype ...

    } else if (type == CPU_TYPE_ARM)
    {
            [cpu appendString:@"ARM"];
            switch(subtype)
            {
                    case CPU_SUBTYPE_ARM_V7:
                    [cpu appendString:@"V7"];
                    break;
                    // ...
            }
    }
    return [cpu autorelease];
}
- (NSString *)getCPUType {
      NSMutableString *cpu = [[NSMutableString alloc] init];
      size_t size;
      cpu_type_t type;
      cpu_subtype_t subtype;
      size = sizeof(type);
      sysctlbyname("hw.cputype", &type, &size, NULL, 0);

      size = sizeof(subtype);
      sysctlbyname("hw.cpusubtype", &subtype, &size, NULL, 0);

      // values for cputype and cpusubtype defined in mach/machine.h
      if (type == CPU_TYPE_X86_64) {
          [cpu appendString:@"x86_64"];
      } else if (type == CPU_TYPE_X86) {
          [cpu appendString:@"x86"];
      } else if (type == CPU_TYPE_ARM) {
          [cpu appendString:@"ARM"];
          switch(subtype)
          {
              case CPU_SUBTYPE_ARM_V6:
                  [cpu appendString:@"V6"];
                  break;
              case CPU_SUBTYPE_ARM_V7:
                  [cpu appendString:@"V7"];
                  break;
              case CPU_SUBTYPE_ARM_V8:
                  [cpu appendString:@"V8"];
                  break;
          }
      }
      return cpu; 
  }
05/03/2017 15:53 下午 posted in  apple

Enum-枚举的正确使用-Effective-Objective-C-读书笔记-Item-5

##前言

Enum,也就是枚举,从C语言开始就有了,C++、Java、Objective-C、Swift这些语言,当然都有对应的枚举类型,功能可能有多有少,但是最核心的还是一个—规范的定义代码中的状态、选项等“常量”。

Item 5 - Use Enumerations for States, Options, and Status Codes

本节的内容就是如何正确的使用枚举。

##状态与选项的区别(states and options)

在用enum之前,我个人觉得,区分一下状态和选项的概念还是很必要的。

状态,同时只能有一种,如“OK”,“Error”,不可能同时是OK和Error。
选项,同时可以有一种或一种以上,如App可以同时支持横屏和竖屏,横屏竖屏在这个时候就是“屏幕方向”的两种不同的选项。

接下来,我们看看如何用枚举定义状态和选项。

##enum与状态(states)

不好的做法
经常看到这样的写法:

#define STATE_OK 0
#define STATE_ERROR 1
#define STATE_UNKNOW 2
//直接用int型变量接收
int STATE = STATE_UNKNOW;

这样做有如下“不恰当”:

  • 宏定义没有类型约束,只是单纯的替换。
  • 无法限制状态的所有情况,如,认为的将STATE赋值成3,程序可能就会出错,找不到匹配的状态,因为编译器不会对“STATE = 3;”提出警告。

正确的做法

typedef enum _TTGState {
    TTGStateOK  = 0,
    TTGStateError,
    TTGStateUnknow
} TTGState;
//指明枚举类型
TTGState state = TTGStateOK;

用的时候就如下:

- (void)dealWithState:(TTGState)state {
    switch (state) {
        case TTGStateOK:
            //...
            break;
        case TTGStateError:
            //...
            break;
        case TTGStateUnknow:
            //...
            break;
    }
}

##enum与选项 (options)

选项,就是说一个“选项变量”的类型要能够同时表示一个或多个组合的选择,如下例子:

//方向,可同时支持一个或多个方向
typedef enum _TTGDirection {
    TTGDirectionNone = 0,
    TTGDirectionTop = 1 << 0,
    TTGDirectionLeft = 1 << 1,
    TTGDirectionRight = 1 << 2,
    TTGDirectionBottom = 1 << 3
} TTGDirection;

看,这里的选项是用位运算的方式定义的,这样的好处就是,我们的选项变量可以如下表示:

//用“或”运算同时赋值多个选项
TTGDirection direction = TTGDirectionTop | TTGDirectionLeft | TTGDirectionBottom;
//用“与”运算取出对应位
if (direction & TTGDirectionTop) {
    NSLog(@"top");
}
if (direction & TTGDirectionLeft) {
    NSLog(@"left");
}
if (direction & TTGDirectionRight) {
    NSLog(@"right");
}
if (direction & TTGDirectionBottom) {
    NSLog(@"bottom");
}

direction变量的实际内存如下:

这样,用位运算,就可以同时支持多个值。

enum在Objective-C中的“升级版”

一般来说,我们不能指定枚举变量的实际类型是什么,就是说,我们不知道枚举最后是int型,还是其他的什么类型。但是从C++ 11开始,我们可以为枚举指定其实际的存储类型,如下语法:

enum TTGState : NSInteger {/*...*/};

但是,我们在定义枚举的时候如何保证兼容性呢?Foundation框架已经为我们提供了更加“统一、便捷”的枚举定义方法,我们重新定义上面的例子:

//NS_ENUM,定义状态等普通枚举
typedef NS_ENUM(NSUInteger, TTGState) {
    TTGStateOK = 0,
    TTGStateError,
    TTGStateUnknow
};
//NS_OPTIONS,定义选项
typedef NS_OPTIONS(NSUInteger, TTGDirection) {
    TTGDirectionNone = 0,
    TTGDirectionTop = 1 << 0,
    TTGDirectionLeft = 1 << 1,
    TTGDirectionRight = 1 << 2,
    TTGDirectionBottom = 1 << 3
};

所以,在开发Mac、iOS程序中,最好所有的枚举都用“NS_ENUM”和“NS_OPTIONS”定义,保证统一。

##总结

充分的用好枚举,可以增强代码的可读性,减少各种“错误”,让代码更加的规范。

04/28/2017 16:46 下午 posted in  apple

iOS给UIimage添加圆角的两种方式

众所周知,给图片添加圆角有CALayer的cornerRadius,

比如:

最直接的方法:

imgView.layer.cornerRadius1=110;
imgView.clipsToBounds = YES;

这事离屏渲染 (off - screen - rendering), 是很消耗性能的;有很多公司面试的时候会问到,你怎么将图片设置圆角,如果你

只回答了这个方法,那么很遗憾,没有加分。

下面我介绍一种更好的方法:

#import "Bys.h"

@implementation Bys

- (UIImage*)imageWithCornerRadius:(CGFloat)radius{
   CGRect rect = (CGRect){0.f,0.f,self.size};
   // void UIGraphicsBeginImageContextWithOptions(CGSize size, BOOL opaque, CGFloat scale);
   //size——同UIGraphicsBeginImageContext,参数size为新创建的位图上下文的大小
   //    opaque—透明开关,如果图形完全不用透明,设置为YES以优化位图的存储。
   //    scale—–缩放因子
   UIGraphicsBeginImageContextWithOptions(self.size, NO, [UIScreen mainScreen].scale);
   //根据矩形画带圆角的曲线
   CGContextAddPath(UIGraphicsGetCurrentContext(), [UIBezierPath bezierPathWithRoundedRect:rect cornerRadius:radius].CGPath);
   [self drawInRect:rect];
   //图片缩放,是非线程安全的
   UIImage * image = UIGraphicsGetImageFromCurrentImageContext();
   //关闭上下文
   UIGraphicsEndImageContext();
   return image;
}

给UIImage添加生成圆角图片的扩展API: 这是on-screen-rendering

04/27/2017 16:02 下午 posted in  UIKit

ReactiveCocoa(RAC)框架

以下是RAC的Github主页:ReactiveCocoa
以及官方给出的用法链接

本文转载于:http://yimouleng.com/2015/12/20/ios-ReactiveCocoa/

##第一部分 简单使用

文本框事件
原来我们在使用textFiled的时候我们需要写到

[textField addTarget:self action:@selector(textChanged:) forControlEvents:UIControlEventEditingChanged];

然后实现textChanged:方法,在RAC中,对于文本框的监听,是非常简单的一件事情,看如下代码:

UITextField * textField = ({
       UITextField * textField = [[UITextField alloc]init];
       textField.backgroundColor = [UIColor cyanColor];
       
       textField;
   });
  [self.view addSubview:textField];
   
   @weakify(self); //  __weak __typeof__(self) self_weak_ = self;
   
   [textField mas_makeConstraints:^(MASConstraintMaker *make) {
       
       @strongify(self);    // __strong __typeof__(self) self = self_weak_;
       make.size.mas_equalTo(CGSizeMake(180, 40));
       make.center.equalTo(self.view);
   }];
   
   [[textField rac_signalForControlEvents:UIControlEventEditingChanged]
    subscribeNext:^(id x) {
        LxDBAnyVar(x);
    }];
   [textField.rac_textSignal subscribeNext:^(NSString *x) {
       LxDBAnyVar(x);
   }];

打印结果:

📍__31-[ViewController textFiledTest]_block_invoke_2 + 215🎈 x = 12
📍__31-[ViewController textFiledTest]_block_invoke241 + 211🎈 x = <UITextField: 0x7fe810c51a90; frame = (97.5 313.5; 180 40); text = '123'; clipsToBounds = YES; opaque = NO; gestureRecognizers = <NSArray: 0x7fe810f58fb0>; layer = <CALayer: 0x7fe810c51600>>
📍__31-[ViewController textFiledTest]_block_invoke_2 + 215🎈 x = 123
📍__31-[ViewController textFiledTest]_block_invoke241 + 211🎈 x = <UITextField: 0x7fe810c51a90; frame = (97.5 313.5; 180 40); text = '1231'; clipsToBounds = YES; opaque = NO; gestureRecognizers = <NSArray: 0x7fe810f58fb0>; layer = <CALayer: 0x7fe810c51600>>
📍__31-[ViewController textFiledTest]_block_invoke_2 + 215🎈 x = 1231
📍__31-[ViewController textFiledTest]_block_invoke241 + 211🎈 x = <UITextField: 0x7fe810c51a90; frame = (97.5 313.5; 180 40); text = '12312'; clipsToBounds = YES; opaque = NO; gestureRecognizers = <NSArray: 0x7fe810f58fb0>; layer = <CALayer: 0x7fe810c51600>>
📍__31-[ViewController textFiledTest]_block_invoke_2 + 215🎈 x = 12312
📍__31-[ViewController textFiledTest]_block_invoke241 + 211🎈 x = <UITextField: 0x7fe810c51a90; frame = (97.5 313.5; 180 40); text = '123123'; clipsToBounds = YES; opaque = NO; gestureRecognizers = <NSArray: 0x7fe810f58fb0>; layer = <CALayer: 0x7fe810c51600>>
📍__31-[ViewController textFiledTest]_block_invoke_2 + 215🎈 x = 123123

我们很容易的监听到textFiled中发生的变化,其中x的类型默认为id类型, 我们已知它的类型的时候我们可以将其改变,就像上面代码,将id改成了NSString类型。

手势

self.view.userInteractionEnabled = YES;
    UITapGestureRecognizer * tap = [[UITapGestureRecognizer alloc]init];
    [[tap rac_gestureSignal] subscribeNext:^(UITapGestureRecognizer * tap) {
        LxDBAnyVar(tap);
    }];
    [self.view addGestureRecognizer:tap];

为了方便,我们直接添加到self.view上,点击屏幕,得到打印结果:

📍__29-[ViewController gestureTest]_block_invoke + 184🎈 tap = <UITapGestureRecognizer: 0x7fa2e3e1f9f0; state = Ended; view = <UIView 0x7fa2e3e20b70>; target= <(action=sendNext:, target=<RACPassthroughSubscriber 0x7fa2e3c064f0>)>>

通知

[[[NSNotificationCenter defaultCenter] rac_addObserverForName:UIApplicationDidEnterBackgroundNotification object:nil] subscribeNext:^(NSNotification * notification) {
       
       LxDBAnyVar(notification);
   }];

我们建立了一个通知,叫做进入后台, 当程序进入后台的时候通知相应,当我们用RAC写通知的时候,我们有一个好处,就是不用removeObserver通知,因为RAC通知的监听者师RAC自己,它会帮你管理释放方法。可以看方法实现如下:

- (RACSignal *)rac_addObserverForName:(NSString *)notificationName object:(id)object {
	@unsafeify(object);
	return [[RACSignal createSignal:^(id<RACSubscriber> subscriber) {
		@strongify(object);
		id observer = [self addObserverForName:notificationName object:object queue:nil usingBlock:^(NSNotification *note) {
			[subscriber sendNext:note];
		}];

		return [RACDisposable disposableWithBlock:^{
			[self removeObserver:observer];
		}];
	}] setNameWithFormat:@"-rac_addObserverForName: %@ object: <%@: %p>", notificationName, [object class], object];

###定时器

//1. 延迟某个时间后再做某件事
[[RACScheduler mainThreadScheduler]afterDelay:2 schedule:^{
    LxPrintAnything(rac);
}];

//2. 每间隔多长时间做一件事
[[RACSignal interval:1 onScheduler:[RACScheduler mainThreadScheduler]]subscribeNext:^(NSDate * date) {
    
    LxDBAnyVar(date);
}];

这是定时器最常用的两种写法,第一种方法,延迟时间去做某件事,更改afterDelay的属性。
第二种方法,每间隔多长时间做一件事,更改interval属性。

###代理

UIAlertView * alertView = [[UIAlertView alloc]initWithTitle:@"RAC" message:@"ReactiveCocoa" delegate:self cancelButtonTitle:@"Cancel" otherButtonTitles:@"Ensure", nil];
   
   [[self rac_signalForSelector:@selector(alertView:clickedButtonAtIndex:) fromProtocol:@protocol(UIAlertViewDelegate)] subscribeNext:^(RACTuple * tuple) {
       
       LxDBAnyVar(tuple);
       
       LxDBAnyVar(tuple.first);
       LxDBAnyVar(tuple.second);
       LxDBAnyVar(tuple.third);
   }];
   [alertView show];
   //	更简单的方式:
   [[alertView rac_buttonClickedSignal]subscribeNext:^(id x) {
       
       LxDBAnyVar(x);
   }];

用RAC去写代理的时候,会有局限,只能取代没有返回值的代理方法,什么是没有返回值的代理呢?比如说tableView的代理方法:

- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath

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

这两个方法一个返回的是CGFloat,一个是void,RAC只能取代void的代理。

###KVO

UIScrollView * scrollView = [[UIScrollView alloc]init];
scrollView.delegate = (id<UIScrollViewDelegate>)self;
[self.view addSubview:scrollView];

UIView * scrollViewContentView = [[UIView alloc]init];
scrollViewContentView.backgroundColor = [UIColor yellowColor];
[scrollView addSubview:scrollViewContentView];

@weakify(self);

[scrollView mas_makeConstraints:^(MASConstraintMaker *make) {
    
    @strongify(self);
    make.edges.equalTo(self.view).insets(UIEdgeInsetsMake(80, 80, 80, 80));
}];

[scrollViewContentView mas_makeConstraints:^(MASConstraintMaker *make) {
    
    @strongify(self);
    make.edges.equalTo(scrollView);
    make.size.mas_equalTo(CGSizeMake(CGRectGetWidth(self.view.frame), CGRectGetHeight(self.view.frame)));
}];

[RACObserve(scrollView, contentOffset) subscribeNext:^(id x) {
   
    LxDBAnyVar(x);
}];

用RAC写KVO的好处就是方法简单,keypath有代码提示。

##第二部分 进阶

###信号

- (RACSignal *)loginSignal
  {
      return [RACSignal createSignal:^RACDisposable *(id<RACSubscriber> subscriber) {
          
          RACDisposable * schedulerDisposable = [[RACScheduler mainThreadScheduler]afterDelay:2 schedule:^{
             
              if (arc4random()%10 > 1) {
                  
                  [subscriber sendNext:@"Login response"];
                  [subscriber sendCompleted];
              }
              else {
                  
                  [subscriber sendError:[NSError errorWithDomain:@"LOGIN_ERROR_DOMAIN" code:444 userInfo:@{}]];
              }
          }];
          
          return [RACDisposable disposableWithBlock:^{
              
              [schedulerDisposable dispose];
          }];
      }];
  }

RAC的核心就是RACSignal,也就是信号,我们可以直接创建信号createSignal,并发送它sendNext,当信号完成后我们同时用dispose方法销毁它。发送信号,我们同时也要订阅信号,订阅信号代码如下:

[signal subscribeNext:^(id x) {
     
     LxDBAnyVar(x);
 } error:^(NSError *error) {
     
     LxDBAnyVar(error);
 } completed:^{
     
     LxPrintAnything(completed);
 }];

在信号发送的时候, 错误的时候,以及完成的时候,我们都可以得到相应。

##信号的处理

###map (映射)

UITextField * textField = ({
        UITextField * textField = [[UITextField alloc]init];
        textField.backgroundColor = [UIColor cyanColor];
        
        textField;
    });
    [self.view addSubview:textField];
    
    @weakify(self); //  __weak __typeof__(self) self_weak_ = self;
    
    [textField mas_makeConstraints:^(MASConstraintMaker *make) {
        
        @strongify(self);    // __strong __typeof__(self) self = self_weak_;
        make.size.mas_equalTo(CGSizeMake(180, 40));
        make.center.equalTo(self.view);
    }];

    [[textField.rac_textSignal map:^id(NSString *text) {
        
       LxDBAnyVar(text);
        
        return @(text.length);
        
    }] subscribeNext:^(id x) {
         LxDBAnyVar(x);
    }];

map这个函数,在这里不是地图的意思,代表映射。map能做的事情就是把监听的rac_textSignal所返回的值,替换成别的就像上面代码中的text的长度。

###filter

为了方便演示,我就不再赋值创建textField的代码了,请到Demo中查看

[[[textField.rac_textSignal map:^id(NSString *text) {
        
       LxDBAnyVar(text);
        
        return @(text.length);
        
    }]filter:^BOOL(NSNumber *value) {
        
        return value.integerValue > 3;
        
    }] subscribeNext:^(id x) {
         LxDBAnyVar(x);
    }];

filter是个BOOL值,它代表的是一个条件,当这个条件发生的时候才会作出相应,比如上面代码中,当长度大于3的时候,才会打印x的值。

###delay

//创建信号
    RACSignal * signal = [[RACSignal createSignal:^RACDisposable *(id<RACSubscriber> subscriber) {
        [subscriber sendNext:@"rac"];
        [subscriber sendCompleted];
        return nil;
    }]delay:2];
    LxPrintAnything(start);
    //创建订阅者
    [signal subscribeNext:^(id x) {
        LxDBAnyVar(x);
    }];

delay的作用就是延迟,或者说等待,如上,等待2秒之后打印了x。

###startWith

RACSignal * signal = [[RACSignal createSignal:^RACDisposable *(id<RACSubscriber> subscriber) {
        
//        [subscriber sendNext:@"123"];//startWith:@"123"等同于这句话 也就是第一个发送,主要是位置
        [subscriber sendNext:@"rac"];
        [subscriber sendCompleted];
        return nil;
    }]startWith:@"123"];
    LxPrintAnything(start);
    //创建订阅者
    [signal subscribeNext:^(id x) {
        LxDBAnyVar(x);
    }];

startWith也就是最开始的意思,看以上代码 startWith:@"123"等同于[subscriber sendNext:@"123"] 也就是第一个发送,主要是位置.

###timeOut

[[[RACSignal createSignal:^RACDisposable *(id<RACSubscriber> subscriber) {
        
        [[RACScheduler mainThreadScheduler]afterDelay:3 schedule:^{
            
            [subscriber sendNext:@"rac"];
            [subscriber sendCompleted];
        }];
        
        return nil;
    }] timeout:2 onScheduler:[RACScheduler mainThreadScheduler]]
     subscribeNext:^(id x) {
         
         LxDBAnyVar(x);
     } error:^(NSError *error) {
         
         LxDBAnyVar(error);
     } completed:^{
         
         LxPrintAnything(completed);
     }];
     ```

上面代码的意思就是,我设置了超时限制为(timeout)2秒钟,但是我代码延迟3秒钟发送,超时了,所以这条信息发生错误,会走error的方法。 这种情况可以用在封装http client中,当然你可能遇到别的需求,也需要它。

###take – skip

RACSignal * signal = [[RACSignal createSignal:^RACDisposable *(id subscriber) {
[subscriber sendNext:@"rac1"];
[subscriber sendNext:@"rac2"];
[subscriber sendNext:@"rac3"];
[subscriber sendNext:@"rac4"];
[subscriber sendCompleted];
return nil;
}]take:2];//Skip

[signal subscribeNext:^(id x) {
LxDBAnyVar(x);
}];


比如说我们发送了很多次请求
>	take表示我们只取前两次
	skip表示跳过前两次
	takeLast表示倒数的前两次
	takeUntil这个值比较特殊,他后面的参数是个信号,它的意思是,当takeUntil发送这个信号的时候,上面的发送信号就会停止发送。


接下来是几个block回调方法
>	takeWhileBlock BOOL值,意思是当返回YES的时候,订阅者才能收到信号
	skipWhileBlock BOOL值,意思是当返回YES的时候,订阅者就会跳过信号,NO的时候才接受
	skipUntilBlock BOOL值,意思是 返回NO的时候,不会收到消息, 直到返回YES的时候才开始收消息。

###即时搜索优化 (throttle,distinctUntilChanged,ignore)

UITextField * textField = [[UITextField alloc]init];
textField.backgroundColor = [UIColor cyanColor];
[self.view addSubview:textField];

@weakify(self);

[textField mas_makeConstraints:^(MASConstraintMaker *make) {

   @strongify(self);
   make.size.mas_equalTo(CGSizeMake(180, 40));
   make.center.equalTo(self.view);

}];
//throttle 后面是个时间 表示rac_textSignal发送消息,0.3秒内没有再次发送就会相应,若是0.3内又发送消息了,便会在新的信息处重新计时
//distinctUntilChanged 表示两个消息相同的时候,只会发送一个请求
//ignore 表示如果消息和ignore后面的消息相同,则会忽略掉这条消息,不让其发送
[[[[[[textField.rac_textSignal throttle:0.3] distinctUntilChanged] ignore:@""] map:^id(id value) {

   return [RACSignal createSignal:^RACDisposable *(id<RACSubscriber> subscriber) {
       
       //  network request
       [subscriber sendNext:value];
       [subscriber sendCompleted];
       
       return [RACDisposable disposableWithBlock:^{
           
           //  cancel request
       }];
   }];

}]switchToLatest] subscribeNext:^(id x) {

   LxDBAnyVar(x);

}];

以上代码,是用textField模拟一个即时搜索优化的功能,其中参数如下:

>	throttle 后面是个时间 表示rac_textSignal发送消息,0.3秒内没有再次发送就会相应,若是0.3内又发送消息了,便会在新的信息处重新计时
	distinctUntilChanged 表示两个消息相同的时候,只会发送一个请求
	ignore 表示如果消息和ignore后面的消息相同,则会忽略掉这条消息,不让其发送


这样做,是不是给服务器减小了很多的压力,更是节省了我们大量的代码。 其中我们用map建立了一个新的信号,我们知道textField的改变是一个信号, map就是在这个信号上,又加了一个信号,即signal of signals。
订阅者所打印的消息x则是,map发出的信号。我们可以再map中发送新的信号,以及取消信号disposable.
当我们用map发送信号的时候,我们则需要使用 switchToLatest这个参数来获取最后一个信号,也就是我们最后所打印的x,就是map最后发错的这个信号。


###repeat

[[[[[RACSignal createSignal:^RACDisposable *(id subscriber) {

   [subscriber sendNext:@"rac"];
   [subscriber sendCompleted];
   
   return nil;

}]delay:1]repeat]take:3] subscribeNext:^(id x) {

   LxDBAnyVar(x);

} completed:^{

   LxPrintAnything(completed);

}];


repeat,顾名思义,就是重复发送这条消息,当我们在后面添加了delay和take的时候,意思就是每隔1秒发送一次这条消息,发送3次后停止。

###merge – concat – zipWith

RACSignal * signalA = [RACSignal createSignal:^RACDisposable *(id subscriber) {

   dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
       LxPrintAnything(a);
       [subscriber sendNext:@"a"];
       [subscriber sendCompleted];
   });
   
   return nil;

}];

RACSignal * signalB = [RACSignal createSignal:^RACDisposable *(id subscriber) {

   dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(3 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
       LxPrintAnything(b);
       [subscriber sendNext:@"b"];
       [subscriber sendCompleted];
   });
   
   return nil;

}];

[[RACSignal merge:@[signalA, signalB]]subscribeNext:^(id x) {

   LxDBAnyVar(x);

}];


我们创建了两个请求,A和B,用GCD的方法A延迟两秒钟,B延迟了3秒钟,我们用merge方法合并了A和B,打印结果为

📍__23-[ViewController merge]_block_invoke_2 + 66🎈 a
📍__23-[ViewController merge]_block_invoke29 + 87🎈 x = a
📍__23-[ViewController merge]_block_invoke_215 + 77🎈 b
📍__23-[ViewController merge]_block_invoke29 + 87🎈 x = b


也就是A和B不管谁发送都会打印x,简单的说就是A和B的打印方法用的是同一个。他们之间关系是独立的,如果A发送失败,B依然会执行。
当我们用concat方法链接A和B之后,意思就是当A执行完了之后才会执行B,他们之间是依赖的关系,如果A发送失败,B也不会执行。
请注意合并(merge)和链接(concat)的区别。
zipWith,当用zipWith链接A和B的时候,只有在A.B每隔都至少发送过一次消息的时候才会执行zipWith的方法,它的返回值是一个集合,也就是数组,同时包含了A和B的打印结果。
zipWith的写法等同于 :

[[RACSignal combineLatestWith:@[signalA, signalB]subscribeNext:^(id x) {

    LxDBAnyVar(x);
}];
亦或者

[[RACSignal combineLatest:@[signalA, signalB]]subscribeNext:^(id x) {

    LxDBAnyVar(x);
}];

但是使用`combineLatest`,可以再后面添加更多的信号.

##RAC(<#TARGET, …#>) 宏

//button setBackgroundColor:forState:

UIButton * button = [UIButton buttonWithType:UIButtonTypeCustom];
[self.view addSubview:button];

@weakify(self);

[button mas_makeConstraints:^(MASConstraintMaker *make) {
    
    @strongify(self);
    make.size.mas_equalTo(CGSizeMake(180, 40));
    make.center.equalTo(self.view);
}];

RAC(button, backgroundColor) = [RACObserve(button, selected) map:^UIColor *(NSNumber * selected) {
    
    return [selected boolValue] ? [UIColor redColor] : [UIColor greenColor];
}];

[[button rac_signalForControlEvents:UIControlEventTouchUpInside]subscribeNext:^(UIButton * btn) {
    
    btn.selected = !btn.selected;
}];

比如Btn的设置背景颜色的属性,OC中并没有button setBackgroundColor:forState:这种方法,我们不能直接设置其选中后的颜色。在RAC中,则可以很简单的改变BTN的背景颜色。不得不说RAC的简单和强大。

做一个秒表

UILabel * label = ({

   UILabel * label = [[UILabel alloc]init];
   label.backgroundColor = [UIColor cyanColor];
   label;

});
[self.view addSubview:label];

@weakify(self);

[label mas_makeConstraints:^(MASConstraintMaker *make) {
@strongify(self);

   make.size.mas_equalTo(CGSizeMake(240, 40));
   make.center.equalTo(self.view);

}];

RAC(label, text) = [[RACSignal interval:1 onScheduler:[RACScheduler mainThreadScheduler]] map:^NSString *(NSDate * date) {

   return date.description;

}];


只有这么多代码,我们便可以完美的做一个秒表,是否很cool?


![](media/14915477632416/14915491586630.gif)


当我们大量使用RAC写代码的时候,会把一个个事件封装成一个个信号,通过触发信号,订阅这个信号来返回各种信息。RAC使我们的代码耦合性根底,聚合性更高。
若有不懂得地方可以留言,若有写错的地方,请及时与我联系,可以留言或者Email等。
文本所用的Demo,下载地址 [戳这里](https://github.com/yimouleng/RACDemo).

04/07/2017 13:49 下午 posted in  ReactiveCocoa

Xcode 快捷键及代码格式化

按住apple键点击类名就可以定位到这个类中查看相关定义(在日后的开发中我们会经常这么来做,毕竟要记住iOS开发中所有的API是不现实的,有些API我们可以通过这种方法来查找)

  1. 文件
    CMD + N: 新文件
    CMD + SHIFT + N: 新项目
    CMD + O: 打开
    CMD + S: 保存
    CMD+OPt+S:保存所有文件
    CMD + SHIFT + S: 另存为
    CMD + W: 关闭窗口
    CMD + Q :退出Xcode
    CMD + SHIFT + W: 关闭文件

  2. 编辑
    CMD + [: 左缩进
    CMD + ]: 右缩进

CMD+shift+F:项目中查找
CMD+G:查找下一个
CMD+shift+G:查找上一个

Ctrl + F :前移光标
Ctrl + B :后移光标
Ctrl + P :移动光标到上一行
Ctrl + N:移动光标到下一行
Ctrl + A : 移动光标到本行行首 (替换Home键)
Ctrl + E : 移动光标到本行行尾 (替换end键)
Ctrl + T :交换光标左右两边的字符
Ctrl + D:删除光标右边的字符
Ctrl + L : 将插入点置于窗口正中
Ctrl + K :删除本行
Ctrl + . : 参数提示
Tab :接受代码提示
Esc :显示代码提示菜单
CMD + /: 注释或取消注释

CMD + CTRL + LEFT: 折叠
CMD + CTRL + RIGHT: 取消折叠
CMD + CTRL + TOP: 折叠全部函数
CMD + CTRL + BOTTOM: 取消全部函数折叠
CTRL + U: 取消全部折叠

CMD + D: 添加书签

  1. 调试
    CMD + : 设置或取消断点
    CMD + OPT + : 允许或禁用当前断点
    CMD + OPT + B: 查看全部断点

CMD + RETURN: 编译并运行(根据设置决定是否启用断点)
CMD + R: 编译并运行(不触发断点)
CMD + Y: 编译并调试(触发断点)
CMD + SHIFT + RETURN: 终止运行或调试
CMD + Alt + P : 继续(在调试中)
CMD + Alt + 0 :跳过
CMD + Alt + I :跳入
CMD + Alt + T :跳出

CMD + B: 编译
CMD + SHIFT + K: 清理

  1. 窗体
    CMD + SHIFT + B: 编译窗口
    CMD + SHIFT + Y: 调试代码窗口
    CMD + SHIFT + R: 调试控制台
    CMD + SHIFT + E: 主编辑窗口调整

  2. 帮助
    CMD + OPT + ?: 开发手册
    CMD + CTRL + ?: 快速帮助

  3. Xcode6 代码格式化/自动排版:
    选中需要格式化代码 -> Editor -> Structure ->Re-Indent 或者
    选中需要格式化代码 -> 右击 ->选中 Structure ->Re-Indent

    快捷键:Ctrl+a全选->ctrl + i 格式化
    

01/22/2017 14:02 下午 posted in  Xcode

iOS开发中的代码布局规范

##1.指导原则
【原则1-1】首先是为人编写程序,其次才是计算机。
说明:这是软件开发的基本要点,软件的生命周期贯穿产品的开发、测试、生产、用户使用、版本升级和后期维护等长期过程,只有易读、易维护的软件代码才具有生命力。
【原则1-2】保持代码的简明清晰,避免过分的编程技巧。
说明:简单是最美。保持代码的简单化是软件工程化的基本要求。不要过分追求技巧,否则会降低程序的可读性。
【原则1-3】编程时首先达到正确性,其次考虑效率。
说明:编程首先考虑的是满足正确性、健壮性、可维护性、可移植性等质量因素,最后才考虑程序的效率和资源占用。
【原则1-4】编写代码时要考虑到代码的可测试性。
说明:不可以测试的代码是无法保障质量的,开发人员要牢记这一点来设计、编码。实现设计功能的同时,要提供可以测试、验证的方法。
【原则1-5】函数(方法)是为一特定功能而编写,不是万能工具箱。
说明:方法是一个处理单元,是有特定功能的,所以应该很好地规划方法,不能是所有东西都放在一个方法里实现

##2.布局

程序布局的目的是显示出程序良好的逻辑结构,提高程序的准确性、连续性、可读性、可维护性。更重要的是,统一的程序布局和编程风格,有助于提高整个项目的开发质量,提高开发效率,降低开发成本。同时,对于普通程序员来说,养成良好的编程习惯有助于提高自己的编程水平,提高编程效率。因此,统一的、良好的程序布局和编程风格不仅仅是个人主观美学上的或是形式上的问题,而且会涉及到产品质量,涉及到个人编程能力的提高,必须引起大家重视。

###2.1.文件布局

【规则2-1-1】遵循统一的布局顺序来书写头文件。
说明:以下内容如果某些节不需要,可以忽略。但是其它节要保持该次序。
头文件布局:

文件头
#import (依次为标准库头文件、非标准库头文件)
全局宏
常量定义
全局数据类型
类定义

正例:

/***************************************************************************
 *                                文件引用
 ***************************************************************************/ 
/***************************************************************************
 *                                 类引用
 ***************************************************************************/

/***************************************************************************
 *                                 宏定义
 ***************************************************************************/
/***************************************************************************
 *                                 常量
 ***************************************************************************/ 
/***************************************************************************
 *                                类型定义
 ***************************************************************************/ 
/ ***************************************************************************
 *                                 类定义
 ***************************************************************************/

【规则2-1-2】遵循统一的布局顺序来书写实现文件
说明:以下内容如果某些节不需要,可以忽略。但是其它节要保持该次序。
实现文件布局:

文件头(参见“注释”一节)
#import (依次为标准库头文件、非标准库头文件)
文件内部使用的宏
常量定义
文件内部使用的数据类型
全局变量
本地变量(即静态全局变量)
类的实现

正例:

/***************************************************************************
 *                                文件引用
 ***************************************************************************/ 
/***************************************************************************
 *                                 宏定义
 ***************************************************************************/
/***************************************************************************
 *                                 常量
 ***************************************************************************/ 
/***************************************************************************
 *                                类型定义
 ***************************************************************************/
/***************************************************************************
 *                                全局变量
 ***************************************************************************/
/***************************************************************************
 *                                 原型
 ***************************************************************************/
/ ***************************************************************************
 *                                类特性
 ***************************************************************************/
/ ***************************************************************************
 *                                类的实现
 ***************************************************************************/

###2.2类结构布局

使用#pragma mark –来分类方法

#pragma mark – Life Cycle

#pragma mark - Events

#pragma mark – Private Methods

#pragma mark - UITextFieldDelegate

#pragma mark - UITableViewDataSource

#pragma mark - UITableViewDelegate

#pragma mark - Custom Delegates

#pragma mark – Getters and Setters

###2.2.1布局中的空格

每个方法或者功能块之间为了结构清晰,应当有且只有一行空格。

@interface SomeClass:NSObject

@property (noatomic, strong) UIView *aView

-(void)someMethod;

@end

@implementation SomeClass

- (void)setAView:(NSInteger )aview {

}

-(void)someMethod {

}

@end

###2.2.2关于布局中的Private Methods块

###2.2.3属性初始化放哪最好?建议在Getter中初始化

我看到很多APP,甚至我公司的项目,很多开发工程师,初始化属性的位置比较随意,有单独添加一个初始化方法类似setupView的,有在init初始化的,各种情况都有,我其实挺崩溃的,首先初始化方式不一致,其次也这样做非常有可能破坏了每个方法功能的单一性(每个方法只做一件事)。我比较习惯一个对象的"私有"属性写在extension里面,然后这些属性的初始化全部放在getter里面做,在init和dealloc之外,是不会出现任何类似_property这样的写法的。就是这样:

@interface CustomObject()

@property (nonatomic, strong) UILabel *label;

@end

@implementation

#pragma mark - getters and setters

- (UILabel *)label {
    if (_label == nil) {
        _label = [[UILabel alloc] init];
        _label.text = @"1234";
        _label.font = [UIFont systemFontOfSize:12];
        ... ...
    }
    return _label;
}
@end
#pragma mark - life cycle

- (void)viewDidLoad {
    [super viewDidLoad];
    [self.view addSubview:self.label];
}

- (void)viewWillAppear:(BOOL)animated {
    [super viewWillAppear:animated];
    self.label.frame = CGRectMake(1, 2, 3, 4);
}

唐巧说他喜欢的做法是用_property这种,然后关于_property的初始化通过[self setupProperty]这种做法去做。从刚才上面的代码来看,就是要在viewDidLoad里面多调用一个setup方法而已,然后我推荐的方法就是不用多调一个setup方法,直接走getter。

嗯,怎么说呢,其实两种做法都能完成需求。但是从另一个角度看,苹果之所以选择让[self getProperty]和self.property可以互相通用,这种做法已经很明显地表达了苹果的倾向:希望每个property都是通过getter方法来获得

###2.2.4Getters and Setters放在最底部

控制器可能会有非常多的view属性和其他属性,而这些代码没有太复杂的逻辑,往往就是一些固定的配置,放置在最后面是为了突出我们以上的逻辑代码,当然了最后面查看也很方便的。
##3.表达式

###3.1 IF语句

表达式大括号和其他大括号(if/else/switch/while 等.)总是在同一行语句打开但在新行中关闭。如果没有else 并且括号内只有一行语句,可以和if语句同行,并且不需要括号。

if (user.isHappy) { 
    //Do something
} else { 
    //Do something else
}

if (somethingIsBad) return something;

###3.2SWITCH语句

大括号在case语句中并不是必须的,除非编译器强制要求。当一个case语句包含多行代码时,大括号应该加上。

 switch (condition) {
        case 1:
            // ...
            break;
        case 2: {
            // ...
            // Multi-line example using braces
            break;
        }
        case 3:
            // ...
            break;
        default: 
            // ...
            break;
    }

当在switch使用枚举类型时,'default'是不需要的。例如:

 RWTLeftMenuTopItemType menuType = RWTLeftMenuTopItemMain;
    switch (menuType) {
        case RWTLeftMenuTopItemMain:
            // ...
            break;
        case RWTLeftMenuTopItemShows:
            // ...
            break;
        case RWTLeftMenuTopItemSchedule:
            // ...
            break;
    }
01/22/2017 13:22 下午 posted in  Program

iOS开发中的命名规范

关于命名

###统一要求
含义清楚,尽量做到不需要注释也能了解其作用,若做不到,就加注释
使用全称,不适用缩写

  1. 一般性原则
    可读性高(简洁且清晰)和防止命名冲突(通过加前缀后缀来保证)。Objective-C 的命名通常都比较长, 名称遵循驼峰式命名法. 一个好的命名标准很简单, 就是做到在开发者一看到名字时, 就能够懂得它的含义和使用方法. 另外, 每个模块都要加上自己的前缀, 前缀在编程接口中非常重要, 可以区分软件的功能范畴并防止不同文件或者类之间命名发生冲突, 比如相册模块(PhotoGallery)的代码都以PG作为前缀: PGAlbumViewController, PGDataManager.
代码 点评
insertObject:atIndex: Good
insert:at: 不清晰;要插⼊什么?“at”表⽰示什么?
removeObjectAtIndex: Good
removeObject: 不错,因为⽅法是⽤用来移除作为参数的对象
remove: 不清晰;要移除什么?
  1. 一致性
    尽可能与Cocoa.编程接⼝命名保持一致。如果你不太确定某个命名的⼀致性,请浏览头文件或参考文档中的范例,在使⽤多态方法的类中,命名的⼀致性⾮常重要。在不同类中实现相同功能的⽅法应该具有同的名称。
代码 点评
– (NSInteger)tag 在 NSView, NSCell, NSControl 中有定义
– (void)setStringValue:(NSString *) 在许多 Cocoa classes 中都有定义

###资源文件命名 (图片,本地化文件)

这个图片资源命名方式,以功能为组织形式,是一个很好的习惯,有利于查看资源文件。
原则:
1)采用单词全拼,或者大家公认无岐义的缩写(比如:nav,bg,btn等)
2)采用“模块+功能”命名法,模块分为公共模块、私有模块。公共模块主要包括统一的背景,导航条,标签,公共的按钮背景,公共的默认图等等;私有模块主要根据app的业务功能模块划分,比如用户中心,消息中心等。
例如用户中心用户头像图片的命名可以为:uc_imageview_user_icon

这部分规范可能是很有经验的设计提供,也有可能是我们开发人员提供,掌握总是没有坏处的。

我们的命名规则的基本思想是把文件名分成三部分,第一部分是图片的逻辑归属分类,第二部分是图片的表现内容,第三部分是图片的内容的类型,有些图片还会有第四部分,表示图片表现的状态。首先有几个规则是:

  • 用英文命名,不用拼音
  • 每一部分用下划线分隔
  • 图片名中两倍图在名字最后要加@2x,三倍图在名字最后要加@3x

###类的命名

类名(以及类别、协议名)应首字母大写,并以驼峰格式分割单词
大驼峰式命名:每个单词的首字母都采用大写字母
例子:MFHomePageViewController

  1. 类的前缀
    1)所有类名、枚举、结构、protocol定义时最好加一个统一的标示符,可以是项目缩写,或者个人项目的名称缩写,例如都加上全大写的Hoo(我的姓氏)作为前缀
    2)根据功能模块可以在给功能模块的类添加功能模块的名称前缀,如用户中心的profileViewController.可以命名为HooUCProfileViewController.
  2. 类的后缀
    所有protocol定义时,都加上后缀Delegate 。如,HooRefreshViewDelegate,表示RefreshView的协议;
    所有的控制器都加上Controller,所有的通知名都加上Notification。

ViewController: 使用ViewController做后缀
例子: MFHomeViewController

View: 使用View做后缀
例子: MFAlertView

UITableCell:使用Cell做后缀
例子: MFNewsCell

Protocol: 使用Delegate或者DataSource作为后缀
例子: UITableViewDelegate

UI控件依次类推

###类别命名
类名+标识+扩展(UIImageView +HP+Web)
例:如果我们想要创建一个基于UIImageView 的类别用于网络请求图片,我们应该把类别放到名字是UIImageView+HPWeb.h的文件里。UIImageView为要扩展的类名,HP为专属标识,Web为扩展的功能。

###方法命名
方法名应遵守小驼峰原则,首字母小写,其他单词首字母大写,每个空格分割的名称以动词开头。执行性的方法应该以动词开头,小写字母开头,返回性的方法应该以返回的内容开头,但之前不要加get。如:

- (void)insertModel:(id)model atIndex:(NSUInteger)atIndex;
- (instancetype)arrayWithArray:(NSArray *)array;

1.代理方法
以发送代理的对象类名作为代理方法名的开始(去掉类名的前缀,并且小写开头)

- (BOOL)tableView:(NSTableView *)tableView shouldSelectRow:(int)row;
- (BOOL)application:(NSApplication *)sender openFile:(NSString *)filename;

###私有变量
小驼峰式命名:第一个单词以小写字母开始,后面的单词的首字母全部大写
例子:firstName、lastName

以 _ 开头,第一个单词首字母小写
例子:NSString * _somePrivateVariable

私有变量放在 .m 文件中声明 

变量名使用小驼峰法, 使变量名尽量可以推测其用途属性具有描述性。别一心想着少打几个字母,让你的代码可以迅速被理解更加重要。每个属性命名都加上类型后缀,如,按钮就加上Button
后缀,模型就加上Model后缀。

@property (nonatomic, strong) UIButton *submitButton;

1)类成员变量名
  成员变量用小驼峰法命名并前缀下划线,如:

UIButton *_submitButton;

2)局部变量名
  遵守小驼峰命名规则,如:

NSInteger numCompletedConnections =3; 

###property变量

小驼峰式命名
例子:///注释
@property (nonatomic, copy) NSString *userName;

禁止使用synthesize关键词
###宏命名
全部大写,单词间用 _ 分隔。[不带参数]
例子: #define THIS_IS_AN_MACRO @"THIS_IS_AN_MACRO"

以字母 k 开头,后面遵循大驼峰命名。[不带参数]
例子:#define kWidth self.frame.size.width

小驼峰命名。[带参数]

#define getImageUrl(url) [NSURL URLWithString:[NSString stringWithFormat:@"%@%@",kBaseUrl,url]]

###const常量

以小写k
开头,后面单词首字母大写,其余小写。如:

const float kMaxHeigt = 100.0f;

如果是特殊含义的常量也建议加上后缀,如通知加上Notification为后缀,如:

extern Nsstring * Const kLoginSuccessNotification

###Enum枚举的命名
Enum类型的命名与类的命名规则一致,以Objective-C的方式命名枚举
Enum中枚举内容的命名需要以该Enum类型名称开头
例子:

typedef NS_ENUM(NSInteger, UIViewAnimationTransition) {
    UIViewAnimationTransitionNone,
    UIViewAnimationTransitionFlipFromLeft,
    UIViewAnimationTransitionFlipFromRight,
    UIViewAnimationTransitionCurlUp,
    UIViewAnimationTransitionCurlDown,
};

###Delegate命名
类的实例必须为回调方法的参数之一, 如

- (NSInteger)tableView:(UITableView*)tableView numberOfRowsInSection:(NSInteger)section

回调方法的参数只有类自己的情况,方法名要符合实际含义, 如:

- (NSInteger)numberOfSectionsInTableView:(UITableView*)tableView

以类的名字开头(回调方法存在两个以上参数的情况)以表明此方法是属于哪个类的, 如:

- (UITableViewCell*)tableView:(UITableView*)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath

使用did和will通知Delegate已经发生的变化或将要发生的变化, 如:

- (NSIndexPath*)tableView:(UITableView*)tableView willSelectRowAtIndexPath:(NSIndexPath*)indexPath;
- (void)tableView:(UITableView*)tableView didSelectRowAtIndexPath:(NSIndexPath*)indexPath;

##私有方法及变量声明
###声明位置
在.m文件中最上方,定义空的category进行声明
例子:

#import "CodeStandardViewController.h"
    // 在这个category(类目)中定义变量和方法
    @interface CodeStandardViewController (){
      // 声明私有变量
    }

     // 私有方法
    - (void)samplePrivateMethod;
    @end

    @implementation CodeStandardViewController
    // 私有方法的实现
    - (void)samplePrivateMethod{
      //some code
    }

##关于注释
代码中尽量少注释,让代码能自我描述。不过当需要注释的时候,能需要清除的解释某个代码块的含义和作用。注释应当保持最新,如果不必要请删除。

  • 最好的代码是不需要注释的 尽量通过合理的命名
  • 良好的代码把含义表达清楚 在必要的地方添加注释
  • 注释需要与代码同步更新
  • 如果做不到命名尽量的见名知意的话,就可以适当的添加一些注释或者mark
    ###属性注释
    例子:
    /// 学生
    @property (nonatomic, strong) Student *student;

###方法声明注释:

/** 
   * @brief 登录验证
   *
   * @param personId 用户名
   * @param password 密码
   * @param complete 执行完毕的block
   *
   * @return
   */
  + (void)loginWithPersonId:(NSString *)personId password:(NSString *)password complete:(void (^)(CheckLogon *result))complete;

###关于UI布局
 使用Interface Builder进行界面布局
 Xib文件的命名与其对应的.h文件保持相同
 Xib文件中控件的组织结构要合理,Xib文件中控件需要有合理的可读性强的命名,方便他人理解

##格式化代码
###指针 "*" 位置
 
定义一个对象时,指针 "*" 靠近变量
例子: NSString *userName;

###方法的声明和定义
在 - 、+ 和 返回值 之间留一个空格,方法名和第一个参数之间不留空格

- (id)initWithNibName:(NSString *)nibNameOrNilbundle:(NSBundle *)nibBundleOrNil
{...}

###代码缩进
使用 xcode 默认缩进,即 tab = 4空格
使用 xcode 中 re-indent 功能定期对代码格式进行整理
相同类型变量声明需要独行声明
例子:

CGFloatoringX = frame.origin.x;
CGFloatoringY = frame.origin.y;
CGFloatlineWidth = frame.size.width;

Method与Method之间空一行
例子:

#pragma mark - private methods
- (void)samplePrivateMethod
{...}

- (void)sampleForIf
{...}

###对method进行分组
使用 #pragma mark - 方式对类的方法进行分组
例子:

#pragma mark - private methods

  - (void)samplePrivateMethod
  {...}

  - (void)sampleForIf
  {...}

  - (void)sampleForWhile
  {...}
  
  - (void)sampleForSwitch
  {...}

  - (void)wrongExamples
  {...}

  #pragma mark - public methods
  - (void)samplePublicMethodWithParam:(NSString*)sampleParam
  {...}

  #pragma mark - life cycle methods
  - (id)initWithNibName:(NSString *)nibNameOrNil bundle:(NSBundle *)nibBundleOrNil
  {...}

  - (void)viewDidLoad
  {...}

  - (BOOL)shouldAutorotateToInterfaceOrientation:(UIInterfaceOrientation)interfaceOrientation
  {...}

###大括号写法
对于类的method: 左括号跟在第一行后边(遵循苹果官方文档)
例子:

- (id)initWithNibName:(NSString *)nibNameOrNilbundle:(NSBundle *)nibBundleOrNil{
      self = [super initWithNibName:nibNameOrNil

      bundle:nibBundleOrNil];

      if (self) {
            // Custom initialization
        }

      return self;
}

- (void)sampleForIf{
    BOOL someCondition = YES;
    if(someCondition) {
        // do something here
    }
}
- (void)sampleForWhile{
    int i = 0;
    while (i < 10) {
        // do something here
        i = i + 1;
    }
}
- (void)sampleForSwitch{
    SampleEnum testEnum = SampleEnumTwo;
    switch(testEnum) {
        caseSampleEnumUndefined:{
            // do something
            break;
        }
        caseSampleEnumOne:{
            // do something
            break;
        }
        caseSampleEnumTwo:{
            // do something
            break;
        }
        default:{
            NSLog(@"WARNING: there is an enum type not handled properly!");
            break;
        }
    }

任何需要写大括号的部分,不得省略
错误示例:

- (void)wrongExamples{
    BOOLsomeCondition = YES;
    if (someCondition)
        NSLog(@"this is wrong!!!");
    while(someCondition)
        NSLog(@"this is wrong!!!");
}
01/19/2017 09:40 上午 posted in  Program

App Thinning

The App Store and operating system optimize the installation of iOS and watchOS apps by tailoring app delivery to the capabilities of the user’s particular device, with minimal footprint. This optimization, called app thinning, lets you create apps that use the most device features, occupy minimum disk space, and accommodate future updates that can be applied by Apple. Faster downloads and more space for other apps and content provides a better user experience.

开发者都知道,当前iOS App的编译打包方式是把适配兼容多个设备的执行文件及资源文件合并一个文件,上传和下载的文件则包含了所有的这些文件,导致占用较多的存储空间。

App Thinning是一个关于节省iOS设备存储空间的功能,它可以让iOS设备在安装、更新及运行App等场景中仅下载所需的资源,减少App的占用空间,从而节省设备的存储空间。

根据Apple官方文档的介绍,App Thinning主要有三个机制:

##Slicing
开发者把App安装包上传到AppStore后,Apple服务会自动对安装包切割为不同的应用变体(App variant),当用户下载安装包时,系统会根据设备型号下载安装对应的单个应用变体。

##On-Demand Resources

ORD(随需资源)是指开发者对资源添加标签上传后,系统会根据App运行的情况,动态下载并加载所需资源,而在存储空间不足时,自动删除这类资源。

##Bitcode

开启Bitcode编译后,可以使得开发者上传App时只需上传Intermediate Representation(中间件),而非最终的可执行二进制文件。 在用户下载App之前,AppStore会自动编译中间件,产生设备所需的执行文件供用户下载安装。

其中,Bitcode的机制可以支持动态的进行App Slicing,而对于Apple未来进行硬件升级的措施,此机制可以保证在开发者不重新发布版本的情况下而兼容新的设备。

如果你的应用也准备启用Bitcode编译机制,就需要注意以下几点:

  1. Xcode 7默认开启Bitcode,如果应用开启Bitcode,那么其集成的其他第三方库也需要是Bitcode编译的包才能真正进行Bitcode编译。
  2. 开启Bitcode编译后,编译产生的.app体积会变大(中间代码,不是用户下载的包),且.dSYM文件不能用来崩溃日志的符号化(用户下载的包是Apple服务重新编译产生的,有产生新的符号文件)。
  3. 通过Archive方式上传AppStore的包,可以在Xcode的Organizer工具中下载对应安装包的新的符号文件。
01/11/2017 10:00 上午 posted in  HTTP

App Transport Security

App Transport Security is a feature that improves the security of connections between an app and web services. The feature consists of default connection requirements that conform to best practices for secure connections.

App Transport Security(ATS)是Apple为提高系统及应用安全性而在iOS 9和OS X EI Capitan中引入的新特性,必然,出于安全性的考虑,在新发布的watchOS 2系统中也会适用。

一旦开启ATS后,应用所有的网络请求将会自动转换为HTPPS传输,且采用一系列配置要求来保证数据传输的安全性,包括:

  • Transport Layer Security协议版本要求TLS1.2以上;
  • 服务的Ciphers配置要求支持Forward Secrecy等;
  • 证书签名算法符合ATS要求等。

这些配置项在升级服务器支持HTTPS过程中都需要严格遵守的,否则就会导致你的HTTPS服务在iOS 9系统中连接仍是失效的。

如果你的App的服务也在升级以适配ATS要求,可以使用如下的方式进行校验:

在OS X EI Capitan系统的终端中通过nscurl命令来诊断检查你的HTTPS服务配置是否满足Apple的ATS要求: $ nscurl --verbose --ats-diagnostics https://<your_server_domain>。

当然,你也可以参考Apple提供官方指南 App Transport Security Technote进行服务的升级配置以满足ATS的要求。

Apple虽然希望开发者可以积极的参与并为系统及应用安全共同努力,但官方仍提供了一些参考配置去禁用ATS功能或降低ATS的安全性要求。

开发者可以在App的Info.plist中添加NSAppTransportSecurity的相关配置,用以禁用ATS或者添加白名单,可用的配置参数如下:

  1. NSAllowsArbitraryLoads - 设置true即支持所有HTTP请求
  2. NSExceptionDomains - 添加白名单
  3. NSExceptionMinimumTLSVersion - 白名单指定域名支持的TLS版本
  4. NSExceptionRequiresForwardSecrecy - 白名单指定域名是否支持Forward Secrecy
  5. NSExceptionAllowsInsecureHTTPLoads - 白名单指定域名禁用ATS
  6. NSThirdPartyExceptionMinimumTLSVersion - 白名单指定第三方服务域名最低支持的TLS版本
  7. NSThirdPartyExceptionRequiresForwardSecrecy - 白名单指定第三方服务域名是否支持Forward Secrecy
  8. NSThirdPartyExceptionAllowsInsecureHTTPLoads - 白名单指定第三方域名禁用ATS
01/11/2017 09:50 上午 posted in  HTTP