<?xml version="1.0" encoding="utf-8"?><feed xmlns="http://www.w3.org/2005/Atom" ><generator uri="https://jekyllrb.com/" version="3.7.4">Jekyll</generator><link href="https://momoai.github.io/feed.xml" rel="self" type="application/atom+xml" /><link href="https://momoai.github.io/" rel="alternate" type="text/html" /><updated>2019-03-25T08:07:53+00:00</updated><id>https://momoai.github.io/feed.xml</id><title type="html">momo’s blog</title><subtitle>这个人很懒</subtitle><entry><title type="html">Effective objective C 2.0笔记（一）</title><link href="https://momoai.github.io/Effective-Objective-C-2.0%E7%AC%94%E8%AE%B0-%E4%B8%80/" rel="alternate" type="text/html" title="Effective objective C 2.0笔记（一）" /><published>2019-03-24T00:00:00+00:00</published><updated>2019-03-24T00:00:00+00:00</updated><id>https://momoai.github.io/Effective%20Objective-C%202.0%E7%AC%94%E8%AE%B0(%E4%B8%80)</id><content type="html" xml:base="https://momoai.github.io/Effective-Objective-C-2.0%E7%AC%94%E8%AE%B0-%E4%B8%80/">&lt;h1 id=&quot;第一章-熟悉objective-c&quot;&gt;第一章 熟悉Objective-C&lt;/h1&gt;
&lt;p&gt;之前看了《Effective Objective-C 2.0 编写高质量iOS与OS X代码的52个有效方法》这本五星好书，受益颇多，在一定程度上提高了编写的代码的质量。现在就对这52个有效方法做简单笔记。&lt;/p&gt;
&lt;h2 id=&quot;第1条了解oc语言的起源&quot;&gt;第1条：了解OC语言的起源&lt;/h2&gt;
&lt;p&gt;OC为C语言添加了面向对象特性，是其超集。OC与其他面向对象的语言在很多方面都有差别。OC语言由Smalltalk（消息型语言鼻祖）演化而来。OC语言使用动态绑定的“消息结构”而非“函数调用”，也就是说在运行时才会检查对象类型，接收一条消息后，究竟执行何种代码，由运行时环境而非编译器来决定。&lt;/p&gt;
&lt;h2 id=&quot;第2条在类的头文件中尽量少引入其他头文件&quot;&gt;第2条：在类的头文件中尽量少引入其他头文件&lt;/h2&gt;
&lt;p&gt;在类的头文件中引入其他头文件可能会有如下问题：&lt;/p&gt;
&lt;ol&gt;
  &lt;li&gt;A类头文件引入了B类的头文件，B类头文件又引入了A类头文件，这两个类就相互引用了，编译不过。&lt;/li&gt;
  &lt;li&gt;A类头文件引入了C类的头文件，当B类引入A类头文件时，不需要的C类也被引入了B类。而另一个类又引入B类时，没有用到的A类C类也被引入了。如此持续下去，则要引入许多根本用不到的内容，会增加编译时间。&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;取而代之，我们应尽量使用向前声明@class声明，而在.m文件中引入头文件。@class让编译器知道有这样一个类，而不需要知道类的全部细节。这样将引入头文件的时机尽量的延后，只在需要的时候才引入。
但是有时候必须要在头文件中引入其他头文件，有如下情况：&lt;/p&gt;
&lt;ol&gt;
  &lt;li&gt;继承至父类的子类，必须在头文件中引入父类的头文件。&lt;/li&gt;
  &lt;li&gt;类遵循某个协议时，该协议必须有完整的定义，编译器要知道该协议的方法，而不能使用@class。（其实也可以在.m文件中的扩展中遵循协议，那么就不用在头文件中去引入协议头文件了）&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;总的说来，每次在头文件中引入其他头文件之前，都要先问问自己这样做是否有必要。除非确有必要，否则不要引入，而应该使用向前声明@class。&lt;/p&gt;
&lt;h2 id=&quot;第3条多用字面量语法少用与之等价的方法&quot;&gt;第3条：多用字面量语法，少用与之等价的方法&lt;/h2&gt;
&lt;p&gt;Foundation框架中NSString,NSNumber,NSArray,NSDictionary都有对应的字面量语法：@””,@1,@[],@{}。字面量语法特点：&lt;/p&gt;
&lt;ol&gt;
  &lt;li&gt;语法精简，缩减代码长度更为易读。&lt;/li&gt;
  &lt;li&gt;字面量语法有利于数组，字典的操作：可以通过下标的形式进行操作，简明扼要。&lt;/li&gt;
  &lt;li&gt;使用字面量创建数组/字典时，若集合中元素对象有nil，则会抛出异常，而非字面量创建时会到nil对象为止。这种差别表明，字面量语法更加安全，抛出异常总比创建的集合元素莫名的少了要好。&lt;/li&gt;
  &lt;li&gt;字面量创建的对象是不可变的，若想要可变的则需复制一份，类似@[].mutableCopy。
    &lt;h2 id=&quot;第4条多用类型常量少用define预处理指令&quot;&gt;第4条：多用类型常量，少用#define预处理指令&lt;/h2&gt;
  &lt;/li&gt;
  &lt;li&gt;预处理指令定义的常量不含类型信息，编译器只是会在编译前据此执行查找与替换操作。在我们用错类型时，编译时并不能及时发现错误。如果不小心重新定义了常量值，编译器也不会产生警告信息，这将导致应用程序的常量值不一致。&lt;/li&gt;
  &lt;li&gt;类型常量包含类型消息，清楚地描述了常量的含义，可以让代码更易被理解。使用时更好了利用编译器的某些特性。&lt;/li&gt;
  &lt;li&gt;常量命名规则：若常量局限于实现文件之内，则在前面加字母k；若常量在类之外可见，则通常以类名为前缀。&lt;/li&gt;
  &lt;li&gt;常量一般使用static const声明，const声明的变量可以防止被修改，如果试图修改会得到编译错误。而static修饰的变量意味着该变量仅在定义此变量的编译单元（及实现文件）中可见。&lt;/li&gt;
  &lt;li&gt;有时需要公开某个常量，以便可以在定义该变量的编译单元之外使用。此类常量需要放在“全局符号表”中，这就需要在头文件用extern关键字声明，而在实现文件中定义（不能使用static）。使用extern关键字，编译器无须查看其定义，即允许代码使用此常量。因为它知道，当链接成二进制文件后，肯定能找到这个常量。&lt;/li&gt;
  &lt;li&gt;预处理指令定义的字符串，编译器可以对相同的字符串进行优化，只保存一份到 .rodata 段。甚至有相同后缀的字符串也可以优化，”Hello world” 与 “world” 两个字符串，只存储前面一个。取的时候只需要给前面和中间的地址，如果是整形、浮点型会有多份拷贝，但这些数写在指令中。占的只是代码段而已，大量用预处理指令会导致二进制文件变大。
    &lt;h2 id=&quot;第5条用枚举表示状态-选项-状态码&quot;&gt;第5条：用枚举表示状态 选项 状态码&lt;/h2&gt;
    &lt;p&gt;对于状态 选项等，使用枚举写出来的代码更易读懂。编译器为枚举分配一个独有的编号，从0开始，每个枚举递增1。也可以自己设值，不使用编译器分配的。设置的枚举值可以用二进制表示，这样通过“按位或操作符”可组合多个选项。
也可以使用typedef关键字重新定义枚举类型，使定义枚举变量的方式更简洁。Foundation框架中定义了一些辅助的宏，可以很方便的定义枚举类型，也可以指定用于保存枚举值的底层数据类型。相关的宏有两种：&lt;/p&gt;
    &lt;div class=&quot;highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;#define NS_ENUM(...) CF_ENUM(__VA_ARGS__)
#define NS_OPTIONS(_type, _name) CF_OPTIONS(_type, _name)
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;    &lt;/div&gt;
    &lt;p&gt;两种宏的区别：作为选项的枚举值经常使用按位或运算组合，在运算两个枚举值时，C++认为运算的结果应该是枚举的底层数据类型也就是NSUInteger。C++不允许将底层类型“隐式转换”为枚举类型本身。若编译器按C++模式编译，而按位运算的类型与枚举底层数据类型不一致，这时使用NS_ENUM会报错。而NS_OPTIONS很好的兼容了这个问题，若非C++模式编译其和NS_ENUM一样，若是C++模式编译，NS_OPTIONS会将按位操作的结果显式转换，为我们省去类型转换的操作。鉴于此，凡是需要按位运算组合的枚举都应使用NS_OPTIONS，若不需要组合，则应使用NS_ENUM。&lt;/p&gt;
    &lt;div class=&quot;highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;typedef NS_OPTIONS(NSUInteger, UIControlState) {
 UIControlStateNormal       = 0,
 UIControlStateHighlighted  = 1 &amp;lt;&amp;lt; 0,                  // used when UIControl isHighlighted is set
 UIControlStateDisabled     = 1 &amp;lt;&amp;lt; 1,
 UIControlStateSelected     = 1 &amp;lt;&amp;lt; 2,                  // flag usable by app (see below)
 UIControlStateFocused NS_ENUM_AVAILABLE_IOS(9_0) = 1 &amp;lt;&amp;lt; 3, // Applicable only when the screen supports focus
 UIControlStateApplication  = 0x00FF0000,              // additional flags available for application use
 UIControlStateReserved     = 0xFF000000               // flags reserved for internal framework use
};
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;    &lt;/div&gt;
    &lt;p&gt;还有就是，在处理枚举类型的switch语句中不要实现default分支。这样的话，加入新枚举后，编译器会提示我们有未处理的枚举，以免遗漏。&lt;/p&gt;
  &lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;第一章的5条总结完毕，持续更新中…&lt;/p&gt;</content><author><name></name></author><summary type="html">第一章 熟悉Objective-C 之前看了《Effective Objective-C 2.0 编写高质量iOS与OS X代码的52个有效方法》这本五星好书，受益颇多，在一定程度上提高了编写的代码的质量。现在就对这52个有效方法做简单笔记。 第1条：了解OC语言的起源 OC为C语言添加了面向对象特性，是其超集。OC与其他面向对象的语言在很多方面都有差别。OC语言由Smalltalk（消息型语言鼻祖）演化而来。OC语言使用动态绑定的“消息结构”而非“函数调用”，也就是说在运行时才会检查对象类型，接收一条消息后，究竟执行何种代码，由运行时环境而非编译器来决定。 第2条：在类的头文件中尽量少引入其他头文件 在类的头文件中引入其他头文件可能会有如下问题： A类头文件引入了B类的头文件，B类头文件又引入了A类头文件，这两个类就相互引用了，编译不过。 A类头文件引入了C类的头文件，当B类引入A类头文件时，不需要的C类也被引入了B类。而另一个类又引入B类时，没有用到的A类C类也被引入了。如此持续下去，则要引入许多根本用不到的内容，会增加编译时间。</summary></entry><entry><title type="html">Effective objective C 2.0笔记（三）</title><link href="https://momoai.github.io/Effective-Objective-C-2.0%E7%AC%94%E8%AE%B0-%E4%B8%89/" rel="alternate" type="text/html" title="Effective objective C 2.0笔记（三）" /><published>2019-03-24T00:00:00+00:00</published><updated>2019-03-24T00:00:00+00:00</updated><id>https://momoai.github.io/Effective%20Objective-C%202.0%E7%AC%94%E8%AE%B0(%E4%B8%89)</id><content type="html" xml:base="https://momoai.github.io/Effective-Objective-C-2.0%E7%AC%94%E8%AE%B0-%E4%B8%89/">&lt;h1 id=&quot;第三章-接口与api设计&quot;&gt;第三章 接口与API设计&lt;/h1&gt;
&lt;h2 id=&quot;第15条用前缀避免命名冲突&quot;&gt;第15条：用前缀避免命名冲突&lt;/h2&gt;
&lt;ul&gt;
  &lt;li&gt;OC没有命名空间，为了避免潜在的命名冲突，可以使用加前缀的方式&lt;/li&gt;
  &lt;li&gt;选择公司，应用程序或有关联的名称作为类名的前缀，并在所有代码中使用这一前缀
    &lt;h2 id=&quot;第16条提供全能初始化方法&quot;&gt;第16条：提供“全能初始化方法”&lt;/h2&gt;
  &lt;/li&gt;
  &lt;li&gt;全能初始化方法：为对象提供必要信息以便其能完成工作的初始化方法，类似UITableViewCell初始化方法：
```&lt;/li&gt;
  &lt;li&gt;(instancetype)initWithStyle:(UITableViewCellStyle)style reuseIdentifier:(nullable NSString *)reuseIdentifier
```
这里必要信息是：style，reuseIdentifier样式及重用标识&lt;/li&gt;
  &lt;li&gt;有多个初始化方法时，可以在其中选定一个作为全能初始化方法，令其他初始化方法都调用他。这样，这有全能初始化方法存储内部数据，需要改变内部结构时，只需要改全能初始化方法而不用改动其他初始化方法&lt;/li&gt;
  &lt;li&gt;如果超类的初始化方法不适用于子类，那么应该覆写这个超类的方法，并在其中抛出异常
##第 17条：实现description方法
通过NSLog打印并查看对象信息时，对象会收到description消息，返回对象相关的信息。但默认的description方法返回的信息，有时并不是我们想要的，通过覆写description可以实现输出我们在定义的信息。类似的，还有debugDescription方法，它和description区别：debugDescription方法是开发者在调试器以控制台命令打印对象时调用的（LLDB “po”命令）。当我们通过LLDB “po”命令打印对象信息时，就可以覆写debugDescription返回我们需要的信息。
    &lt;h2 id=&quot;第18条尽量使用不可变对象&quot;&gt;第18条：尽量使用不可变对象&lt;/h2&gt;
  &lt;/li&gt;
  &lt;li&gt;使用属性时，默认情况下属性是“可读可写”的，这样设计出来的类都是“可变的”。&lt;/li&gt;
  &lt;li&gt;为了防止对象被更改，应该尽量把对外公布的属性设为只读，而只在必要的时候才对外公布。&lt;/li&gt;
  &lt;li&gt;若某属性仅可于对象内部修改，可以在分类中将readonly属性扩展为readwrite属性。&lt;/li&gt;
  &lt;li&gt;不要把可变的集合（collection）作为属性公开，而应提供方法，通过方法修改对象的可变collection。
    &lt;h2 id=&quot;第19条使用清晰而协调的命名方式&quot;&gt;第19条：使用清晰而协调的命名方式&lt;/h2&gt;
  &lt;/li&gt;
  &lt;li&gt;方法命名：方法名使用“驼峰大小写命名法”；使用“in”，“for”，“with”等介词连接，使得代码读起来和句子一样；方法名要明确每个参数等含义，把表示参数类型的名词放在参数前面；方法有返回值时，方法名的首个词最好是返回值的类型；布尔属性应该根据其功能，选用类似has， is当前缀；不要使用类似str简称，应该使用全称；方法名也不能长得太过分，应尽量在用意表达清楚的基础上做到言简意赅；&lt;/li&gt;
  &lt;li&gt;类和协议的命名：类和协议的名称要加上前缀，避免命名冲突；命名应该把词汇组织好，从左至右读起来通顺；定义委托协议时，把委托接口的类名放在前面，后面加Delegate一词。
    &lt;h2 id=&quot;第20条为私有方法名加前缀&quot;&gt;第20条：为私有方法名加前缀&lt;/h2&gt;
    &lt;p&gt;为私有方法名加前缀，很容易区分公共方法和私有方法，有助于调试。使用何种前缀，由个人喜好决定，一般用p_作为前缀，尽量不要单独使用_作为前缀只是预留给苹果爸爸的。&lt;/p&gt;
    &lt;h2 id=&quot;第21条理解错误模型&quot;&gt;第21条：理解错误模型&lt;/h2&gt;
    &lt;ol&gt;
      &lt;li&gt;用异常机制处理错误&lt;/li&gt;
    &lt;/ol&gt;
  &lt;/li&gt;
  &lt;li&gt;通过@throw抛出异常（NSException）&lt;/li&gt;
  &lt;li&gt;通过@try @catch捕获并处理异常&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;但这种机制，如果抛出异常，那么本应在作用域末尾释放的对象不会自动释放，也就是说默认情况下不是“异常安全的”；所以，异常只应该应用于极其严重的错误，异常抛出后无须考虑恢复问题，而且应用程序此时应该退出。&lt;/p&gt;
&lt;ol&gt;
  &lt;li&gt;NSError
    &lt;ul&gt;
      &lt;li&gt;处理不是很严重的错误，表明有错误发生，程序不用退出。&lt;/li&gt;
      &lt;li&gt;用法灵活，对象里封装了3条消息：Error domain错误范围；Error code错误码；Userinfo用户信息；&lt;/li&gt;
      &lt;li&gt;NSError一个常见用法是通过委托协议传递此错误。
```&lt;/li&gt;
      &lt;li&gt;(void)connection:(NSURLConnection *)connection didFailWithError:(NSError *)error;
```&lt;/li&gt;
      &lt;li&gt;另一个用法是经由方法的输出参数返回调用者,传递的参数是个指针，这个指针又指向另一个指针，另一个指针指向NSError对象。
```&lt;/li&gt;
      &lt;li&gt;(BOOL)createDirectoryAtPath:(NSString *)path withIntermediateDirectories:(BOOL)createIntermediates attributes:(nullable NSDictionary&amp;lt;NSFileAttributeKey, id&amp;gt; *)attributes error:(NSError **)error
```
        &lt;h2 id=&quot;第22条理解nscopying协议&quot;&gt;第22条：理解NSCopying协议&lt;/h2&gt;
      &lt;/li&gt;
      &lt;li&gt;若想令自己所写的对象具有拷贝功能（copy），需要实现NSCoping协议。通过实现- (id)copyWithZone:(NSZone *)zone方法返回新的对象&lt;/li&gt;
      &lt;li&gt;如果自定义的类分为可变和不可变版本，要同时实现NSCopying和NSMutableCopying协议&lt;/li&gt;
    &lt;/ul&gt;
  &lt;/li&gt;
&lt;/ol&gt;

&lt;h2 id=&quot;第四章-协议与分类&quot;&gt;第四章 协议与分类&lt;/h2&gt;
&lt;h3 id=&quot;第23条通过委托与数据源协议进行对象间通信&quot;&gt;第23条：通过委托与数据源协议进行对象间通信&lt;/h3&gt;
&lt;ul&gt;
  &lt;li&gt;委托模式（代理模式）是一种实现对象间通信的设计模式，可将数据与业务逻辑解耦；主旨是定义一套接口，某一对象想接受另一对象的委托，需遵循此协议，成为其代理；另一对象可以给委托对象回传消息；&lt;/li&gt;
  &lt;li&gt;委托协议名通常是相关类名后加Delegate，方便理解；&lt;/li&gt;
  &lt;li&gt;委托协议的方法默认是@require的，使用@optional可以指明可选方法；委托对象调用可选方法，必须提前使用类型信息查询方法判断是否响应选择器：&lt;code class=&quot;highlighter-rouge&quot;&gt;respondsToSelector:&lt;/code&gt;&lt;/li&gt;
  &lt;li&gt;定义委托协议方法时，应该提供一个委托发起对象的实例作为参数，方便根据该实例分别执行不同的代码；
    &lt;h3 id=&quot;第24条将类的实现代码分散到便于管理的数个分类之中&quot;&gt;第24条：将类的实现代码分散到便于管理的数个分类之中&lt;/h3&gt;
  &lt;/li&gt;
  &lt;li&gt;一个类的方法过多时会变得难于管理，可以使用分类把类的实现代码划分成便于管理的小块；&lt;/li&gt;
  &lt;li&gt;将不想公开的方法归入单独一个分类中，隐藏实现细节；
    &lt;h3 id=&quot;第25条总是为第三方类的分类名称加前缀&quot;&gt;第25条：总是为第三方类的分类名称加前缀&lt;/h3&gt;
    &lt;h3 id=&quot;第26条勿在分类中声明属性&quot;&gt;第26条：勿在分类中声明属性&lt;/h3&gt;
  &lt;/li&gt;
  &lt;li&gt;除了Extension外，其他分类都无法向类中新增实例变量，因此无法把属性所需的实例变量合成出来；&lt;/li&gt;
  &lt;li&gt;可以通过关联对象技术解决分类中不能合成实例变量的问题，实现为分类添加属性，这样做在内存管理问题上容易出错，应该尽量避免&lt;/li&gt;
  &lt;li&gt;有时只读属性还是可以在分类中使用，实现属性getter方法，就不会为该属性合成实例变量了；（类似于swift中计算属性的使用）
    &lt;h3 id=&quot;第27条使用class-continuation分类隐藏实现细节&quot;&gt;第27条：使用&lt;code class=&quot;highlighter-rouge&quot;&gt;class-continuation&lt;/code&gt;分类隐藏实现细节&lt;/h3&gt;
    &lt;p&gt;class-continuation分类即Extension&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;可以通过Extension为类添加属性，Extension定义在原类的实现文件中&lt;/li&gt;
  &lt;li&gt;可以将“私有”的属性声明在Extension里面，这样外界既不能访问该属性也完全不知道这个属性，从而隐藏相关细节&lt;/li&gt;
  &lt;li&gt;类所遵循协议也可以在Extension中声明，从而使协议也不为外界所知&lt;/li&gt;
  &lt;li&gt;如果属性在.h文件声明为readonly，而类的内部又要改动该属性，可以在Extension中将属性声明为readwrite
    &lt;h3 id=&quot;第28条通过协议提供匿名对象&quot;&gt;第28条：通过协议提供匿名对象&lt;/h3&gt;
    &lt;p&gt;匿名对象：协议可以提供匿名类型：遵从该协议的纯id类型，如委托对象delegate&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;需要隐藏类型名称，可以使用匿名对象；&lt;/li&gt;
  &lt;li&gt;如果具体类型不重要，只要对象能响应协议的方法，也可以使用匿名对象；&lt;/li&gt;
&lt;/ul&gt;</content><author><name></name></author><summary type="html">第三章 接口与API设计 第15条：用前缀避免命名冲突 OC没有命名空间，为了避免潜在的命名冲突，可以使用加前缀的方式 选择公司，应用程序或有关联的名称作为类名的前缀，并在所有代码中使用这一前缀 第16条：提供“全能初始化方法” 全能初始化方法：为对象提供必要信息以便其能完成工作的初始化方法，类似UITableViewCell初始化方法： ``` (instancetype)initWithStyle:(UITableViewCellStyle)style reuseIdentifier:(nullable NSString *)reuseIdentifier ``` 这里必要信息是：style，reuseIdentifier样式及重用标识 有多个初始化方法时，可以在其中选定一个作为全能初始化方法，令其他初始化方法都调用他。这样，这有全能初始化方法存储内部数据，需要改变内部结构时，只需要改全能初始化方法而不用改动其他初始化方法 如果超类的初始化方法不适用于子类，那么应该覆写这个超类的方法，并在其中抛出异常 ##第 17条：实现description方法 通过NSLog打印并查看对象信息时，对象会收到description消息，返回对象相关的信息。但默认的description方法返回的信息，有时并不是我们想要的，通过覆写description可以实现输出我们在定义的信息。类似的，还有debugDescription方法，它和description区别：debugDescription方法是开发者在调试器以控制台命令打印对象时调用的（LLDB “po”命令）。当我们通过LLDB “po”命令打印对象信息时，就可以覆写debugDescription返回我们需要的信息。 第18条：尽量使用不可变对象 使用属性时，默认情况下属性是“可读可写”的，这样设计出来的类都是“可变的”。 为了防止对象被更改，应该尽量把对外公布的属性设为只读，而只在必要的时候才对外公布。 若某属性仅可于对象内部修改，可以在分类中将readonly属性扩展为readwrite属性。 不要把可变的集合（collection）作为属性公开，而应提供方法，通过方法修改对象的可变collection。 第19条：使用清晰而协调的命名方式 方法命名：方法名使用“驼峰大小写命名法”；使用“in”，“for”，“with”等介词连接，使得代码读起来和句子一样；方法名要明确每个参数等含义，把表示参数类型的名词放在参数前面；方法有返回值时，方法名的首个词最好是返回值的类型；布尔属性应该根据其功能，选用类似has， is当前缀；不要使用类似str简称，应该使用全称；方法名也不能长得太过分，应尽量在用意表达清楚的基础上做到言简意赅； 类和协议的命名：类和协议的名称要加上前缀，避免命名冲突；命名应该把词汇组织好，从左至右读起来通顺；定义委托协议时，把委托接口的类名放在前面，后面加Delegate一词。 第20条：为私有方法名加前缀 为私有方法名加前缀，很容易区分公共方法和私有方法，有助于调试。使用何种前缀，由个人喜好决定，一般用p_作为前缀，尽量不要单独使用_作为前缀只是预留给苹果爸爸的。 第21条：理解错误模型 用异常机制处理错误 通过@throw抛出异常（NSException） 通过@try @catch捕获并处理异常</summary></entry><entry><title type="html">Effective objective C 2.0笔记（二）</title><link href="https://momoai.github.io/Effective-Objective-C-2.0%E7%AC%94%E8%AE%B0-%E4%BA%8C/" rel="alternate" type="text/html" title="Effective objective C 2.0笔记（二）" /><published>2019-03-24T00:00:00+00:00</published><updated>2019-03-24T00:00:00+00:00</updated><id>https://momoai.github.io/Effective%20Objective-C%202.0%E7%AC%94%E8%AE%B0(%E4%BA%8C)</id><content type="html" xml:base="https://momoai.github.io/Effective-Objective-C-2.0%E7%AC%94%E8%AE%B0-%E4%BA%8C/">&lt;p&gt;#第二章 对象 消息 运行期
##第6条：理解“属性”这一概念
属性是OC的一项特性，用于封装对象中的数据（通常数据保存为各种实例变量）。实例变量一般通过存取方法getter和setter方法读取和写入，使用属性，编译器自动编写了相关的存取方法（自动合成），我们也可以自己编写存取方法，但一定要满足命名规范。使用属性，编译器还自动向类中添加了适当类型的实例变量，并在属性名前加_作为实例变量的名称。同时属性引入了“点语法”（点语法其实就是调用了getter和setter方法），我们可以容易的访问存放于属性中的数据。
属性有关的关键字：@synthesize @dynamic
@synthesize可以指定实例变量的名称，@dynamic编译器不会创建实现属性的实例变量，也不会合成存取方法。
属性的特质会影响到存取方法，属性拥有四种特质：&lt;/p&gt;
&lt;ol&gt;
  &lt;li&gt;原子性：atomic是“原子的”，合成的方法中会加同步锁。nonatomic是“非原子的”，合成方法不会加同步锁。一般情况下使用nonatomic，因为加同步锁意义不大，因为并不能确保线程安全，而且使用同步锁开销较大，会带来性能问题。&lt;/li&gt;
  &lt;li&gt;读写权限：readwrite,属性拥有setter和getter方法；readonly，属性仅拥有获取方法，外部不能更改数据。&lt;/li&gt;
  &lt;li&gt;内存管理语义：assign,setter方法只会执行针对“纯量类型”的简单赋值操作；strong,属性定义了一种拥有关系，setter方法会保留新值，释放旧值，再将新值设置上去；weak,属性定义了一种非拥有关系，setter方法不会保留新值，也不释放旧值，当属性指向的对象销毁时，属性值会自动清空；unsafe_unretained,和weak类似，区别是当属性指向的对象销毁时，属性值不会自动清空（不安全）；copy,和strong类似，然而setter方法并不保留新值，而是将其拷贝。当类型有可变和不可变类型时，不可变的属性一般需要使用copy特质，因为设置的新值可能指向可变的类型，如果不用copy，属性值可能在不知情的情况下遭人修改。&lt;/li&gt;
  &lt;li&gt;方法名：getter=&lt;name&gt; 指定获取方法的方法名；setter=&lt;name&gt;指定设置方法的方法名。
##第7条：在对象内部尽量直接访问实例变量
直接访问实例变量和通过属性访问实例变量区别：&lt;/name&gt;&lt;/name&gt;&lt;/li&gt;
  &lt;li&gt;直接访问实例变量速度相对较快，因为不需要经过“方法派发”。编译器所生成的代码会直接访问保存对象实例变量的那块内存。&lt;/li&gt;
  &lt;li&gt;直接访问实例变量，不会调用getter，setter方法，这就绕过了属性所定义的内存管理语义。（修饰属性的strong,copy等没用了）&lt;/li&gt;
  &lt;li&gt;直接访问实例变量，不会触发KVO。&lt;/li&gt;
  &lt;li&gt;通过属性访问实例变量，可以通过getter，setter方法监控属性的调用者和其访问时机，方便调试。&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;综合以上，折中的选择是：&lt;/p&gt;
&lt;ol&gt;
  &lt;li&gt;读取数据时，应该直接通过实例变量读取；写入数据时，应该通过属性写入（setter方法）。&lt;/li&gt;
  &lt;li&gt;init方法及dealloc方法中，应该直接通过实例变量来读写数据。&lt;/li&gt;
  &lt;li&gt;使用懒加载时，必须通过属性读写数据，而懒加载方法内必须直接通过实例变量读取数据（避免死循环）。&lt;/li&gt;
  &lt;li&gt;使用KVO时，需通过属性读取数据。
##第8条：理解“对象等同性”这一概念
关于“等同性”，==操作符比较的是两个指针本身，而不是其所指的对象。比如我们要判断俩个字符串是不是一样的，就不能使用==操作符，而必须使用内建的isEqualToString:方法。NSObject协议提供了两个判断等同性的关键方法：
    &lt;div class=&quot;highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;- (BOOL)isEqual: (id) object;
- (NSUInteger)hash;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;    &lt;/div&gt;
    &lt;p&gt;我们可以实现协议的这两个方法，实现我们定义的“等同性”，类似于NSString类的isEqualToString:&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;isEqual: 方法实现判断相等的条件。&lt;/li&gt;
  &lt;li&gt;如果isEqual: 方法判定两个对象相等（返回YES）,那么hash方法也必须返回同一个值。&lt;/li&gt;
  &lt;li&gt;编写hash方法时，应该使用计算速度快而且哈希码碰撞几率低的算法。最常用的算法是把类的每个判等的属性的哈希值进行异或运算，这样做既能保持较高的效率，又能使生成的哈希码至少位于一定范围内，而不会频繁的重复。
##第9条：以类族模式隐藏实现细节
类族（类簇）:基类提供创建各子类的方法，并提供接口，子类通过接口实现相应的方法。用户无需自己创建子类实例，只需要调用基类方法创建即可。这样将子类的实现细节隐藏在抽象基类后面。工厂模式是创建类族的方法之一。系统框架中普遍使用类族，例如NSArray(大部分集合类),NSArray的alloc方法获取实例时，该方法首先会分配一个属于某类的实例充当“占位数组”。该数组稍后会转为另一个类的实例，而这个类就是NSArray的实体子类。
又如UIButton:
    &lt;div class=&quot;highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;+ (instancetype)buttonWithType:(UIButtonType)buttonType;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;    &lt;/div&gt;
    &lt;p&gt;UIButton通过这个类方法创建button，button的类型取决于传入的按钮buttonType，他会返回对应类型的UIButton的子类对象，它们的基类都是UIButton。而具体的每个子类的实现细节，即各种类型button的创建过程不需要我们考虑，我们只需调用基类的方法即可。
类族的一个注意点：类似NSArray alloc创建的对象，其实返回的是对应的子类而非NSArray基类，所以判断创建的对象是否是NSArray类时会有问题：&lt;/p&gt;
    &lt;div class=&quot;highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;- (BOOL)isKindOfClass:(Class)aClass; // YES
- (BOOL)isMemberOfClass:(Class)aClass; // NO
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;    &lt;/div&gt;
    &lt;p&gt;##第10条：在既有类中使用关联对象存放自定义数据&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;可以通过objc_setAssociatedObject方法给对象关联其他对象，objc_getAssociatedObject获取关联的对象。
    &lt;div class=&quot;highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;objc_setAssociatedObject(id _Nonnull object, const void * _Nonnull key,
                      id _Nullable value, objc_AssociationPolicy policy)
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;    &lt;/div&gt;
    &lt;p&gt;参数object为关联的对象；key是区分关联对象的“键”，key一般需要声明为static全局变量；policy为存储策略，是用来维护相应的“内存管理语义”，和属性的内存管理语义是等效的。&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;通过关联对象，我们可以在分类中实现添加属性的功能。&lt;/li&gt;
  &lt;li&gt;只有在其他做法不可行时才应选用关联对象，因为这种做法通常会引入难以查找的bug
##第11条：理解objc_msgSend的作用
OC是一门消息型语言，在对象上调用方法，就是传递消息的过程。消息有“名称name”或“选择器selector”,可以接受参数而且可能有返回值：消息由接收者,选择子及参数构成。如果向某对象传递消息，那就会使用“动态绑定”机制来决定需要调用的方法，究竟该调用哪个方法完全于运行期决定，甚至在程序运行时改变，这使得OC能成为一门真正的动态语言。发送消息时，编译器会将消息转换为标准的C语言函数调用，这个函数即消息传递机制中的核心函数objc_msgSend，其原型如下：
    &lt;div class=&quot;highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;void objc_msgSend(id self, SEL cmd, ...)
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;    &lt;/div&gt;
    &lt;p&gt;这是个可变参数的函数，能接受两个及以上的参数。第一个参数为接受者，第二个参数cmd为选择器，后续参数为消息本来的那些参数，顺序也一致。
objc_msgSend函数会依据接受者与选择器来调用适当的方法：首先需要在接收者所属的类中搜寻“方法列表”（选择器的名称则是查表所用的key），如果能找到与选择器名称相符的方法，就跳至对应的实现代码。若是找不到，那就沿着继承体系向上查找，找到了合适的方法后再跳转。如果最终找不到，就会执行消息转发操作。
##第12条：理解消息转发机制
当对象接收到无法解读的消息后（对象无法响应某个选择器），则进入消息转发流程。
消息转发分为以下阶段：&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;动态方法解析：这时可以通过 + (BOOL)resolveInstanceMethod:(SEL)sel方法动态添加一个方法。&lt;/li&gt;
  &lt;li&gt;备援接收者：当前面一个阶段仍没处理时，会有第二次机会处理未知的选择器，这一步可以通过- (id)forwardingTargetForSelector:(SEL)aSelector返回备援对象将消息转给其他接收者来处理。&lt;/li&gt;
  &lt;li&gt;完整的消息转发：当以上两步都未处理时，那么唯一能做的就是启用完整的消息转发机制。首先通过- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector方法返回函数签名由后续方法- (void)forwardInvocation:(NSInvocation *)anInvocation执行：NSInvocation对象anInvocation里封装了未处理的消息有关的全部细节（选择器，target及参数）。通过这个方法可以某种方式改变消息内容，如添加参数，更换选择器，替换target（和备援接收者等效）等。&lt;/li&gt;
  &lt;li&gt;当前面几步都未处理消息时，会调用- (void)doesNotRecognizeSelector:(SEL)aSelector以抛出异常，表明选择器最终未能得到处理。
&lt;img src=&quot;https://upload-images.jianshu.io/upload_images/2427856-ccdda21a5a7e4e14.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240&quot; alt=&quot;盗得图&quot; /&gt;
接收者在每一步中都有机会处理消息，步骤越后，处理消息的代价就越大。最好在前面的步骤完成处理。
##第13条：黑魔法：“方法调配”（method swizzling）
与给定的选择器名称相对应的方法实现也可以在运行期改变，通过这个特性，我们既不需要源代码也不需要通过继承子类来覆写就能改变这个类的本身功能。这种方案就是“方法调配”（method swizzling）。
类的方法列表会把选择器的名称映射到相关的方法实现上，使得“动态消息派发系统”能够据此找到应该调用的方法，这些方法均以函数指针的形式来表示，这种指针叫做IMP，
    &lt;div class=&quot;highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;id (* IMP) (id, SEL, ...)
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;    &lt;/div&gt;
    &lt;p&gt;OC运行期系统提供了几个方法能够操作这张表：&lt;/p&gt;
    &lt;div class=&quot;highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;// 新增方法
class_addMethod(Class _Nullable cls, SEL _Nonnull name, IMP _Nonnull imp,  const char * _Nullable types) 
// 交换方法
method_exchangeImplementations(Method _Nonnull m1, Method _Nonnull m2) 
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;    &lt;/div&gt;
    &lt;p&gt;&lt;img src=&quot;https://upload-images.jianshu.io/upload_images/2427856-b01b1aafd45cedc5.jpg?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240&quot; alt=&quot;timg.jpg&quot; /&gt;
但是这种方法不宜滥用，若是滥用反而令代码变得不易读懂且难于维护。
##第14条：理解“类对象”的用意
每个OC对象实例都是指向某块内存数据的指针，所以在声明变量时，类型后面要跟一个“&lt;em&gt;”。对于通用对象类型id，由于它本身已经是指针了，不用带“&lt;/em&gt;”。
描述OC对象使用的数据结构如下：&lt;/p&gt;
    &lt;div class=&quot;highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;typedef struct objc_object *id;
struct objc_object {
 Class _Nonnull isa  OBJC_ISA_AVAILABILITY;
};
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;    &lt;/div&gt;
    &lt;p&gt;由此可见，每个对象结构体都有一个指向Class对象的指针isa（通常称为“is a”指针）,其定义了对象所属的类，表明其类型，而Class对象则构成了类的继承体系。Class结构如下：
```
typedef struct objc_class *Class;
struct objc_class {
 Class _Nonnull isa  OBJC_ISA_AVAILABILITY;&lt;/p&gt;
  &lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;#if !&lt;strong&gt;OBJC2&lt;/strong&gt;
    Class _Nullable super_class                              OBJC2_UNAVAILABLE;
    const char * _Nonnull name                               OBJC2_UNAVAILABLE;
    long version                                             OBJC2_UNAVAILABLE;
    long info                                                OBJC2_UNAVAILABLE;
    long instance_size                                       OBJC2_UNAVAILABLE;
    struct objc_ivar_list * _Nullable ivars                  OBJC2_UNAVAILABLE;
    struct objc_method_list * _Nullable * _Nullable methodLists                    OBJC2_UNAVAILABLE;
    struct objc_cache * _Nonnull cache                       OBJC2_UNAVAILABLE;
    struct objc_protocol_list * _Nullable protocols          OBJC2_UNAVAILABLE;
#endif&lt;/p&gt;

&lt;p&gt;} OBJC2_UNAVAILABLE;
```
结构体存放类的元数据：类的实例变量（ivars），类的实例实现方法（methodLists）等信息。同样，它也有isa指针，这说明Class本身也是OC对象，这个isa指针指向的是类对象所属的类型，叫做“元类”。类方法就定义在“元类”中。super_class定义了本类的超类。
&lt;img src=&quot;https://upload-images.jianshu.io/upload_images/2427856-7f1434bfabc0f33f.jpg?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240&quot; alt=&quot;类继承体系&quot; /&gt;
在类继承体系中查询类型信息：&lt;/p&gt;
&lt;ol&gt;
  &lt;li&gt;isMenberOfClass 能够判断对象是否为某个特定类的实例；&lt;/li&gt;
  &lt;li&gt;isKindOfClass 能够判断出对象是否为某类或其派生类的实例。
（第9条也有提及）&lt;/li&gt;
&lt;/ol&gt;</content><author><name></name></author><summary type="html">#第二章 对象 消息 运行期 ##第6条：理解“属性”这一概念 属性是OC的一项特性，用于封装对象中的数据（通常数据保存为各种实例变量）。实例变量一般通过存取方法getter和setter方法读取和写入，使用属性，编译器自动编写了相关的存取方法（自动合成），我们也可以自己编写存取方法，但一定要满足命名规范。使用属性，编译器还自动向类中添加了适当类型的实例变量，并在属性名前加_作为实例变量的名称。同时属性引入了“点语法”（点语法其实就是调用了getter和setter方法），我们可以容易的访问存放于属性中的数据。 属性有关的关键字：@synthesize @dynamic @synthesize可以指定实例变量的名称，@dynamic编译器不会创建实现属性的实例变量，也不会合成存取方法。 属性的特质会影响到存取方法，属性拥有四种特质： 原子性：atomic是“原子的”，合成的方法中会加同步锁。nonatomic是“非原子的”，合成方法不会加同步锁。一般情况下使用nonatomic，因为加同步锁意义不大，因为并不能确保线程安全，而且使用同步锁开销较大，会带来性能问题。 读写权限：readwrite,属性拥有setter和getter方法；readonly，属性仅拥有获取方法，外部不能更改数据。 内存管理语义：assign,setter方法只会执行针对“纯量类型”的简单赋值操作；strong,属性定义了一种拥有关系，setter方法会保留新值，释放旧值，再将新值设置上去；weak,属性定义了一种非拥有关系，setter方法不会保留新值，也不释放旧值，当属性指向的对象销毁时，属性值会自动清空；unsafe_unretained,和weak类似，区别是当属性指向的对象销毁时，属性值不会自动清空（不安全）；copy,和strong类似，然而setter方法并不保留新值，而是将其拷贝。当类型有可变和不可变类型时，不可变的属性一般需要使用copy特质，因为设置的新值可能指向可变的类型，如果不用copy，属性值可能在不知情的情况下遭人修改。 方法名：getter= 指定获取方法的方法名；setter=指定设置方法的方法名。 ##第7条：在对象内部尽量直接访问实例变量 直接访问实例变量和通过属性访问实例变量区别： 直接访问实例变量速度相对较快，因为不需要经过“方法派发”。编译器所生成的代码会直接访问保存对象实例变量的那块内存。 直接访问实例变量，不会调用getter，setter方法，这就绕过了属性所定义的内存管理语义。（修饰属性的strong,copy等没用了） 直接访问实例变量，不会触发KVO。 通过属性访问实例变量，可以通过getter，setter方法监控属性的调用者和其访问时机，方便调试。</summary></entry><entry><title type="html">Effective objective C 2.0笔记（四）</title><link href="https://momoai.github.io/Effective-Objective-C-2.0%E7%AC%94%E8%AE%B0-%E5%9B%9B/" rel="alternate" type="text/html" title="Effective objective C 2.0笔记（四）" /><published>2019-03-24T00:00:00+00:00</published><updated>2019-03-24T00:00:00+00:00</updated><id>https://momoai.github.io/Effective%20Objective-C%202.0%E7%AC%94%E8%AE%B0(%E5%9B%9B)</id><content type="html" xml:base="https://momoai.github.io/Effective-Objective-C-2.0%E7%AC%94%E8%AE%B0-%E5%9B%9B/">&lt;h2 id=&quot;第五章-内存管理&quot;&gt;第五章 内存管理&lt;/h2&gt;
&lt;h3 id=&quot;第29条理解引用计数&quot;&gt;第29条：理解引用计数&lt;/h3&gt;
&lt;ul&gt;
  &lt;li&gt;OC使用引用计数管理内存，引用计数机制通过递增递减的计数器来管理内存。对象创建后，保留计数为1。若保留计数为正，则对象继续存活，保留计数降为0时，对象被销毁&lt;/li&gt;
  &lt;li&gt;对象有三个方法用于操作计数器：&lt;code class=&quot;highlighter-rouge&quot;&gt;retain&lt;/code&gt;:递增保留计数，&lt;code class=&quot;highlighter-rouge&quot;&gt;release&lt;/code&gt;:递减保留计数，&lt;code class=&quot;highlighter-rouge&quot;&gt;autorelease&lt;/code&gt;:待自动释放池销毁时，再递减保留计数；&lt;/li&gt;
  &lt;li&gt;MRC下对象调用release，保留计数如果降为0，对象所占内存会被回收；再访问对象&lt;strong&gt;可能&lt;/strong&gt;使程序崩溃;因为对象所占内存dealloc后，只是放回“可用内存池”中；若果对象内存未被覆写，那么该对象仍然有效，程序不会崩溃；反之才会造成崩溃；为了防止访问野指针造成的程序崩溃，在对象release后应手动将指针置nil
    &lt;div class=&quot;highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;[object release];
object = nil;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;    &lt;/div&gt;
  &lt;/li&gt;
  &lt;li&gt;属性存取方法中的内存管理：若属性为“strong”类型，setter方法的处理方式为：保留新值再释放旧值，然后更新实例变量令其指向新值；
```&lt;/li&gt;
  &lt;li&gt;(void)setFoo:(Foo *)foo {
[foo retain];
[_foo release];
_foo = foo;
}
```
这个顺序极其重要，假如还未保留新值就释放旧值，两个对象又指向了同一个对象，那么先执行的release操作可能导致系统将此对象永久回收，后续的retain操作则无法令这个被彻底回收的对象复生；&lt;/li&gt;
  &lt;li&gt;autorelease对象的释放时机：如果创建了自己的自动释放池，对象会在自动释放池销毁时释放（即出了@autoreleasepool {    }作用域后释放）；否则就是等到当前线程的下一次事件循环（Runloop）时释放，因为一次事件循环会开启新的自动释放池；由此，autorelease能延长对象生命周期；
    &lt;h3 id=&quot;第30条以arc简化引用计数&quot;&gt;第30条：以ARC简化引用计数&lt;/h3&gt;
  &lt;/li&gt;
  &lt;li&gt;ARC会自动执行retain, release,autorelease等内存管理操作，在ARC下调用remain,release,autorelease,dealloc,ratinCount等方法是非法的，编译会报错；&lt;/li&gt;
  &lt;li&gt;ARC在调用内存管理方法时，是直接调用底层C语言，这样性能更好，因为保留，释放操作比较频繁，直接调用底层函数能节省很多CPU周期；&lt;/li&gt;
  &lt;li&gt;ARC确定的硬性规定：方法名以&lt;code class=&quot;highlighter-rouge&quot;&gt;alloc&lt;/code&gt;,&lt;code class=&quot;highlighter-rouge&quot;&gt;new&lt;/code&gt;,&lt;code class=&quot;highlighter-rouge&quot;&gt;copy&lt;/code&gt;,&lt;code class=&quot;highlighter-rouge&quot;&gt;mutableCopy&lt;/code&gt;开头的方法创建对象，返回的对象归调用者所有（负责释放对象）；若方法名不以这四个词语开头，则表示所返回的对象不归调用者所有，返回的对象会自动释放；实际上就是调用了autorelease方法；因为ARC的这个硬性规定，所以声明属性名称时不能以new等开头，因为这样属性setter方法的内存语意会分不清；&lt;/li&gt;
  &lt;li&gt;ARC也会执行手工操作无法完成的优化：在编译期，ARC会把能够相互抵消的retain，release，autorelease操作约简，如果发现同一个对象上执行了多次保留与释放操作，ARC有时可以成对移除这两个操作；&lt;/li&gt;
  &lt;li&gt;应用程序中，可用下列修辞符来改变局部变量与实例变量的语义：
&lt;code class=&quot;highlighter-rouge&quot;&gt;__strong&lt;/code&gt;:默认语义，保留值；
&lt;code class=&quot;highlighter-rouge&quot;&gt;__unsafe_unretained&lt;/code&gt;:不保留值，变量不会自动清空，可能不安全，会发生野指针问题；
&lt;code class=&quot;highlighter-rouge&quot;&gt;__weak&lt;/code&gt;:不保留值，但变量会自动清空，是安全的；
&lt;code class=&quot;highlighter-rouge&quot;&gt;__autorelease&lt;/code&gt;:自动释放；&lt;/li&gt;
  &lt;li&gt;ARC只负责管理OC对象的内存，对于OC对象，ARC会自动生成回收对象所执行的代码，但是对于非OC对象，如CoreFoundation中的对象或由malloc()分配在堆中的内存，那么仍需手动清理。
```&lt;/li&gt;
  &lt;li&gt;(void)dealloc {
CFRelease(_coreFoundationObject);
free(_heapAllocaatedMemoryBlob);
}
```
    &lt;h3 id=&quot;第31条在dealloc方法中只释放引用并解除监听&quot;&gt;第31条：在dealloc方法中只释放引用并解除监听&lt;/h3&gt;
    &lt;p&gt;在dealloc方法中，应该做的是：释放指向其他对象的引用；移除KVO或NSNotificationCenter通知；&lt;/p&gt;
    &lt;h3 id=&quot;第32条编写异常安全代码时留意内存管理问题&quot;&gt;第32条：编写“异常安全代码”时留意内存管理问题&lt;/h3&gt;
    &lt;p&gt;异常一般只应在严重错误后才抛出（第21条）；
在@try块中，如果先保留了某个对象，然后在释放它之前又抛出异常，除非@catch块能处理该问题，否则就发生内存泄露了；&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;MRC模式下解决方法：在@finally块中调用release释放对象，因为@finally块，无论是否抛出异常，代码都会运行；
    &lt;div class=&quot;highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;  Object *ojbect;
  @try {
      object = [[Object alloc] init];
      exception
      ....
  }
  @catch(NSExpression *expression) {
      ...
  }
  @finally {
      [object release];
  }
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;    &lt;/div&gt;
  &lt;/li&gt;
  &lt;li&gt;ARC模式，默认情况下ARC不会自动处理这种情况，又不能调用release。对于异常处理更加棘手；解决方法是：通过设置&lt;code class=&quot;highlighter-rouge&quot;&gt;-fobjec-arc-exceptions&lt;/code&gt;编译标志来开启ARC生成安全处理异常代码的模式；这可以使ARC安全处理异常；
    &lt;h3 id=&quot;第33条以弱引用避免保留环循环引用&quot;&gt;第33条：以弱引用避免保留环（循环引用）&lt;/h3&gt;
  &lt;/li&gt;
  &lt;li&gt;循环引用会导致内存泄露，避免循环引用的最佳方式就是弱引用；这种引用表示“非拥有关系”；&lt;/li&gt;
  &lt;li&gt;“非拥有关系”的修辞符有&lt;code class=&quot;highlighter-rouge&quot;&gt;weak&lt;/code&gt;,&lt;code class=&quot;highlighter-rouge&quot;&gt;assign&lt;/code&gt;,&lt;code class=&quot;highlighter-rouge&quot;&gt;unsafe_unretained&lt;/code&gt;;assign一般用于基本类型（int ,float,struct），unsafe_unretained一般用于对象类型，是不安全的；weak也用于对象类型，但是安全的；assign也可以用于对象类型，但也是不安全的；weak不能用于基本类型;&lt;/li&gt;
  &lt;li&gt;weak之所以安全，是因为变量指向的实例被释放回收时，weak属性会指向nil，而unsafe_unretained属性仍然指向已被回收的对象，这时在访问属性时会发生错误；&lt;/li&gt;
  &lt;li&gt;weak属性自动置为nil的原理：weak属性对象会被写入一张哈希表中，以weak属性指向的对象地址为key，weak指针为value；当指向的对象销毁时，会根据对象地址去表中查找weak指针并置为nil;
    &lt;h3 id=&quot;第34条以自动释放池降低内存峰值&quot;&gt;第34条：以自动释放池降低内存峰值&lt;/h3&gt;
    &lt;p&gt;非alloc,new,copy,mutableCopy词开头方法创建的对象，都是autorelease对象，如果没有手动创建自动释放池，那么autorelease对象要等到下个Runloop后才释放；如果在下个Runloop之前，autorelease对象已经很多（比如for循环创建对象），那么内存峰值将会很高；那么，可以在适当的时机，手动创建autorelease pool使得对象及时释放以降低内存：&lt;/p&gt;
    &lt;div class=&quot;highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;    for (int i = 0; i &amp;lt; 100000; i ++) {
          @autoreleasepool {
              Object *obj = [Object ojbect];
          }
      }
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;    &lt;/div&gt;
  &lt;/li&gt;
&lt;/ul&gt;

&lt;h3 id=&quot;第35条用僵尸对象调试内存管理问题&quot;&gt;第35条：用“僵尸对象”调试内存管理问题&lt;/h3&gt;
&lt;ul&gt;
  &lt;li&gt;僵尸对象：内存已被回收的对象；指向僵尸对象的指针就是野指针&lt;/li&gt;
  &lt;li&gt;向僵尸对象发送消息是不安全的，有可能造成程序崩溃；崩溃与否，取决于对象所占内存有没有为其他内容所覆写；如果内存未被覆写，那访问仍然有效；如果被覆写了，消息会发送给新对象，新对象可能刚好能应答那访问也有效；（29条有类似描述）&lt;/li&gt;
  &lt;li&gt;可以使用Xcode开启僵尸对象选项，避免访问僵尸对象发生的不安全行为；&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;img src=&quot;https://upload-images.jianshu.io/upload_images/2427856-7c3da7e5cff04686.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240&quot; alt=&quot;zombie&quot; /&gt;&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;僵尸对象选项原理：勾选Zombie Object选项后，运行时系统会把已经回收的实例转化为特殊的“僵尸对象”，而不会真正回收它们。没有回收，也就不可能被覆写。僵尸对象收到消息后，会抛出异常，且准确说明发送的消息及描述回收前的那个对象信息；&lt;/li&gt;
  &lt;li&gt;Zombie Object运行时系统处理过程：系统在回收对象时，如果发现环境变量设置了Zombie Object选项，那么会把该对象转化为有“僵尸”标识的对象；具体做法是：使用黑魔法swizzle method替换dealloc方法，通过这个dealloc方法动态的创建一个以&lt;code class=&quot;highlighter-rouge&quot;&gt;_NSZombie_&lt;/code&gt;前缀+原类名为类名的新类，然后将这个新建的类设置为原实例的class，对象就变成了“僵尸”标识的类；&lt;code class=&quot;highlighter-rouge&quot;&gt;_NSZombie_&lt;/code&gt;前缀的新类，并未实现任何方法，所以发给它的消息就要经过“消息转发”，这个过程中将收到的消息及原来所属的类打印出来，抛出异常，程序终止。（整个过程，其实和KVO实现过程有点类似）&lt;/li&gt;
&lt;/ul&gt;

&lt;h3 id=&quot;第36条不要使用retaincount&quot;&gt;第36条：不要使用retainCount&lt;/h3&gt;</content><author><name></name></author><summary type="html">第五章 内存管理 第29条：理解引用计数 OC使用引用计数管理内存，引用计数机制通过递增递减的计数器来管理内存。对象创建后，保留计数为1。若保留计数为正，则对象继续存活，保留计数降为0时，对象被销毁 对象有三个方法用于操作计数器：retain:递增保留计数，release:递减保留计数，autorelease:待自动释放池销毁时，再递减保留计数； MRC下对象调用release，保留计数如果降为0，对象所占内存会被回收；再访问对象可能使程序崩溃;因为对象所占内存dealloc后，只是放回“可用内存池”中；若果对象内存未被覆写，那么该对象仍然有效，程序不会崩溃；反之才会造成崩溃；为了防止访问野指针造成的程序崩溃，在对象release后应手动将指针置nil [object release]; object = nil; 属性存取方法中的内存管理：若属性为“strong”类型，setter方法的处理方式为：保留新值再释放旧值，然后更新实例变量令其指向新值； ``` (void)setFoo:(Foo *)foo { [foo retain]; [_foo release]; _foo = foo; } ``` 这个顺序极其重要，假如还未保留新值就释放旧值，两个对象又指向了同一个对象，那么先执行的release操作可能导致系统将此对象永久回收，后续的retain操作则无法令这个被彻底回收的对象复生； autorelease对象的释放时机：如果创建了自己的自动释放池，对象会在自动释放池销毁时释放（即出了@autoreleasepool { }作用域后释放）；否则就是等到当前线程的下一次事件循环（Runloop）时释放，因为一次事件循环会开启新的自动释放池；由此，autorelease能延长对象生命周期； 第30条：以ARC简化引用计数 ARC会自动执行retain, release,autorelease等内存管理操作，在ARC下调用remain,release,autorelease,dealloc,ratinCount等方法是非法的，编译会报错； ARC在调用内存管理方法时，是直接调用底层C语言，这样性能更好，因为保留，释放操作比较频繁，直接调用底层函数能节省很多CPU周期； ARC确定的硬性规定：方法名以alloc,new,copy,mutableCopy开头的方法创建对象，返回的对象归调用者所有（负责释放对象）；若方法名不以这四个词语开头，则表示所返回的对象不归调用者所有，返回的对象会自动释放；实际上就是调用了autorelease方法；因为ARC的这个硬性规定，所以声明属性名称时不能以new等开头，因为这样属性setter方法的内存语意会分不清； ARC也会执行手工操作无法完成的优化：在编译期，ARC会把能够相互抵消的retain，release，autorelease操作约简，如果发现同一个对象上执行了多次保留与释放操作，ARC有时可以成对移除这两个操作； 应用程序中，可用下列修辞符来改变局部变量与实例变量的语义： __strong:默认语义，保留值； __unsafe_unretained:不保留值，变量不会自动清空，可能不安全，会发生野指针问题； __weak:不保留值，但变量会自动清空，是安全的； __autorelease:自动释放； ARC只负责管理OC对象的内存，对于OC对象，ARC会自动生成回收对象所执行的代码，但是对于非OC对象，如CoreFoundation中的对象或由malloc()分配在堆中的内存，那么仍需手动清理。 ``` (void)dealloc { CFRelease(_coreFoundationObject); free(_heapAllocaatedMemoryBlob); } ``` 第31条：在dealloc方法中只释放引用并解除监听 在dealloc方法中，应该做的是：释放指向其他对象的引用；移除KVO或NSNotificationCenter通知； 第32条：编写“异常安全代码”时留意内存管理问题 异常一般只应在严重错误后才抛出（第21条）； 在@try块中，如果先保留了某个对象，然后在释放它之前又抛出异常，除非@catch块能处理该问题，否则就发生内存泄露了； MRC模式下解决方法：在@finally块中调用release释放对象，因为@finally块，无论是否抛出异常，代码都会运行； Object *ojbect; @try { object = [[Object alloc] init]; exception .... } @catch(NSExpression *expression) { ... } @finally { [object release]; } ARC模式，默认情况下ARC不会自动处理这种情况，又不能调用release。对于异常处理更加棘手；解决方法是：通过设置-fobjec-arc-exceptions编译标志来开启ARC生成安全处理异常代码的模式；这可以使ARC安全处理异常； 第33条：以弱引用避免保留环（循环引用） 循环引用会导致内存泄露，避免循环引用的最佳方式就是弱引用；这种引用表示“非拥有关系”； “非拥有关系”的修辞符有weak,assign,unsafe_unretained;assign一般用于基本类型（int ,float,struct），unsafe_unretained一般用于对象类型，是不安全的；weak也用于对象类型，但是安全的；assign也可以用于对象类型，但也是不安全的；weak不能用于基本类型; weak之所以安全，是因为变量指向的实例被释放回收时，weak属性会指向nil，而unsafe_unretained属性仍然指向已被回收的对象，这时在访问属性时会发生错误； weak属性自动置为nil的原理：weak属性对象会被写入一张哈希表中，以weak属性指向的对象地址为key，weak指针为value；当指向的对象销毁时，会根据对象地址去表中查找weak指针并置为nil; 第34条：以自动释放池降低内存峰值 非alloc,new,copy,mutableCopy词开头方法创建的对象，都是autorelease对象，如果没有手动创建自动释放池，那么autorelease对象要等到下个Runloop后才释放；如果在下个Runloop之前，autorelease对象已经很多（比如for循环创建对象），那么内存峰值将会很高；那么，可以在适当的时机，手动创建autorelease pool使得对象及时释放以降低内存： for (int i = 0; i &amp;lt; 100000; i ++) { @autoreleasepool { Object *obj = [Object ojbect]; } }</summary></entry><entry><title type="html">Gcd多线程的使用及分析(swift)</title><link href="https://momoai.github.io/GCD%E5%A4%9A%E7%BA%BF%E7%A8%8B%E7%9A%84%E4%BD%BF%E7%94%A8%E5%8F%8A%E5%88%86%E6%9E%90(Swift)/" rel="alternate" type="text/html" title="Gcd多线程的使用及分析(swift)" /><published>2019-03-24T00:00:00+00:00</published><updated>2019-03-24T00:00:00+00:00</updated><id>https://momoai.github.io/GCD%E5%A4%9A%E7%BA%BF%E7%A8%8B%E7%9A%84%E4%BD%BF%E7%94%A8%E5%8F%8A%E5%88%86%E6%9E%90(Swift)</id><content type="html" xml:base="https://momoai.github.io/GCD%E5%A4%9A%E7%BA%BF%E7%A8%8B%E7%9A%84%E4%BD%BF%E7%94%A8%E5%8F%8A%E5%88%86%E6%9E%90(Swift)/">&lt;p&gt;&lt;img src=&quot;https://upload-images.jianshu.io/upload_images/2427856-b21820e8c7999d36.jpg?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240&quot; alt=&quot;timg.jpg&quot; /&gt;&lt;/p&gt;

&lt;p&gt;GCD全称Grand Central Dispatch，从名称可以看出GCD就是起到中央调度的作用。这个调度作用就是对线程做的处理：GCD 会自动管理线程的生命周期（创建线程、调度任务、销毁线程），程序员只需要告诉 GCD 想要执行什么任务，不需要编写任何线程管理代码。
在使用GCD多线程编程前，我们需要理解两个概念：队列，任务。&lt;/p&gt;

&lt;h1 id=&quot;队列与任务&quot;&gt;队列与任务&lt;/h1&gt;

&lt;div class=&quot;highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;        let queue = DispatchQueue.init(label: &quot;queue_label&quot;)
        queue.async {
            print(&quot;task&quot;)
        }
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;queue就是我们创建的GCD队列，async后面的闭包就是我们添加到队列里的任务。队列负责调度线程，而任务的执行会根据是同步sync还是异步async来开启线程。
GCD其实已经弱化了线程的概念，转而使用了队列这个东西。GCD队列其实就是对线程的封装，所有对线程的操作已经交由队列处理，而且还结合性能的开销情况帮我们做了一系列优化。我们不用再像使用NSThead那样管理线程，GCD可以做到更高效性能更佳。我们只要在闭包写好要多线程处理的任务，然后扔给队列就行了。至于怎么开启线程，开多少线程都交由GCD去完成了。
任务分为同步执行和异步执行。&lt;/p&gt;
&lt;h1 id=&quot;同步执行&quot;&gt;同步执行&lt;/h1&gt;

&lt;div class=&quot;highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;        queue.sync {
            // task
        }
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;同步执行不会开启线程，任务执行需要等待前面任务完成。&lt;/p&gt;
&lt;h1 id=&quot;异步执行&quot;&gt;异步执行&lt;/h1&gt;

&lt;div class=&quot;highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;        queue.async {
            // task
        }
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;异步执行会开启线程，任务执行无需等待并发完成。对于线程，并不是有多少个任务就开启多少个线程。开启一个线程是会消耗CPU的资源的，GCD并不会帮我们开启太多线程，它会合理的控制线程数。对于多个线程，GCD会创建类似UITableView缓存池的线程池，达到重用的目的也不需要开启那么多线程。这个接下来会具体分析。
我们再来看看GCD的队列
&lt;img src=&quot;https://upload-images.jianshu.io/upload_images/2427856-c5b2e270bfbcac82.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240&quot; alt=&quot;queue&quot; /&gt;
GCD的队列分为串行队列和并行队列，系统提供了两个队列:main,global。main为主队列属于串行队列，在主线程执行。global为全局并行队列。顾名思义串行队列就是按顺序一个一个执行任务的，而并行队列是并发处理任务的。我们都知道队列是FIFO先进先出的，那么并发队列为何能并发处理呢？我们来看看串行和并行队列的本质
#串行队列
前面说了，队列负责调度线程。而串行队列只能调度一个线程，但并行队列是可以调度多个线程的。这就是它们本质的区别。
&lt;img src=&quot;https://upload-images.jianshu.io/upload_images/2427856-e4d356538fc0ddb7.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240&quot; alt=&quot;线程调度&quot; /&gt;
串行队列里的任务是需要等待队列里前面一个任务结束了才老老实实开始的，就好比我们去车站排队买票，前面那个同志付钱买好了才轮得到你。用图来描述一下：
&lt;img src=&quot;https://upload-images.jianshu.io/upload_images/2427856-58a1a47c23a6e636.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240&quot; alt=&quot;串行队列&quot; /&gt;
写个函数验证下：&lt;/p&gt;

&lt;div class=&quot;highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;  func sQueueASync() -&amp;gt; Void {
        print(&quot;Hello, World!&quot;);
        let queue = DispatchQueue.init(label: &quot;queue_label&quot;)
        queue.async {
            print(&quot;task1!----\(Thread.current)&quot;)
        }
        queue.async {
            sleep(3)
            print(&quot;task2!----\(Thread.current)&quot;)
        }
        queue.async {
            sleep(2)
            print(&quot;task3!-----\(Thread.current)&quot;)
        }
        queue.async {
            print(&quot;task4!-----\(Thread.current)&quot;)
        }
        
        print(&quot;End&quot;)
    }
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;输出结果如下&lt;/p&gt;
&lt;blockquote&gt;
  &lt;p&gt;Hello, World!
End
task1!—-&amp;lt;NSThread: 0x15662220&amp;gt;{number = 2, name = (null)}
task2!—-&amp;lt;NSThread: 0x15662220&amp;gt;{number = 2, name = (null)}
task3!—–&amp;lt;NSThread: 0x15662220&amp;gt;{number = 2, name = (null)}
task4!—–&amp;lt;NSThread: 0x15662220&amp;gt;{number = 2, name = (null)}&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;func sQueueASync() -&amp;gt; Void {}函数里面的操作，其实就是添加在主队列里的一个任务，主队列使用的就是主线程。queue队列使用async异步执行会开启子线程，不会阻塞当前的主线程，所以主队列中“End”不会受task1,2,3,4影响。在输出“Hello, World!”后马上执行了打印“End”的任务。而在我们手动创建的queue队列中，虽然使用async异步开启多个线程执行，但结果4个task都只使用了同一个线程。由于串行队列中任务必须等前面任务完成后才开始的，所以不管task2,task3睡多久，都是按照1，2，3，4的顺序执行的。异步执行可以开启多个线程，但因为串行队列只能调度一个线程，也没必要开启多个线程了。所以在某个时间点，串行队列的任务只会开启一个线程(主队列又不一样了，因为主队列已经有了主线程不会再开启线程)。把手动创建的串行队列改成主队列看看：&lt;/p&gt;

&lt;div class=&quot;highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;  func sQueueASync() -&amp;gt; Void {
        print(&quot;Hello, World!&quot;);
        let queue = DispatchQueue.main
        queue.async {
            print(&quot;task1!----\(Thread.current)&quot;)
        }
        queue.async {
            sleep(3)
            print(&quot;task2!----\(Thread.current)&quot;)
        }
        queue.async {
            sleep(2)
            print(&quot;task3!-----\(Thread.current)&quot;)
        }
        queue.async {
            print(&quot;task4!-----\(Thread.current)&quot;)
        }
        
        print(&quot;End&quot;)
    }
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;blockquote&gt;
  &lt;p&gt;Hello, World!
End
task1!—-&amp;lt;NSThread: 0x17e450c0&amp;gt;{number = 1, name = main}
task2!—-&amp;lt;NSThread: 0x17e450c0&amp;gt;{number = 1, name = main}
task3!—–&amp;lt;NSThread: 0x17e450c0&amp;gt;{number = 1, name = main}
task4!—–&amp;lt;NSThread: 0x17e450c0&amp;gt;{number = 1, name = main}&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;可以看到，执行的顺序和上面是一样的，因为都是串行队列，只是都是在主线程执行了。也就是说，在主队列中也只能调度一个线程，且这个线程为主线程。
大家肯定注意到了，前面着重强调了”在某个时间点”这个条件。这个条件是什么意思呢？这和Runloop有关系。GCD会有一个线程的缓存池，类似UITableView的缓存池起到重用线程的作用。在Runloop一个循环内，线程池不会销毁会一直存在，缓存池中线程可以供下个任务使用。在下个循环开始，线程池销毁，池里没有可用线程就需要重新再开启一个线程了。也就是说在”另一个时间点”需要再开启线程。可以模拟一下这种情况:&lt;/p&gt;

&lt;div class=&quot;highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;func sQueueASync() -&amp;gt; Void {
        print(&quot;Hello, World!&quot;);
        let queue = DispatchQueue.init(label: &quot;queue_label&quot;)
        queue.async {
            print(&quot;task1!----\(Thread.current)&quot;)
        }
        queue.async {
            sleep(3)
            print(&quot;task2!----\(Thread.current)&quot;)
        }
        queue.async {
            sleep(2)
            print(&quot;task3!-----\(Thread.current)&quot;)
        }
        queue.asyncAfter(deadline: DispatchTime.now() + 30) {
            print(&quot;task4!-----\(Thread.current)&quot;)
        }
        
        print(&quot;End&quot;)
    }
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;blockquote&gt;
  &lt;p&gt;Hello, World!
End
task1!—-&amp;lt;NSThread: 0x155a1e10&amp;gt;{number = 2, name = (null)}
task2!—-&amp;lt;NSThread: 0x155a1e10&amp;gt;{number = 2, name = (null)}
task3!—–&amp;lt;NSThread: 0x155a1e10&amp;gt;{number = 2, name = (null)}
task4!—–&amp;lt;NSThread: 0x155ae030&amp;gt;{number = 3, name = (null)}&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;这里把task4稍微改动了下，加了30s延迟，结果是task4重新开启了新的线程。30s后其实是处于另一个循环了，之前的task1,2,3的线程已销毁。&lt;/p&gt;

&lt;p&gt;前面分析的都是串行队列异步执行，接下来再看看串行队列同步执行的情况。还是上面的代码，把异步async改成同步sync。&lt;/p&gt;

&lt;div class=&quot;highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;  func sQueueSync() -&amp;gt; Void {
        print(&quot;Hello, World!&quot;);
        let queue = DispatchQueue.init(label: &quot;queue_label&quot;)
        queue.sync {
            print(&quot;task1!----\(Thread.current)&quot;)
        }
        queue.sync {
            sleep(3)
            print(&quot;task2!----\(Thread.current)&quot;)
        }
        queue.sync {
            sleep(2)
            print(&quot;task3!-----\(Thread.current)&quot;)
        }
        queue.sync {
            print(&quot;task4!-----\(Thread.current)&quot;)
        }
        
        print(&quot;End&quot;)
    }
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;blockquote&gt;
  &lt;p&gt;Hello, World!
task1!—-&amp;lt;NSThread: 0x14570ac0&amp;gt;{number = 1, name = main}
task2!—-&amp;lt;NSThread: 0x14570ac0&amp;gt;{number = 1, name = main}
task3!—–&amp;lt;NSThread: 0x14570ac0&amp;gt;{number = 1, name = main}
task4!—–&amp;lt;NSThread: 0x14570ac0&amp;gt;{number = 1, name = main}
End&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;queue队列里的4个task和主队列异步执行的情况基本一致：任务按顺序一个一个执行且没有开启新的线程。唯一不同的就是主队列”End”任务的顺序。这里的”End”任务一定是最后执行的，因为串行队列同步执行没有开启线程，同步执行的任务会阻塞主线程，”End”任务需要等待4个task任务执行完才执行。而且由于queue是我们手动创建的串行队列，和主队列不会造成互相等待，不会造成死锁。
同样的，把上面代码中手动创建的串行队列换成main队列，看看是什么结果&lt;/p&gt;

&lt;div class=&quot;highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;  func sQueueSync() -&amp;gt; Void {
        print(&quot;Hello, World!&quot;);
        let queue = DispatchQueue.main
        queue.sync {
            print(&quot;task1!----\(Thread.current)&quot;)
        }
        queue.sync {
            sleep(3)
            print(&quot;task2!----\(Thread.current)&quot;)
        }
        queue.sync {
            sleep(2)
            print(&quot;task3!-----\(Thread.current)&quot;)
        }
        queue.sync {
            print(&quot;task4!-----\(Thread.current)&quot;)
        }
        
        print(&quot;End&quot;)
    }
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;blockquote&gt;
  &lt;p&gt;Hello, World!&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;输出“Hello, World!”后程序卡死，这段代码就发生了死锁。发生死锁的原因是什么？画个图分析下：
&lt;img src=&quot;https://upload-images.jianshu.io/upload_images/2427856-e4ffd0ace34129f6.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240&quot; alt=&quot;死锁&quot; /&gt;&lt;/p&gt;

&lt;p&gt;4个task添加至主队列在主线程执行，而sQueueSync()本身就是主队列里的一个任务，所以主队列现在可以看做有5个任务。而且这5个任务是是按顺序执行的。而sQueueSync()里main队列任务可以分成3块：1,打印”Hello,World!”，2.把task1,2,3,4添加至主队列执行，3.打印”End”。很明显这是一个嵌套的任务。前面分析了，串行队列是必须等到前面任务完全执行完后才执行的，main队列中Hello, World!执行完后，接着准备执行4个task，等待执行完4个task后才会执行”End”任务。而这时4个task的任务必须等到队列中sQueueSync()任务完成后才开始执行，这就造成了相互等待，死锁发生了。
到这里，基本把串行队列的情况都列举了下。再回顾下，串行队列有以下4种情况。&lt;/p&gt;
&lt;ul&gt;
  &lt;li&gt;串行队列异步执行&lt;/li&gt;
  &lt;li&gt;主队列异步执行&lt;/li&gt;
  &lt;li&gt;串行队列同步执行&lt;/li&gt;
  &lt;li&gt;主队列同步执行
歇了一口气，接着讲并行队列
    &lt;h1 id=&quot;并行队列&quot;&gt;并行队列&lt;/h1&gt;
    &lt;p&gt;并行队列可以调度多个线程，它可以并发的执行多个任务而无需等待前一个任务完成。
&lt;img src=&quot;https://upload-images.jianshu.io/upload_images/2427856-06b9bb8a6af5fee6.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240&quot; alt=&quot;调度线程&quot; /&gt;&lt;/p&gt;
  &lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;咦？队列不是先进先出按顺序一个一个来的吗？没错，并发队列也还是先进先出按顺序的，这里说的并发只是说前面一个任务刚执行还没结束下一个任务马上开始，这中间的速度很快。看起来就像是并发一起执行的，这里只是相对串行队列需要等待前面一个任务执行完后才执行而言的。同样我们还是拿买车票做比喻：我们还是去车站排队买票，不同的是这时排队的窗口不止一个售票员。那么我们前面的那个同志小明先我们一步去了售票员A那里买票了，这时售票员B是空闲的，我们就没必要傻傻的非要等到小明买完了结清账了再去A那买票，我们就直奔B那里了。而小明可能需要买很多张票，他可能帮全家人一起买，而我们潇潇洒洒就买了一张票。等我们买好了出来了，发现小明那里还在刷身份证。这时你不能说我插了小明的队吧，我们还是老老实实排队来的。所以说并行队列也还是按顺序执行的，只是它不需要等待，而且哪个先执行完也是不确定的。还是作个图清楚些：
&lt;img src=&quot;https://upload-images.jianshu.io/upload_images/2427856-0fd582f892574b55.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240&quot; alt=&quot;并行队列&quot; /&gt;&lt;/p&gt;

&lt;p&gt;对比之前串行队列的图示，区别还是比较明显的。
和串行队列一样，并行队列也分同步执行、异步执行。&lt;/p&gt;

&lt;div class=&quot;highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;    func cSync() -&amp;gt; Void {
        print(&quot;Hello, World!&quot;);
        let queue = DispatchQueue.init(label: &quot;queue_label&quot;, attributes: .concurrent)
        queue.sync {
            print(&quot;task11!----\(Thread.current)&quot;)
        }
        queue.sync {
            sleep(3)
            print(&quot;task22!----\(Thread.current)&quot;)
        }
        queue.sync {
            print(&quot;task33!-----\(Thread.current)&quot;)
        }
        queue.sync {
            print(&quot;task44!-----\(Thread.current)&quot;)
        }
        
        print(&quot;End&quot;)
    }
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;blockquote&gt;
  &lt;p&gt;Hello, World!
task11!—-&amp;lt;NSThread: 0x14e3a7c0&amp;gt;{number = 1, name = main}
task22!—-&amp;lt;NSThread: 0x14e3a7c0&amp;gt;{number = 1, name = main}
task33!—–&amp;lt;NSThread: 0x14e3a7c0&amp;gt;{number = 1, name = main}
task44!—–&amp;lt;NSThread: 0x14e3a7c0&amp;gt;{number = 1, name = main}
End&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;这里创建了.concurrent的并行队列，而且是同步执行。从输出结果可以看出任务都是按顺序执行的，但前面我们分析了并行队列是不需要等待前面任务完成后再执行的，按理说task2需要3s其他task能立即执行完，但task3和task4还是在task2后执行。其实任务是顺序执行还是并发执行是要结合队列类型及任务执行方式综合分析的，如同之前的串行队列异步执行：虽然异步会开启线程任务应该能并发执行，但由于是串行队列只会调度一个线程任务也需要等待，结果还是按顺序执行了。这里的并行队列同步执行道理是一样的：虽然并行队列能调度多个线程，但由于是同步的方式也就不会开启线程它会阻塞线程。并行队列没有可以调度的线程，也就是仍需等待前面任务完成才能开始。还是举买票这个栗子，我们排队的窗口本来是可以有多个售票员的，但这时车站的人员不够也就只安排了一个售票员。那这时我们还是得等前面的小明买好票离开才开始。现在不管小明花多长时间，他就算一直和售票员跑火车，我们也没办法还是得等他。现在看着小明是不是很不顺眼。所以说串行队列并行队列和同步异步方式都是相互关联的，不能说并发队列或异步执行就一定会并发处理。既然如此，现在把sync改成async试试：&lt;/p&gt;

&lt;div class=&quot;highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;func cAsync() -&amp;gt; Void {
        print(&quot;Hello, World!&quot;);
        let queue = DispatchQueue.init(label: &quot;queue_label&quot;, attributes: .concurrent)
        queue.async {
            print(&quot;task11!----\(Thread.current)&quot;)
        }
        queue.async {
            sleep(3)
            print(&quot;task22!----\(Thread.current)&quot;)
        }
        queue.async {
            print(&quot;task33!-----\(Thread.current)&quot;)
        }
        queue.async {
            print(&quot;task44!-----\(Thread.current)&quot;)
        }
        
        print(&quot;End&quot;)
    }
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;blockquote&gt;
  &lt;p&gt;Hello, World!
End
task11!—-&amp;lt;NSThread: 0x1758fe10&amp;gt;{number = 2, name = (null)}
task33!—–&amp;lt;NSThread: 0x17673220&amp;gt;{number = 3, name = (null)}
task44!—–&amp;lt;NSThread: 0x1758fe10&amp;gt;{number = 2, name = (null)}
task22!—-&amp;lt;NSThread: 0x17580d70&amp;gt;{number = 4, name = (null)}&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;结果在意料之中，主线程没有被阻塞，Hello, World!  End执行不受queue里任务影响。queue队列开启了3个线程，任务是并发执行的。这里也验证了之前说的另一个结论：并不是有多少任务就开启多少线程的，task1和task4都是线程2。为了更明显看出这种结果，现在在原先代码了多增加几个任务。&lt;/p&gt;

&lt;div class=&quot;highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;func cAsync() -&amp;gt; Void {
        print(&quot;Hello, World!&quot;);
        let queue = DispatchQueue.init(label: &quot;queue_label&quot;, attributes: .concurrent)
        queue.async {
            print(&quot;task11!----\(Thread.current)&quot;)
        }
        queue.async {
            sleep(3)
            print(&quot;task22!----\(Thread.current)&quot;)
        }
        queue.async {
            print(&quot;task33!-----\(Thread.current)&quot;)
        }
        queue.async {
            print(&quot;task44!-----\(Thread.current)&quot;)
        }
        queue.async {
            print(&quot;task55!-----\(Thread.current)&quot;)
        }
        queue.async {
            print(&quot;task66!-----\(Thread.current)&quot;)
        }
        queue.async {
            print(&quot;task77!-----\(Thread.current)&quot;)
        }
        
        print(&quot;End&quot;)
    }
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;blockquote&gt;
  &lt;p&gt;Hello, World!
End
task11!—-&amp;lt;NSThread: 0x1457fbe0&amp;gt;{number = 2, name = (null)}
task33!—–&amp;lt;NSThread: 0x1457fbe0&amp;gt;{number = 2, name = (null)}
task44!—–&amp;lt;NSThread: 0x1457fbe0&amp;gt;{number = 2, name = (null)}
task55!—–&amp;lt;NSThread: 0x1457fbe0&amp;gt;{number = 2, name = (null)}
task66!—–&amp;lt;NSThread: 0x145800b0&amp;gt;{number = 3, name = (null)}
task77!—–&amp;lt;NSThread: 0x1457fbe0&amp;gt;{number = 2, name = (null)}
task22!—-&amp;lt;NSThread: 0x1457c330&amp;gt;{number = 4, name = (null)}&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;现在是7个任务，但还是3个线程。但这段代码最好在真机上运行调试，你可以在模拟器上试下，会发现我被啪啪啪打脸：模拟器上运行有多少任务就开启了多少线程。应该是因为模拟器的CPU和内存和电脑一样的缘故，具体原因还没深入研究。
至此，GCD串行队列，并行队列，同步执行，异步执行的组合情况都分析完毕了。现再总结下：&lt;/p&gt;
&lt;ul&gt;
  &lt;li&gt;串行队列异步执行&lt;/li&gt;
  &lt;li&gt;主队列异步执行&lt;/li&gt;
  &lt;li&gt;串行队列同步执行&lt;/li&gt;
  &lt;li&gt;主队列同步执行&lt;/li&gt;
  &lt;li&gt;并行队列异步执行&lt;/li&gt;
  &lt;li&gt;并行队列同步执行
    &lt;ol&gt;
      &lt;li&gt;串行队列同步执行没有任何实际意义&lt;/li&gt;
      &lt;li&gt;并行队列同步执行会阻塞当前线程，所有任务都按顺序执行，没有任何实际意义&lt;/li&gt;
      &lt;li&gt;主队列同步执行会死锁&lt;/li&gt;
      &lt;li&gt;串行队列异步执行会开启线程，不会阻塞主线程，队列里的任务顺序执行&lt;/li&gt;
      &lt;li&gt;并行队列异步执行会开启线程，不会阻塞主线程，队列里的任务并发执行&lt;/li&gt;
    &lt;/ol&gt;
  &lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;列个表格总结下
&lt;img src=&quot;https://upload-images.jianshu.io/upload_images/2427856-8b7d605c95be00c0.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240&quot; alt=&quot;总结&quot; /&gt;
可以看到，串行队列异步执行和并行队列异步执行才是最实用的，这也是我们平时项目里用的最多的。具体用哪种，就要看你是想并发执行还是顺序执行了。
有了这个基础后，我们再来分析嵌套任务的情况：&lt;/p&gt;
&lt;h1 id=&quot;嵌套任务&quot;&gt;嵌套任务&lt;/h1&gt;
&lt;p&gt;之前的主队列同步/异步执行就属于嵌套任务的情况，只是嵌套的是主队列。我们看下自己创建的队列嵌套任务的情况：&lt;/p&gt;

&lt;div class=&quot;highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;func nestQueue1() -&amp;gt; Void {
        print(&quot;Hello, World!&quot;)
        let squeue = DispatchQueue.init(label: &quot;queue_label&quot;)
        squeue.sync {
            print(&quot;task11!----\(Thread.current)&quot;)
            squeue.sync {
                print(&quot;task22!----\(Thread.current)&quot;)
            }
            print(&quot;task33!-----\(Thread.current)&quot;)
        }
        print(&quot;End&quot;)
    }
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;这里还是串行队列同步执行，不过添加至squeue队列里的任务（即squeue.sync{}闭包）本身又追加了一个同步任务到squeue。我们先不管主队列的print(“Hello, World!”)和print(“End”)，单看squeue.sync {}这一块，是不是似曾相识？没错这就和上面的主队列同步执行一样，只是队列换成了我们自己创建的串行队列。除去print(“Hello, World!”)和print(“End”)，其实它就等价于下面代码&lt;/p&gt;

&lt;div class=&quot;highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;func nestQueue1() -&amp;gt; Void {
        print(&quot;task11!----\(Thread.current)&quot;)
        let squeue = DispatchQueue.main
        squeue.sync {
             print(&quot;task22!----\(Thread.current)&quot;)
        }
        print(&quot;task33!-----\(Thread.current)&quot;)
    }
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;毫无疑问，它也会发生死锁：&lt;/p&gt;
&lt;blockquote&gt;
  &lt;p&gt;Hello, World!
task11!—-&amp;lt;NSThread: 0x16546a70&amp;gt;{number = 1, name = main}&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;接下来，再把刚才嵌套的同步任务换成异步试试：&lt;/p&gt;

&lt;div class=&quot;highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;func nestQueue2() -&amp;gt; Void {
        print(&quot;Hello, World!&quot;);
        let squeue = DispatchQueue.init(label: &quot;queue_label&quot;)
        squeue.sync {
            print(&quot;task11!----\(Thread.current)&quot;)
            squeue.async {
                print(&quot;task22!----\(Thread.current)&quot;)
            }
            sleep(3)
            print(&quot;task33!-----\(Thread.current)&quot;)
        }
        print(&quot;End&quot;)
    }
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;blockquote&gt;
  &lt;p&gt;Hello, World!
task11!—-&amp;lt;NSThread: 0x15d1d120&amp;gt;{number = 1, name = main}
task33!—–&amp;lt;NSThread: 0x15d1d120&amp;gt;{number = 1, name = main}
End
task22!—-&amp;lt;NSThread: 0x15e4d1a0&amp;gt;{number = 2, name = (null)}&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;由于squeue.sync {}里嵌套的是squeue.async{}不会造成相互等待，task2也在另一个线程执行，不会造成死锁。最外层squeue.async{}是同步执行会阻塞主线程，所以主队列的print(“End”)任务需要等待squeue里任务执行完，也就是说“End”一定是在task3之后。嵌套的task2是异步执行的但是添加至同一个串行队列squeue中，根据前面分析的串行队列异步执行的情况，task2不会阻塞当前任务执行但并也不能并发执行，所以task2还是需要等待前面的任务执行完后才开始。也就是说task2也肯定会是在task3后。还是看图会比较清楚些：
&lt;img src=&quot;https://upload-images.jianshu.io/upload_images/2427856-7602b8a5fca1bd60.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240&quot; alt=&quot;嵌套情况&quot; /&gt;&lt;/p&gt;

&lt;p&gt;把刚才的代码内层和外层的同步和异步对调下：&lt;/p&gt;

&lt;div class=&quot;highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;func nestQueue3() -&amp;gt; Void {
        print(&quot;Hello, World!&quot;);
        let squeue = DispatchQueue.init(label: &quot;queue_label&quot;)
        squeue.async {
            print(&quot;task11!----\(Thread.current)&quot;)
            squeue.sync {
                print(&quot;task22!----\(Thread.current)&quot;)
            }
            print(&quot;task33!-----\(Thread.current)&quot;)
        }
        print(&quot;End&quot;)
    }
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;blockquote&gt;
  &lt;p&gt;Hello, World!
End
task11!—-&amp;lt;NSThread: 0x15d612a0&amp;gt;{number = 2, name = (null)}&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;很不幸的是，发生了死锁。
squeue最外层的任务为异步执行，主队列不会受到影响。死锁就是发生在squeue.async {}里面 。squeue嵌套的任务squeue.sync{}是同步执行,且是串行队列这就造成了squeue.async {}与squeue.sync {}相互等待。
以上都是串行队列嵌套的情况，串行队列的任务需要等待，所以嵌套的任务是同步执行的话都会发生死锁。当然，还有并行队列嵌套的情况，这里就不具体分析了。直接来看另外几个实用的功能吧;
#DisPatchGroup
DisPatchGroup是GCD中一个组的概念，可以把相关的任务归并到一个组内来执行，通过监听组内所有任务的执行情况来做相应处理。&lt;/p&gt;

&lt;div class=&quot;highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;func group() -&amp;gt; Void {
        print(&quot;Hello, World!&quot;);
        let group = DispatchGroup()
        let queue = DispatchQueue.init(label: &quot;queue_label&quot;, attributes: .concurrent)
        queue.async(group:group) { // 将任务添加至group组内
            sleep(2)
            print(&quot;task11!----\(Thread.current)&quot;)
        }
        queue.async(group:group) {
            sleep(2)
            print(&quot;task22!----\(Thread.current)&quot;)
        }
        group.notify(queue: queue) { // 监听任务组内任务的执行情况
            print(&quot;task33!----\(Thread.current)&quot;)
        }
        print(&quot;End&quot;)
    }
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;blockquote&gt;
  &lt;p&gt;Hello, World!
End
task22!—-&amp;lt;NSThread: 0x145622d0&amp;gt;{number = 2, name = (null)}
task11!—-&amp;lt;NSThread: 0x14649c40&amp;gt;{number = 3, name = (null)}
task33!—-&amp;lt;NSThread: 0x14649c40&amp;gt;{number = 3, name = (null)}&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;这样既保证了queue里的task1和task2并发执行，又保证了task3同步执行：必须在task1和task2执行完后开始执行。
&lt;img src=&quot;https://upload-images.jianshu.io/upload_images/2427856-4ca74e69a561d366.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240&quot; alt=&quot;DisPatchGroup&quot; /&gt;&lt;/p&gt;
&lt;h1 id=&quot;barrier&quot;&gt;Barrier&lt;/h1&gt;
&lt;p&gt;顾名思义，Barrier是栅栏的意思，在GCD中起到隔断任务的左右。&lt;/p&gt;

&lt;div class=&quot;highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt; func barrier() -&amp;gt; Void {
        print(&quot;Hello, World!&quot;);
        let queue = DispatchQueue.init(label: &quot;queue_label&quot;, attributes: .concurrent)
        queue.async {
            sleep(2)
            print(&quot;task11!----\(Thread.current)&quot;)
        }
        queue.async {
            sleep(2)
            print(&quot;task22!----\(Thread.current)&quot;)
        }
        queue.async(flags: .barrier) { // 设置flags为barrier
            print(&quot;barrier!-----\(Thread.current)&quot;)
        }
        queue.async {
            sleep(2)
            print(&quot;task33!-----\(Thread.current)&quot;)
        }
        queue.async {
            sleep(2)
            print(&quot;task44!-----\(Thread.current)&quot;)
        }
        print(&quot;End&quot;)
    }
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;swift的barrier创建与OC使用dispatch_barrier不同，swift是通过传一个barrier的DispatchWorkItemFlags枚举实现的。&lt;/p&gt;
&lt;blockquote&gt;
  &lt;p&gt;Hello, World!
End
task11!—-&amp;lt;NSThread: 0x165caeb0&amp;gt;{number = 2, name = (null)}
task22!—-&amp;lt;NSThread: 0x166a5180&amp;gt;{number = 3, name = (null)}
barrier!—–&amp;lt;NSThread: 0x165caeb0&amp;gt;{number = 2, name = (null)}
task44!—–&amp;lt;NSThread: 0x165caeb0&amp;gt;{number = 2, name = (null)}
task33!—–&amp;lt;NSThread: 0x166a5180&amp;gt;{number = 3, name = (null)}&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;使用barrier确保了task1，task2和task3，task4并发执行，但同时可以确保barrier后的task3,task4在barrier前的task1,task2后面执行。很明显可以看出，barrier和group作用是类似的，就是通过阻塞queue来实现同步功能的，这段代码同样可以使用group实现。但barrier更方便。
&lt;img src=&quot;https://upload-images.jianshu.io/upload_images/2427856-09d60c62cf05af37.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240&quot; alt=&quot;barrier&quot; /&gt;
刚才的barrier是通过queue.async(flags: .barrier) {}异步方式创建的，它还有同步方式queue.sync(flags: .barrier) {}（而group只有async方式），两者的区别是什么呢？前面多次提到同步执行需要等待，queue.async(flags: .barrier) {}通过栅栏的形式只会阻塞当前queue这个队列，而如果使用queue.sync(flags: .barrier) {}的方式，那么它还会阻塞主线程，print(“End”)任务就会在barrier后执行。&lt;/p&gt;

&lt;div class=&quot;highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;func barrier() -&amp;gt; Void {
        print(&quot;Hello, World!&quot;);
        let queue = DispatchQueue.init(label: &quot;queue_label&quot;, attributes: .concurrent)
        queue.async {
            sleep(2)
            print(&quot;task11!----\(Thread.current)&quot;)
        }
        queue.async {
            sleep(2)
            print(&quot;task22!----\(Thread.current)&quot;)
        }
        queue.sync(flags: .barrier) { // 设置flags为barrier
            print(&quot;barrier!-----\(Thread.current)&quot;)
        }
        queue.async {
            sleep(2)
            print(&quot;task33!-----\(Thread.current)&quot;)
        }
        queue.async {
            sleep(2)
            print(&quot;task44!-----\(Thread.current)&quot;)
        }
        print(&quot;End&quot;)
    }
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;blockquote&gt;
  &lt;p&gt;Hello, World!
task11!—-&amp;lt;NSThread: 0x16e50940&amp;gt;{number = 2, name = (null)}
task22!—-&amp;lt;NSThread: 0x16e50ca0&amp;gt;{number = 3, name = (null)}
barrier!—–&amp;lt;NSThread: 0x16e2b1e0&amp;gt;{number = 1, name = main}
End
task33!—–&amp;lt;NSThread: 0x16e50940&amp;gt;{number = 2, name = (null)}
task44!—–&amp;lt;NSThread: 0x16e50ca0&amp;gt;{number = 3, name = (null)}&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;断断续续的终于把这块写完了，但这里只讲了GCD的一部分，以上也是我个人对GCD的一点理解。如有不对的地方，还请大家多多指教。&lt;/p&gt;</content><author><name></name></author><summary type="html"></summary></entry><entry><title type="html">Nsstring内存相关，从一个小demo说起</title><link href="https://momoai.github.io/NSString%E5%86%85%E5%AD%98%E7%9B%B8%E5%85%B3-%E4%BB%8E%E4%B8%80%E4%B8%AA%E5%B0%8Fdemo%E8%AF%B4%E8%B5%B7/" rel="alternate" type="text/html" title="Nsstring内存相关，从一个小demo说起" /><published>2019-03-24T00:00:00+00:00</published><updated>2019-03-24T00:00:00+00:00</updated><id>https://momoai.github.io/NSString%E5%86%85%E5%AD%98%E7%9B%B8%E5%85%B3,%E4%BB%8E%E4%B8%80%E4%B8%AA%E5%B0%8Fdemo%E8%AF%B4%E8%B5%B7</id><content type="html" xml:base="https://momoai.github.io/NSString%E5%86%85%E5%AD%98%E7%9B%B8%E5%85%B3-%E4%BB%8E%E4%B8%80%E4%B8%AA%E5%B0%8Fdemo%E8%AF%B4%E8%B5%B7/">&lt;p&gt;&lt;img src=&quot;https://upload-images.jianshu.io/upload_images/2427856-43b29741255cac80.jpg?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240&quot; alt=&quot;timg1.jpg&quot; /&gt;&lt;/p&gt;

&lt;p&gt;首先，我们看下第一段代码：&lt;/p&gt;

&lt;div class=&quot;highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;- (void)stringTest {
    NSString *string1 = @&quot;string&quot;;
    NSString *string2 = [NSString stringWithString:@&quot;string&quot;];
    NSString *string3 = [[NSString alloc] initWithString:@&quot;string&quot;];
    NSString *string4 = [NSString stringWithFormat:@&quot;string&quot;];
    NSString *string5 = [[NSString alloc] initWithFormat:@&quot;string&quot;];
    NSLog(@&quot;1----%p&quot;,string1);
    NSLog(@&quot;2----%p&quot;,string2);
    NSLog(@&quot;3----%p&quot;,string3);
    NSLog(@&quot;4----%p&quot;,string4);
    NSLog(@&quot;5----%p&quot;,string5);
}
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;blockquote&gt;
  &lt;p&gt;2018-04-27 17:36:17.419547+0800 StringTest[19381:2393524] 1—-0x10dd070e8
2018-04-27 17:36:17.419789+0800 StringTest[19381:2393524] 2—-0x10dd070e8
2018-04-27 17:36:17.420025+0800 StringTest[19381:2393524] 3—-0x10dd070e8
2018-04-27 17:36:17.420166+0800 StringTest[19381:2393524] 4—-0xa00676e697274736
2018-04-27 17:36:17.420258+0800 StringTest[19381:2393524] 5—-0xa00676e697274736&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;这里分别用了不同方法创建了NSString对象
哪它们到底有什么区别呢？我们先看看存储区域的划分：&lt;/p&gt;
&lt;h1 id=&quot;存储区域&quot;&gt;存储区域&lt;/h1&gt;
&lt;ul&gt;
  &lt;li&gt;栈区（stack） 
　　　由编译器自动分配释放   ，存放函数的参数值，局部变量的值等。其操作方式类似于数据结构中的栈。&lt;/li&gt;
  &lt;li&gt;堆区（heap）
　　　一般由程序员分配释放，   若程序员不释放，程序结束时可能由OS回收 。与数据结构中的堆是两回事，分配方式倒是类似于链表。&lt;/li&gt;
  &lt;li&gt;全局区（静态区）（static）
　　　全局变量和静态变量的存储是放在一块的（全局变量就是采取静态存储方式的），初始化的全局变量和静态变量在一块区域，未初始化的全局变量和未初始化的静态变量在相邻的另 
  一块区域， 程序结束后由系统释放。&lt;/li&gt;
  &lt;li&gt;文字常量区 &lt;br /&gt;
　　　常量字符串就是放在这里的，程序结束后由系统释放。&lt;/li&gt;
  &lt;li&gt;代码区
　　　存放函数体的二进制代码。&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;img src=&quot;https://upload-images.jianshu.io/upload_images/2427856-4d1801f6766bb974.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240&quot; alt=&quot;内存分区&quot; /&gt;&lt;/p&gt;

&lt;ol&gt;
  &lt;li&gt;string1通过字面量创建，它是一个常量存储在常量区。如果其它对象存储的内容一样，则指针指向相同的地址。不会初始化内存空间，所以使用结束后不会释放内存。&lt;/li&gt;
  &lt;li&gt;string2通过类方法初始化string创建，是通过copy @”string”返回一个字符串，且这个copy是浅拷贝，会指向同一块地址。其实就是相当于字面量创建的方式，完全是多余的，所以这段代码会有警告：Using ‘initWithString:’ with a literal is redundant。&lt;/li&gt;
  &lt;li&gt;string3通过实例方法初始化string创建，和string2类似:相当于字面量创建，也会有警告。&lt;/li&gt;
  &lt;li&gt;string4通过类方法初始化format创建，需要初始化一段动态内存空间，存储在堆中，使用结束后需释放内存。&lt;/li&gt;
  &lt;li&gt;string5通过实例方法初始化format创建，和string4类似。&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;initWith…和stringWith…这两种实例方法和类方法的内存分配情况都是一样的，但内存释放却又有区别。什么区别呢，我们继续看第二段代码：&lt;/p&gt;

&lt;div class=&quot;highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;k&quot;&gt;@interface&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;ViewController&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;()&lt;/span&gt;

&lt;span class=&quot;k&quot;&gt;@property&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;nonatomic&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;weak&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;NSString&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;*&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;string1&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;@property&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;nonatomic&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;weak&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;NSString&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;*&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;string2&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;

&lt;span class=&quot;k&quot;&gt;@end&lt;/span&gt;

&lt;span class=&quot;k&quot;&gt;@implementation&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;ViewController&lt;/span&gt;

&lt;span class=&quot;k&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;kt&quot;&gt;void&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;viewDidLoad&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;super&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;viewDidLoad&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;];&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;self&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;stringTest&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;];&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;NSLog&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;@&quot;%@&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;_string1&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;NSLog&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;@&quot;%@&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;_string2&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;

&lt;span class=&quot;o&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;kt&quot;&gt;void&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;stringTest&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;NSString&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;*&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;string1&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;NSString&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;stringWithFormat&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;@&quot;string string1&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;];&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;NSString&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;*&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;string2&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;[[&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;NSString&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;alloc&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;initWithFormat&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;@&quot;string string2&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;];&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;self&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;string1&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;string1&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;self&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;string2&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;string2&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;

&lt;span class=&quot;k&quot;&gt;@end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;这里两个string属性都用了weak修饰而没有使用strong，这样就可以通过打印它们的值分析string1,string2内存释放的情况了。一般情况下，只在需要避免循环引用时使用weak修饰符。runtime 对注册的类， 会进行布局，对于 weak 对象会放入一个 hash 表中。 用 weak 指向的对象内存地址作为 key，当此对象的引用计数为0的时候会 dealloc。因此weak修饰的变量所引用对象被废弃时会经过以下步骤：&lt;/p&gt;
&lt;blockquote&gt;
  &lt;p&gt;1.从weak表中获取废弃对象的地址为键值的记录。
2.将包含在记录中的weak修饰符变量的地址赋值为nil.
3.从weak表中删除该记录。
4.从引用计数表中删除废弃对象的地址为键值的记录。&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;这会消耗相应的CPU资源。这里加weak只是为了方便分析。
这段代码的输出结果是什么呢？
由于_string1和_string2都是弱引用，而局部变量string1, string2在调用stringTest方法后就由系统销毁了，你可能会不假思索的回答：结果都为null，但我们看下实际情况：&lt;/p&gt;
&lt;blockquote&gt;
  &lt;p&gt;2018-04-28 11:36:20.445170+0800 StringTest[22110:2816244] string string1
2018-04-28 11:36:20.445294+0800 StringTest[22110:2816244] (null)&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;为什么string1还会有值呢？string1引用的对象在方法作用域外不是应该销毁了吗？其实这里涉及到iOS内存管理的另外一个知识点：自动释放池autoreleasepool。
#autorelease
我们知道autorelease是一种内存自动回收机制，autorelease的对象会被添加到autoreleasepool。autoreleasepool中的对象不会马上release。在正常情况下，创建的对象会在超出其作用域的时候release，但是如果将对象加入autoreleasepool，那么该对象会等到autoreleasepool销毁的时候再释放，这使得对象超出其指定的生存范围时能够自动并正确地释放。通过类似+ (instancetype)stringWithFormat:(NSString *)string方法创建的string1对象，就是被添加到了autoreleasepool中是一个autorelease对象。因为在我们没有手动加autoreleasepool的情况下，autorelease对象要在当前的runloop迭代结束时才废弃的，也就是说string1要在循环结束后才释放（因为runloop每次循环过程中autoreleasepool被生成或废弃）
因此，我们有两种方式改写代码，让string1释放：
第一种方式：手动加@autoreleasepool，在@autoreleasepool {}后对象就会释放了&lt;/p&gt;

&lt;div class=&quot;highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;- (void)viewDidLoad {
    [super viewDidLoad];
    @autoreleasepool {
        [self stringTest];
    }
    NSLog(@&quot;%@&quot;,_string1);
    NSLog(@&quot;%@&quot;,_string2);
}
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;第二种方式：模拟runloop迭代&lt;/p&gt;

&lt;div class=&quot;highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;- (void)viewDidLoad {
    [super viewDidLoad];
    [self stringTest];
    dispatch_async(dispatch_get_main_queue(), ^{
        NSLog(@&quot;%@&quot;,_string1);
        NSLog(@&quot;%@&quot;,_string2);
    });
}
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;打印结果都是一样的：&lt;/p&gt;
&lt;blockquote&gt;
  &lt;p&gt;2018-04-28 14:18:10.769891+0800 StringTest[22742:2893498] (null)
2018-04-28 14:18:10.770081+0800 StringTest[22742:2893498] (null)&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;现在我们大致知道了，stringWithFormat创建的对象会被添加到自动释放池是自动释放对象，而initWithFormat创建的对象不会被添加到自动释放池。不只是NSString类，其他类都是如此的。ARC下生成的对象不能调用autorelease，ARC下为了区分生成的对象是不是autorelease，就确立了硬性规则。这些规则简单地体现在了方法名上。
使用alloc/new/copy/mutableCopy名称开头的方法意味着生成的对象调用者持有,这些自己生成并持有的对象通过release释放(&lt;code class=&quot;highlighter-rouge&quot;&gt;额外说下，这也是声明一个new前缀的属性编译会报错的原因，因为这个属性的getter方法以new开头，按照硬性规则内存可能就不对了&lt;/code&gt;)。而其他名称类似string array dictionary的方法生成对象，不归调用者持有，这种情况下，对象是自动释放。也就是说&lt;/p&gt;

&lt;div class=&quot;highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;  NSString *string1 = [NSString stringWithFormat:@&quot;string string1&quot;];
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;等同于&lt;/p&gt;

&lt;div class=&quot;highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;    NSString *string1 = [[[NSString alloc] initWithFormat:@&quot;string string1&quot;] autorelease];
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;眼尖的小伙伴肯定注意到了，上面的第二段代码中string1和string2初始化的值分别是@”string string1”,@”string string2”，这个是我特意这样写的为的是字符串的长度。那这个长度对打印的结果有什么影响呢？把第二段代码改下：&lt;/p&gt;

&lt;div class=&quot;highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;- (void)viewDidLoad {
    [super viewDidLoad];
    [self stringTest];
    NSLog(@&quot;%@&quot;,_string1);
    NSLog(@&quot;%@&quot;,_string2);
}

- (void)stringTest {
    NSString *string1 = [NSString stringWithFormat:@&quot;string1&quot;];
    NSString *string2 = [[NSString alloc] initWithFormat:@&quot;string2&quot;];
    self.string1 = string1;
    self.string2 = string2;
}
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;blockquote&gt;
  &lt;p&gt;2018-04-28 16:10:17.572271+0800 StringTest[23270:2961270] string1
2018-04-28 16:10:17.572456+0800 StringTest[23270:2961270] string2&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;有没有发现，string2也没有释放了。刚才不是说了init创建的对象不会添加到autoreleasepool，怎么改了字符串值就没有释放了呢？这个其实已经与autoreleasepool没有关系了，我们可以试下像之前一样手动加@autoreleasepool但结果并不会改变。这里又涉及到另个一知识点了：&lt;/p&gt;
&lt;h1 id=&quot;tagged-pointer&quot;&gt;Tagged Pointer&lt;/h1&gt;
&lt;p&gt;我们通过lldb看下string1的信息：&lt;/p&gt;
&lt;blockquote&gt;
  &lt;p&gt;(lldb) p string1
(NSTaggedPointerString *) $1 = 0xa31676e697274737 @”string1”&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;可以看到NSTaggedPointerString这样奇怪的类，它就是Tagged Pointer对象。那它时干什么的呢？
假设要存储一个NSNumber对象，其值是一个整数。正常情况下，如果这个整数只是一个NSInteger的普通变量，那么它所占用的内存是与CPU的位数有关，在32位CPU下占4个字节，在64位CPU下是占8个字节的。而指针类型的大小通常也是与CPU位数相关，一个指针所占用的内存在32位CPU下为4个字节，在64位CPU下也是8个字节。所以一个普通的iOS程序，从32位机器迁移到64位机器中后，虽然逻辑没有任何变化，但这种NSNumber、NSDate一类的对象所占用的内存会翻倍。而且为了存储和访问一个NSNumber对象，我们需要在堆上为其分配内存，另外还要维护它的引用计数，管理它的生命期。这些都给程序增加了额外的逻辑，造成运行效率上的损失。
&lt;img src=&quot;https://upload-images.jianshu.io/upload_images/2427856-9f0d417b1616df42.jpg?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240&quot; alt=&quot;大神的图&quot; /&gt;
为了改进上面提到的内存占用和效率问题，苹果提出了Tagged Pointer对象。由于NSNumber、NSDate一类的变量本身的值需要占用的内存大小常常不需要8个字节，拿整数来说，4个字节所能表示的有符号整数就可以达到21亿多(2^31)。所以我们可以将一个对象的指针拆成两部分，一部分直接保存数据，另一部分作为特殊标记，表示这是一个特别的指针，不指向任何一个地址。所以，实际上它不再是一个对象了，它只是披着对象皮的普通变量而已。所以，它的内存并不存储在堆中，也不需要malloc和free。假设你调用NSNumber的integerValue，它将从数据部分中提取数值并返回。这样，每访问一个对象，就省下了一次真正对象的内存分配，省下了一次间接取值的时间。同时引用计数可以是空指令，因为没有内存需要释放。对于常用的类，这将是一个巨大的性能提升。引入Tagged Pointer对象后，64位CPU下NSNumber内存图如下：
&lt;img src=&quot;https://upload-images.jianshu.io/upload_images/2427856-93d3a6876206353c.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240&quot; alt=&quot;大神的图&quot; /&gt;
写下示例代码：&lt;/p&gt;

&lt;div class=&quot;highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;- (void)numberTest {
    NSNumber *number = [NSNumber numberWithInt:1];
    NSLog(@&quot;%p&quot;,number);
}
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;blockquote&gt;
  &lt;p&gt;StringTest[1309:126876] 0xb000000000000012&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;这里的指针地址中包含了对象特殊标记及指针指向的内容：地址0xb000000000000012最高四位的b就是NSNumber对象特殊标记，最低四位的2是用来标记number值的类型，2表示int类型(3:long,4:float,5:double)。而其余56位就是用来存储数值本身内容的，也就是说当数值超过56位存储上限的时候，那么NSNumber才会用真正的64位内存地址存储数值，然后用指针指向该内存地址。&lt;/p&gt;

&lt;div class=&quot;highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;- (void)numberTest {
    NSNumber *number1 = [NSNumber numberWithInt:1];
    NSNumber *number2 = [NSNumber numberWithLong:2];
    NSNumber *number3 = [NSNumber numberWithFloat:3];
    NSNumber *number4 = @(pow(2, 54));
    NSNumber *normalNumber = @(pow(2, 55));
    NSLog(@&quot;%p\n%p\n%p\n%p\n%p&quot;,number1,number2,number3,number4,normalNumber);
}
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;blockquote&gt;
  &lt;p&gt;0xb000000000000012
0xb000000000000023
0xb000000000000034
0xb400000000000005
0x604000038800&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;可以明显的看到，数值为2^55或更大时才在内存中分配一个NSNumber的对象来存储然后用指针指向该内存地址。可见，Tagged Pointer是可以与普通类共存的，即对一些值使用Tagged Pointer，另一些则使用一般的指针。&lt;/p&gt;

&lt;p&gt;那么NSString对象同样也适用于Tagged Pointer。&lt;/p&gt;

&lt;div class=&quot;highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;- (void)stringTest {
    NSString *string1 = [NSString stringWithFormat:@&quot;11&quot;];
    NSString *string2 = [NSString stringWithFormat:@&quot;a&quot;];
    NSLog(@&quot;%p\n%p&quot;,string1,string2);
}
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;blockquote&gt;
  &lt;p&gt;0xa000000000031312
0xa000000000000611&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;和NSNumber一样，地址最高四位的a就是NSString对象特殊标记，而最低四位的是用来标记string的长度，其余56位就是用来存储字符串内容的（字符串内容转为为ASCII码存储）。我们能猜测当字符串所需内存小于56位时会使用Tagged Pointer，相反就会使用真正的NSString对象。实际情况就是如此吗？&lt;/p&gt;

&lt;div class=&quot;highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;- (void)stringTest {
    NSString *string1 = [NSString stringWithFormat:@&quot;1234567&quot;];
    NSString *string2 = [NSString stringWithFormat:@&quot;12345678&quot;];
    NSLog(@&quot;%@---%p\n%@---%p&quot;,[string1 class],string1,[string2 class],string2);
}
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;blockquote&gt;
  &lt;p&gt;NSTaggedPointerString—0xa373635343332317
NSTaggedPointerString—0xa007a87dcaecc2a8&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;可以看到，string2内存64位，但还是使用了Tagged Pointer存储。只是编码的方式不一样了。具体的编码方式可以参考这篇&lt;a href=&quot;http://www.cocoachina.com/ios/20150918/13449.html&quot;&gt;博客&lt;/a&gt;，这里就简单列下不同字符串长度的编码方式：&lt;/p&gt;
&lt;blockquote&gt;
  &lt;p&gt;1：如果长度介于0到7，直接用八位编码存储字符串。
2：如果长度是8或9，用六位编码存储字符串，使用编码表“eilotrm.apdnsIc ufkMShjTRxgC4013bDNvwyUL2O856P-B79AFKEWV_zGJ/HYX”。
3：如果长度是10或11，用五位编码存储字符串,使用编码表“eilotrm.apdnsIc ufkMShjTRxgC4013”&lt;/p&gt;
&lt;/blockquote&gt;

&lt;div class=&quot;highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;- (void)stringTest {
    NSString *string1 = [NSString stringWithFormat:@&quot;123456789&quot;];
    NSString *string2 = [NSString stringWithFormat:@&quot;1234567890&quot;];
    NSLog(@&quot;%@---%p\n%@---%p&quot;,[string1 class],string1,[string2 class],string2);
}
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;blockquote&gt;
  &lt;p&gt;NSTaggedPointerString—0xa1ea1f72bb30ab19
__NSCFString—0x600000420060&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;当长度大于9时，使用真正的NSString对象存储。现在string2释放的问题就很明朗了：string2赋值为@”string string2”(长度：14)出了作用域后正常释放，string2赋值为@”string2”(长度：7)出了作用域不会释放。另外当字符串的内容有中文或者特殊字符（非 ASCII 字符）时，只能用NSString对象存储。字面量字符串不会使用Tagged Pointer。&lt;/p&gt;

&lt;p&gt;#总结
我这里用了一道题来总结以上有关内存的内容：
在64位架构下，以下代码输出的结果是？&lt;/p&gt;

&lt;div class=&quot;highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;- (void)viewDidLoad {
    [super viewDidLoad];
    [self stringTest];
    NSLog(@&quot;%@&quot;,_string1);
    NSLog(@&quot;%@&quot;,_string2);
    NSLog(@&quot;%@&quot;,_string3);
    NSLog(@&quot;%@&quot;,_string4);
}

- (void)stringTest {
    NSString *string1 = @&quot;1234567890&quot;;
    NSString *string2 = [NSString stringWithFormat:@&quot;1&quot;];
    NSString *string3 = [[NSString alloc] initWithFormat:@&quot;2&quot;];
    NSString *string4 = [[NSString alloc] initWithFormat:@&quot;1234567890&quot;];
    _string1 = string1;
    _string2 = string2;
    _string3 = string3;
    _string4 = string4;
}
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;相信大家很快就有了正确的答案。
写的比较杂，望多多指教。&lt;/p&gt;</content><author><name></name></author><summary type="html"></summary></entry><entry><title type="html">Nstimer、cadisplaylink那些事</title><link href="https://momoai.github.io/NSTimer-CADisplayLink%E9%82%A3%E4%BA%9B%E4%BA%8B/" rel="alternate" type="text/html" title="Nstimer、cadisplaylink那些事" /><published>2019-03-24T00:00:00+00:00</published><updated>2019-03-24T00:00:00+00:00</updated><id>https://momoai.github.io/NSTimer%E3%80%81CADisplayLink%E9%82%A3%E4%BA%9B%E4%BA%8B</id><content type="html" xml:base="https://momoai.github.io/NSTimer-CADisplayLink%E9%82%A3%E4%BA%9B%E4%BA%8B/">&lt;blockquote&gt;
  &lt;p&gt;NSTimer计时器对象，就是一个能在从现在开始的后面的某一个时刻或者周期性的执行我们指定的方法的对象。
CADisplayLink也是一个计时器，它的timeInterval和屏幕刷新频率一致（60帧/秒）。&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;CADisplayLink和NSTimer类似，接下来只着重讲解NSTimer；&lt;/p&gt;
&lt;h4 id=&quot;创建nstimer实例&quot;&gt;创建NSTimer实例&lt;/h4&gt;
&lt;p&gt;创建NSTimer实例的方法共有8种，其中实例方法2个，类方法6个。这8种方法基本都大同小异，大致可以分为两类，现分别以两个常用方法说明两者区别：&lt;/p&gt;
&lt;div class=&quot;highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;+ (NSTimer *)timerWithTimeInterval:(NSTimeInterval)ti target:(id)aTarget selector:(SEL)aSelector userInfo:(nullable id)userInfo repeats:(BOOL)yesOrNo;
+ (NSTimer *)scheduledTimerWithTimeInterval:(NSTimeInterval)ti target:(id)aTarget selector:(SEL)aSelector userInfo:(nullable id)userInfo repeats:(BOOL)yesOrNo;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
&lt;p&gt;我们分别用这两个方法创建实例，看下轮询结果：&lt;/p&gt;
&lt;div class=&quot;highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;- (void)test {
    NSTimer *timer = [NSTimer scheduledTimerWithTimeInterval:1 target:self selector:@selector(testTimer) userInfo:nil repeats:NO];
}

- (void)testTimer {
    NSLog(@&quot;%s&quot;,__func__);
}
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
&lt;p&gt;输出结果：[ViewController testTimer]&lt;/p&gt;

&lt;div class=&quot;highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;- (void)test {
    NSTimer *timer = [NSTimer scheduledTimerWithTimeInterval:1 target:self selector:@selector(testTimer) userInfo:nil repeats:NO];
    
}

- (void)testTimer {
    NSLog(@&quot;%s&quot;,__func__);
}
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
&lt;p&gt;无结果，testTimer未调用&lt;/p&gt;

&lt;p&gt;区别很明显了（其他scheduled，timerWith开头的方法也是一样）：scheduledTimerWithTimeInterval方法创建实例后执行了轮询的方法，这是因为这个方法创建的timer会被自动添加到runloop中，由runloop处理轮询事件。而timerWithTimeInterval方法创建timer后，需我们手动添加至runloop中；
CADisplayLink只有一个实例化方法，同样道理，也需要手动添加至runloop中：&lt;/p&gt;
&lt;div class=&quot;highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;+ (CADisplayLink *)displayLinkWithTarget:(id)target selector:(SEL)sel;
- (void)addToRunLoop:(NSRunLoop *)runloop forMode:(NSRunLoopMode)mode;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
&lt;p&gt;scheduledTimerWithTimeInterval方法虽能自动添加至runloop中，但这个runloop只会是当前runloop，runloop的模式也只是NSDefaultRunLoopMode；而手动添加至runloop，则可以自己设置指定的runloop及runloopMode；我们改写第二段代码看下：&lt;/p&gt;
&lt;div class=&quot;highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;- (void)test {
    NSTimer *timer = [NSTimer timerWithTimeInterval:1 target:self selector:@selector(testTimer) userInfo:nil repeats:NO];
    [[NSRunLoop currentRunLoop] addTimer:timer forMode:NSRunLoopCommonModes];
}
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
&lt;blockquote&gt;
  &lt;p&gt;[ViewController testTimer]&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;那timer为什么一定要添加至runloop中才会执行呢？&lt;/p&gt;

&lt;h4 id=&quot;nstimer与nsrunloop&quot;&gt;NSTimer与NSRunLoop&lt;/h4&gt;
&lt;p&gt;在iOS中，所有消息都会被添加到NSRunloop中，在NSRunloop中分为‘input source’跟’timer source’，并在循环中检查是不是有事件需要发生，如果需要那么就调用相应的函数处理。NSTimer这种’timer source’资源要想起作用，那肯定需要加到runloop中才会执行了。
将NSTimer添加至runloop中，也需指定runloop mode；runloop mode有很多种，接下来我们主要讲下以下3种：&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;NSDefaultRunLoopMode&lt;/li&gt;
  &lt;li&gt;UITrackingRunLoopMode&lt;/li&gt;
  &lt;li&gt;NSRunLoopCommonModes&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;code class=&quot;highlighter-rouge&quot;&gt;NSDefaultRunLoopMode：&lt;/code&gt;这个是默认的运行模式，也是最常用的运行模式，NSTimer与NSURLConnection默认运行该模式下。默认模式中几乎包含了所有输入源(NSConnection除外)，一般情况下应使用此模式。
&lt;code class=&quot;highlighter-rouge&quot;&gt;UITrackingRunLoopMode：&lt;/code&gt;当滑动屏幕的时候，比如UIScrollView或者它的子类UITableView、UICollectionView等滑动时runloop处于这个模式下。在此模式下会限制输入事件的处理。
&lt;code class=&quot;highlighter-rouge&quot;&gt;NSRunLoopCommonModes：&lt;/code&gt;这是一组可配置的常用模式，是一个伪模式，其为一组runloop mode的集合。将输入源与此模式相关联还将其与组中的每种模式相关联，当输入源加入此模式意味着在CommonModes中包含的所有模式下都可以处理。 对于iOS应用程序，默认情况下此设置包括NSDefaultRunLoopMode和UITrackingRunLoopMode。可使用&lt;code class=&quot;highlighter-rouge&quot;&gt;CFRunLoopAddCommonMode&lt;/code&gt;方法在CommonModes中添加自定义modes。&lt;/p&gt;

&lt;p&gt;了解这几种runloop modes后，我们就知道平时开发中timer在屏幕滑动的时候停止执行的原因了，也能轻易的解决这个问题：
一个timer可以被添加到runloop的多个模式，因此如果想让timer在UIScrollView等滑动的时候也能够触发，就可以分别添加到UITrackingRunLoopMode，UITrackingRunLoopMode这两个模式下。或者直接添加到NSRunLoopCommonModes这一个模式集，它包含了上面的两种模式。&lt;/p&gt;

&lt;p&gt;另外，一个线程有且只有一个runloop。主线程的runloop默认是开启的，其他线程runloop并不是默认开启的。所以当在子线程中添加timer时，还需手动开启这个runloop：&lt;/p&gt;
&lt;div class=&quot;highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;   dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0), ^{
        NSTimer *timer = [NSTimer timerWithTimeInterval:1 target:self selector:@selector(testTimer) userInfo:nil repeats:NO];
        [[NSRunLoop currentRunLoop] addTimer:timer forMode:NSRunLoopCommonModes];
        [[NSRunLoop currentRunLoop] run];
    });
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
&lt;h4 id=&quot;nstimer触发事件的准时性&quot;&gt;NSTimer触发事件的准时性&lt;/h4&gt;
&lt;p&gt;NSTimer不是一个实时系统，不管是一次性的还是周期性的timer的实际触发事件的时间可能都会跟我们预想的会有出入。NSTimer并不是准时的，造成不准时的原因如下：&lt;/p&gt;
&lt;ul&gt;
  &lt;li&gt;触发点可能被跳过去
    &lt;ol&gt;
      &lt;li&gt;线程处理比较耗时的事情时会发生这种情况，当线程处理的事情比较耗时时，timer就会延迟到该事件执行完以后才会执行。&lt;/li&gt;
      &lt;li&gt;timer添加到的runloop mode不是runloop当前运行的mode，也会发生这种情况。&lt;/li&gt;
    &lt;/ol&gt;

    &lt;p&gt;虽然跳过去，但是接下来的执行不会依据被延迟的时间加上间隔时间，而是根据之前的时间来执行。比如：
定时时间间隔为2秒，t1秒添加成功，那么会在t2、t4、t6、t8、t10秒注册好事件，并在这些时间触发。假设第3秒时，执行了一个超时操作耗费了5.5秒，则触发时间是：t2、t8.5、t10，第4和第6秒就被跳过去了，虽然在t8.5秒触发了一次，但是下一次触发时间是t10，而不是t10.5。&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;不准点
比如上面说的t2、t4、t6、t8、t10，并不会在准确的时间触发，而是会延迟个很小的时间。也有两个因素：
    &lt;ol&gt;
      &lt;li&gt;RunLoop为了节省资源，并不会在非常准确的时间点触发。&lt;/li&gt;
      &lt;li&gt;线程有耗时操作，或者其它线程有耗时操作也会影响&lt;/li&gt;
    &lt;/ol&gt;

    &lt;p&gt;好在，这种不准点对我们开发影响并不大（基本是毫秒级别以下的延迟）&lt;/p&gt;
  &lt;/li&gt;
&lt;/ul&gt;

&lt;h4 id=&quot;nstimer内存问题&quot;&gt;NSTimer内存问题&lt;/h4&gt;
&lt;p&gt;重点来了！！！
还是先看段代码&lt;/p&gt;
&lt;div class=&quot;highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;- (void)test {
    self.timer = [NSTimer timerWithTimeInterval:1 target:self selector:@selector(testTimer) userInfo:nil repeats:NO];
    [[NSRunLoop currentRunLoop] addTimer:timer forMode:NSRunLoopCommonModes];
}

- (void)dealloc {
    NSLog(@&quot;%s&quot;,__func__);
}

- (void)touchesBegan:(NSSet&amp;lt;UITouch *&amp;gt; *)touches withEvent:(UIEvent *)event {
    NSLog(@&quot;%s&quot;,__func__);
    [self dismissViewControllerAnimated:YES completion:nil];
}
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
&lt;p&gt;点击屏幕，输出结果：
[TestViewController touchesBegan:withEvent:]
[TestViewController dealloc]
接下来，我们将创建timer时的repeats改为YES再看下：&lt;/p&gt;
&lt;div class=&quot;highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;self.timer = [NSTimer timerWithTimeInterval:1 target:self selector:@selector(testTimer) userInfo:nil repeats:YES];
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
&lt;p&gt;点击屏幕，输出结果：
[TestViewController touchesBegan:withEvent:]
可以看出，repeats改为YES后TestViewController没有被释放，发生了内存泄露。为什么会这样呢？
前面说了，NSTimer是要添加至runloop中的，runloop会对timer有强引用。而timer会对目标对象target进行强引用（类似UIButton等并不会强引用target）。如果viewController也强引用了timer，那viewController和timer循环引用了。而就算viewController没有强引用timer，由于runloop对timer的强引用一直存在，timer又强引用着viewController，viewController也不能释放。它们的引用关系如下：&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;https://upload-images.jianshu.io/upload_images/2427856-5182442b57a01b0b.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240&quot; alt=&quot;haha&quot; /&gt;&lt;/p&gt;

&lt;p&gt;对于单次事件的timer来说，在事件执行完后，timer会把本身从NSRunLoop中移除，然后就是释放对‘target’对象的强引用，所以不会有内存泄露问题。NSTimer的- (void)invalidate方法就是这个作用，所以对于重复性的timer在不需要的时候调用invalidate方法也能解决内存问题：&lt;/p&gt;
&lt;div class=&quot;highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;- (void)touchesBegan:(NSSet&amp;lt;UITouch *&amp;gt; *)touches withEvent:(UIEvent *)event {
    NSLog(@&quot;%s&quot;,__func__);
    [_timer invalidate];
    [self dismissViewControllerAnimated:YES completion:nil];
}
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
&lt;p&gt;点击屏幕，输出结果：
[TestViewController touchesBegan:withEvent:]
[TestViewController dealloc]&lt;/p&gt;

&lt;p&gt;But,
调用invalidate方法，这是在我们能明确timer销毁的时机的基础上调用的（就像上面的代码，我们能明确的知道touchesBegan事件时停止timer）。但大部分时候的需求并没有明确的时机，而是让timer一直执行直到所在VC销毁时才停止timer，也就是说我们必须在dealloc方法中调用timer的invalidate方法以确保万无一失。但timer未调用invalidate方法VC并不会销毁，不会调用dealloc方法；dealloc没调用，那timer的invalidate调用不了。这就尴尬了，陷入了死结。这该如何是好？
这种情况，其实有多种解决方案。最简单的方案就是：在viewWillAppear方法中创建、执行timer，在viewWillDisappear中销毁timer。这种方式虽然能解决内存问题，但实现过程比较low会有潜在的bug。其实也算不上解决方案，不推荐使用。
下面，介绍比较高大上比较有技术含量，并且能完美解决内存泄漏问题的方案；
首先我们分析下解决timer造成内存泄漏的突破点：
我们再看下上面的内存引用图。runloop对timer的强引用是一直存在的，我们无法改变这种状态（除非timer调用invalidate方法）；前面也分析了：当runloop强引用timer时，viewController对timer是否是强引用viewController都释放不了。那么唯一的突破点就是打破timer对target（即viewController）的强引用，让viewController能正常释放，然后调用dealloc方法进而销毁timer将timer本身从NSRunLoop中移除。如何能做到呢？
这有两种方案：&lt;/p&gt;
&lt;ul&gt;
  &lt;li&gt;
    &lt;p&gt;创建NSTimer分类，将target设置为本身的NSTimer类对象，将selector的方式改为block回调方式；
```
@interface NSTimer (JSBlockSupport)&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;(NSTimer *)js_scheduledTimerWithTimeInterval:(NSTimeInterval)ti block:(void(^)(void))block repeats:(BOOL)yesOrNo;&lt;/p&gt;
  &lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;@end&lt;/p&gt;

&lt;p&gt;@implementation NSTimer (JSBlockSupport)&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;
    &lt;p&gt;(NSTimer *)js_scheduledTimerWithTimeInterval:(NSTimeInterval)ti block:(void (^)(void))block repeats:(BOOL)yesOrNo {
  return [self scheduledTimerWithTimeInterval:ti target:self selector:@selector(js_blockInvoke:) userInfo:[block copy] repeats:yesOrNo];
}&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;(void)js_blockInvoke:(NSTimer *)timer {
  void (^block)(void) = timer.userInfo;
  if (block) {
      block();
  }
}&lt;/p&gt;
  &lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;@end&lt;/p&gt;
&lt;div class=&quot;highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
&lt;ul&gt;
  &lt;li&gt;
    &lt;p&gt;(void)test {
  __weak typeof (self) weakSelf = self;
  self.timer = [NSTimer js_scheduledTimerWithTimeInterval:1 block:^{
      __strong typeof (weakSelf) strongSelf = weakSelf;
      [strongSelf testTimer];
  } repeats:YES];
}&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;(void)dealloc {
  NSLog(@”%s”,&lt;strong&gt;func&lt;/strong&gt;);
}&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;(void)touchesBegan:(NSSet&amp;lt;UITouch *&amp;gt; *)touches withEvent:(UIEvent *)event {
  NSLog(@”%s”,&lt;strong&gt;func&lt;/strong&gt;);
//    [_timer invalidate];
  [self dismissViewControllerAnimated:YES completion:nil];
}
```
点击屏幕，输出结果：
[TestViewController touchesBegan:withEvent:]
[TestViewController dealloc]
这里很巧妙的利用了timer的userInfo属性，将执行的操作作为block赋值给userInfo（id类型）,在分类中的selector取出userInfo并执行这个block。
在调用分类方法时，有个注意点：block里需使用weak对象，再用strong对象调用方法。这是因为viewController强引用timer，timer强引用block，block会捕获里面的viewController，这同样循环引用了，本来我们是为了处理内存问题的，结果一不小心就全白忙了。用strong对象调用方法，是为了保证对象一直存活执行方法。&lt;/p&gt;
  &lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;我们再回过头来看下CADisplayLink如何处理，由于CADisplayLink没有类似userInfo这样的属性，所以我们需要为CADisplayLink分类添加属性，为分类添加属性就要用到runtime关联属性了：&lt;/p&gt;
&lt;div class=&quot;highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;k&quot;&gt;@interface&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;CADisplayLink&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;JSBlockSupport&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;

&lt;span class=&quot;k&quot;&gt;+&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;CADisplayLink&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;*&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;js_displayLinkWithBlock&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:(&lt;/span&gt;&lt;span class=&quot;kt&quot;&gt;void&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;^&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)(&lt;/span&gt;&lt;span class=&quot;kt&quot;&gt;void&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;))&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;block&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;

&lt;span class=&quot;k&quot;&gt;@end&lt;/span&gt;

&lt;span class=&quot;k&quot;&gt;static&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;void&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;*&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;JSDisplayLinkKey&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&quot;JSDisplayLinkKey&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;

&lt;span class=&quot;k&quot;&gt;@implementation&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;CADisplayLink&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;JSBlockSupport&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;

&lt;span class=&quot;k&quot;&gt;+&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;CADisplayLink&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;*&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;js_displayLinkWithBlock&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:(&lt;/span&gt;&lt;span class=&quot;kt&quot;&gt;void&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;^&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)(&lt;/span&gt;&lt;span class=&quot;kt&quot;&gt;void&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;))&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;block&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;CADisplayLink&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;*&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;displayLink&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;self&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;displayLinkWithTarget&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;self&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;selector&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;@selector&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;js_blockInvoke&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)];&lt;/span&gt;
    &lt;span class=&quot;c1&quot;&gt;// 直接关联了一个block对象
&lt;/span&gt;    &lt;span class=&quot;n&quot;&gt;objc_setAssociatedObject&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;displayLink&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;JSDisplayLinkKey&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;block&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;OBJC_ASSOCIATION_COPY&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;displayLink&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;

&lt;span class=&quot;k&quot;&gt;+&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;kt&quot;&gt;void&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;js_blockInvoke&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;CADisplayLink&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;*&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;displayLink&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;kt&quot;&gt;void&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;^&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;block&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)(&lt;/span&gt;&lt;span class=&quot;kt&quot;&gt;void&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;objc_getAssociatedObject&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;displayLink&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;JSDisplayLinkKey&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;block&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;n&quot;&gt;block&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;();&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;

&lt;span class=&quot;k&quot;&gt;@end&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;div class=&quot;highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;- (void)test {
    __weak typeof (self) weakSelf = self;
    self.displayLink = [CADisplayLink js_displayLinkWithBlock:^{
        __strong typeof (weakSelf) strongSelf = weakSelf;
        [strongSelf testTimer];
    }];
    [self.displayLink addToRunLoop:[NSRunLoop currentRunLoop] forMode:NSRunLoopCommonModes];
}
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
&lt;ul&gt;
  &lt;li&gt;为timer原来的target设置NSProxy代理对象，将这个代理设置为timer的target。这样timer没有强引用原本target对象了，而代理对象收到的消息通过消息转发机制又转发给原本target处理。
```&lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;(void)test {
  self.timer = [NSTimer timerWithTimeInterval:1 target:[YYWeakProxy proxyWithTarget:self] selector:@selector(testTimer) userInfo:nil repeats:YES];
  [[NSRunLoop currentRunLoop] addTimer:timer forMode:NSRunLoopCommonModes];
}
```
点击屏幕，输出结果：
[TestViewController touchesBegan:withEvent:]
[TestViewController dealloc]
这里创建NSProxy代理对象，使用了大神写的YYWeakProxy类，具体实现可以下载看下其源码。&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;iOS10添加了block方式的初始化方法，同样也解决了内存泄漏问题（但一般项目都要适配iOS10之前的系统，所以也并没有什么用处）：
```&lt;/li&gt;
  &lt;li&gt;(NSTimer *)timerWithTimeInterval:(NSTimeInterval)interval repeats:(BOOL)repeats block:(void (^)(NSTimer *timer))block API_AVAILABLE(macosx(10.12), ios(10.0), watchos(3.0), tvos(10.0));
```&lt;/li&gt;
&lt;/ul&gt;</content><author><name></name></author><summary type="html">NSTimer计时器对象，就是一个能在从现在开始的后面的某一个时刻或者周期性的执行我们指定的方法的对象。 CADisplayLink也是一个计时器，它的timeInterval和屏幕刷新频率一致（60帧/秒）。</summary></entry><entry><title type="html">Objective C实现链式编程</title><link href="https://momoai.github.io/Objective-C%E5%AE%9E%E7%8E%B0%E9%93%BE%E5%BC%8F%E7%BC%96%E7%A8%8B/" rel="alternate" type="text/html" title="Objective C实现链式编程" /><published>2019-03-24T00:00:00+00:00</published><updated>2019-03-24T00:00:00+00:00</updated><id>https://momoai.github.io/Objective-C%E5%AE%9E%E7%8E%B0%E9%93%BE%E5%BC%8F%E7%BC%96%E7%A8%8B</id><content type="html" xml:base="https://momoai.github.io/Objective-C%E5%AE%9E%E7%8E%B0%E9%93%BE%E5%BC%8F%E7%BC%96%E7%A8%8B/">&lt;p&gt;首先，我们看下优秀的自动布局第三方框架Masonry/SnapKit的链式语法应用：&lt;/p&gt;
&lt;div class=&quot;highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;      // Masonry oc
     [box mas_makeConstraints:^(MASConstraintMaker *make) {
          make.width.height.equalTo(@50);
          make.center.equalTo(self.view);
      }];

&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
&lt;div class=&quot;highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;        // SnapKit  swift
        box.snp.makeConstraints { (make) -&amp;gt; Void in
           make.width.height.equalTo(50)
           make.center.equalTo(self.view)
        }
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
&lt;p&gt;Objective-C、Swift版都使用了连续的.语法，这就是链式编程的特点。链式编程就是将多个方法用点语法链接起来，让代码更加简洁，可读性更强。
附一篇讲解Masonry源码的博文：&lt;a href=&quot;https://www.cnblogs.com/ludashi/p/5591572.html&quot;&gt;iOS开发之Masonry框架源码解析&lt;/a&gt;
###那么用Objective-C怎么实现链式编程呢？
以Masonry那段代码为例，连续调用中有width，height等不带（）参数的，也有equalTo(@50)这种带参数的。我们知道OC是通过[ ]语法调用方法的，能使用点语法的只能是类的属性了（getter setter方法）。&lt;/p&gt;
&lt;ul&gt;
  &lt;li&gt;对于不带参数的，直接通过点语法调用getter方法就能实现。但getter方法返回的是属性本身，而连续的点语法需要返回调用的对象以此才能调用下个点语法。一种做法是将属性的类型声明为调用对象类型，但这样做耦合度太高。因此要使用另一种方式，这需要用到代理：A对象为链式调用者，B对象作为A的属性，A设为B的代理且B中定义和A一样的属性名。这样即使调用B的getter方法返回的是B对象，再调用下个点语法时B通过代理又调用了A的方法。&lt;/li&gt;
  &lt;li&gt;对于带参数的，一般属性类似于self.title通过点语法访问，后面是不带()的。看到()我们很容易联想到block，block是通过()调用的。所以这个属性一定是block，block可以设置参数。而要实现连续点语法，这个block一定要有返回值，且返回值是调用block的对象。整个思路就出来了（不带参数的也同样能用block实现）&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;通过demo实现链式编程简单的计算器功能，方便直观的理解这种写法：&lt;/p&gt;
&lt;h5 id=&quot;代理方式&quot;&gt;代理方式：&lt;/h5&gt;
&lt;div class=&quot;highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;cp&quot;&gt;#import &amp;lt;Foundation/Foundation.h&amp;gt;
&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;@class&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;Mediator&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;

&lt;span class=&quot;k&quot;&gt;@protocol&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;MediatorDelegate&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;NSObject&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt;

&lt;span class=&quot;o&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Mediator&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;*&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;add&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Mediator&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;*&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;sub&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;kt&quot;&gt;float&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;result&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;

&lt;span class=&quot;k&quot;&gt;@end&lt;/span&gt;


&lt;span class=&quot;k&quot;&gt;@interface&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;Mediator&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;NSObject&lt;/span&gt;

&lt;span class=&quot;k&quot;&gt;@property&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;nonatomic&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;strong&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;Mediator&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;*&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;add&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;@property&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;nonatomic&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;strong&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;Mediator&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;*&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;sub&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;@property&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;nonatomic&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;assign&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;float&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;result&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;@property&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;nonatomic&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;weak&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;id&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;MediatorDelegate&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;delegate&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;

&lt;span class=&quot;k&quot;&gt;@end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
&lt;div class=&quot;highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;cp&quot;&gt;#import &quot;Mediator.h&quot;
&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;@implementation&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;Mediator&lt;/span&gt;

&lt;span class=&quot;k&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Mediator&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;*&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;add&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;self&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;delegate&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;add&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;];&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;

&lt;span class=&quot;o&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Mediator&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;*&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;sub&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;self&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;delegate&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;sub&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;];&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;

&lt;span class=&quot;o&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;kt&quot;&gt;float&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;result&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;self&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;delegate&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;result&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;];&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;

&lt;span class=&quot;k&quot;&gt;@end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
&lt;div class=&quot;highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;cp&quot;&gt;#import &amp;lt;Foundation/Foundation.h&amp;gt;
#import &quot;Mediator.h&quot;
&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;@interface&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;Calculator&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;NSObject&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;MediatorDelegate&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt;

&lt;span class=&quot;k&quot;&gt;@property&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;nonatomic&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;assign&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;float&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;result&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;c1&quot;&gt;// 存储计算结果
&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;@property&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;nonatomic&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;strong&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;Mediator&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;*&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;add&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;@property&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;nonatomic&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;strong&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;Mediator&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;*&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;sub&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;

&lt;span class=&quot;k&quot;&gt;@end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
&lt;div class=&quot;highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;cp&quot;&gt;#import &quot;Calculator.h&quot;
&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;@implementation&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;Calculator&lt;/span&gt;

&lt;span class=&quot;k&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Mediator&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;*&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;add&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;!&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;_add&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;n&quot;&gt;_add&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;[[&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Mediator&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;alloc&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;init&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;];&lt;/span&gt;
        &lt;span class=&quot;n&quot;&gt;_add&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;delegate&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;self&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;_result&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;++&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;_add&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;

&lt;span class=&quot;o&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Mediator&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;*&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;sub&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;!&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;_sub&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;n&quot;&gt;_sub&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;[[&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Mediator&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;alloc&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;init&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;];&lt;/span&gt;
        &lt;span class=&quot;n&quot;&gt;_sub&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;delegate&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;self&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;_result&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;--&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;_sub&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;

&lt;span class=&quot;o&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;kt&quot;&gt;float&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;result&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;_result&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;

&lt;span class=&quot;k&quot;&gt;@end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
&lt;div class=&quot;highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;k&quot;&gt;@implementation&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;ViewController&lt;/span&gt;

&lt;span class=&quot;k&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;kt&quot;&gt;void&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;viewDidLoad&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;super&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;viewDidLoad&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;];&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;Calculator&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;*&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;calculator&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;[[&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Calculator&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;alloc&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;init&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;];&lt;/span&gt;
    &lt;span class=&quot;kt&quot;&gt;float&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;result&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;calculator&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;add&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;sub&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;add&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;result&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;c1&quot;&gt;// 1
&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;@end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h5 id=&quot;block方式&quot;&gt;block方式：&lt;/h5&gt;
&lt;div class=&quot;highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;cp&quot;&gt;#import &amp;lt;Foundation/Foundation.h&amp;gt;
&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;@class&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;Calculator&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;

&lt;span class=&quot;k&quot;&gt;typedef&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;Calculator&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;*&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;^&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;CalculatorBlock&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)(&lt;/span&gt;&lt;span class=&quot;kt&quot;&gt;float&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;value&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;

&lt;span class=&quot;k&quot;&gt;@interface&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;Calculator&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;NSObject&lt;/span&gt;

&lt;span class=&quot;k&quot;&gt;@property&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;nonatomic&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;assign&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;float&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;result&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;c1&quot;&gt;// 存储计算的结果
&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;@property&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;nonatomic&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;copy&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;CalculatorBlock&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;add&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;@property&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;nonatomic&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;copy&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;CalculatorBlock&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;sub&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;

&lt;span class=&quot;k&quot;&gt;@end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
&lt;div class=&quot;highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;cp&quot;&gt;#import &quot;Calculator.h&quot;
&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;@implementation&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;Calculator&lt;/span&gt;

&lt;span class=&quot;k&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;CalculatorBlock&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;add&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;^&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Calculator&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;*&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;kt&quot;&gt;float&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;value&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;n&quot;&gt;self&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;result&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;+=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;value&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;self&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;};&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;

&lt;span class=&quot;o&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;CalculatorBlock&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;sub&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;^&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Calculator&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;*&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;kt&quot;&gt;float&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;value&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;n&quot;&gt;self&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;result&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;-=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;value&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;self&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;};&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;

&lt;span class=&quot;k&quot;&gt;@end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
&lt;p&gt;计算器Calculator类定义了加法add和减法sub两个block属性，这个block有一个float参数，返回值为Calculator对象。然后通过重写getter方法实现自己的计算，并用属性result记录。因为block返回的是self，在调用第一个block后仍可以调用下个block，点语法的链条就形成了。
现在我们就可以通过链式调用计算器加减法了：&lt;/p&gt;
&lt;div class=&quot;highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;cp&quot;&gt;#import &quot;ViewController.h&quot;
#import &quot;Calculator.h&quot;
&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;@interface&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;ViewController&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;()&lt;/span&gt;

&lt;span class=&quot;k&quot;&gt;@end&lt;/span&gt;

&lt;span class=&quot;k&quot;&gt;@implementation&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;ViewController&lt;/span&gt;

&lt;span class=&quot;k&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;kt&quot;&gt;void&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;viewDidLoad&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;super&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;viewDidLoad&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;];&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;Calculator&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;*&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;calculator&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;[[&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Calculator&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;alloc&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;init&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;];&lt;/span&gt;
    &lt;span class=&quot;kt&quot;&gt;float&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;result&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;calculator&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;add&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;).&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;sub&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;2&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;).&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;add&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;3&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;).&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;result&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;c1&quot;&gt;// 2
&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;

&lt;span class=&quot;k&quot;&gt;@end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
&lt;p&gt;为了更清楚理解这种写法，我把calculator.add(1).sub(2).add(3).result;这句代码过程再逐个拆解下：&lt;/p&gt;
&lt;div class=&quot;highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;- (void)viewDidLoad {
    [super viewDidLoad];
    Calculator *calculator = [[Calculator alloc] init];
//    float result = calculator.add(1).sub(2).add(3).result;
    
    CalculatorBlock block1 = calculator.add;
    calculator = block1(1);
    
    CalculatorBlock block2 = calculator.sub;
    calculator = block2(2);
    
    CalculatorBlock block3 = calculator.add;
    calculator = block3(3);
    
    float result = calculator.result; // 2
}
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
&lt;h4 id=&quot;swift语言的链式编程&quot;&gt;swift语言的链式编程&lt;/h4&gt;
&lt;p&gt;大部分面向对象语言都是可以通过点语法调用方法的，这些语言写链式代码就不会像OC这么麻烦了。这里再用swift语法写刚才那个计算器，对比一下：&lt;/p&gt;
&lt;div class=&quot;highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;import UIKit

class Calculator: NSObject {
    public var result:Float = 0;
    
    public func add(_ value:Float) -&amp;gt; Calculator {
        result += value;
        return self;
    }
    
    public func sub(_ value:Float) -&amp;gt; Calculator {
        result -= value;
        return self;
    }
}
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
&lt;div class=&quot;highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;import UIKit

class ViewController: UIViewController {
    override func viewDidLoad() {
        super.viewDidLoad()
        let calculator = Calculator()
        let result = calculator.add(1).sub(2).add(3).result // 2
    }
}
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;</content><author><name></name></author><summary type="html">首先，我们看下优秀的自动布局第三方框架Masonry/SnapKit的链式语法应用： ``` // Masonry oc [box mas_makeConstraints:^(MASConstraintMaker *make) { make.width.height.equalTo(@50); make.center.equalTo(self.view); }];</summary></entry><entry><title type="html">Uibutton的titleedgeinsets和imageedgeinsets</title><link href="https://momoai.github.io/UIButton%E7%9A%84titleEdgeInsets%E5%92%8CimageEdgeInsets/" rel="alternate" type="text/html" title="Uibutton的titleedgeinsets和imageedgeinsets" /><published>2019-03-24T00:00:00+00:00</published><updated>2019-03-24T00:00:00+00:00</updated><id>https://momoai.github.io/UIButton%E7%9A%84titleEdgeInsets%E5%92%8CimageEdgeInsets</id><content type="html" xml:base="https://momoai.github.io/UIButton%E7%9A%84titleEdgeInsets%E5%92%8CimageEdgeInsets/">&lt;p&gt;之前好不容易弄明白了UIButton的titleEdgeInsets和imageEdgeInsets属性，然而今天应用到的时候又磕磕绊绊的花了好长时间才理清楚。长痛不如短痛，今天就在这里花点时间做个笔记，同时希望可以方便不了解这两个属性用法的伙伴有所理解。&lt;/p&gt;

&lt;p&gt;我们知道UIButton由一个titleLabel和imageView组成的，显而易见titleEdgeInsets和imageEdgeInsets分别对应着UIButton中的titleLabel和imageView。这两个属性是UIEdgeInsets结构体：&lt;/p&gt;
&lt;div class=&quot;highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;typedef struct UIEdgeInsets {
    CGFloat top, left, bottom, right;  // specify amount to inset (positive) for each of the edges. values can be negative to 'outset'
} UIEdgeInsets;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
&lt;p&gt;titleEdgeInsets和imageEdgeInsets作用类似于UIScrollView的contentInset（UIButton也有contentEdgeInsets）,top,left,bottom,right分别设置距离上左下右边的内边距，默认都为0。这里要说明下top,left,bottom,right正负值的情况，正值是向内缩，负值向外扩。
&lt;img src=&quot;https://upload-images.jianshu.io/upload_images/2427856-e2dbddab0905976c.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240&quot; alt=&quot;正值/负值效果&quot; /&gt;&lt;/p&gt;

&lt;p&gt;通过具体应用场景来分析下：&lt;/p&gt;
&lt;h1 id=&quot;应用场景1-uibutton图片的大小&quot;&gt;应用场景1: UIButton图片的大小&lt;/h1&gt;
&lt;p&gt;设置button图片的时候（非背景图片backgroundImage）,当设置的图片本身的size比button的size要大时，默认情况下图片会填充整个button。这时有这样的需求：button的大小不变但图片需要调小（一般是为了让button在界面显示大小适中，但点击的范围又要尽量大），而button的imageView的frame设置是无效的。一个解决方法是把原图切小到符合要求的尺寸，再设置图片。另一个简单的方法就是设置imageEdgeInsets。
button的size为100x100，设置的图片size也是100x100。我们可以设置button. imageEdgeInsets = UIEdgeInsetsMake(25, 25, 25, 25)来达到缩小图片的目的。这样button看起来只有50x50大，但实际图片上下左右25范围内仍能触发点击事件。（一些新手是这样实现的：创建了一个100x100的透明的button，然后创建50x50的imageView添加到button上）&lt;/p&gt;
&lt;h1 id=&quot;应用场景2调整titlelabel和imageview的相对位置&quot;&gt;应用场景2：调整titleLabel和imageView的相对位置&lt;/h1&gt;
&lt;p&gt;我们知道UIButton的默认布局是图片在左，文字在右的。但很多情况下，需求都要求图片在右文字在左，或者图片文字为上下布局。很多小伙伴都冲动的直接去自定义button了，同样我们只要设置titleEdgeInsets和imageEdgeInsets就能简单的实现这样的需求。我们以一个demo为例：
&lt;img src=&quot;https://upload-images.jianshu.io/upload_images/2427856-d829c1a94d1220ca.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240&quot; alt=&quot;创建button&quot; /&gt;&lt;/p&gt;

&lt;p&gt;这里我拖了一个红色背景100x100的button，设置的图片为50x50。默认情况titleEdgeInsets和imageEdgeInsets都为0。现在我们要把图片和文字调换过来，前面的场景是调整了图片的大小，现在调换位置其实原理是一样的。因为都是左右移动，那么上下边距就不用调节。图片需要右移，也就是图片左边left往内缩右边right往外扩。前面说了正值是内缩，负值是向外扩，现在就可以确定imageEdgeInsets的left为正right需为负。具体数值可以通过titleLabel的frame确定：&lt;/p&gt;
&lt;div class=&quot;highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;    CGFloat labelW = _button.titleLabel.frame.size.width;
    _button.imageEdgeInsets = UIEdgeInsetsMake(0, labelW, 0, -labelW);
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
&lt;p&gt;&lt;img src=&quot;https://upload-images.jianshu.io/upload_images/2427856-234e680a1452a4e0.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240&quot; alt=&quot;图片移动&quot; /&gt;
同样的文字需要左移，也就是文字左边left为负往外扩右边right为正往内缩：&lt;/p&gt;
&lt;div class=&quot;highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;    CGFloat imageW = _button.imageView.frame.size.width;
    _button.titleEdgeInsets = UIEdgeInsetsMake(0, -imageW, 0, imageW);
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
&lt;p&gt;&lt;img src=&quot;https://upload-images.jianshu.io/upload_images/2427856-f7631837a20093a5.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240&quot; alt=&quot;label移动&quot; /&gt;
至此图片和文字调换过来了，它们的frame也改变了：&lt;/p&gt;
&lt;blockquote&gt;
  &lt;p&gt;(lldb) po _button.imageView.frame.origin.x
39.333333333333343
(lldb) po _button.titleLabel.frame.origin.x
10.666666666666666&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;图片和文字为上下布局时，也是类似的调整，只是设置的就是top和bottom了。
从上面两个场景可以总结出，设置titleEdgeInsets和imageEdgeInsets能实现button的titleLabel和imageView的缩放及平移。
UIButton还有contentEdgeInsets，设置后对图片文字都生效的。&lt;/p&gt;
&lt;blockquote&gt;
  &lt;p&gt;_button.contentEdgeInsets = UIEdgeInsetsMake(20, 0, 0, 0);&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;img src=&quot;https://upload-images.jianshu.io/upload_images/2427856-bd73624ac8fa400c.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240&quot; alt=&quot;content&quot; /&gt;&lt;/p&gt;</content><author><name></name></author><summary type="html">之前好不容易弄明白了UIButton的titleEdgeInsets和imageEdgeInsets属性，然而今天应用到的时候又磕磕绊绊的花了好长时间才理清楚。长痛不如短痛，今天就在这里花点时间做个笔记，同时希望可以方便不了解这两个属性用法的伙伴有所理解。</summary></entry><entry><title type="html">[转]sql server中的事务与锁</title><link href="https://momoai.github.io/%E8%BD%AC-SQL-Server%E4%B8%AD%E7%9A%84%E4%BA%8B%E5%8A%A1%E4%B8%8E%E9%94%81/" rel="alternate" type="text/html" title="[转]sql server中的事务与锁" /><published>2019-03-24T00:00:00+00:00</published><updated>2019-03-24T00:00:00+00:00</updated><id>https://momoai.github.io/%5B%E8%BD%AC%5DSQL%20Server%E4%B8%AD%E7%9A%84%E4%BA%8B%E5%8A%A1%E4%B8%8E%E9%94%81</id><content type="html" xml:base="https://momoai.github.io/%E8%BD%AC-SQL-Server%E4%B8%AD%E7%9A%84%E4%BA%8B%E5%8A%A1%E4%B8%8E%E9%94%81/">&lt;p&gt;转自&lt;a href=&quot;https://www.cnblogs.com/knowledgesea/p/3714417.html&quot;&gt;SQL Server中的事务与锁&lt;/a&gt;&lt;/p&gt;

&lt;h1 id=&quot;了解事务和锁&quot;&gt;了解事务和锁&lt;/h1&gt;

&lt;p&gt;事务：保持逻辑数据一致性与可恢复性，必不可少的利器。&lt;/p&gt;

&lt;p&gt;锁：多用户访问同一数据库资源时，对访问的先后次序权限管理的一种机制，没有他事务或许将会一塌糊涂，不能保证数据的安全正确读写。&lt;/p&gt;

&lt;p&gt;死锁：是数据库性能的重量级杀手之一，而死锁却是不同事务之间抢占数据资源造成的。&lt;/p&gt;

&lt;p&gt;不懂的听上去，挺神奇的，懂的感觉我在扯淡，下面带你好好领略下他们的风采，嗅査下他们的狂骚。。&lt;/p&gt;

&lt;h1 id=&quot;先说事务概念分类&quot;&gt;先说事务–概念，分类&lt;/h1&gt;

&lt;p&gt;用华仔无间道中的一句来给你诠释下：去不了终点，回到原点。&lt;/p&gt;

&lt;p&gt;举例说明：&lt;/p&gt;

&lt;p&gt;在一个事务中，你写啦2条sql语句，一条是修改订单表状态,一条是修改库存表库存-1 。 如果在修改订单表状态的时候出错，事务能够回滚，数据将恢复到没修改之前的数据状态，下面的修改库存也就不执行，这样确保你关系逻辑的一致，安全。。&lt;/p&gt;

&lt;p&gt;事务就是这个样子，倔脾气，要么全部执行，要么全部不执行，回到原数据状态。&lt;/p&gt;

&lt;p&gt;书面解释：事务具有原子性，一致性，隔离性，持久性。&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;原子性：事务必须是一个自动工作的单元，要么全部执行，要么全部不执行。&lt;/li&gt;
  &lt;li&gt;一致性：事务结束的时候，所有的内部数据都是正确的。&lt;/li&gt;
  &lt;li&gt;隔离性：并发多个事务时，各个事务不干涉内部数据，处理的都是另外一个事务处理之前或之后的数据。&lt;/li&gt;
  &lt;li&gt;持久性：事务提交之后，数据是永久性的，不可再回滚。&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;然而在SQL Server中事务被分为3类常见的事务：&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;自动提交事务：是SQL Server默认的一种事务模式，每条Sql语句都被看成一个事务进行处理，你应该没有见过，一条Update 修改2个字段的语句，只修该了1个字段而另外一个字段没有修改。。&lt;/li&gt;
  &lt;li&gt;显式事务：T-sql标明，由Begin Transaction开启事务开始，由Commit Transaction 提交事务、Rollback Transaction 回滚事务结束。&lt;/li&gt;
  &lt;li&gt;隐式事务：使用Set IMPLICIT_TRANSACTIONS ON 将将隐式事务模式打开，不用Begin Transaction开启事务，当一个事务结束，这个模式会自动启用下一个事务，只用Commit Transaction 提交事务、Rollback Transaction 回滚事务即可。&lt;/li&gt;
&lt;/ul&gt;

&lt;h1 id=&quot;显式事务的应用&quot;&gt;显式事务的应用&lt;/h1&gt;

&lt;p&gt;常用语句就四个。&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;Begin Transaction：标记事务开始。&lt;/li&gt;
  &lt;li&gt;Commit Transaction：事务已经成功执行，数据已经处理妥当。&lt;/li&gt;
  &lt;li&gt;Rollback Transaction：数据处理过程中出错，回滚到没有处理之前的数据状态，或回滚到事务内部的保存点。&lt;/li&gt;
  &lt;li&gt;Save Transaction：事务内部设置的保存点，就是事务可以不全部回滚，只回滚到这里，保证事务内部不出错的前提下。&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;上面的都是心法，下面的给你来个招式，要看仔细啦。&lt;/p&gt;

&lt;div class=&quot;highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt; 1 ---开启事务
 2 begin tran
 3 --错误扑捉机制，看好啦，这里也有的。并且可以嵌套。
 4 begin try 
 5    --语句正确
 6    insert into lives (Eat,Play,Numb) values ('猪肉','足球',1)
 7    --Numb为int类型，出错
 8    insert into lives (Eat,Play,Numb) values ('猪肉','足球','abc')
 9    --语句正确
10    insert into lives (Eat,Play,Numb) values ('狗肉','篮球',2) 
11 end try 
12 begin catch 
13    select Error_number() as ErrorNumber,  --错误代码
14           Error_severity() as ErrorSeverity,  --错误严重级别，级别小于10 try catch 捕获不到
15           Error_state() as ErrorState ,  --错误状态码
16           Error_Procedure() as ErrorProcedure , --出现错误的存储过程或触发器的名称。
17           Error_line() as ErrorLine,  --发生错误的行号
18           Error_message() as ErrorMessage  --错误的具体信息
19    if(@@trancount&amp;gt;0) --全局变量@@trancount，事务开启此值+1，他用来判断是有开启事务
20       rollback tran  ---由于出错，这里回滚到开始，第一条语句也没有插入成功。
21 end catch 
22 if(@@trancount&amp;gt;0) 
23 commit tran  --如果成功Lives表中，将会有3条数据。
24 
25 --表本身为空表，ID ,Numb为int 类型，其它为nvarchar类型
26 select * from lives
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;&lt;img src=&quot;https://upload-images.jianshu.io/upload_images/2427856-1bd92d6954ba6d2c.jpg?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240&quot; alt=&quot;1111.jpg&quot; /&gt;&lt;/p&gt;

&lt;div class=&quot;highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;---开启事务
begin tran
--错误扑捉机制，看好啦，这里也有的。并且可以嵌套。
begin try 
--语句正确
   insert into lives (Eat,Play,Numb) values ('猪肉','足球',1) 
--加入保存点
   save tran pigOneIn 
--Numb为int类型，出错
   insert into lives (Eat,Play,Numb) values ('猪肉','足球','hehe') 
--语句正确
   insert into lives (Eat,Play,Numb) values ('狗肉','篮球',3) 
end try 
begin catch 
select Error_number() as ErrorNumber,  --错误代码
          Error_severity() as ErrorSeverity,  --错误严重级别，级别小于10 try catch 捕获不到
          Error_state() as ErrorState ,  --错误状态码
          Error_Procedure() as ErrorProcedure , --出现错误的存储过程或触发器的名称。
          Error_line() as ErrorLine,  --发生错误的行号
          Error_message() as ErrorMessage  --错误的具体信息
   if(@@trancount&amp;gt;0) --全局变量@@trancount，事务开启此值+1，他用来判断是有开启事务
      rollback tran   ---由于出错，这里回滚事务到原点，第一条语句也没有插入成功。
end catch 
if(@@trancount&amp;gt;0) 
rollback tran pigOneIn --如果成功Lives表中，将会有3条数据。

--表本身为空表，ID ,Numb为int 类型，其它为nvarchar类型
select * from lives
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;&lt;img src=&quot;https://upload-images.jianshu.io/upload_images/2427856-b64d4097f5784baa.jpg?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240&quot; alt=&quot;222222.jpg&quot; /&gt;&lt;/p&gt;

&lt;h1 id=&quot;使用set-xact_abort&quot;&gt;使用set xact_abort&lt;/h1&gt;

&lt;p&gt;设置 xact_abort on/off , 指定是否回滚当前事务，为on时如果当前sql出错，回滚整个事务，为off时如果sql出错回滚当前sql语句，其它语句照常运行读写数据库。&lt;/p&gt;

&lt;p&gt; 需要注意的时：xact_abort只对运行时出现的错误有用，如果sql语句存在编译时错误，那么他就失灵啦。&lt;/p&gt;
&lt;div class=&quot;highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;delete lives  --清空数据
set xact_abort off
begin tran 
    --语句正确
   insert into lives (Eat,Play,Numb) values ('猪肉','足球',1) --Numb为int类型，出错,如果1234..那个大数据换成'132dsaf' xact_abort将失效
   insert into lives (Eat,Play,Numb) values ('猪肉','足球',12345646879783213) --语句正确
   insert into lives (Eat,Play,Numb) values ('狗肉','篮球',3) commit tran
select * from lives
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;&lt;img src=&quot;https://upload-images.jianshu.io/upload_images/2427856-086853628f35d91e.jpg?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240&quot; alt=&quot;33333.jpg&quot; /&gt;&lt;/p&gt;

&lt;p&gt;为on时，结果集为空，因为运行是数据过大溢出出错，回滚整个事务。&lt;/p&gt;

&lt;h1 id=&quot;事务把死锁给整出来啦&quot;&gt;事务把死锁给整出来啦&lt;/h1&gt;

&lt;p&gt;跟着做：打开两个查询窗口，把下面的语句，分别放入2个查询窗口，在5秒内运行2个事务模块。&lt;/p&gt;
&lt;div class=&quot;highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;begin tran 
  update lives set play='羽毛球'
  waitfor delay '0:0:5'  
  update dbo.Earth set Animal='老虎' 
commit tran
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;div class=&quot;highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;begin tran 
  update Earth set Animal='老虎' 
  waitfor  delay '0:0:5' --等待5秒执行下面的语句
  update lives set play='羽毛球'
commit tran
select * from lives select * from Earth
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;&lt;img src=&quot;https://upload-images.jianshu.io/upload_images/2427856-3587a7c7ec5e60ee.jpg?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240&quot; alt=&quot;5555.jpg&quot; /&gt;&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;https://upload-images.jianshu.io/upload_images/2427856-2d452ff9d3012f7a.jpg?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240&quot; alt=&quot;6666.jpg&quot; /&gt;&lt;/p&gt;

&lt;p&gt;为什么呢，下面我们看看锁，什么是锁。&lt;/p&gt;

&lt;h1 id=&quot;并发事务成败皆归于锁锁定&quot;&gt;并发事务成败皆归于锁——锁定&lt;/h1&gt;

&lt;p&gt;在多用户都用事务同时访问同一个数据资源的情况下，就会造成以下几种数据错误。&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;更新丢失：多个用户同时对一个数据资源进行更新，必定会产生被覆盖的数据，造成数据读写异常。&lt;/li&gt;
  &lt;li&gt;不可重复读：如果一个用户在一个事务中多次读取一条数据，而另外一个用户则同时更新啦这条数据，造成第一个用户多次读取数据不一致。&lt;/li&gt;
  &lt;li&gt;脏读：第一个事务读取第二个事务正在更新的数据表，如果第二个事务还没有更新完成，那么第一个事务读取的数据将是一半为更新过的，一半还没更新过的数据，这样的数据毫无意义。&lt;/li&gt;
  &lt;li&gt;幻读：第一个事务读取一个结果集后，第二个事务，对这个结果集经行增删操作，然而第一个事务中再次对这个结果集进行查询时，数据发现丢失或新增。&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;然而锁定，就是为解决这些问题所生的，他的存在使得一个事务对他自己的数据块进行操作的时候，而另外一个事务则不能插足这些数据块。这就是所谓的锁定。&lt;/p&gt;

&lt;p&gt;锁定从数据库系统的角度大致可以分为6种：&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;共享锁（S）：还可以叫他读锁。可以并发读取数据，但不能修改数据。也就是说当数据资源上存在共享锁的时候，所有的事务都不能对这个资源进行修改，直到数据读取完成，共享锁释放。&lt;/li&gt;
  &lt;li&gt;排它锁（X）：还可以叫他独占锁、写锁。就是如果你对数据资源进行增删改操作时，不允许其它任何事务操作这块资源，直到排它锁被释放，防止同时对同一资源进行多重操作。&lt;/li&gt;
  &lt;li&gt;更新锁（U）：防止出现死锁的锁模式，两个事务对一个数据资源进行先读取在修改的情况下，使用共享锁和排它锁有时会出现死锁现象，而使用更新锁则可以避免死锁的出现。资源的更新锁一次只能分配给一个事务，如果需要对资源进行修改，更新锁会变成排他锁，否则变为共享锁。&lt;/li&gt;
  &lt;li&gt;意向锁：SQL Server需要在层次结构中的底层资源上（如行，列）获取共享锁，排它锁，更新锁。例如表级放置了意向共享锁，就表示事务要对表的页或行上使用共享锁。在表的某一行上上放置意向锁，可以防止其它事务获取其它不兼容的的锁。意向锁可以提高性能，因为数据引擎不需要检测资源的每一列每一行，就能判断是否可以获取到该资源的兼容锁。意向锁包括三种类型：意向共享锁（IS），意向排他锁（IX），意向排他共享锁（SIX）。&lt;/li&gt;
  &lt;li&gt;架构锁：防止修改表结构时，并发访问的锁。&lt;/li&gt;
  &lt;li&gt;大容量更新锁：允许多个线程将大容量数据并发的插入到同一个表中，在加载的同时，不允许其它进程访问该表。&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;这些锁之间的相互兼容性，也就是，是否可以同时存在。 &lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;https://upload-images.jianshu.io/upload_images/2427856-95542a5464c98e0b.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240&quot; alt=&quot;777.png&quot; /&gt;&lt;/p&gt;

&lt;p&gt;锁兼容性具体参见：http://msdn.microsoft.com/zh-cn/library/ms186396.aspx
锁粒度和层次结构参见：http://msdn.microsoft.com/zh-cn/library/ms189849(v=sql.105).aspx&lt;/p&gt;

&lt;h1 id=&quot;死锁&quot;&gt; 死锁&lt;/h1&gt;

&lt;p&gt;什么是死锁，为什么会产生死锁。我用 “事务把死锁给整出来啦” 标题下的两个事务产生的死锁来解释应该会更加生动形象点。&lt;/p&gt;

&lt;p&gt;例子是这样的：&lt;/p&gt;

&lt;p&gt;第一个事务（称为A）：先更新lives表 —»停顿5秒—-»更新earth表&lt;/p&gt;

&lt;p&gt;第二个事务（称为B）：先更新earth表—»停顿5秒—-»更新lives表&lt;/p&gt;

&lt;p&gt;先执行事务A—-5秒之内—执行事务B，出现死锁现象。&lt;/p&gt;

&lt;p&gt;过程是这样子的：&lt;/p&gt;

&lt;ol&gt;
  &lt;li&gt;A更新lives表，请求lives的排他锁，成功。&lt;/li&gt;
  &lt;li&gt;B更新earth表，请求earth的排他锁，成功。&lt;/li&gt;
  &lt;li&gt;5秒过后&lt;/li&gt;
  &lt;li&gt;A更新earth，请求earth的排它锁，由于B占用着earth的排它锁，等待。&lt;/li&gt;
  &lt;li&gt;B更新lives，请求lives的排它锁，由于A占用着lives的排它锁，等待。&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;这样相互等待对方释放资源，造成资源读写拥挤堵塞的情况，就被称为死锁现象，也叫做阻塞。而为什么会产生，上例就列举出来啦。&lt;/p&gt;

&lt;p&gt;然而数据库并没有出现无限等待的情况，是因为数据库搜索引擎会定期检测这种状况，一旦发现有情况，立马选择一个事务作为牺牲品。牺牲的事务，将会回滚数据。有点像两个人在过独木桥，两个无脑的人都走在啦独木桥中间，如果不落水，必定要有一个人给退回来。这种相互等待的过程，是一种耗时耗资源的现象，所以能避则避。&lt;/p&gt;

&lt;p&gt;哪个人会被退回来，作为牺牲品，这个我们是可以控制的。控制语法：&lt;/p&gt;
&lt;div class=&quot;highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;set deadlock_priority  &amp;lt;级别&amp;gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
&lt;p&gt;死锁处理的优先级别为 low&amp;lt;normal&amp;lt;high，不指定的情况下默认为normal，牺牲品为随机。如果指定，牺牲品为级别低的。&lt;/p&gt;

&lt;p&gt;还可以使用数字来处理标识级别：-10到-5为low，-5为normal，-5到10为high。&lt;/p&gt;

&lt;h1 id=&quot;减少死锁的发生提高数据库性能&quot;&gt;减少死锁的发生，提高数据库性能&lt;/h1&gt;

&lt;p&gt;死锁耗时耗资源，然而在大型数据库中，高并发带来的死锁是不可避免的，所以我们只能让其变的更少。&lt;/p&gt;

&lt;ol&gt;
  &lt;li&gt;按照同一顺序访问数据库资源，上述例子就不会发生死锁啦&lt;/li&gt;
  &lt;li&gt;保持是事务的简短，尽量不要让一个事务处理过于复杂的读写操作。事务过于复杂，占用资源会增多，处理时间增长，容易与其它事务冲突，提升死锁概率。&lt;/li&gt;
  &lt;li&gt;尽量不要在事务中要求用户响应，比如修改新增数据之后在完成整个事务的提交，这样延长事务占用资源的时间，也会提升死锁概率。&lt;/li&gt;
  &lt;li&gt;尽量减少数据库的并发量。&lt;/li&gt;
  &lt;li&gt;尽可能使用分区表，分区视图，把数据放置在不同的磁盘和文件组中，分散访问保存在不同分区的数据，减少因为表中放置锁而造成的其它事务长时间等待。&lt;/li&gt;
  &lt;li&gt;避免占用时间很长并且关系表复杂的数据操作。&lt;/li&gt;
  &lt;li&gt;使用较低的隔离级别，使用较低的隔离级别比使用较高的隔离级别持有共享锁的时间更短。这样就减少了锁争用。&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;可参考：http://msdn.microsoft.com/zh-cn/library/ms191242(v=sql.105).aspx&lt;/p&gt;

&lt;p&gt;查看锁活动情况：&lt;/p&gt;
&lt;div class=&quot;highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;--查看锁活动情况
select * from sys.dm\_tran\_locks --查看事务活动情况
dbcc opentran
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
&lt;p&gt;可参考：http://msdn.microsoft.com/zh-cn/library/ms190345.aspx&lt;/p&gt;

&lt;h1 id=&quot;为事务设置隔离级别&quot;&gt;为事务设置隔离级别&lt;/h1&gt;

&lt;p&gt;所谓事物隔离级别，就是并发事务对同一资源的读取深度层次。分为5种。&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;read uncommitted：这个隔离级别最低啦，可以读取到一个事务正在处理的数据，但事务还未提交，这种级别的读取叫做脏读。&lt;/li&gt;
  &lt;li&gt;read committed：这个级别是默认选项，不能脏读，不能读取事务正在处理没有提交的数据，但能修改。&lt;/li&gt;
  &lt;li&gt;repeatable read：不能读取事务正在处理的数据，也不能修改事务处理数据前的数据。&lt;/li&gt;
  &lt;li&gt;snapshot：指定事务在开始的时候，就获得了已经提交数据的快照，因此当前事务只能看到事务开始之前对数据所做的修改。&lt;/li&gt;
  &lt;li&gt;serializable：最高事务隔离级别，只能看到事务处理之前的数据。
    &lt;div class=&quot;highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;--语法
set tran isolation level &amp;lt;级别&amp;gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;    &lt;/div&gt;
    &lt;p&gt;&lt;strong&gt;read uncommitted隔离级别的例子：&lt;/strong&gt;&lt;/p&gt;
    &lt;div class=&quot;highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;begin tran 
  set deadlock_priority low update Earth set Animal='老虎' 
  waitfor  delay '0:0:5' --等待5秒执行下面的语句
rollback tran
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;    &lt;/div&gt;
    &lt;p&gt;开另外一个查询窗口执行下面语句&lt;/p&gt;
    &lt;div class=&quot;highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;set tran isolation level read uncommitted
select * from Earth  --读取的数据为正在修改的数据 ，脏读
waitfor  delay '0:0:5'  --5秒之后数据已经回滚
select * from Earth  --回滚之后的数据
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;    &lt;/div&gt;
    &lt;p&gt;&lt;img src=&quot;https://upload-images.jianshu.io/upload_images/2427856-9ecb070b056e1010.jpg?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240&quot; alt=&quot;888.jpg&quot; /&gt;&lt;/p&gt;
  &lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;read committed隔离级别的例子：&lt;/strong&gt;&lt;/p&gt;
&lt;div class=&quot;highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;begin tran 
  update Earth set Animal='老虎' 
  waitfor  delay '0:0:10' --等待5秒执行下面的语句
rollback tran
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
&lt;div class=&quot;highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;set tran isolation level read committed
select * from Earth ---获取不到老虎，不能脏读
update Earth set Animal='猴子1'   --可以修改
waitfor  delay '0:0:10'  --10秒之后上一个事务已经回滚
select * from Earth  --修改之后的数据，而不是猴子
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;&lt;img src=&quot;https://upload-images.jianshu.io/upload_images/2427856-8410af4ffceb9103.jpg?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240&quot; alt=&quot;999.jpg&quot; /&gt;&lt;/p&gt;

&lt;p&gt;剩下的几个级别，不一一列举啦，自己理解吧。&lt;/p&gt;

&lt;h1 id=&quot;设置锁超时时间&quot;&gt;设置锁超时时间&lt;/h1&gt;

&lt;p&gt;发生死锁的时候，数据库引擎会自动检测死锁，解决问题，然而这样子是很被动，只能在发生死锁后，等待处理。&lt;/p&gt;

&lt;p&gt;然而我们也可以主动出击，设置锁超时时间，一旦资源被锁定阻塞，超过设置的锁定时间，阻塞语句自动取消，释放资源，报1222错误。&lt;/p&gt;

&lt;p&gt;好东西一般都具有两面性，调优的同时，也有他的不足之处，那就是一旦超过时间，语句取消，释放资源，但是当前报错事务，不会回滚，会造成数据错误，你需要在程序中捕获1222错误，用程序处理当前事务的逻辑，使数据正确。&lt;/p&gt;
&lt;div class=&quot;highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;--查看超时时间,默认为-1
select @@lock_timeout
--设置超时时间
set lock_timeout 0 --为0时，即为一旦发现资源锁定，立即报错，不在等待，当前事务不回滚，设置时间需谨慎处理后事啊，你hold不住的。
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
&lt;p&gt;查看与杀死锁和进程&lt;/p&gt;
&lt;div class=&quot;highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;--检测死锁 --如果发生死锁了，我们怎么去检测具体发生死锁的是哪条SQL语句或存储过程？ --这时我们可以使用以下存储过程来检测，就可以查出引起死锁的进程和SQL语句。SQL Server自带的系统存储过程sp\_who和sp\_lock也可以用来查找阻塞和死锁, 但没有这里介绍的方法好用。 

use master go
create procedure sp\_who\_lock as
begin
declare @spid int,@bl int, @intTransactionCountOnEntry  int, @intRowcount    int, @intCountProperties   int, @intCounter    int

 create table #tmp\_lock\_who (
 id int identity(1,1),
 spid smallint,
 bl smallint) IF @@ERROR&amp;lt;&amp;gt;0 RETURN @@ERROR
 
 insert into #tmp\_lock\_who(spid,bl) select  0 ,blocked from (select * from sysprocesses where  blocked&amp;gt;0 ) a where not exists(select * from (select * from sysprocesses where  blocked&amp;gt;0 ) b where a.blocked=spid) union select spid,blocked from sysprocesses where  blocked&amp;gt;0

 IF @@ERROR&amp;lt;&amp;gt;0 RETURN @@ERROR 
  
-- 找到临时表的记录数
 select  @intCountProperties = Count(*),@intCounter = 1
 from #tmp\_lock\_who IF @@ERROR&amp;lt;&amp;gt;0 RETURN @@ERROR 
 
 if @intCountProperties=0
  select '现在没有阻塞和死锁信息' as message -- 循环开始
while @intCounter &amp;lt;= @intCountProperties
begin
-- 取第一条记录
  select  @spid = spid,@bl = bl from #tmp\_lock\_who where Id = @intCounter 
 begin
  if @spid =0 
            select '引起数据库死锁的是: '+ CAST(@bl AS VARCHAR(10)) + '进程号,其执行的SQL语法如下'
 else
            select '进程号SPID：'+ CAST(@spid AS VARCHAR(10))+ '被' + '进程号SPID：'+ CAST(@bl AS VARCHAR(10)) +'阻塞,其当前进程执行的SQL语法如下'
 DBCC INPUTBUFFER (@bl ) end 

-- 循环指针下移
 set @intCounter = @intCounter + 1
end

drop table #tmp\_lock\_who return 0
end

--杀死锁和进程 --如何去手动的杀死进程和锁？最简单的办法，重新启动服务。但是这里要介绍一个存储过程，通过显式的调用，可以杀死进程和锁。

use master go

if exists (select * from dbo.sysobjects where id = object_id(N'\[dbo\].\[p_killspid\]') and OBJECTPROPERTY(id, N'IsProcedure') = 1) drop procedure \[dbo\].\[p_killspid\]
GO

create proc p_killspid @dbname varchar(200)    --要关闭进程的数据库名
as  
    declare @sql  nvarchar(500) declare @spid nvarchar(20) declare #tb cursor for
        select spid=cast(spid as varchar(20)) from master..sysprocesses where dbid=db_id(@dbname) open #tb fetch next from #tb into @spid
    while @@fetch_status=0
    begin  
        exec('kill '+@spid) fetch next from #tb into @spid
    end  
    close #tb deallocate #tb go

--用法 
exec p_killspid  'newdbpy' 

--查看锁信息 --如何查看系统中所有锁的详细信息？在企业管理管理器中，我们可以看到一些进程和锁的信息，这里介绍另外一种方法。 --查看锁信息
create table #t(req_spid int,obj_name sysname) declare @s nvarchar(4000)
    ,@rid int,@dbname sysname,@id int,@objname sysname declare tb cursor for 
    select distinct req_spid,dbname=db_name(rsc\_dbid),rsc\_objid from master..syslockinfo where rsc_type in(4,5) open tb fetch next from tb into @rid,@dbname,@id
while @@fetch_status=0
begin
    set @s='select @objname=name from \['+@dbname+'\]..sysobjects where id=@id'
    exec sp_executesql @s,N'@objname sysname out,@id int',@objname out,@id
    insert into #t values(@rid,@objname) fetch next from tb into @rid,@dbname,@id
end
close tb deallocate tb select 进程id=a.req_spid
    ,数据库=db_name(rsc_dbid)
    ,类型=case rsc_type when 1 then 'NULL 资源（未使用）'
        when 2 then '数据库'
        when 3 then '文件'
        when 4 then '索引'
        when 5 then '表'
        when 6 then '页'
        when 7 then '键'
        when 8 then '扩展盘区'
        when 9 then 'RID（行 ID)'
        when 10 then '应用程序'
    end ,对象id=rsc_objid
    ,对象名=b.obj_name
    ,rsc_indid from master..syslockinfo a left join #t b on a.req_spid=b.req_spid go
drop table #t
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;仔细阅读，希望能分享给你一点点东西，谢谢，over。&lt;/p&gt;</content><author><name></name></author><summary type="html">转自SQL Server中的事务与锁</summary></entry></feed>