<i id='9k4m'></i>

<dl id='9k4m'></dl>
    <fieldset id='9k4m'></fieldset>

    <i id='9k4m'><div id='9k4m'><ins id='9k4m'></ins></div></i>
    <acronym id='9k4m'><em id='9k4m'></em><td id='9k4m'><div id='9k4m'></div></td></acronym><address id='9k4m'><big id='9k4m'><big id='9k4m'></big><legend id='9k4m'></legend></big></address>

  1. <ins id='9k4m'></ins>

      <span id='9k4m'></span>

          <code id='9k4m'><strong id='9k4m'></strong></code>

        1. <tr id='9k4m'><strong id='9k4m'></strong><small id='9k4m'></small><button id='9k4m'></button><li id='9k4m'><noscript id='9k4m'><big id='9k4m'></big><dt id='9k4m'></dt></noscript></li></tr><ol id='9k4m'><table id='9k4m'><blockquote id='9k4m'><tbody id='9k4m'></tbody></blockquote></table></ol><u id='9k4m'></u><kbd id='9k4m'><kbd id='9k4m'></kbd></kbd>

        2. 开发小知识(二)

          • 时间:
          • 浏览:24
          • 来源:124软件资讯网

          开发小知识(一)

          开发小知识(二)

          目录

          • 五十一、关联工具

          • 五十二、TCP 面向毗连的本质是什么 ?TCP 和 UDP 的区别 ?

          • 五十三、高效宁静读写方案

          • 五十四、死锁

          • 五十五、怎样明白署理和协议 ?

          • 五十六、MVP && MMVM

          • 五十七、简朴工厂和工厂模式

          • 五十八、适配器模式观点及应用

          • 五十九、外观模式观点及应用

          • 六十、计谋模式观点及应用

          • 六十一、界面卡顿缘故原由

          • 六十二、[UIApplication sharedApplication].delegate.window&& [UIApplication sharedApplication].keyWindow的区别

          • 六十三、单例注重事项

          • 六十四、性能优化总结

          • 六十五、内存区域

          • 六十六、符号表

          • 六十七、指针和引用

          • 六十八、static & const & extern

          • 六十九、枚举

          • 七十、验证码的作用

          • 七十一、帧率优化

          • 七十二、内存数据擦除

          • 七十三、内存泄露监测原理

          • 七十四、卡顿代码监测原理

          五十一、关联工具

          关联工具的 key

          现实开发中一样平常使用属性名作为key  。

          objc_setAssociatedObject(obj, @"property", value, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
          objc_getAssociatedObject(obj, @"property");

          另外一种方式是使用get要领的@selecor作为key  。这里要知道 _cmd现实上等价于 @selector(getter) ,两者都是 SEL类型  。

          objc_setAssociatedObject(obj, @selector(getter), value, OBJC_ASSOCIATION_RETAIN_NONATOMIC)
          // 隐式参数 _cmd == @selector(getter)
          objc_getAssociatedObject(obj, _cmd)
          objc_getAssociatedObject(obj, @selector(getter))

          关联工具的懒加载

          - (UIView *) testView{
              UIView * testView = objc_getAssociatedObject(self, _cmd);
              if (! testView) {
                  testView = [[UIView alloc]init];
                  objc_setAssociatedObject(self, _cmd, testView, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
              }
              return testView;
          }

          五十二、TCP 面向毗连的本质是什么  ?TCP 和 UDP 的区别  ?

          一样平常面试的时间问UDP和TCP这两个协议的区别 ,大部门人会回覆  ,TCP 是面向毗连的 ,UDP 是面向无毗连的  。什么叫面向毗连  ,什么叫无毗连呢 ?在互通之前  ,面向毗连的协议会先建设毗连  。例如  ,TCP 会三次握手  ,而 UDP 不会  。为什么要建设毗连呢 ?所谓的建设毗连  ,是为了在客户端和服务端维护毗连  ,而建设一定的数据结构来维护双方交互的状态  ,用这样的数据结构来保证所谓的面向毗连的特征 。

          为了维护这个毗连 ,双方都要维护一个状态机  ,在毗连建设的历程中  ,双方的状态转变状态如下  。最初  ,客户端和服务端都处于 CLOSED 状态  。首先  ,服务端处于 LISTEN 状态 ,主要为了自动监听某个端口  。客户端自动提倡毗连 SYN  ,变为 SYN-SENT 状态  。然后 ,服务端收到提倡的毗连  ,返回 SYN ,而且 ACK 客户端的 SYN  ,之后处于 SYN-RCVD 状态  。客户端收到服务端发送的 SYN 和 ACK 之后  ,发送 ACK 的 ACK ,之后处于ESTABLISHED 状态  ,由于一发一收获功了  。服务端收到 ACK 的 ACK 之后  ,也同样变为 ESTABLISHED 状态  。

          另外  ,TCP 是可以拥塞控制的  。它意识到包抛弃了或者网络的情况欠好了  ,就会凭据情形调整自己的行为  ,看看是不是发快了 ,要不要发慢点  。UDP 就不会 ,应用让发就发  ,从不思量网络状态  。

          五十三、高效宁静读写方案

          读写操作中为了保证线程宁静可以为读和写操作都添加锁 。可是此种情形似乎有些铺张 ,往往都是由于写操作会引发线程宁静问题  ,而读操作一样平常不会引发线程宁静问题  。为了优化读写效率 ,一样平常是允许统一时间有多个读操作 ,但统一时间不能有多个写操作  ,且统一时间不能既有读操作又有写操作  。针对该种情形  ,一样平常有两种处置惩罚要领:读写锁和异步栅栏函数  。

          读写锁方案pthread_rwlock_t

          @property (assign, nonatomic) pthread_rwlock_t lock;
          pthread_rwlock_init(&_lock, NULL);// 初始化锁
          
          - (void)read {
              pthread_rwlock_rdlock(&_lock);
              sleep(1);
              NSLog(@"%s", __func__);
              pthread_rwlock_unlock(&_lock);
          }
          - (void)write{
              pthread_rwlock_wrlock(&_lock);
              sleep(1);
              NSLog(@"%s", __func__);
              pthread_rwlock_unlock(&_lock);
          }
          - (void)dealloc{
              pthread_rwlock_destroy(&_lock);
          }

          异步栅栏函数方案  。

          每次必须等前面所有读操作执行完之后  ,才气执行写操作 。数据的准确性主要取决于写入操作 ,只要保证写入时  ,线程即是宁静的 ,即便读取操作是并发的  ,也可以保证数据的准确性  。dispatch_barrier_async 使得操作在同队伍列里“有序举行”  ,保证了写入操作的使命是在串行行列里 ,即必须等所有读操作执行完毕后再执行写操作  。注重这里的行列必须是dispatch_queue_create建立的 ,若是dispatch_barrier_async中传入的是全局并发行列  ,该函数就等同于dispatch_async效果  。

          @property (strong, nonatomic) dispatch_queue_t queue;
          self.queue = dispatch_queue_create("rw_queue", DISPATCH_QUEUE_CONCURRENT);
              for (int i = 0; i < 10; i++) {
                  dispatch_async(self.queue, ^{
                      [self read];
                  });
                  dispatch_async(self.queue, ^{
                      [self read];
                  });
                  dispatch_async(self.queue, ^{
                      [self read];
                  });
                  dispatch_barrier_async(self.queue, ^{
                      [self write];
                  });
              }

          另外提醒 ,若是仅仅只是对写操作加锁  ,读操作不做任那边理 ,并不能保证线程宁静  ,仅对写操作加锁仅仅只能保证不会同时泛起两个或多个写操作  ,并不能制止统一时刻既有写操作又有读操作  。实现正在举行读操作  ,此时来了第一个写操作  ,可是相关锁并没有加锁  ,以是读写操作可同时举行  。

          增补:对于线程宁静方案  ,除了加锁之外 ,还可以借助串行行列确保代码执行的顺序 ,保证线程宁静 。

          五十四、死锁

          在说死锁之前要知道同步和异步主要决议是否具备开启新线程的能力  。串行和并发主要决议使命执行的方式  。

          所谓死锁  ,通常指有两个线程 T1 和 T2 都卡住了 ,并等候对方完成某些操作 。T1 不能完成是由于它在等候 T2 完成  。T2 也不能完成  ,由于在等候 T1 完成 。于是各人都完不成 ,就导致了死锁(DeadLock) ,就类似一条比力窄的马路有两辆车想向而行  ,相互等着对方过了之后再过 。

          - (void)ViewDidLoad{
            NSLog(@"1");// 使命1
            dispatch_sync(dispatch_get_main_queue(),^{
                NSLog(@"2");// 使命2
            });
            NSLog(@"3");// 使命3
          }

          dispatch_sync 中主要传入两个参数  ,行列和使命回调block 。上述代码仅会执行使命 1  。   主线程原本存在使命 1 、sync 、使命3 ,主行列中原本仅存在viewDidLoad  ,当主线程从使命 1  依次执行到 sync 时  ,此时会往主行列中追加使命 2  。dispatch_sync 有个特点  ,要求立马在当前线程执行使命  ,执行完毕才气继续往下执行 ,可是此时主行列中的viewDidLoad 还没执行完  ,自然就不能将使命 2 从主行列取出放入主线程执行  ,意味着 sync 无法完成  ,进而意味着使命 3 无法执行 ,再进而意味着 viewDidLoad 无法执行完  ,viewDidLoad 无法执行完  ,也意味着无法从主行列中取出使命 2 。云云一来造成一个循环  ,使命3 要等同步线程中热使命2执行完才气执行  ,而使命 2 排在使命 3 后面需要等候使命 3 执行完  ,最终谁也无法执行完形成死锁  。可参照下面两幅图加深明白  。

          上述只是死锁的一种形式  ,另一种情形是锁和锁之间发生的冲突 。  。  。  。  。  。 。  。  。

          五十五、怎样明白署理和协议  ?

          现实面试历程中有问到应试者对协媾和署理的明白  ,个体应试者只知道署理和协议的用法  ,连协媾和署理的意义都说不清晰  。举个简朴的例子:一位导演很忙 ,由于他要把主要精神放到影戏创作上 。因此需要找署理人把主要的琐事分管出去 ,或者说把主要的琐事让”署理人”去做  。其中的署理人就是代码中署理  ,协议主要是划定了署理人要做的事  。 协议的用处另有许多  ,可看看此篇文章  。

          五十六、MVP && MMVM


          MVP 同 MVC 相比 ,本质上是将 Controller 的职责给分散出去  ,根据功效和营业逻辑划分为若干个 Presenter  。Controller 中引入 Presenter ,Presenter 中同样也引入 Controller  ,Presenter 中处置惩罚种种营业逻辑  ,须要的时间再通过署理或 block 等形式回传到 Controller 中  。要注重  ,为了制止循环引用 Presenter 要弱引用 Controller  。笔者以为 MPV 存在的一个显着弱点是

          @interface ViewController ()
          @property (strong, nonatomic) Presenter *presenter;
          @end
          @interface Presenter()
          @property (weak, nonatomic) UIViewController *controller;
          @end

          MVVM

          MVVM 总的来说和 MVP 很是类似 ,唯一差别点在于 View 和 ViewModel 双向绑定  。ViewModel 会照搬照抄一份 Model 的属性给自己  ,View 中会引入 ViewModel 给 View 设置内容 ,而且 View 还会监听 ViewModel 的转变  ,当 ViewModel 转变时 ,通过监听更新 View 上对应内容  ,实现双向绑定 。这种监听可以通过监听实现 ,可以通过 RAC 实现  ,可是 RAC 过重  ,有一定的学习和维护成本  。建议使用 KVOController 实现这种监听  ,如下一段代码是 View 中引入 ViewModel  ,重写 ViewModel 的 set 要领 ,并监听 ViewModel 的转变刷新 UI  。笔者以为没有绝对好的架构模式  ,适合特定营业场景的架构模式才是好的架构  。MVVM 特殊适合那种模子和视图双向反馈较多的场景  ,好比列表页面的选中和非选中状态  ,通过改变 ViewModel 很轻松就能实现数据和界面的统一 。 可是对于一样平常的营业场景而言(双向反馈较少的场景) ,MVVM 同 MVC 相比处置惩罚能拆分 Controller 的营业逻辑之外  ,貌似也没太多的优点  ,反而会增添调试的难度  。假设泛起一些 bug  ,该 bug 可能源于视图也可能源于 ViewModel ,会增添 bug 定位的难度 。

          - (void)setViewModel:(ViewModel *)viewModel{
              _viewModel = viewModel;
              __weak typeof(self) waekSelf = self;
              [self.KVOController observe:viewModel keyPath:@"name" options:NSKeyValueObservingOptionNew block:^(id  _Nullable observer, id  _Nonnull object, NSDictionary * _Nonnull change) {
                  waekSelf.nameLabel.text = change[NSKeyValueChangeNewKey];
              }];
          }

          五十七、简朴工厂和工厂模式

          简朴工厂和工厂模式都属于类建立型模式  。

          简朴工厂模式

          简朴工厂主要有三个部门组成:

          • 抽象产物:抽象产物是工厂所建立的所有产物工具的父类  ,卖力声明所有产物实例所共有的公共接口  。

          • 详细产物:详细产物是工厂所建立的所有产物工具类  ,它以自己的方式来实现其配合父类声明的接口  。

          • 工厂类:实现建立所有产物实例的逻辑 。

          //抽象产物
          //Operate.h文件
          @interface Operate : NSObject
          @property(nonatomic,assign)CGFloat numOne;
          @property(nonatomic,assign)CGFloat numTwo;
          - (CGFloat)getResult;
          @end
          //Operate.m文件
          @implementation Operate
          - (CGFloat)getResult{
              return 0.0;
          }
          @end
          //详细产物1
          //OperateAdd.m文件
          @implementation OperateAdd
          - (CGFloat)getResult{
              return self.numOne + self.numTwo;
          }
          @end
          //详细产物2
          //OperateSub.m文件
          @implementation OperateSub
          - (CGFloat)getResult{
              return self.numOne - self.numTwo;
          }
          @end
          //工厂类
          //OperateFactory.h文件
          @class Operate;
          @interface OperateFactory : NSObject
          + (Operate *)createOperateWithStr:(NSString *)str;
          @end
          
          //OperateFactory.m文件
          @implementation OperateFactory
          + (Operate *)createOperateWithStr:(NSString *)str{
              if ([str isEqualToString:@"+"]) {
                  OperateAdd *operateAdd = [[OperateAdd alloc] init];
                  return operateAdd;
              }else if ([str isEqualToString:@"-"]){
                  OperateSub *operateSub = [[OperateSub alloc] init];
                  return operateSub;
              }else{
                  return [[Operate alloc]init];
              }
          }
          @end
          //使用
          - (void)simpleFactoryTest{
              Operate *operate = [OperateFactory createOperateWithStr:@"+"];
              operate.numOne = 1;
              operate.numTwo = 2;
              NSLog(@"%f",[operate getResult]);
          }

          优点:最大的优点在于工厂类中包罗了须要的判断逻辑  ,凭据客户端的选择条件动态实例化相关的类 ,对于客户端而言去除了与详细产物的依赖 。有了简朴工厂类后  ,客户端在使用的时间只需要传入“+” 或“-”即可  ,使用上相对来说简朴了许多  。

          弱点: 试想此时若是想在上述例子的基础上增添乘法或除法操作 ,除了增添响应的子类之外  ,开发职员还需要在工厂类中改写 if else 分支 ,至少要更改两处地方  。显然 ,工厂类的改动违反了开放-关闭原则(对扩展是开放的  ,对更改是关闭的)  。正因云云 ,才泛起了所谓的工厂模式 ,工厂模式仅仅需要添加新的详细产物和新的详细工厂就能实现  ,原有代码无需改动  。

          笔者在现实开发历程中使用过简朴工厂模式  ,详细说来:UICollectionView上有许多可动态设置的模块 ,当地代码提前写好差别的模块  ,然后凭据后端接口返回的数据所包罗的差别模块标志  ,用工厂类动态建立差别的模块  ,从而实现模块的动态设置 。每个模块现实是一个 UICollectionViewCell  ,它们统一继续一个基类  ,基类中包罗一个统一渲染的要领  ,由于各个差别模块的基本参数设置一直  ,以是比力适合走统一抽象渲染接口  。另外  ,类簇是简朴工厂的应用如:NSNumber 的工厂要领传入差别类型的数据  ,则会返回差别数据所对应的 NSNumber 的子类  。

          工厂模式

          工厂模式主要由四部门组成 。

          • 抽象产物:同简朴工厂  。

          • 详细产物:同简朴工厂  。

          • 抽象工厂:声明详细工厂的建立产物的接口  。

          • 详细工厂:卖力建立特定的产物  ,每一个详细产物对应一个详细工厂 。

          上述三个抽象产物和详细产物类无转变  ,即 Operate、OperateAdd 和 OperateSub 三个类无转变  。

          //抽象工厂
          //OperationFactoryProtocol协议
          @class Operate;
          @protocol OperationFactoryProtocol + (Operate *)createOperate;
          @end
          //详细工厂1
          //AddFactory.h文件
          @interface AddFactory : NSObject@end
          //AddFactory.m文件
          @implementation AddFactory
          + (Operate *)createOperate{
              return [[OperateAdd alloc]init];
          }
          @end
          //详细工厂2
          //SubFactory.h文件
          @interface SubFactory : NSObject@end
          //SubFactory.m文件
          @implementation SubFactory
          + (Operate *)createOperate{
              return [[OperateSub alloc]init];
          }
          @end

          优点

          • 工厂模式相比简朴工厂而言  ,在扩展新的详细产物时间代码改动更小  。

          • 用户只需要体贴其所需产物对应的详细工厂是哪一个即可 ,不需要体贴产物的建立细节 ,也不需要知道详细产物类的类名  。

          弱点

          • 当系统中加入新产物时 ,除了需要提供新的产物类之外  ,还要提供与其对应的详细工厂类 。随着类的个数增添  ,系统庞大度也会有所增添  。

          • 简朴工厂类只有一个工厂类 ,该工厂类可以建立多个工具;工厂模式中每个子类对应一个工厂类  ,每个工厂仅能建立一个工具  。

          五十八、适配器模式观点及应用

          适配器设计模式数据接口适配相关设计模式  。现实开发中有个场景特殊使用适配器设计模式  ,一个封装好的视图组件可能在工程中差别的地方使用到  ,可是差别的地方使用的数据模子并不相同  ,此时可以借助工具适配器  ,建立新的适配器模子数据  ,而不应该在组件内部引入差别的数据模子  ,依据类型值举行判断 ,使用差别模子的差别数据 。如电商网站中的加减按钮可能在差别的页面中使用到  ,但差别页面依赖的数据模子差别  ,此种情形就特殊适合使用适配器模式 。

          两个模子类  。

          @interface DataModel : NSObject
          @property (nonatomic, copy)NSString *name;
          @property (nonatomic, copy)NSString *phoneNumber;
          @property (nonatomic, strong)UIColor *lineColor;
          @end
          
          @interface NewDataModel : NSObject
          @property (nonatomic, copy)NSString *name;
          @property (nonatomic, copy)NSString *phoneNumber;
          @end

          适配器协议 。

          @protocol BusinessCardAdapterProtcol - (NSString *)name;
          - (NSString *)phoneNumber;
          @end

          适配器类 。

          //.h 文件
          @interface ModelAdapter : NSObject@property (nonatomic, weak)id data;
          - (instancetype)initWithData:(id)data;
          @end
          //.m 文件
          - (instancetype)initWithData:(id)data{
              self = [super init];
              if (self) {
                  self.data = data;
              }
              return self;
          }
          //凭据类名适配
          - (NSString *)name{
              NSString *name = nil;
              if ([self.data isMemberOfClass:[DataModel class]]) {
                   DataModel *data = self.data;
                   name = data.name;
              }else if ([self.data isMemberOfClass:[NewDataModel class]]){
                  NewDataModel *data = self.data;
                  name = data.name;
              }
              return name;
          }
          - (NSString *)phoneNumber{
              NSString *phoneNumber = nil;
              if ([self.data isMemberOfClass:[DataModel class]]) {
                  DataModel *data = self.data;
                  phoneNumber = data.phoneNumber;
              }else if ([self.data isMemberOfClass:[NewDataModel class]]){
                  NewDataModel *data = self.data;
                  phoneNumber = data.phoneNumber;
              }
              return phoneNumber;
          }

          视图 。

          //.h 文件
          @interface BusinessCardView : UIView
          - (void)loadData:(id)data;
          @property (nonatomic, copy) NSString *name;
          @property (nonatomic, copy) NSString *phoneNumber;
          @end
          
          //.m 文件
          - (void)loadData:(id)data{
              self.name = [data name];
              self.phoneNumber = [data phoneNumber];
          }
          - (void)setName:(NSString *)name{
              _name = name;
              _nameLabel.text = name;
          }
          - (void)setPhoneNumber:(NSString *)phoneNumber{
              _phoneNumber = phoneNumber;
              _phoneNumberLabel.text = phoneNumber;
          }

          使用 。

          - (void)viewDidLoad {
              [super viewDidLoad];
              // 建立UI控件
              cardView = [[BusinessCardView alloc] initWithFrame:CGRectMake(0, 0, 375, 667.5)];
              cardView.center = self.view.center;
              [self.view addSubview:cardView];
              // 初始化两种差别d类型的模子
              model = [[DataModel alloc] init];
              model.name = @"测试一";
              model.phoneNumber = @"电话1";
              newmodel = [[NewDataModel alloc]init];
              newmodel.name = @"测试二";
              newmodel.phoneNumber = @"电话2";
              //设置初始数据
              BusinessCardAdapter *adapter = [[BusinessCardAdapter alloc] initWithData:model];
              [cardView loadData:adapter];
              UISwitch *btn = [[UISwitch alloc]initWithFrame:CGRectMake(50, 340, 50, 20)];
              [btn addTarget:self action:@selector(change:) forControlEvents:UIControlEventValueChanged];
              [self.view addSubview:btn];
          }
          - (void)change:(UISwitch *)btn{
              //切换数据
              ModelAdapter *adapter;
              if (btn.on == YES) {
                 adapter = [[ModelAdapter alloc] initWithData:newmodel];
              }else{
                 adapter = [[ModelAdapter alloc] initWithData:model];
              }
              //cardView与适配器毗连
              [cardView loadData:adapter];
          }

          五十九、外观模式观点及应用

          外观模式相对比力好明白  ,主要为子系统中的一组接口提供一个统一的接口 。外观模式界说了一个更高条理的接口  ,这个接口使得这一子系统越发容易使用 。以下情形下可以思量使用外观模式:

          设计初期阶段  ,应该有意识的将差别层分散  ,层与层之间建设外观模式  。

          开发阶段 ,子系统越来越庞大 ,增添外观模式提供一个简朴的挪用接口  。

          维护一个大型遗留系统的时间  ,可能这个系统已经很是难以维护和扩展  ,但又包罗很是主要的功效 ,为其开发一个外观类  ,以便新系统与其交互  。

          说的再直白一些  ,外观模式就相当于在客户端和子系统中心加了一其中间层  。使用外观模式可以使项目更好的分层  ,增强了代码的扩展性  。另外  ,客户端屏障了子系统组件  ,使客户端和子系统之间实现了松耦合关系  。纵然将厥后想替换子系统客户端也无需改动  。

          六十、计谋模式观点及应用

          计谋模式由三部门组成:抽象计谋、详细计谋以及引入计谋的主体 。现实开发中有一种场景特殊适合使用计谋模式  ,输入框 UITextField 的输入规则可以使用该设计模式  ,判断是输入电话号码、邮箱等花样是否准确 。

          抽象计谋:

          //.h 文件
          @interface InputValidator : NSObject
          @property (strong, nonatomic)NSString *errorMessage;
          - (BOOL)validateInput:(UITextField *)input;
          @end
          //.m 文件
          @implementation InputValidator
          - (BOOL)validateInput:(UITextField *)input {
              return NO;
          }
          @end

          两个详细计谋:

          //邮箱计谋
          @implementation EmailValidator
          - (BOOL)validateInput:(UITextField *)input {
              if (input.text.length <= 0) {
                  self.errorMessage = @"没有输入";
              } else {
                  BOOL isMatch = [input.text isEqualToString:@"1214729173@qq.com"];
                  if (isMatch == NO) {
                      self.errorMessage = @"请输入正确的邮箱";
                  } else {
                      self.errorMessage = nil;
                  }
              }
              return self.errorMessage == nil ? YES : NO;
          }
          @end
          
          //电话号码策略
          @implementation PhoneNumberValidator
          - (BOOL)validateInput:(UITextField *)input {
              if (input.text.length <= 0) {
                  self.errorMessage = @"没有输入";
              } else {
                  BOOL isMatch = [input.text isEqualToString:@"15201488116"];
                  if (isMatch == NO) {
                      self.errorMessage = @"请输入正确的手机号码";
                  } else {
                      self.errorMessage = nil;
                  }
              }
              return self.errorMessage == nil ? YES : NO;
          }
          @end

          引入计谋的主体:

          //.h 文件
          @interface CustomTextField : UITextField
          //抽象的计谋
          @property (strong, nonatomic) InputValidator *validator;
          //初始化
          - (instancetype)initWithFrame:(CGRect)frame;
          //验证输入正当性
          - (BOOL)validate;
          @end
          
          //.m 文件
          @implementation CustomTextField
          - (instancetype)initWithFrame:(CGRect)frame {
              self = [super initWithFrame:frame];
              if (self) {
                  [self setup];
              }
              return self;
          }
          - (void)setup {
              UIView *leftView = [[UIView alloc] initWithFrame:CGRectMake(0, 0, 5, self.frame.size.height)];
              self.leftView = leftView;
              self.leftViewMode = UITextFieldViewModeAlways;
              self.font = [UIFont fontWithName:@"Avenir-Book" size:12.f];
              self.layer.borderWidth = 0.5f;
          }
          - (BOOL)validate {
              return [self.validator validateInput:self];
          }
          @end

          外部使用:

          @implementation ViewController
          - (void)viewDidLoad {
              [super viewDidLoad];
              [self initButton];
              [self initCustomTextFields];
          }
          - (void)initCustomTextFields {
              self.emailTextField = [[CustomTextField alloc] initWithFrame:CGRectMake(30, 80, Width - 60, 30)];
              self.emailTextField.placeholder = @"请输入邮箱";
              self.emailTextField.delegate = self;
              self.emailTextField.validator = [EmailValidator new];
              [self.view addSubview:self.emailTextField];
              
              self.phoneNumberTextField = [[CustomTextField alloc] initWithFrame:CGRectMake(30, 80 + 40, Width - 60, 30)];
              self.phoneNumberTextField.placeholder = @"请输入电话号码";
              self.phoneNumberTextField.delegate = self;
              self.phoneNumberTextField.validator = [PhoneNumberValidator new];
              [self.view addSubview:self.phoneNumberTextField];
          }
          #pragma mark - 文本框署理
          - (void)textFieldDidEndEditing:(UITextField *)textField {
              CustomTextField *customTextField = (CustomTextField *)textField;
              if ([customTextField validate] == NO) {
                  UIAlertController *alertController = [UIAlertController alertControllerWithTitle:@"" message:customTextField.validator.errorMessage preferredStyle:UIAlertControllerStyleAlert];
                  UIAlertAction *alertAction = [UIAlertAction actionWithTitle:@"确定" style:UIAlertActionStyleCancel handler:^(UIAlertAction * _Nonnull action) {
                  }];
                  [alertController addAction:alertAction];
                  [self presentViewController:alertController animated:YES completion:nil];
              }
          }
          - (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event{
              [self.view endEditing:YES];
          }

          六十一、界面卡顿缘故原由

          屏幕成像的历程如下图:

          根据60FPS的刷帧率 ,每隔16ms就会有一次 VSync 到来(垂直同步信号) 。VSync 到来意味着要将 GPU 渲染好的数据拿出来显示到屏幕上  ,可是下图中红色区域中  ,由于CPU + GPU 的处置惩罚时间在 VSync 之后  ,以是此时红色框右边的时间段显示的始终是上一帧的画面 ,因此泛起卡顿征象  。以是现实开发中无论是 CPU 照旧 GPU 消耗资源较多都可能造成卡顿征象  。

          六十二、[UIApplication sharedApplication].delegate.window&& [UIApplication sharedApplication].keyWindow的区别

          参考此篇文章 ,现实开发中要格外注意  [UIApplication sharedApplication].keyWindow 的坑 。

          六十三、单例注重事项

          建立单例的时间除了要思量工具的唯一性和线程宁静之外  ,还要思量alloc init、 copy 和 mutableCopy 要领返回统一个实例工具 。关于allocWithZone可看此篇文章  。

          + (instancetype)sharedInstance {
              return [[self alloc] init];
          }
          - (instancetype)init {
              if (self = [super init]) {
                  
              }
              return self;
          }
          + (instancetype)allocWithZone:(struct _NSZone *)zone {
              static LogManager * _sharedInstanc = nil;
              static dispatch_once_t onceToken;
              dispatch_once(&onceToken, ^{
                  _sharedInstanc = [super allocWithZone:zone];//最先执行  ,只执行了一次
              });
              return _sharedInstanc;
          }
          -(id)copyWithZone:(struct _NSZone *)zone{
              return [LogManager sharedInstance];
          }
          -(id)mutableCopyWithZone:(NSZone *)zone{
              return [LogManager sharedInstance];
          }

          六十四、性能优化总结

          待更新  。  。  。 。  。

          六十五、内存区域

          1、栈:局部变量(基本数据类型、指针变量)作用域执行完毕之后 ,就会被系统立刻收回  ,无需法式员治理(分配地址由高到低分配)  。

          2、堆:法式运行的历程中动态分配的存储空间(建立的工具)  ,需要自动申请和释放  。

          3、BSS 段:没有初始化的全局变量和静态变量 ,一旦初始化就会从 BSS 段中收回掉  ,转存到数据段中  。

          4、(全局区)数据段:存放已经初始化的全局变量和静态变量  ,以及常量数据 ,直到法式竣事才会被立刻收回  。

          5、代码段:法式编译后的代码内容 ,直到竣事法式才会被收回  。

          六十六、符号表

          iOS 构建时发生的符号表  ,是内存地址、函数名、文件名和行号的映射表  。花样或许是:

             []

          Crash 时的客栈信息  ,全是二进制的地址信息  。若是使用这些二进制的地址信息来定位问题是不行能的  ,因此我们需要将这些二进制的地址信息还原成源代码种的函数以及行号 ,这时间符号表就起作用了  。使用符号表将原始的 Crash 的二进制客栈信息还原成包罗行号的源代码文件信息  ,可以快速定位问题  。iOS 中的符号表文件(DSYM) 是在编译源代码后  ,处置惩罚完 Asset Catalog 资源和 info.plist 文件后最先天生  ,天生符号表文件(DSYM)之后  ,再举行后续的链接、打包、署名、校验等步骤  。

          六十七、指针和引用

          在 C 和 OC 语言中 ,使用指针(Pointer)可以间接获取、修改某个变量的值  ,C++中 ,使用引用(Reference)可以起到跟指针类似的功效  。引用相当于是变量的别名 ,对引用做盘算  ,就是对引用所指向的变量做盘算  ,在界说的时间就必须初始化  ,一旦指向了某个变量  ,就不行以再改变从一而终  。以是这也是用用存在的价值之一:比指针更宁静、函数返回值可以被赋值  。引用的本质就是指针 ,只是编译器削弱了它的功效  ,以是引用就是弱化了的指针 。

          六十八、static & const & extern


          • static修饰局部变量:将局部变量的原来分配在栈区改为分配在静态存储区 ,静态存储区陪同着整个应用 ,也就延伸下场部变量的生命周期 。

          • static修饰全局变量:原来是在整个源法式的所有文件都可见 ,static修饰后  ,改为只在说明自己的文件可见  ,即修改了作用域  。


          • const:修饰变量主要强调变量是不行修改的  。const 修饰的是其右边的值  ,也就是 const 右边的这个整体的值不能改变  。

          //如下代码无法编译通过
          //const修饰str指针 ,以是str指针的内存地址无法改变  ,也即str指针不能改变内存地址指向  。
           NSString * const str = @"test";
           //该行代码表现:str指针指向了其它的内存
           str = @"123";
          //const修饰 *str  ,也即str指针指向的内存地址  ,以是对修改str指针的指向无任何影响  。
          NSString const *str = @"test";
          //该行代码表现:str指针指向了其它的内存
           str = @"123";

          一样平常团结使用static和const来界说一个只能在本文件中使用的 ,不能修改的变量  。相对于用#define来界说的话  ,优点就在于它指定了变量的类型 。

          //防止 reuseIdentifier 指针指向其它内存
          static NSString * const reuseIdentifier = @"reuseIdentifier";
          • extern:主要是用来引用全局变量  ,先在本文件中查找  ,本文件中查找不到再到其他文件中查找 。常把 extern 和 const 团结使用在项目中建立一个文件  ,这个文件中包罗整个项目中都能会见的全局常量  。

          六十九、枚举

          枚举的目的只是为了增添代码的可读性  。iOS6 中引入了两个宏来重新界说枚举类型 NS_ENUM 与 NS_OPTIONS  ,两者在本质上并没有差异 ,NS_ENUM多用于一样平常枚举, NS_OPTIONS 则多用于带有移位运算的枚举 。

          NS_ENUM

          typedef NS_ENUM(NSInteger, Test){
              TestA = 0,
              TestB,
              TestC,
              TestD
          };

          NS_OPTIONS

          typedef NS_OPTIONS(NSUInteger, Test) {
              TestA = 1 << 0,
              TestB = 1 << 1,
              TestC = 1 << 2,
              TestD = 1 << 3
          };

          使用按位或(|)为枚举 变量test 同时赋值枚举成员TestA、TestB、TestC  。

          Test test = TestA | TestB;
          test |= TestC;

          使用按位异或(^)为枚举 变量 test 去掉一个枚举成员 TestC  。ps: 两者相等为0 ,不等为1 。

          Test test = TestA | TestB | TestC;
          test ^= TestC;

          使用按位与(&)判断枚举 变量test 是否赋值了枚举成员 TestA  。

          Test test = TestA | TestB;
          if (test & TestA){
              NSLog(@"yes");
          }else{
              NSLog(@"no");
          }

          七十、验证码的作用

          待更新  。  。 。  。

          七十一、帧率优化

          Color Blended Layers(red)

          png 图片是支持透明的,对系统性能也会有影响的  。最好不要设置透明度  ,由于透明的图层和其他图层重叠在一块的部门  ,CPU 会做处置惩罚图层叠加颜色盘算  ,这种处置惩罚是比力消耗资源的  。

          Color Copied Images (cyan)

          苹果的 GPU 只剖析 32bit 的颜色花样  。

          若是一张图片  ,颜色花样不是 32bit  ,CPU 会先举行颜色花样转换  ,再让 GPU 渲染  。 就算异步转换颜色  ,也会导致性能消耗  ,好比电量增多、发烧等等 。解决措施是让设计师提供 32bit 颜色花样的图片 。图片颜色科普文章:图片的颜色深度/颜色花样(32bit,24bit,12bit)

          Color Misaligned Images 像素对齐(yellow)

          iOS装备上 ,有逻辑像素(point)和 物理像素(pixel)之分  ,像素对齐指的是物理像素对齐  ,对齐就是像素点的值是整数  。UI 设计师提供的设计稿标注以及中的 frame 是 逻辑像素  。GPU在渲染图形之前  ,系统会将逻辑像素换算成 物理像素  。point  和 pixel 的比例是通过[[UIScreen mainScreen] scale] 来制订的  。在没有视网膜屏之前 ,1point = 1pixel;可是2x和3x的视网膜屏出来之后  ,1point = 2pixel 或 3pixel 。

          逻辑像素乘以 2 或 3 获得整数值就像素对齐了  ,反之则像素差池齐 。像素差池齐会导致 GPU 渲染时 ,对没对齐的边缘举行插值盘算  ,插值盘算会有性能消耗 。

          原图片巨细和视图控件巨细纷歧致  ,图片为了对应在控件的响应的位置就需要做一些盘算  ,然后确定图片的位置  ,该种情形也比力消耗资源  。一样平常可以通过绘制指定尺寸巨细、不透明的图片来优化性能  。

          Color Off-screen Rendered (yellow)

          cornerRadius 属性只应用于 layer 的配景色和边线  。将 masksToBounds 属性设置为 YES 才气把内容按圆角形状裁剪  。同时设置 cornerRadius 和 masksToBounds = YES  ,而且屏幕中同时显示的圆角个数过多  ,就会显着感受到卡顿和跳帧  ,只是设置 cornerRadius 并不会触发此种征象  。当使用圆角  ,阴影  ,遮罩的时间 ,图层属性的混淆体被指定为在未预合成之前不能直接在屏幕中绘制  ,以是就需要屏幕外渲染被唤起  。使用离屏渲染的时间会很容易造成性能消耗 ,由于在 OpenGL 里离屏渲染会单独在内存中建立一个屏幕外缓冲区并举行渲染 ,而屏幕外缓冲区跟当前屏幕缓冲区上下文切换是很耗性能的  。iOS9 之后系统设置圆角不再发生离屏渲染  。设置 shadow***相关阴影属性也会发生离屏渲染  ,解决要领是设置阴影路径 shadowPath  。

          法制止离屏渲染的时间可实验使用光栅化来进一步做优化  。光栅化是指将图转化为一个个栅格组成的图象  。shouldRasterize = YES 在其他属性触发离屏渲染的同时 ,会将光栅化后的内容缓存起来  ,若是对应的layer 及其 sublayers 没有发生改变  ,在下一帧的时间可以直接复用  ,从而淘汰渲染的频率  。当使用光栅化时 ,可以在 Core Animation 开启 Color Hits Green and Misses Red 来检查该场景下光栅化操作是否是一个好的选择  。绿色表现缓存被复用  ,红色表现缓存在被重复建立 。若是光栅化的层变红得太频仍那么光栅化对优化可能没有几多用处  ,反之就可以开启  。

          七十二、内存数据擦除

          敏感数据不想一直保留在内存中  ,可以通过特定的 API 擦除内存中的数据 ,好比 NSString:

          @implementation NSString (MemoryClear)
          /**
           内存数据实时擦除
           */
          -(void)memoryClearStirng{
              const char*string = (char *)CFStringGetCStringPtr((CFStringRef)self,CFStringGetSystemEncoding());
              memset(&string, 0, sizeof(self));
          }
          @end

          七十三、内存泄露监测原理

          待更新  。 。  。 。

          七十四、卡顿代码监测原理

          待更新  。  。  。 。

          七十五、像素和点

          待更新  。  。 。 。

          七十六、