<?xml version="1.0" encoding="UTF-8"?>
<rss xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:atom="http://www.w3.org/2005/Atom" version="2.0"><channel><title><![CDATA[秦小风's博客]]></title><description><![CDATA[匠心独运]]></description><link>http://shavekevin.com/</link><generator>Ghost 0.5</generator><lastBuildDate>Wed, 22 Apr 2026 22:31:00 GMT</lastBuildDate><atom:link href="http://shavekevin.com/rss/" rel="self" type="application/rss+xml"/><ttl>60</ttl><item><title><![CDATA[面试小记]]></title><description><![CDATA[<blockquote>
  <p>最近面试了几个公司，现在把面试问题总结了一下，权当是对工作这几年的记录总结吧，查漏补缺。</p>
</blockquote>

<h2 id="a">面试-A公司</h2>

<h4 id="1">1.面试题</h4>

<ul>
<li>如果要你设计一个复杂页面你会怎么处理？</li>
<li>谈谈你对模块化和组件化的看法。他们有哪些优势和缺点。如何做到解耦。</li>
<li>你认为NSDictionary是怎么实现的？</li>
<li>为什么会产生卡顿,什么是离屏渲染？为什么会产生离屏渲染？</li>
<li>NStimer和cgd的定时器有什么区别，为什么说NSTimer的定时器是不准确的。还有什么方式来实现定时器</li>
<li>iOS开发中都有哪些锁，有什么区别。@synchronized
这个锁里有一个参数self。有什么用，如果它被释放了有什么后果，可不可以是其他对象，为什么？</li>
<li>启动优化有哪些？</li>
<li>如何让你实现一个addobserver你会怎么设计。有哪些需要注意的点(生成的新类是强引用还是弱引用)开源库kvoviewcontroller以及rac的addobserve是如何做到不产生循环引用的</li>
<li>如果任务a依赖于任务b和c的执行，你怎么处理</li>
<li>你对flutter和RN有什么看法</li>
<li>性能优化你做了哪些工作</li>
<li>socket和tcp udp有什么区别，socket原理是什么，socket在app之间传输用的什么。引申到http如何使用长连接 keep-alive的使用。</li>
<li>gcd的底层实现。我们能开辟的最大线程数是多少？默认值是多少？</li>
<li>gcd中的notify和barrier有什么区别</li>
</ul>

<h4 id="2">2.算法题</h4>

<ul>
<li>如何判断两个树是否相等</li>
<li>我们在统计过程中需要知道一个VC上有多少个子view 你怎么处理？用算法实现。</li>
</ul>

<h4 id="3">3.小细节</h4>

<ul>
<li>setobject：forkey key除了用字符串能用对象吗？</li>
</ul>

<h2 id="b">面试题-B公司</h2>

<h4 id="">面试题：</h4>

<ul>
<li>谈谈你对事件响应机制的看法</li>
<li>内存相关
<ul><li>谈谈你对内存管理的看法</li>
<li>arc下能否自己控制对象的生命周期</li>
<li>coreFoundation中我们用__bridge来处理与oc之间的转换为什么呢？结构体中为什么不能使用oc对象，为什么？</li></ul></li>
<li>多线程问题
<ul><li>开发过程中你遇到过哪些线程的问题，你是怎么解决的？</li></ul></li>
<li>锁相关 
<ul><li>iOS开发中有哪些锁？他们有哪些应用场景？他们有什么区别？</li></ul></li>
<li>循环引用问题
<ul><li>循环引用是如何产生的？你是怎么解决的？你是怎么检测循环引用的？如果用第三方工具 请简述其原理</li></ul></li>
<li>性能优化相关
<ul><li>为什么会产生离屏渲染？为什么圆角会产生离屏渲染 怎么检测 所有的UI都会产生离屏渲染吗？为什么？</li>
<li>如何设计出一个复杂的页面？</li>
<li>使用autolayout是否会造成卡顿，为什么？怎么解决？</li>
<li>你是怎么做性能优化的？</li>
<li>项目中单例多吗？单例过多的话你怎么处理？</li>
<li>包瘦身是怎么做的？比如删除无用图片 压缩图片以及删除无用文件
<ul><li>怎么删除无用文件的？怎么判断方法是否使用以及类是否使用    </li></ul></li>
<li>mach-o是干嘛用的？</li>
<li>bitcode是做什么的？</li>
<li>假设你使用的是友盟，友盟是怎么根据我们的崩溃信息定位到崩溃代码位置的？</li></ul></li>
<li>模块化 组件化问题
<ul><li>项目为什么使用模块化 组件化 他们有什么不同，有什么优缺点</li></ul></li>
<li>apns相关
<ul><li>你了解过第三方推送吗?第三方例如极光是怎么实现消息推送的？应用杀死的时候和应用处于后台以及前台的时候，传输方式有什么不同？apns和socket有什么不同？</li></ul></li>
<li>runloop和runtime相关
<ul><li>runloop的启动以及退出方式有哪些？</li>
<li>谈谈你对runtime和runloop的理解。</li></ul></li>
<li>定时器相关
<ul><li>NSTimer为什么是不准确的？你还知道哪些定时器？他们的各自使用场景有哪些？</li></ul></li>
<li>jpg和png有什么区别？   </li>
<li>网络相关
<ul><li>http和https有什么不同 他们是怎么建立起链接的？</li>
<li>tcp 和udp有什么不同</li>
<li>抓包的原理是什么？如何避免自己的app不被别人抓包</li>
<li>谈谈你对socket有什么看法</li>
<li>假如app由登录模式变为游客模式，如何取消登录模式下的请求或任务，你有什么方案？</li></ul></li>
<li>底层原理相关
<ul><li>你有没有看过gcd的源码，谈谈你对gcdglobal的看法</li>
<li>如果让你实现一个kvo你怎么实现？有哪些需要注意的点。第三方开源的KVOController和kvo比有什么优缺点。rac中的addobserver原理是什么？</li></ul></li>
<li>三方库源码相关：
<ul><li>如果要你设计一个网络框架你怎么设计？</li>
<li>如果要你设计一个图片缓存框架你怎么设计？</li></ul></li>
<li>flutter问题
<ul><li>为什么flutter可以hotrelaod 而oc不可以？如果oc要实现hot reload你认为有什么技术难度？</li></ul></li>
<li>java问题
<ul><li>在oc中如何实现java接口的功能</li></ul></li>
</ul>

<h2 id="c">面试题-C公司</h2>

<h4 id="">面试题：</h4>

<ul>
<li>属性关键字相关
<ul><li>你都使用过哪些属性关键字？</li>
<li>atomic实现原理是什么？他是怎么保证安全的？</li>
<li>如果用readonly修饰系统会自动为当前属性生成setter和getter方法吗？</li>
<li>用strong修饰符修饰的有哪些？如果改为copy会有什么隐患？</li></ul></li>
<li>@dynamic @synthesize 有什么区别？他们的应用场景在哪里？(系统会自动为我们生成setter和getter方法吗？)</li>
<li>内存管理相关
<ul><li>谈谈mrc和arc下的copy</li>
<li>mrc和arc混编的时候 如果mrc要调用arc中的block 要怎么处理？
对象什么时候会被释放？如果对象在应该释放的时候没有释放，有哪些可能？</li>
<li>谈谈iOS是怎么实现引用计数机制的？</li>
<li>dealloc方法的执行步骤有哪些？</li></ul></li>
<li>category 和extention 有什么区别？</li>
<li>对代理添加属性的时候，系统会为我们自动生成setter和getter方法吗？</li>
<li>+load和+initialize有什么不同 他们都应用在哪个场景？假如分类和当前类都有load方法那么方法的执行顺序是怎么样的？如果当前类和分类中都有initialize方法执行顺序又是什么样呢？如果有多个分类呢？</li>
<li>假如当前类和分类中有相同方法名的方法，他们的执行顺序是什么样的呢？为什么？如果想要执行方法的时候优先执行当前类的方法你怎么处理？</li>
<li>通过关联对象生成的属性，它是当前类维护还是应用统一来维护？关联对象什么时候被释放？</li>
<li>集合相关深拷贝和浅拷贝以及集合对象的实现原理。假如10000条数据需要存储并且需要查询，你会选用数组还是字典还是集合。为什么？集合和字典的实现方式有什么区别？</li>
<li>生命周期相关 UIViewController的生命周期都会执行哪些方法？从A视图push到B视图的时候 A会执行哪些方法 B会执行哪些方法？</li>
<li>VC上有一个按钮点击按钮原本是要跳转到下一个页面的，但是现在事件不响应了，有哪些原因造成的。</li>
<li>UIView和CALayer有什么区别？他们之间分工不同，符合哪一个设计原则？</li>
<li>循环引用相关
<ul><li>谈谈你对循环引用的理解？哪些场景会产生循环引用</li></ul></li>
<li>数据存储相关 
<ul><li>你都用过哪些数据存储方式？他们各自的应用场景有哪些？他们有什么不同？NSUserDefaults 归档接档 plist  数据库</li></ul></li>
<li>数据库相关
<ul><li>一些简单的数据库查询语句 条件查询等 以及表创建应该怎么写</li>
<li>如果数据库需要更新100w条数据你有什么方法进行更新</li>
<li>FMDB 是怎么实现事务的？谈谈你对事务的理解。</li></ul></li>
<li>网络相关
<ul><li>http请求的cache你是怎么用的？实例：假如我有一个请求，如果服务端数据不变的时候我不再进行UI处理了，这个怎么解决？</li>
<li>关于https的握手和挥手谈谈你的理解。</li></ul></li>
</ul>

<h2 id="d">面试题-D公司</h2>

<h4 id="">面试题：</h4>

<ul>
<li>你是怎么做性能优化的(这个有点宽泛，可以从内存上，启动速度上，检测内存泄漏以及卡顿上来说)你用什么工具来检测的，检测的原理是什么？有没有做一些自动化的检测处理。</li>
<li>你是怎么处理包瘦身的？</li>
<li>如何处理才能让app省电。你是怎么检测到耗电的？有以下场景：有个地方要频繁的去请求，你怎么处理？</li>
<li>卡顿的原因是什么？你怎么去处理和定位卡顿的问题。</li>
<li>如果让你设计一个埋点统计的方案你会怎么处理？</li>
<li>swift和oc相比有什么优缺点。你认为swift比oc打包体积大的原因是什么？</li>
<li>假如现在数据库中有100w条数据。这些数据需要更新现在有200w条数据你怎么能查出相同的和不同的数据。实现更新和插入操作。</li>
<li>iOS13有什么新功能？你是怎么处理刘海屏的适配以及深色模式的。</li>
<li>wwdc你平常看吗？讲一下最新的一些技术。你学习的途径有哪些？</li>
<li>遇到技术问题你是通过哪些途径解决的？</li>
<li>你是怎么做代码管理的？</li>
</ul>

<h3 id="">算法题：</h3>

<p>这是笔试题上的算法的题可供参考，关于OC的一些笔试题参考意义不大。比如：怎么读写文件，写出相关代码。怎么实现图片缩放，写出相关代码。
* 写一个函数计算一个整数的阶乘，如果n非常大，如何处理？
* 求一个字符串最长连续子串，举例，abcdeab，输出结果abcde。</p>

<blockquote>
  <p>关于面试题答案：这些基本都在我<a href='https://github.com/shaveKevin/iOSBasicDemo' >githubdemo</a>里面，这里不再单独给出答案。</p>
</blockquote>

<p>向大家推荐小专栏《秦小风的博客》 <a href='https://xiaozhuanlan.com/shavekevin?rel=2397810208' >https://xiaozhuanlan.com/shavekevin?rel=2397810208</a></p>]]></description><link>http://shavekevin.com/2019/12/05/2019-mian-shi-xiao-ji/</link><guid isPermaLink="false">a14bc002-1240-4da8-9d8a-c8e4d6aff39a</guid><category><![CDATA[面试胸有成竹]]></category><dc:creator><![CDATA[shavekevin]]></dc:creator><pubDate>Thu, 05 Dec 2019 03:32:12 GMT</pubDate></item><item><title><![CDATA[知识星球-iOS基础知识实践(一)]]></title><description><![CDATA[<h3 id="1">1.#基础题#继承之后打印显示问题</h3>

<p>A 类 有方法  </p>

<pre><code>- (id)printClass {return self;}
</code></pre>

<p>B 继承自A  实现  </p>

<pre><code>- (id)printClass {return [super printClass];}
</code></pre>

<p>在B初始化方法里打印以下结果是什么？为什么？</p>

<pre><code>NSLog(@"%@",[self printClass]);  
NSLog(@"%@",[super printClass]);  
</code></pre>

<p>答：答案都是输出B类的对对象。
 这里主要考察的是OC中关于self和super的理解。 一般的我们都知道self是指向的调用这几个方法的这个类的本身。那么super呢。其实super是一个Magic Keyword，
它的本质是一个编译器标示符，和self都指向同一个消息接受者。他们的不同点在于super会告诉编译器，调用class这个方法的时候，要去父类的方法，而不是本类里的。
 当使用self调用方法的时候，会从当前类的方法列表中开始找，如果没有，就从父类再找；而当使用super的时候，则从父类的方法列表中开始找，然后调用父类的这个方法。
 这也就是为什么不推荐在init方法中使用点语法。如果想访问实例变量iVar应该使用下划线(_iVar) 而非点语法(self.ivar)</p>

<h4 id="">总结:</h4>

<ol>
<li><p>当我们在调用<code>[self class]</code>的时候，实际先调用的是<code>objc_msgSend</code>函数，第一个参数是当前<code>B</code>这个实例，然后在<code>B</code>类里面去找<code>-(Class)class;</code> 这个方法,如果没有就去父类(A)里面找,如果也没有那就到了基类NSObject类。而在runtime中对class方法的实现返回的是当前类本身。</p></li>
<li><p>当我们在调用<code>[super class]</code>的时候，会被转换成<code>objc_msgSendSuper</code>函数，第一步先构造objc_super结构体，结构体第一个成员就是self，第二个成员就是 <br />
<code>(id)class_getSuperclass(objc_getClass("B"))</code>实际上这个时候返回的是SKObjectObject 第二步是从A类中去找<code>-(Class)class;</code>
没有，然后去NSObject中去找，最后内部是使用<code>objc_msgSend(objc_super receiver</code>, <code>@selector(class))</code>去调用。 这个时候的调用和<code>[self class ]</code>基本一样。所以结果返回仍然是<code>B</code>。</p></li>
</ol>

<h4 id="">参考链接：</h4>

<p><a href='https://blog.csdn.net/u011344883/article/details/41512683' >刨根问底Objective－C Runtime（1）－ Self &amp; Super</a></p>

<h3 id="2runloop">2.#基础题#runloop和线程有什么关系？</h3>

<p>总的来说，RunLoop正如其名，loop表示一种循环，和run放在一起就表示一直运行着的循环。实际上，runloop和线程是紧密相连的。
  可以这样说runloop是为了线程而生，没有线程，它就没有存在的必要。run loops是线程的基础架构部分，cocoa和corefundation都提供了run loop对象
  方便配置和管理线程run loop。 每个线程，包括程序的主线程 多有与之相对应的run loop 对象。</p>

<h4 id="runloop">runloop和线程的关系</h4>

<p>1.主线程的runloop默认是启动的。
  在iOS 的应用程序里面，程序启动后会有一个如下的main函数</p>

<pre><code> int main(int argc, char * argv[]) {
      @autoreleasepool {
          return UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class]));
      }
  }
</code></pre>

<p>重点是这个UIApplicationMain()函数，这个方法会为main thread 设置一个NSRunLoop对象，这就解释了：为什么我们可以应用在无人操作的时候休息。需要它干活的时候又能马上响应。</p>

<p>2.对于其他线程来说，runloop默认是没有启动的。如果你需要更多的线程交互则可以手动配置和启动。如果线程只是去执行长时间的已确定的任务则不需要。</p>

<p>3.在任何一个cocoa程序的线程中，都可以通过下面的代码来获取当前线程的runloop
   <code>NSRunLoop *currentLoop = [NSRunLoop currentRunLoop];</code></p>

<h4 id="">参考链接：</h4>

<p><a href='https://blog.csdn.net/wzzvictory/article/details/9237973' >Objective-C之run loop详解</a></p>

<h3 id="3nstimer">3.#基础题# NSTimer会不会产生循环引用？如果产生了怎么解决？</h3>

<p>答：会产生循环引用。产生循环引用的链条是： 对象强引用<code>timer</code>  <code>timer</code> 强引用对象。。 并且runloop也引用了<code>timer</code>。 如果我们按照正常的像block一样解除循环引用的方法是行不通的。因为虽然对象对timer的引用为弱引用，但是timer对对象的引用却还是强引用。
解决方法：参考yykit中的YYWeakProxy。它的原理是：对象-> timer -> proxy(这里的proxy继承自NSProxy)-> (消息转发)target -> 对象。
这个时候target为弱引用。如果对象的引用计数为0了，那么target就会被置为nil。这样就打破循环引用了。</p>

<p>可参考链接：<a href='https://juejin.im/post/5a30f86ef265da4325294b3b' >YYKit--YYWeakProxy</a></p>

<p>还有一种解决方法是：在vc中使用视图将要消失的时候 停掉定时器 并且置为nil。（在停掉定时器的时候，runloop也被移除）
 还有另外一种方式是 不用target 使用block的初始化方式。 这种缺点是iOS10+并且由于是block所以要处理block循环引用的问题。(注意:这样写不保险，首先不能确定这里消失的时机 是否要停掉定时器.)</p>

<pre><code> if (@available(iOS 10.0, *)) {
        self.schemeTimer = [NSTimer timerWithTimeInterval:1.0f repeats:YES block:^(NSTimer * _Nonnull timer) {
        }];
    } else {
        // Fallback on earlier versions
    }
</code></pre>

<ol>
<li><p>如果NSTimer当前处于NSDefaultRunLoopMode中，此时界面上有滚动事件发生，则RunLoop会瞬间切换至UITrackingRunLoopMode模式。这意味着NSTimer会被暂停调用响应方法，直至滚动事件被处理完，当滚动事件被处理完后，RunLoop又会被瞬间切换至NSDefaultRunLoopMode模式，此时线程会查看该mode下是否有相应事件等待处理，有则继续，没有的话，runloop会进入休眠状态，直至被事件再次唤醒</p></li>
<li><p>占位mode：NSRunLoopCommonModes，它包含以上两种mode，处于该mode下的事件会在两种mode下都有效。也即mode切换过程中不会中断事件的处理。</p></li>
<li><p>（如果想让NSTimer同时在两种mode下都有效，该怎么办呢？ <br />
答：要么将该NSTimer单独添加至两种mode下，要么一次性加入两种mode下NSRunLoopCommonModes。</p></li>
<li><p>Mode应用场景：轮播时，如果想拖拽查看某一页时暂停定时器；上下滑动UITableView时，不影响分页滚动界面的自动滚动。</p>

<p>正常情况下，轮播时，定时器会自动暂停等待，不需要额外的暂停操作，但是当拖拽事件结束后，会发现恢复自动滚动瞬间，滚动的比较快，影响体验；所以正确的做法是先invalidate &amp; nil,拖拽结束后再重新创建NSTimer。</p>

<h3 id="4nstimer">4.#基础题#  使用NSTimer有什么缺点？你还知道哪些可以实现定时器？</h3>

<p>答：定时器的缺点：</p>

<ol><li>精确度不够，由于定时器在一个runloop中被检测一次，所以在这一次的runloop中如果做了耗时的操作，当前runloop持续的时间超过了定时器的间隔时间，那么下次执行的时间就会被延后。这就导致了精度的问题。解决方法:可以在子线程处理数据，然后在主线程进行打印或者做其他与UI相关的处理。</li>
<li>runloop模式的影响，由于runloop有在scrollview滑动的时候有另外一种模式 如果设置runloop模式的时候设置成default模式 那么在scrollview滚动的时候当前定时器会暂停执行。直到滑动结束runloop模式切换为default的时候才会恢复。</li>
<li>循环引用。具体原因见： <a href='https://t.zsxq.com/mEMZrfU' >https://t.zsxq.com/mEMZrfU</a>  或者本文第三题</li></ol>

<p>实现方式：</p></li>
</ol>

<p>通过以下三种方式也可以实现NSTimer <code>mach_absolute_time()</code> <code>CADisplayLink</code> 以及GCD的<code>dispatch_source_t</code></p>

<ol>
<li><p>使用mach<em>absolute</em>time()来实现更高精度的定时器。iPhone上有这么一个均匀变化的东西来提供给我们作为时间参考，就是CPU的时钟周期数（ticks）。 通过mach<em>absolute</em>time()获取CPU已运行的tick数量。将tick数经过转换变成秒或者纳秒，从而实现时间的计算。见matchAbsoluteTime。  理论上来说这个是最精准的定时器->纳秒级别的定时器。</p>

<ol><li><p>CADisplayLink是一个频率能达到屏幕刷新率的定时器类。iPhone屏幕刷新频率为60帧/秒，也就是说最小间隔可以达到1/60s。 所以一般来说这个设置的时候是以1s为基准的。CADisplayLink的使用也需要手动加到runloop中，使用的时候仍然需要注意循环引用。</p></li>
<li><p>GCD实现使用了dispatch<em>source</em>t 来实现，使用的时候注意要创建、配置、开启timer。由于这里调用的时候用的是block 所以要注意block产生的循环引用问题。</p></li></ol></li>
</ol>

<h4 id="cadisplaylinknstimer">CADisplayLink 和NSTimer 的不同之处：</h4>

<ol>
<li><p>原理不同 CADisplayLink以特定模式注册到runloop后，每当屏幕显示内容刷新结束的时候，runloop就会向CADisplayLink指定的target发送一次指定的selector消息， CADisplayLink类对应的selector就会被调用一次。
NSTimer以指定的模式注册到runloop后，每当设定的周期时间到达后，runloop会向指定的target发送一次指定的selector消息。</p></li>
<li><p>精确度不同
NSTimer的精确度就显得低了点，比如NSTimer的触发时间到的时候，runloop如果在忙于别的调用，触发时间就会推迟到下一个runloop周期。更有甚者，在OS X v10.9以后为了尽量避免在NSTimer触发时间到了而去中断当前处理的任务，NSTimer新增了tolerance属性，让用户可以设置可以容忍的触发的时间范围。</p></li>
<li><p>使用场合不同
CADisplayLink使用场合相对专一，适合做界面的不停重绘，比如视频播放的时候需要不停地获取下一帧用于界面渲染。
NSTimer的使用范围要广泛的多，各种需要单次或者循环定时处理的任务都可以使用。</p>

<p>总结：一般来说NSTimer已经足够用，如果想要精度高一点的可以使用gcd的或者CADisplayLink，但是CADisplayLink 有限制。刷新频率根据系统的屏幕刷新频率，这也限制了使用的场景。mach<em>absolute</em>time 这个精确度更高一点一般用于计算函数的执行时间。</p>

<p>参考链接：<a href='https://blog.csdn.net/luolianxi/article/details/78618549' >NSTimer到底准不准?</a></p>

<p>编辑/整理：小风</p></li>
</ol>

<h2 id="">说明：</h2>

<p>这是一个根据面试题总结的答案，并非全部原创。
<a href='https://github.com/shaveKevin/iOSInterViewDemo' >工程代码在这里</a> 
这里更新会落后于星球，星球内容存活时间为一年。星球收费是为了设置门槛，如果想要免费看最新的内容请联系微信：shavekevin001 邀请入星球。(加的时候请备注星球来的)
<img src='https://tvax1.sinaimg.cn/large/a5a05ef3gy1g7ynnnga3zj20j60puq66.jpg'  alt="WechatIMG7" /></p>]]></description><link>http://shavekevin.com/2019/10/15/iosbase-partone/</link><guid isPermaLink="false">c11b2d75-c621-4f01-b037-9c572bb058b6</guid><category><![CDATA[iOS基础知识]]></category><dc:creator><![CDATA[shavekevin]]></dc:creator><pubDate>Tue, 15 Oct 2019 02:04:55 GMT</pubDate></item><item><title><![CDATA[一次方法执行引起的"血案"]]></title><description><![CDATA[<h2 id="performselector">最近在研究函数调用performSelector的时候遇到下面的问题：</h2>

<p><img src='https://tvax2.sinaimg.cn/large/006mQyr2gy1g7gi70i6ovj30zb09cq4e.jpg'  alt="WechatIMG13" /></p>

<ul>
<li>有参数为返回值</li>
</ul>

<pre><code>  id perfromSele  = [self performSelector:@selector(performSelectorS:arg2:) withObject:@"第一个参数" withObject:@"第二个参数"];
    if (perfromSele) {
        NSLog(@"打印结果为===%@",perfromSele);
    } else {
        NSLog(@"没有返回值");
    }

    - (void )performSelectorS:(NSString *)args1 arg2:(NSString *)args2 {
    NSLog(@"performSelectorS args1 %@   args2 %@",args1,args2);
}
</code></pre>

<ul>
<li>无参数无返回值</li>
</ul>

<p><img src='https://tvax1.sinaimg.cn/large/006mQyr2gy1g7gi7olkrij30qz08aab7.jpg'  alt="WechatIMG721" /></p>

<pre><code> id returnValue = [self performSelector:@selector(testReturnValueMethod)];
    if (returnValue) {
        NSLog(@"returnValue===%@",returnValue);
    } else {
        NSLog(@"returnValue ==没有返回值");
    }
    - (void)testReturnValueMethod { 
    // 这个时候打开注释会闪退
    //    NSLog(@"this is method");

    }
</code></pre>

<p>神奇的一幕发生了：有参数有返回值的时候。当传入第一个参数为字符串的时候，返回值perfromSele 为<code>__NSCFConstantString</code> 当第一个参数不为string 的时候，返回值为0x0 也就是说没有返回值。无参数无返回值的时候，如果执行的方法里有代码用返回值接收。会闪退。如果执行方法里什么都不写，那么正常执行返回值为0x0。</p>

<p>一位搞逆向的朋友告诉我说:这是汇编的问题,他是这样解释的：</p>

<pre><code>(汇编问题)原因应该是performSelector的returnvalue为0x0， 当执行方法里面为空的时候，没有改变寄存器的地址。所以0x0在PerformSelector的时候地址还为0x0.如果方法里面调用堆栈的时候，最后导致0x0不能被access所以就报bad access错误了。
</code></pre>]]></description><link>http://shavekevin.com/2019/09/29/performselector-question/</link><guid isPermaLink="false">3f4c143e-e80c-4665-9773-cb048d9d920a</guid><category><![CDATA[iOS基础API]]></category><dc:creator><![CDATA[shavekevin]]></dc:creator><pubDate>Sun, 29 Sep 2019 09:16:37 GMT</pubDate></item><item><title><![CDATA[Flutter学习资源整理]]></title><description><![CDATA[<ol>
<li><p><a href='https://zhuanlan.zhihu.com/p/43631849' >Flutter快速上车之Widget</a>  </p></li>
<li><p><a href='https://segmentfault.com/a/1190000017164263' >【开发经验】浅谈flutter的优点</a></p></li>
<li><p><a href='https://www.jianshu.com/p/ece7d6dbe108' >Flutter Widgets Elements RenderObjects的一点理解</a></p></li>
<li><p><a href='https://segmentfault.com/a/1190000015211309' >flutter中的生命周期</a> </p></li>
<li><p><a href='https://segmentfault.com/a/1190000015150843' >Flutter跳转问题</a></p></li>
<li><p><a href='https://blog.csdn.net/ww897532167/article/details/80374371' >Flutter ListView以及GridView的列表展示与Item点击事件</a></p></li>
<li><p><a href='https://github.com/zhujian1989/flutter_study' >flutter知识点学习与实战，问题解决方案记录</a></p></li>
<li><p><a href='https://dartpad.dartlang.org/' >dart 语法练习</a></p></li>
<li><p><a href='https://github.com/AweiLoveAndroid/Flutter-learning/blob/master/readme/Dart%E8%AF%AD%E6%B3%95.md' >dart 语法</a></p></li>
<li><p><a href='https://flutterchina.club/' >Flutter官网学习资源一</a></p></li>
<li><p><a href='https://book.flutterchina.club/' >Flutter官网学习电子书</a></p></li>
<li><p><a href='https://www.jianshu.com/p/388d986c0f48' >Dart API 学习记录系列</a></p></li>
<li><p><a href='https://github.com/SmallStoneSK/flutter_training_app' >Flutter练习APP</a></p></li>
<li><p><a href='https://juejin.im/post/5bf65a776fb9a049ba41359c' >如果做自己的flutter组件</a></p></li>
<li><p><a href='https://juejin.im/post/5bb033515188255c5e66f500' >iOS Native混编Flutter交互实践</a></p></li>
<li><p><a href='https://github.com/Solido/awesome-flutter' >Flutter的学习资料集合</a></p></li>
<li><p><a href='https://github.com/yang7229693/flutter-study' >Flutter学习文章整理</a></p></li>
<li><p><a href='https://juejin.im/post/5c8f8e62e51d456a0f23d0fe' >系统全面学习flutter  作者：来自阿里</a></p></li>
</ol>]]></description><link>http://shavekevin.com/2019/07/31/flutter-learning-source/</link><guid isPermaLink="false">85aa09f2-7748-4aa5-9692-7988c7c3bd40</guid><category><![CDATA[Flutter]]></category><dc:creator><![CDATA[shavekevin]]></dc:creator><pubDate>Wed, 31 Jul 2019 08:39:48 GMT</pubDate></item><item><title><![CDATA[Flutter开发中遇到的问题(1)]]></title><description><![CDATA[<h3 id="1">问题1：</h3>

<p>项目地址在:<a href='https://github.com/shaveKevin/FlutterLearningSet' >github</a></p>

<p>在使用<code>FloatingActionButton</code>这个Widget的时候，在使用的时候把<code>FloatingActionButton</code>控件放置到 home所在的page中(也就是iOS中的ViewController 安卓中的Activity)。这个时候的设置是这个样子的：</p>

<pre><code> //渲染多个浮动按钮
  Widget renderFloatingActionButton() {
    // 一组按钮ButtonBar
    return ButtonBar(
      // 按钮的在水平方向上的对齐方式。
      // spaceAround 分散对齐
      alignment: MainAxisAlignment.spaceAround,
      // 当前组中的组员
      children: &lt;Widget&gt;[
        // 第一个浮动按钮
        FloatingActionButton(
          //点击事件
          onPressed: () {
            choosePic(ImageSource.camera);
          },
          //这只是一个标识长按文字提示
          tooltip: 'photo_camera',
          // 按钮的设置
          child: Icon(Icons.photo_camera),
        ),
        // 第二个浮动按钮
        FloatingActionButton(
          onPressed: () {
            choosePic(ImageSource.gallery);
          },
          tooltip: 'photo_library',
          child: Icon(Icons.photo_library),
        )
      ],
    );
  }
</code></pre>

<p>看起来没有什么问题运行起来也没有用什么问题。
但是。。当我把自己练习的demo放在一起的时候，这个页面的上一层是一个item，点击item跳转到当前page。。问题终于出现了。。。。</p>

<p>问题描述：点击item跳转到另外一个页面，另外一个页面底部有两个悬浮的button，点击之后页面黑屏，去掉一个按钮之后，页面正常显示。黑屏的时候控制台报错如下：</p>

<p>报错讯息可能有点长，可以忽略一大部分，这里给出一些能提示你找问题的地方(如果要看详细的报错信息请在页面最后查看)：</p>

<pre><code>The following assertion was thrown during a scheduler callback:  
There are multiple heroes that share the same tag within a subtree.  
Within each subtree for which heroes are to be animated (i.e. a PageRoute subtree), each Hero must have a unique non-null tag.  
In this case, multiple heroes had the following tag: &lt;default FloatingActionButton tag&gt;  
</code></pre>

<p>大意是：一个子树只能拥有一个tag值。不同子树的tag值不能相同，而你现在的路径中有不止一个子树，这些子树都用了一个默认的tag值。这样就报错了。
知道问题描述了，下面开始追踪到底是哪里出了问题：</p>

<pre><code>Hero(tag: &lt;default FloatingActionButton tag&gt;, state: _HeroState#7f098)  
...
 └Tooltip("photo_library", position: below, dependencies: [TickerMode], state:_TooltipState#232f8
</code></pre>

<p>OK，这里定位到了<code>FloatingActionButton</code> 中的<code>Tooltip("photo_library"）</code> 也就是我们所写的控件一。 <br />
既然说这里用的是默认tag，那么我们更改下tag值问题应该就不存在了吧。接着，我们来看
<code>FloatingActionButton</code>给我们提供了哪些属性：</p>

<pre><code>  FloatingActionButton.extended({
    Key key,
    this.tooltip,
    this.foregroundColor,
    this.backgroundColor,
    this.focusColor,
    this.hoverColor,
    this.heroTag = const _DefaultHeroTag(),
    this.elevation,
    this.focusElevation,
    this.hoverElevation,
    this.highlightElevation,
    this.disabledElevation,
    @required this.onPressed,
    this.shape,
    this.isExtended = true,
    this.materialTapTargetSize,
    this.clipBehavior = Clip.none,
    this.focusNode,
    Widget icon,
    @required Widget label,
  })
</code></pre>

<p>我们看到一个属性<code>heroTag</code>而他返回了一个默认值;
我们来看一下这个<code>heroTag</code>以及它的默认值:</p>

<pre><code>/// If this is not explicitly set, then there can only be one
  /// [FloatingActionButton] per route (that is, per screen), since otherwise
  /// there would be a tag conflict (multiple heroes on one route can't have the
  /// same tag). The material design specification recommends only using one
  /// floating action button per screen.
</code></pre>

<p>默认值：</p>

<pre><code>class _DefaultHeroTag {  
  const _DefaultHeroTag();
  @override
  String toString() =&gt; '&lt;default FloatingActionButton tag&gt;';
}
</code></pre>

<p>根据<code>heroTag</code>的定义我们可以知道，在一个页面中，只能存在一个相同的tag值，多个heroes不能使用相同的tag值。而我们的<code>floating action button</code>就是一个<code>hero</code>，并且一个页面出现了两次，如果不设置<code>heroTag</code>就会产生冲突。我们对这个两个<code>FloatingActionButton</code>添加<code>heroTag</code>之后，冲突就解决了。(这个herotag主要是方便做动画效果，具体可参考hero动画)</p>

<p>错误描述全部：</p>

<pre><code>flutter: ══╡ EXCEPTION CAUGHT BY SCHEDULER LIBRARY ╞═════════════════════════════════════════════════════════  
flutter: The following assertion was thrown during a scheduler callback:  
flutter: There are multiple heroes that share the same tag within a subtree.  
flutter: Within each subtree for which heroes are to be animated (i.e. a PageRoute subtree), each Hero must  
flutter: have a unique non-null tag.  
flutter: In this case, multiple heroes had the following tag: &lt;default FloatingActionButton tag&gt;  
flutter: Here is the subtree for one of the offending heroes:  
flutter: # Hero(tag: &lt;default FloatingActionButton tag&gt;, state: _HeroState#7f098)  
flutter: # └SizedBox(renderObject: RenderConstrainedBox#0b125 relayoutBoundary=up8 NEEDS-PAINT)  
flutter: #  └Offstage(offstage: false, renderObject: RenderOffstage#81335 relayoutBoundary=up9 NEEDS-PAINT)  
flutter: #   └TickerMode(mode: enabled)  
flutter: #    └KeyedSubtree-[GlobalKey#9c6ce]  
flutter: #     └Tooltip("photo_library", position: below, dependencies: [TickerMode], state:  
flutter: _TooltipState#232f8(ticker inactive))  
flutter: #      └GestureDetector(startBehavior: start)  
flutter: #       └RawGestureDetector(state: RawGestureDetectorState#58fc4(gestures: [long press], behavior:  
flutter: opaque))  
flutter: #        └Listener(listeners: [down], behavior: opaque, renderObject: RenderPointerListener#24b9a  
flutter: relayoutBoundary=up10 NEEDS-PAINT)  
flutter: #         └Semantics(container: false, properties: SemanticsProperties, label: "photo_library",  
flutter: value: null, hint: null, hintOverrides: null, dependencies: [Directionality], renderObject:  
flutter: RenderSemanticsAnnotations#c6cc8 relayoutBoundary=up11 NEEDS-PAINT)  
flutter: #          └RawMaterialButton(state: _RawMaterialButtonState#681f5)  
flutter: #           └Semantics(container: true, properties: SemanticsProperties, label: null, value: null,  
flutter: hint: null, hintOverrides: null, renderObject: RenderSemanticsAnnotations#86632  
flutter: relayoutBoundary=up12 NEEDS-PAINT)  
flutter: #            └_InputPadding(renderObject: _RenderInputPadding#90535 relayoutBoundary=up13  
flutter: NEEDS-PAINT)  
flutter: #             └Focus(dependencies: [_FocusMarker], state: _FocusState#ee3c4)  
flutter: #              └_FocusMarker  
flutter: #               └ConstrainedBox(BoxConstraints(w=56.0, h=56.0), renderObject:  
flutter: RenderConstrainedBox#c2ddc relayoutBoundary=up14 NEEDS-PAINT)  
flutter: #                └Material(type: button, elevation: 6.0, color: Color(0xff2196f3),  
flutter: textStyle.debugLabel: (((englishLike button 2014).merge(whiteCupertino button)).copyWith).copyWith,  
flutter: textStyle.inherit: false, textStyle.color: Color(0xffffffff), textStyle.family: .SF UI Text,  
flutter: textStyle.size: 14.0, textStyle.weight: 500, textStyle.letterSpacing: 1.2, textStyle.baseline:  
flutter: alphabetic, textStyle.decoration: TextDecoration.none, shape:  
flutter: CircleBorder(BorderSide(Color(0xff000000), 0.0, BorderStyle.none)), dependencies: [TickerMode],  
flutter: state: _MaterialState#9380e)  
flutter: #                 └_MaterialInterior(duration: 200ms, shape:  
flutter: CircleBorder(BorderSide(Color(0xff000000), 0.0, BorderStyle.none)), elevation: 6.0, color:  
flutter: Color(0xff2196f3), shadowColor: Color(0xff000000), dependencies: [TickerMode, Directionality],  
flutter: state: _MaterialInteriorState#374a9(ticker inactive))  
flutter: #                  └PhysicalShape(clipper: ShapeBorderClipper, elevation: 6.0, color:  
flutter: Color(0xff2196f3), shadowColor: Color(0xff000000), renderObject: RenderPhysicalShape#81092  
flutter: NEEDS-PAINT)  
flutter: #                   └_ShapeBorderPaint(dependencies: [Directionality])  
flutter: #                    └CustomPaint(renderObject: RenderCustomPaint#79891 NEEDS-PAINT)  
flutter: #                     └NotificationListener&lt;LayoutChangedNotification&gt;  
flutter: #                      └_InkFeatures-[GlobalKey#4fbd0 ink renderer](renderObject:  
flutter: _RenderInkFeatures#a166b NEEDS-PAINT)  
flutter: #                       └AnimatedDefaultTextStyle(duration: 200ms, debugLabel: (((englishLike button  
flutter: 2014).merge(whiteCupertino button)).copyWith).copyWith, inherit: false, color: Color(0xffffffff),  
flutter: family: .SF UI Text, size: 14.0, weight: 500, letterSpacing: 1.2, baseline: alphabetic, decoration:  
flutter: TextDecoration.none, softWrap: wrapping at box width, overflow: clip, dependencies: [TickerMode],  
flutter: state: _AnimatedDefaultTextStyleState#0aef0(ticker inactive))  
flutter: #                        └DefaultTextStyle(debugLabel: (((englishLike button  
flutter: 2014).merge(whiteCupertino button)).copyWith).copyWith, inherit: false, color: Color(0xffffffff),  
flutter: family: .SF UI Text, size: 14.0, weight: 500, letterSpacing: 1.2, baseline: alphabetic, decoration:  
flutter: TextDecoration.none, softWrap: wrapping at box width, overflow: clip)  
flutter: #                         └InkWell(gestures: [tap], clipped to BoxShape.rectangle, dependencies:  
flutter: [_FocusMarker], state: _InkResponseState&lt;InkResponse&gt;#527c4)  
flutter: #                          └Listener(listeners: [enter, exit], behavior: translucent, renderObject:  
flutter: RenderPointerListener#e73bd NEEDS-PAINT)  
flutter: #                           └GestureDetector(startBehavior: start)  
flutter: #                            └RawGestureDetector(state: RawGestureDetectorState#14fe4(gestures:  
flutter: [tap], behavior: opaque))  
flutter: #                             └_GestureSemantics(renderObject: RenderSemanticsGestureHandler#18230  
flutter: NEEDS-PAINT)  
flutter: #                              └Listener(listeners: [down], behavior: opaque, renderObject:  
flutter: RenderPointerListener#0c4e6 NEEDS-PAINT)  
flutter: #                               └Builder(dependencies: [IconTheme])  
flutter: #                                └IconTheme(IconThemeData#15fa8(color: Color(0xffffffff)))  
flutter: #                                 └Container(padding: EdgeInsets.zero)  
flutter: #                                  └Padding(padding: EdgeInsets.zero, dependencies:  
flutter: [Directionality], renderObject: RenderPadding#48145 NEEDS-PAINT)  
flutter: #                                   └Center(alignment: center, widthFactor: 1.0, heightFactor: 1.0,  
flutter: dependencies: [Directionality], renderObject: RenderPositionedBox#bd5b3 NEEDS-PAINT)  
flutter: #                                    └Builder(dependencies: [IconTheme])  
flutter: #                                     └IconTheme(IconThemeData#15fa8(color: Color(0xffffffff)))  
flutter: #                                      └Icon(IconData(U+0E413), dependencies: [Directionality,  
flutter: IconTheme])  
flutter: #                                       └Semantics(container: false, properties:  
flutter: SemanticsProperties, label: null, value: null, hint: null, hintOverrides: null, renderObject:  
flutter: RenderSemanticsAnnotations#9a63c relayoutBoundary=up1 NEEDS-PAINT)  
flutter: #                                        └ExcludeSemantics(excluding: true, renderObject:  
flutter: RenderExcludeSemantics#b80fe relayoutBoundary=up2 NEEDS-PAINT)  
flutter: #                                         └SizedBox(width: 24.0, height: 24.0, renderObject:  
flutter: RenderConstrainedBox#b6771 relayoutBoundary=up3 NEEDS-PAINT)  
flutter: #                                          └Center(alignment: center, dependencies:  
flutter: [Directionality], renderObject: RenderPositionedBox#d5b8f NEEDS-PAINT)  
flutter: #                                           └RichText(textDirection: ltr, softWrap: wrapping at box  
flutter: width, overflow: visible, maxLines: unlimited, text: "", dependencies:  
flutter: [_LocalizationsScope-[GlobalKey#955e1]], renderObject: RenderParagraph#df683 relayoutBoundary=up1  
flutter: NEEDS-PAINT)  
flutter:  
flutter:  
flutter: When the exception was thrown, this was the stack:  
flutter: #0      Hero._allHeroesFor.addHero.&lt;anonymous closure&gt; (package:flutter/src/widgets/heroes.dart:265:11)  
flutter: #1      Hero._allHeroesFor.addHero (package:flutter/src/widgets/heroes.dart:275:8)  
flutter: #2      Hero._allHeroesFor.visitor (package:flutter/src/widgets/heroes.dart:288:20)  
flutter: #3      SingleChildRenderObjectElement.visitChildren (package:flutter/src/widgets/framework.dart:5080:14)  
flutter: #4      Hero._allHeroesFor.visitor (package:flutter/src/widgets/heroes.dart:302:15)  
flutter: #5      ComponentElement.visitChildren (package:flutter/src/widgets/framework.dart:3955:14)  
flutter: #6      Hero._allHeroesFor.visitor (package:flutter/src/widgets/heroes.dart:302:15)  
flutter: #7      SingleChildRenderObjectElement.visitChildren (package:flutter/src/widgets/framework.dart:5080:14)  
flutter: #8      Hero._allHeroesFor.visitor (package:flutter/src/widgets/heroes.dart:302:15)  
flutter: #9      MultiChildRenderObjectElement.visitChildren (package:flutter/src/widgets/framework.dart:5181:16)  
flutter: #10     Hero._allHeroesFor.visitor (package:flutter/src/widgets/heroes.dart:302:15)  
flutter: #11     SingleChildRenderObjectElement.visitChildren (package:flutter/src/widgets/framework.dart:5080:14)  
flutter: #12     Hero._allHeroesFor.visitor (package:flutter/src/widgets/heroes.dart:302:15)  
flutter: #13     ComponentElement.visitChildren (package:flutter/src/widgets/framework.dart:3955:14)  
flutter: #14     Hero._allHeroesFor.visitor (package:flutter/src/widgets/heroes.dart:302:15)  
flutter: #15     SingleChildRenderObjectElement.visitChildren (package:flutter/src/widgets/framework.dart:5080:14)  
flutter: #16     Hero._allHeroesFor.visitor (package:flutter/src/widgets/heroes.dart:302:15)  
flutter: #17     ComponentElement.visitChildren (package:flutter/src/widgets/framework.dart:3955:14)  
flutter: #18     Hero._allHeroesFor.visitor (package:flutter/src/widgets/heroes.dart:302:15)  
flutter: #19     SingleChildRenderObjectElement.visitChildren (package:flutter/src/widgets/framework.dart:5080:14)  
flutter: #20     Hero._allHeroesFor.visitor (package:flutter/src/widgets/heroes.dart:302:15)  
flutter: #21     ComponentElement.visitChildren (package:flutter/src/widgets/framework.dart:3955:14)  
flutter: #22     Hero._allHeroesFor.visitor (package:flutter/src/widgets/heroes.dart:302:15)  
flutter: #23     MultiChildRenderObjectElement.visitChildren (package:flutter/src/widgets/framework.dart:5181:16)  
flutter: #24     Hero._allHeroesFor.visitor (package:flutter/src/widgets/heroes.dart:302:15)  
flutter: #25     ComponentElement.visitChildren (package:flutter/src/widgets/framework.dart:3955:14)  
flutter: #26     Hero._allHeroesFor.visitor (package:flutter/src/widgets/heroes.dart:302:15)  
flutter: #27     ComponentElement.visitChildren (package:flutter/src/widgets/framework.dart:3955:14)  
flutter: #28     Hero._allHeroesFor.visitor (package:flutter/src/widgets/heroes.dart:302:15)  
flutter: #29     ComponentElement.visitChildren (package:flutter/src/widgets/framework.dart:3955:14)  
flutter: #30     Hero._allHeroesFor.visitor (package:flutter/src/widgets/heroes.dart:302:15)  
flutter: #31     MultiChildRenderObjectElement.visitChildren (package:flutter/src/widgets/framework.dart:5181:16)  
flutter: #32     Hero._allHeroesFor.visitor (package:flutter/src/widgets/heroes.dart:302:15)  
flutter: #33     ComponentElement.visitChildren (package:flutter/src/widgets/framework.dart:3955:14)  
flutter: #34     Hero._allHeroesFor.visitor (package:flutter/src/widgets/heroes.dart:302:15)  
flutter: #35     ComponentElement.visitChildren (package:flutter/src/widgets/framework.dart:3955:14)  
flutter: #36     Hero._allHeroesFor.visitor (package:flutter/src/widgets/heroes.dart:302:15)  
flutter: #37     ComponentElement.visitChildren (package:flutter/src/widgets/framework.dart:3955:14)  
flutter: #38     Hero._allHeroesFor.visitor (package:flutter/src/widgets/heroes.dart:302:15)  
flutter: #39     SingleChildRenderObjectElement.visitChildren (package:flutter/src/widgets/framework.dart:5080:14)  
flutter: #40     Hero._allHeroesFor.visitor (package:flutter/src/widgets/heroes.dart:302:15)  
flutter: #41     ComponentElement.visitChildren (package:flutter/src/widgets/framework.dart:3955:14)  
flutter: #42     Hero._allHeroesFor.visitor (package:flutter/src/widgets/heroes.dart:302:15)  
flutter: #43     SingleChildRenderObjectElement.visitChildren (package:flutter/src/widgets/framework.dart:5080:14)  
flutter: #44     Hero._allHeroesFor.visitor (package:flutter/src/widgets/heroes.dart:302:15)  
flutter: #45     ComponentElement.visitChildren (package:flutter/src/widgets/framework.dart:3955:14)  
flutter: #46     Hero._allHeroesFor.visitor (package:flutter/src/widgets/heroes.dart:302:15)  
flutter: #47     ComponentElement.visitChildren (package:flutter/src/widgets/framework.dart:3955:14)  
flutter: #48     Hero._allHeroesFor.visitor (package:flutter/src/widgets/heroes.dart:302:15)  
flutter: #49     ComponentElement.visitChildren (package:flutter/src/widgets/framework.dart:3955:14)  
flutter: #50     Hero._allHeroesFor.visitor (package:flutter/src/widgets/heroes.dart:302:15)  
flutter: #51     ComponentElement.visitChildren (package:flutter/src/widgets/framework.dart:3955:14)  
flutter: #52     Hero._allHeroesFor.visitor (package:flutter/src/widgets/heroes.dart:302:15)  
flutter: #53     ComponentElement.visitChildren (package:flutter/src/widgets/framework.dart:3955:14)  
flutter: #54     Hero._allHeroesFor.visitor (package:flutter/src/widgets/heroes.dart:302:15)  
flutter: #55     ComponentElement.visitChildren (package:flutter/src/widgets/framework.dart:3955:14)  
flutter: #56     Hero._allHeroesFor.visitor (package:flutter/src/widgets/heroes.dart:302:15)  
flutter: #57     SingleChildRenderObjectElement.visitChildren (package:flutter/src/widgets/framework.dart:5080:14)  
flutter: #58     Hero._allHeroesFor.visitor (package:flutter/src/widgets/heroes.dart:302:15)  
flutter: #59     ComponentElement.visitChildren (package:flutter/src/widgets/framework.dart:3955:14)  
flutter: #60     Hero._allHeroesFor.visitor (package:flutter/src/widgets/heroes.dart:302:15)  
flutter: #61     SingleChildRenderObjectElement.visitChildren (package:flutter/src/widgets/framework.dart:5080:14)  
flutter: #62     Element.visitChildElements (package:flutter/src/widgets/framework.dart:2820:5)  
flutter: #63     Hero._allHeroesFor (package:flutter/src/widgets/heroes.dart:305:13)  
flutter: #64     HeroController._startHeroTransition (package:flutter/src/widgets/heroes.dart:783:51)  
flutter: #65     HeroController._maybeStartHeroTransition.&lt;anonymous closure&gt; (package:flutter/src/widgets/heroes.dart:757:11)  
flutter: #66     _WidgetsFlutterBinding&amp;BindingBase&amp;GestureBinding&amp;ServicesBinding&amp;SchedulerBinding._invokeFrameCallback (package:flutter/src/scheduler/binding.dart:1016:15)  
flutter: #67     _WidgetsFlutterBinding&amp;BindingBase&amp;GestureBinding&amp;ServicesBinding&amp;SchedulerBinding.handleDrawFrame (package:flutter/src/scheduler/binding.dart:966:9)  
flutter: #68     _WidgetsFlutterBinding&amp;BindingBase&amp;GestureBinding&amp;ServicesBinding&amp;SchedulerBinding._handleDrawFrame (package:flutter/src/scheduler/binding.dart:874:5)  
flutter: #72     _invoke (dart:ui/hooks.dart:236:10)  
flutter: #73     _drawFrame (dart:ui/hooks.dart:194:3)  
flutter: (elided 3 frames from package dart:async)  
flutter: ════════════════════════════════════════════════════════════════════════════════════════════════════  
</code></pre>

<p>QQ技术交流群：214541576 </p>

<p>热爱生活，分享快乐。好记性不如烂笔头。多写，多记，多实践，多思考。</p>]]></description><link>http://shavekevin.com/2019/07/25/flutter-questions-partone/</link><guid isPermaLink="false">1e802db0-2464-4959-adfa-e611a1a6f62b</guid><category><![CDATA[Flutter]]></category><dc:creator><![CDATA[shavekevin]]></dc:creator><pubDate>Thu, 25 Jul 2019 07:13:00 GMT</pubDate></item><item><title><![CDATA[Flutter初探]]></title><description><![CDATA[<p>以下内容来自组内分享，如有侵权，请联系作者删除。</p>

<p>目录：</p>

<ul>
<li>什么是Flutter？</li>
<li>Flutter环境配置</li>
<li>如何创建一个Flutter app?</li>
<li>Flutter开发语言以及代码结构</li>
<li>Flutter优缺点</li>
</ul>

<h2 id="1flutter">1.什么是Flutter？</h2>

<p><strong>背景：</strong>在全球，随着Flutter被越来越多的知名公司应用在自己的商业APP中，Flutter这门新技术也逐渐进入了移动开发者的视野，尤其是当Google在2018年IO大会上发布了第一个Preview版本后，国内刮起来一股学习Flutter的热潮。目前使用flutter 开发的app有闲鱼。</p>

<p><strong>简介：</strong>Flutter 是 Google推出并开源的移动应用开发框架，主要是跨平台、高保真、高性能。开发者可以通过 Dart语言开发 App，一套代码同时运行在 iOS 和 Android平台。 Flutter提供了丰富的组件、接口，开发者可以很快地为 Flutter添加 native扩展。同时 Flutter还使用 Native引擎渲染视图，为用户提供良好的体验。</p>

<h2 id="2flutter">2.Flutter环境配置</h2>

<ul>
<li>操作系统:MACOS</li>
<li>磁盘空间：700M(不包括IDE所占空间)</li>
<li>工具：bash mkdir rm git curl unzip which
<strong>镜像使用：</strong></li>
<li>国内访问Flutter受限制，Flutter官方为中国开发者搭建了临时镜像。把镜像添加到用户环境变量中。（临时镜像）</li>
<li>export PUB<em>HOSTED</em>URL=<a href='https://pub.flutter-io.cn/' >https://pub.flutter-io.cn</a></li>
<li>export FLUTTER<em>STORAGE</em>BASE_URL=<a href='https://storage.flutter-io.cn/' >https://storage.flutter-io.cn</a>
<img src='https://ws1.sinaimg.cn/large/006mQyr2ly1g4kc9xw40fj30op0dyjug.jpg'  alt="" /></li>
<li>因为flutter-io.cn服务器是GDG China 维护的Flutter依赖项和临时包的临时镜像。Flutter团队无法保证这个服务的长期可用性。如果有兴趣设置自己的影像可以联系flutter开发团队(lutter-dev@googlegroups.com)。</li>
</ul>

<p>社区运行的镜像站点：
* 上海交通大学Linux用户组：FLUTTER<em>STORAGE</em>BASE<em>URL:https//mirrors.sjtug.sjtu.edu.cn
* PUB</em>HOSTED_URL: <a href='https://dart-pub.mirrors.sjtug.edu.cn/' >https://dart-pub.mirrors.sjtug.edu.cn</a></p>

<h3 id="flutter">配置flutter环境（命令行）</h3>

<p><code>
 $ export PUB_HOSTED_URL=https://pub.flutter-io.cn
$ export FLUTTER_STORAGE_BASE_URL=https://storage.flutter-io.cn
$ git clone -b dev https://github.com/flutter/flutter.git
$ export PATH="$PWD/flutter/bin:$PATH"
$ cd ./flutter
$ flutter doctor
 flutter doctor  作用：检查是否需要安装其他依赖来完成安装
</code>
<img src='http://ww1.sinaimg.cn/large/006mQyr2ly1g4kctgkj0vj30op0dyjug.jpg'  alt="" /></p>

<h4 id="">环境配置注意项</h4>

<p>使用zsh客户端的时候终端启动时~/.bash<em>profile将不会被加载，解决办法就是修改~./zshrc，在其中添加：source ~/.bash</em>profile</p>

<h3 id="ios">iOS设置</h3>

<ul>
<li>安装xcode</li>
<li>设置iOS模拟器 open -a Simulator</li>
<li>启动应用程序flutter run命令(工程目录下)</li>
</ul>

<h3 id="">配置编辑器</h3>

<ul>
<li>使用的是VS Code（版本1.20.1及以上版本）</li>
<li>安装flutter插件</li>
<li>通过flutter  doctor 验证设置</li>
</ul>

<h3 id="psflutter">PS:如何安装flutter 插件</h3>

<ul>
<li>启动VS Code</li>
<li>调用view->command palette</li>
<li>输入install 选择Extentions:Install Extention action</li>
<li>搜索flutter 选择后 点击install</li>
<li>install完成之后点击OK重新启动VS Code</li>
</ul>

<h2 id="3flutterapp">3.如何创建一个Flutter app?</h2>

<p>VSCode->view->command palette->Flutter new project->输入项目名称(小写字母可以使用下划线)->等待项目创建完成</p>

<h3 id="">工程结构</h3>

<ul>
<li>.idea IntelliJ IDEA </li>
<li>.vscode  vscode 附属相关</li>
<li>.android  安卓工程</li>
<li>.build  编译产物</li>
<li>.ios   iOS代码</li>
<li>lib 写代码的地方</li>
<li>test</li>
<li>.packages</li>
<li>pubspec.lock 编写文件配设的版本</li>
<li>pubspec.yaml 编写文件配置的语言</li>
<li>README.md
<img src='https://ws1.sinaimg.cn/large/006mQyr2ly1g4kchtvmdkj30ey0j00td.jpg'  alt="工程结构" /></li>
</ul>

<h2 id="4flutter">4.学习flutter需要了解什么？？</h2>

<h3 id="widget">什么是widget?</h3>

<p>Flutter Widget采用现代响应式框架构建，这是从 React 中获得的灵感，中心思想是用widget构建你的UI。 Widget描述了他们的视图在给定其当前配置和状态时应该看起来像什么。当widget的状态发生变化时，widget会重新构建UI，Flutter会对比前后变化的不同， 以确定底层渲染树从一个状态转换到下一个状态所需的最小更改  </p>

<h3 id="dart">什么是dart？</h3>

<p>Dart 是一个静态语言，这也是相对于js的一个优势。Dart可以被编译成js，虽然看起来像java。静态语言可以避免错误，获得更多的编辑器提示词。极大的增加了可维护性。</p>

<h3 id="pubyaml">pub依赖之yaml文件</h3>

<p>Flutter工程之间的依赖管理是通过Pub来管理的，依赖的产物是直接源码依赖，这种依赖方式和iOS中的Pod有点像，都可以进行依赖库版本号的区间限定与Git远程依赖等，其中具体声明依赖是在pubspec.yaml文件中，其中的依赖编写是基于YAML语法，YAML是一个专门用来编写文件配置的语言，声明依赖后，通过运行flutter packages get命名，会从远程或本地拉取对应的依赖，同时会生成pubspec.lock文件，这个文件和IOS中的Podfile.lock极其相似，会在本地锁定当前依赖的库以及对应版本号，只有当执行flutter packages upgrade时，这时才会更新。</p>

<h2 id="5flutter">5.Flutter 优缺点</h2>

<h3 id="flutter">Flutter优点</h3>

<ul>
<li><p>性能强大，流畅 </p>

<ul><li>Flutter 和weex以及rn相比，性能好很多。谷歌直接在两个平台上重写了各自的UIKit。对接到平台的底层，减少了UI层的多层转换。UI性能可以和原生媲美。</li></ul></li>
<li><p>优秀的动画设计</p>

<ul><li>Flutter动画比较简单，只需要将一个组件的属性通过补间(Tween)关联到动画对象上，Flutter会确保每一帧渲染正确的组件上，形成连贯的动画。</li></ul></li>
<li>UI跨平台稳定
<ul><li>google直接在两个平台底层上重写了UIKit 不依赖于css等外部解释器。</li></ul></li>
</ul>

<h3 id="flutter">Flutter缺点</h3>

<ul>
<li><p>假装跨平台，避不开原生代码组合而不是继承的思路</p>

<ul><li>这个框架其实就是UI跨平台，具有与原生代码交互的能力，硬性要求啊 前端同学别轻易入坑。。(前端同学那里知道UIViewcontroller和Activity。。成本略高)再说了交互问题 不明白原生的怎么玩的转交互</li></ul></li>
<li><p>采用组合的方式而不是继承</p>

<ul><li>Flutter提倡组合，而不是继承。原生开发中我们可能会继承自UIView在这个基础上进行自定义。而flutter中的属性都是final的。你继承一个Container，不能在它的生命周期来修改属性。所以需要组合嵌套多种widget比如说：row container listview等widget。</li></ul></li>
<li><p>Wall</p>

<ul><li>这点不用多说了。</li></ul></li>
</ul>

<h4 id="">总结:</h4>

<p>Flutter开发主要在于要了解原生的环境。(好像跨平台的框架都是这样)，想要通过跨平台的api拿下两端开发是不太现实的。(平台本身存在差异性)有点也很明显，动画比较流畅。  </p>

<h4 id="">参考链接:</h4>

<p><a href='https://flutterchina.club/setup-macos/' >https://flutterchina.club/setup-macos/</a> <br />
<a href='https://segmentfault.com/a/1190000017164263' >https://segmentfault.com/a/1190000017164263</a></p>]]></description><link>http://shavekevin.com/2019/07/01/flutterchu-tan/</link><guid isPermaLink="false">15bd3061-5925-4004-b6d7-8fbae8b11091</guid><dc:creator><![CDATA[shavekevin]]></dc:creator><pubDate>Mon, 01 Jul 2019 07:09:21 GMT</pubDate></item><item><title><![CDATA[一次支付宝电话面试记录]]></title><description><![CDATA[<p>某日，接到支付宝基础架构组的电话面试。面试问题大概有以下几个方面：</p>

<ul>
<li>简单介绍下自己。
这里主要介绍最近的一份工作就好。</li>
<li>讲一下什么时候开始做架构的？做架构的背景是什么？</li>
<li>讲一下运行时机制以及工程中的应用场景。</li>
<li>讲一下APP优化中启动时长、APP瘦身、以及APP性能优化你都做了哪些工作？</li>
<li>讲一下项目是怎么实现组件化的？如何进行组件间的通信。</li>
</ul>]]></description><link>http://shavekevin.com/2019/04/27/alipayinterview/</link><guid isPermaLink="false">9358f190-3bb8-4c9a-ac23-07761636c5ef</guid><dc:creator><![CDATA[shavekevin]]></dc:creator><pubDate>Sat, 27 Apr 2019 15:41:45 GMT</pubDate></item><item><title><![CDATA[CocoaPods使用问题总结]]></title><description><![CDATA[<h2 id="cocoapods">如何安装Cocoapods</h2>

<h3 id="cocoapods">更改Cocoapods的源</h3>

<p>因为在国内资源被墙，所以需要切换源。目前使用的是ruby-china提供的镜像。</p>

<pre><code>gem sources --remove https://rubygems.org/  
gem sources -a https://gems.ruby-china.org/  
</code></pre>

<p>安装完之后进行查看源，检查是否安装正确</p>

<pre><code>gem source -l  
// *** CURRENT SOURCES ***
//https://gems.ruby-china.org/
</code></pre>

<h3 id="cocoapods">安装Cocoapods</h3>

<pre><code>sudo gem install -n /usr/local/bin cocoapods  
</code></pre>

<h3 id="pod">配置pod</h3>

<pre><code>pod setup  
</code></pre>

<p>安装完成。</p>

<h2 id="cocoapods">如何安装指定版本的Cocoapods？</h2>

<h3 id="">问题描述：</h3>

<p>我们在使用<code>Cocoapods</code>的工程中进行协同开发的时候，避免不了对<code>podfile</code>文件的更改，当<code>podfile</code>文件发生改变的时候，对应的<code>pofile.lock</code>也会发生更改。当然团队在开发过程中也不希望每次拉一次代码就执行以下<code>pod install</code> 命令。正因为<code>podfile</code>以及<code>podfile.lock</code>的改变，我们才需要去执行<code>pod install</code> 或者<code>pod update</code>命令。<code>podfile</code>文件的更改，我们开发中可以控制，但是如果团队开发者<code>Cocoapods</code>版本不一致的时候。每更改一次都需要执行<code>pod install</code> 或者<code>pod update</code>命令。如果成员都不对<code>podfile</code>文件做更改，并且Cocoapods版本都一致的时候，每次进行代码提交以及代码拉取的时候都不需要执行pod 更新的命令，那么，如果版本不一致怎么办呢？统一版本呗。</p>

<h3 id="">解决方案：</h3>

<p><code>Cocoapods</code>版本的更新这里分两种情况：</p>

<p>Case1：当前安装的<code>Cocoapods</code>版本比目标版本低但是目标版本又不是最新的，不能够直接用更新命令(因为这样会更新到最新版本)。这个时候我们要怎么做呢？</p>

<p>假设当前<code>Cocoapods</code>版本为<code>1.5.0</code>,目标版本为<code>1.5.2</code>。</p>

<p>这个时候需要执行命令：</p>

<pre><code>sudo gem install cocoapods --version 1.5.2  
</code></pre>

<p>Case2：当前安装的<code>Cocoapods</code>版本比目标版本高，这个时候我们不能按照Case1的做法，因为这个时候执行的只是安装版本1.5.2,版本1.5.3还在。cocoapods在使用的时候默认的就是最高版本的cocoapods。那么，我们应该怎么做呢？ <br />
* 首先是卸载高版本<code>Cocoapods</code>。查找当前环境安装的cocoapods版本可以使用命令 <code>gem list</code>。此时控制台打印的可能为：</p>

<pre><code>cocoapods (1.5.3,1.5.0, 1.3.1, 1.1.1)  
cocoapods-core (1.5.3,1.5.0, 1.3.1, 1.1.1)  
cocoapods-deintegrate (1.0.2, 1.0.1)  
cocoapods-downloader (1.2.0, 1.1.3)  
cocoapods-plugins (1.0.0)  
cocoapods-search (1.0.0)  
cocoapods-stats (1.0.0)  
cocoapods-trunk (1.3.0, 1.2.0)  
cocoapods-try (1.1.0)  
</code></pre>

<p>1.5.2才是我们的目标版本。1.5.3是最新的版本，我们需要把它卸载掉。命令如下：</p>

<pre><code>sudo gem uninstall cocoapods -v 1.5.3  
sudo gem uninstall  cocoapods-core -v 1.5.3  
</code></pre>

<p>注意：这里必须把cocoapods和cocoapods-core都卸载掉。</p>

<ul>
<li><p>其次是安装指定版本。命令和Case1的命令一样。
<code>
sudo gem install cocoapods --version 1.5.2
</code></p>

<p>这样，我们就安装了指定版本的<code>cocoapods</code>。</p></li>
</ul>

<p>总结一下：</p>

<ol>
<li>当前版本比目标版本低的时候，直接安装指定版本的<code>cocoapods</code>。  </li>
</ol>

<pre><code>sudo gem install cocoapods --version 目标版本  
</code></pre>

<ol>
<li>当前版本比目标版本高的时候 <br />
分两步：</li>
</ol>

<p>卸载当前版本：</p>

<pre><code>sudo gem uninstall cocoapods -v 当前版本  
sudo gem uninstall  cocoapods-core -v 当前版本  
</code></pre>

<p>安装指定版本：</p>

<pre><code>sudo gem install cocoapods --version 目标版本  
</code></pre>

<h2 id="swift">Swift引用报错的问题</h2>

<h3 id="">问题描述:</h3>

<p>当我们使用<code>cocoapods</code>安装一个第三方的时候，安装成功以后，在引用第三方头文件的时候可能会报错，报错信息的大概意思是找不到这个头文件(<code>"Cannot load underlying module for 'SnapKit'"</code> 类似这样的语句 )。</p>

<h3 id="">解决方案：</h3>

<p>com+b(把工程编译一下就行了) <br />
// Xcode编译器在第一次使用cocoapods的时候。提示不是很友好，编译一下就好了。</p>

<h2 id="podsearch">pod search 不到或者搜索到的第三方库版本太低</h2>

<h3 id="">问题描述：</h3>

<p>我们在组件化的时候，组件组的同事提交了最新版本的本地库，然而你搜索到的还是历史版本的，不能使用最新的代码，怎么办？</p>

<h3 id="">解决方案：</h3>

<p>这是因为本地pod的索引没有及时进行同步。
命令行执行：</p>

<pre><code>pod repo update  
</code></pre>

<p>更新完本地索引之后，就可以search到最新版本的库了。
如果这种方法不行的话，那么试试删除~/Library/Caches/CocoaPods目录下的search_index.json文件</p>

<ul>
<li>pod setup成功后，依然不能pod search，是因为之前你执行pod search生成了search_index.json，此时需要删掉。</li>
<li>终端输入：rm ~/Library/Caches/CocoaPods/search_index.json</li>
<li>删除成功后，再执行pod search。</li>
</ul>

<p>此时终端输入：<code>pod search SKDispatcher</code>(不区分大小写)</p>

<p>输出：<code>Creating search index for spec repo 'master'.. Done!</code>，稍等片刻······就会出现所有带有SKDispatcher字段的类库。</p>]]></description><link>http://shavekevin.com/2018/09/21/solve-cocoapods-problem/</link><guid isPermaLink="false">8ae02ae7-e1da-4eb7-93eb-529277042f58</guid><category><![CDATA[iOS工具]]></category><dc:creator><![CDATA[shavekevin]]></dc:creator><pubDate>Fri, 21 Sep 2018 08:58:00 GMT</pubDate></item><item><title><![CDATA[Swift4.1 第二章 Basic Operators]]></title><description><![CDATA[<h2 id="">基本运算符</h2>

<p>一个运算符是一个特殊的字符或短语，你可以用它来检查，改变，合并值。例如：加号(<code>+</code>)表示两个数相加,例如<code>let i = 1 + 2</code>。还有逻辑运算符 <code>AND (&amp;&amp;)</code>用来关联两个布尔值，例如：<code>if enteredDoorCode &amp;&amp; passedRetinaScan</code>。</p>

<p><code>Swift</code>支持大多数标准的<code>C</code>语言的操作符，并且改进了许多特性用来减少常规的编译错误。操作符<code>=</code>不意味着返回一个值，这是为了和赋值运算符<code>==</code>进行区分，避免因为错写为<code>=</code>出现错误。算数运算符(<code>+,-,*,/</code>等)会检测并不允许值的溢出，这主要是用来避免因为值过大或者过小而超出它的类型所承载的范围导致异常的结果。你可以使用swift溢出的运算符来实现溢出，具体请参照<a href='https://docs.swift.org/swift-book/LanguageGuide/AdvancedOperators.html' #ID37">溢出运算符</a>。</p>

<p><code>Swift</code>和<code>c</code>语言不同的是，swift提供了区间运算符。例如：<code>a..&lt;b</code>和<code>a...b</code>，这方便我们表达一个区间内的数值。</p>

<p>这个小节描述了<code>Swift</code>中的基本操作符，<a href='https://docs.swift.org/swift-book/LanguageGuide/AdvancedOperators.html' >高级运算符</a>会包含Swift中的高级运算符，以及如何进行自定义运算符和如何用你自己的方式来实现标准的操作符。</p>

<h3 id="">术语</h3>

<p> 运算符分为一元，二元和三元运算符。</p>

<ul>
<li><p>一元运算符是对单一目标进行操作的(例如<code>-a</code>)。一元运算符有前缀和或追之分，前缀运算符往往出现在操作对象的前面(例如<code>!b</code>),后缀运算符往往出现在操作对象的后面 (例如：<code>c!</code>)</p></li>
<li><p>二元运算符是操作对象是两个目标，运算符的位置在操作对象的中间(例如：<code>a+b</code>)。</p></li>
<li><p>三元运算符的操作对象是三个目标，和<code>c</code>语言一样，swift只有一个三元运算符，就是三目运算符(<code>a?b:c</code>)。</p>

<p>受操作符影响的值叫操作者。在表达式<code>1+2</code>中，操作符<code>+</code>是一个二元运算符，它的两个操作者的值是<code>1</code>和<code>2</code>。</p></li>
</ul>

<h3 id="">赋值运算符</h3>

<p>赋值运算符用于初始化和更新<code>a</code>和<code>b</code>的值(例：<code>a=b</code>)。</p>

<pre><code>let b = 10

var a = 5

a = b

// 现在a的值为10
</code></pre>

<p>如果赋值运算符的右边是一个具有多值的元组，那么它的成员可以被立即分解成多个常量和变量。</p>

<pre><code>let (x, y) = (1,2)

// x的值为1 y的值为2
</code></pre>

<p>赋值运算符在swift中和在Objective-C以及C语言中是不同的，赋值运算符在<code>swift</code>中不会返回一个值。下面的语句是不合法的：</p>

<pre><code>if x = y {

 // 这是不合法的，因为等式 x = y没有返回值。

}
</code></pre>

<p><code>Swift</code>中的这个特性避免了在使用等式运算符(<code>==</code>)中因为误用赋值运算符(<code>=</code>)而导致的错误。当语句<code>if x = y</code>不合法的时候，<code>swift</code>会在你的代码中提示你避免出现这样的错误。</p>

<h3 id="">算术运算符</h3>

<p>swift为所有的数字类型提供了四种标准的算术运算符。</p>

<ul>
<li><p>加号运算符(<code>+</code>)</p></li>
<li><p>减号运算符(<code>-</code>)</p></li>
<li><p>乘号运算符(<code>*</code>)</p></li>
<li><p>除号运算符(<code>/</code>)</p></li>
</ul>

<pre><code>1 + 2 // 等于3

5 - 3 // 等于2

2 * 3 // 等于6

10.0 / 2.5 // 等于4.0
</code></pre>

<p>和<code>Objective-C</code>和<code>C</code>语言不同的是，<code>Swift</code>的算术运算符不允许值超出默认范围。但是你可以使用<code>Swift</code>的溢出运算符来进行值溢出的运算。(例：<code>a &amp;+ b</code>),具体请参见<a href='https://docs.swift.org/swift-book/LanguageGuide/AdvancedOperators.html' #ID37">溢出运算符</a>。</p>

<p>加号运算符同样使用于字符串的拼接：</p>

<pre><code class=" ">"hello," + “world” // 等价于hello,world
</code></pre>

<h4 id="">求余运算符</h4>

<p>求余运算符(<code>a%b</code>)是计算<code>b</code>的多少倍刚好可以放下<code>a</code>，然后返回一个多出来的值（也就是求余数）。(比如：4%2 余数为0，3%2 余数为1)。</p>

<p>注意：</p>

<p>求余运算符(<code>%</code>)在其他语言中也就做求模运算符。然而严格来说，在swift中这个操作符是对于负数来说的，这里叫做求余比求模更加贴切。</p>

<p>这里给大家讲一下求余运算符是怎么工作的。计算<code>9%4</code>，首先你要计算出<code>4</code>的多少倍能够放得下<code>9</code>：</p>

<p><img src='http://upload-images.jianshu.io/upload_images/904675-e9ac7c0237da8229.jpg?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240'  alt="求余运算" /></p>

<p>你可以发现4的多少倍能够放得下<code>9</code>，余数为<code>1</code>(橘色展示的就是)</p>

<p>在Swift中，求余是这样写的：</p>

<pre><code>9 % 4 // 等于1
</code></pre>

<p>为了计算出<code>a%b</code>的值，操作符<code>%</code>用下面的等式来计算并返回了余数作为输出。</p>

<pre><code>a = (b * 倍数) + 余数
</code></pre>

<p>当<code>倍数</code>是b的一个很大的倍数的时候，能够放的下<code>a</code>。</p>

<p>把 <code>9</code> 和<code>4</code> 带入等式以后：</p>

<pre><code>9 = (4 * 2) + 1
</code></pre>

<p>当a的值为负数的时候，计算余数的方法同样适应：</p>

<pre><code>-9 % 4 // 等于1
</code></pre>

<p>余数的值为<code>-1</code>。</p>

<p>在对<code>b</code>求余的时候，<code>b</code>的符号可以被忽略掉，这就是说<code>a % b</code>和<code>a % -b</code>  结果是一样的。</p>

<h4 id="">一元负号运算符</h4>

<p>数值类型正负可以用在前面加<code>-</code>来进行转换，这就是所谓的一元负号运算符。</p>

<pre><code>let three = 3

let minusThree = -three// 等价于 -3

let plusThree = -minusThree // 等价于3
</code></pre>

<p>一元负号运算符在使用的时候，直接加载常量和变量之后，他们之间不需要添加空格。</p>

<h4 id="">一元加号运算符</h4>

<p>当一元加号运算符使用的时候，操作符返回的值是他本身，没有发生任何改变。</p>

<pre><code>let minusSix = -6

let alsoMinusSix = +minusSix

//alsoMinusSix 的值仍为-6
</code></pre>

<p>尽管一元加号运算符在使用的时候没有什么实际意义，但是在你使用一元负号来表示负数的时候，可以用一元加号来表示一个正数，这样会使你的代码看起来对称。</p>

<h3 id="">组合运算符</h3>

<p>和C语言一样，Swift也提供了组合运算符用(<code>=</code>)和其他操作符的组合。下面是一个加号的组合运算符：</p>

<pre><code>var a = 1

a += 2 // 这里a的值为3

print("aa value is \(a)")
</code></pre>

<p>表达式<code>a += 2</code>  是表达式<code>a = a + 2</code>的缩写，一个组合加号运算符就是把加号运算和赋值运算符组合成一个，同时完成两个运算任务。</p>

<p>注意：</p>

<p>组合运算符不会返回一个值，例如：你不能这样写：<code>let b = a += 2</code>。</p>

<p>关于更多swift标准操作符的问题，请查看<a href='https://developer.apple.com/documentation/swift/swift_standard_library/operator_declarations' >运算符声明</a>。</p>

<h3 id="">比较运算符</h3>

<p><code>Swift</code>支持所有标准的C语言比较符。</p>

<ul>
<li><p>相等(<code>a == b</code>)</p></li>
<li><p>不相等(<code>a != b</code>)</p></li>
<li><p>大于(<code>a &gt; b</code>)</p></li>
<li><p>小于(<code>a &lt; b</code>)</p></li>
<li><p>大于等于(<code>a &gt;= b</code>)</p></li>
<li><p>小于等于(<code>a &lt;= b</code>)</p></li>
</ul>

<p>注意：</p>

<p>Swift也提供了两个操作符  恒等和不恒等(<code>===</code> and <code>!==</code>)用这两个操作符你可以判断两个对象是否引用同一个实体实例。了解更多请参照<a href='https://docs.swift.org/swift-book/LanguageGuide/ClassesAndStructures.html' >类与结构体</a>。</p>

<p>每一个比表运算符都会返回一个布尔值表示这个表达式是不是对的。</p>

<pre><code>1 == 1 // true 因为1和1 相等

2 != 1 // true 因为2 不等于1

2 &gt; 1 // true 因为2 大于 1

1 &lt; 2 // true 因为1 小于2

1 &gt;= 1 // true 因为1 等于1 满足1 大于等于1

2 &lt;= 1 // false 因为2 小于等于1
</code></pre>

<p>比较运算符也经常用在条件语句中，比如说<code>if</code>语句：</p>

<pre><code>let name = "world"

if name == "world" {

 print("Hello world")

} else {

 print("I'm sorry \(name),but I don't recognize you")

}

// 打印的结果是 Hello world 因为name 等于“world”
</code></pre>

<p>更多关于<code>if</code>语句的用法，请参看<a href='https://docs.swift.org/swift-book/LanguageGuide/ControlFlow.html' >控制流</a>。</p>

<p>如果两个元组的类型相同并且有相同个数的子元素，那么这两个元组可以用来比较。元组从左到右一个值一个值的进行比较，直到发现有两个不同的值为止。在进行比较的过程中，如果元组中每个元素都相同，那么我们可以说这两个元组相等。例如：</p>

<pre><code>(1,"zebra") &lt; (2,"apple") //返回true 因为1比2小，"zebra"和"apple"是不能被比较的

(3,"apple") &lt; (3,"bird") // 返回true 因为3 等于 3，"apple" 比"bird"少

(4,"dog") &lt; (4,"dog") // 返回true，因为4 等于4 “dog”和“dog”相同。
</code></pre>

<p>在上面的这个例子中，你可以看到从左到右的比较行为出现在第一行。因为<code>1</code>比<code>2</code>小，<code>(1,"zebra")</code>已经被认为比<code>(2,"apple")</code>小了，不管元组的其他值，所以<code>"zebra"&gt;"apple"</code>对结果没有影响,因为在比较的时候元组的第一个元素就已经把结果定了。不过当第一个元素相等的时候，就会对第二个第三个元素进行比较了。</p>

<p>当元素的每个元素都能够用操作符进行比较的时候，我们可以对元组进行比较。例如：在下面的代码中你可以对两个<code>(String，Int)</code>类型的元组进行比较，因为<code>String</code>和<code>Int</code>都可以用操作符<code>&lt;</code>进行比较。相反，<code>(String,Bool)</code>类型的元组不能用操作符<code>&lt;</code>进行比较，因为Bool不能用操作符<code>&lt;</code>进行比较。</p>

<pre><code>("blue",-1) &lt; ("purple",1)// OK ，返回结果为true

("blue",false) &lt; ("purple",true)//Binary operator '&lt;' cannot be applied to two '(String, Bool)' operands 布尔类型的值不能进行比较(本身比较已经是true和false了。。再进行比较，过分了啊)
</code></pre>

<p>注意：</p>

<p><code>Swift</code>标准库里是包含元素小于7个的元组之间的比较的。如果要比较超过7个元素的元组的话，你必须自己实现比较的操作符。</p>

<h3 id="">三元运算符</h3>

<p>三元运算符是一个特殊的操作符是由三个操作数组成，比如：<code>question？answer1：answer2</code>这种形式。这个是基于<code>question</code>是<code>true</code>还是<code>false</code>的一种表达。如果<code>question</code>是<code>true</code>那么它的返回<code>answer1</code>的值，否则，返回<code>answer2</code>的值。</p>

<p>三元运算符的简写如下：</p>

<pre><code>//let question = true

//let answer1 = "333"

//let answer2 = "333"

//print("the answer is \(question ? answer1:answer2)")

if question {

 answer1

} else {

 answer2

}
</code></pre>

<p>这里有一个计算表格行的高度的例子。题意大概是这样的，内容的高度是固定的，当这一行有表头的话高度加50如果没有表头的话加20。</p>

<pre><code>let contentHeight = 40

let hasHeader = true

let rowHeight = contentHeight + (hasHeader ? 50:20)// 结果为90
</code></pre>

<p>上面的这个语句是下面的语句的简写:</p>

<pre><code>let contentHeight = 40

let hasHeader = true

let rowHeight:Int

if hasHeader {

 rowHeight = contentHeight + 50

} else {

 rowHeight = contentHeight + 20

}

// rowHeight 结果为90
</code></pre>

<p>在第一个例子中，用的是三元操作运算符意味着<code>rowHeight</code>一行低吗就能得到正确的值，这比第二个例子的代码更加简洁。</p>

<p>三元条件运算符提供了一个高效的判断两个参数表达式的标准，需要注意的是，过度的使用三元运算符会使简洁的代码看的不容易懂。我们应该避免在一个组合的语句中使用多个三元运算符。</p>

<h3 id="">合并空值运算符</h3>

<p>合并空值运算符(<code>a ?? b</code>)对可选类型<code>a</code>如果包含一个值的时候被打开，或者当<code>a</code>的值为空(<code>nil</code>)的时候返回一个默认的值<code>b</code>。表达式<code>a</code>也是一个可选类型。默认值b的类型必须要和a所存储的值的类型保持一致(也就是说既然<code>b</code>是<code>a</code>的默认值，那么<code>b</code>的值的类型就必须和<code>a</code>保持一致，这才是默认值的意义所在，如果<code>a</code>没值那么默认值就顶上来)。</p>

<p>合并空值运算符最简单的代码表达式这样的：</p>

<pre><code>a != nil ? a! : b
</code></pre>

<p>上面的示例代码中使用了三元运算符，当可选类型<code>a</code>不为空的时候，强制打开a(<code>a!</code>)，访问<code>a</code>的值，反之，返回默认值<code>b</code>。合并空值运算符提供了一个更加优雅的方式来对条件进行判断和封装，这增加了代码的简洁性和易读性。</p>

<p>注意：</p>

<p>如果a的值是非空的时候，b的值永远不会被使用。这也就是所谓的短路求值。</p>

<p>下面的例子采用的合并空值运算符实现了在默认颜色和可选自定义颜色中进行选择。</p>

<pre><code>let defaultColorName = "red"

var userDefinedColorName: String?//默认值是nil

var colorNameToUse = userDefinedColorName ?? defaultColorName

// 因为userDefinedColorName是nil，所以colorNameToUse被设置为默认值"red"
</code></pre>

<p>变量<code>userDefinedColorName</code>定义的是默认值为<code>nil</code>的可选的变量，因为变量<code>userDefinedColorName</code>是可选的类型，因此你可以使用合并空值运算符来空值它的值。在上面的例子中，操作符被用来决定一个字符串变量<code>colorNameToUse</code>的默认值。因为<code>userDefinedColorName</code>的值为空，表达式<code>userDefinedColorName ?? defaultColorName</code>返回了<code>defaultColorName</code>也就是<code>"red"</code>。</p>

<p>如果你给<code>userDefinedColorName</code>赋一个非空的值，然后让合并空值运算符来检查一下，那么<code>userDefinedColorName</code>的值就会替换掉默认值。</p>

<pre><code>userDefinedColorName = "green"

var colorNameToUse = userDefinedColorName ?? defaultColorName // 因为userDefinedColorName 不为空，所以colorNameToUse的值为"green"。
</code></pre>

<h3 id="">区间运算符</h3>

<p>Swift包含了几个区间运算符，他们表示了一个范围的值的便捷方式。</p>

<h4 id="">闭区间运算符</h4>

<p>闭区间运算符(<code>a...b</code>)定义了一个只能在<code>a</code>和<code>b</code>之间的运行范围，它包括边界a和b。<code>a</code>的值必须必<code>b</code>小。</p>

<p>如果你想在某个范围内的值被使用，那么闭区间操作符很有用。比如说<code>for-in</code>循环。</p>

<pre><code>for index in 1...5 {

 print("\(index) times 5 is \(index * 5)")

}

// 打印的结果是

//1 times 5 is 5

//2 times 5 is 10

//3 times 5 is 15

//4 times 5 is 20

//5 times 5 is 25
</code></pre>

<p>了解更多关于<code>for-in</code>信息，请参看<a href='https://docs.swift.org/swift-book/LanguageGuide/ControlFlow.html' >控制流</a>。</p>

<h4 id="">半开区间运算符</h4>

<p>半开区间运算符(<code>a ..&lt; b</code>)定义了一个能在<code>a</code>和<code>b</code>之间运行的范围，但是不包括<code>b</code>。也就是说，半开区间包括了它的首值，但不包括尾值(也就是说是半开半闭区间)。和闭区间操作符一样，<code>a</code>的值总是小于<code>b</code>的。如果a的值和b的值相同，那么结果区间就是空的。</p>

<p>当你在处理从0开始的数组的时候，半开运算符显得很有用，它从0开始遍历到数组的长度(但是不包含数组的长度)。</p>

<pre><code>let names = ["Anna","Alex","Brian","Jack"]

let count = names.count

for i in 0 ..&lt; count {

 print("Person \(i + 1) is called \(names[i])")

}

// 打印结果是：

//Person 1 is called Anna

//Person 2 is called Alex

//Person 3 is called Brian

//Person 4 is called Jack
</code></pre>

<p>我们可以观察到，数组包括四个元素，但是<code>0 ..&lt; count</code>仅包括到3(数组的最后一个元素的索引)，因为这是半开半闭区间。关于更多数组相关信息，请查看<a href='https://docs.swift.org/swift-book/LanguageGuide/CollectionTypes.html' #ID107">数组</a>。</p>

<h4 id="">单侧运算符</h4>

<p>闭区间有另外一种形式让区间朝着一个方向尽可能长的延伸。例如：一个包含所有数组元素的区间，从索引为2的地方开始到数组的结束。在这种情况下，你可以省略掉操作符一边的值。因为操作符仅一边有值，所以它叫做单侧区间。例如：</p>

<pre><code>for name in names[2...] {

 print("\(name)")

}

// 打印结果是：

// Brian

// Jack

for name in names[...2] {

 print("\(name)")

}

// 打印结果是：

// Anna

// Alex

// Brian
</code></pre>

<p>半开区间范围操作符也有单侧的形式，只需要你写下末值即可。和半开区间中两侧都包含值一样，末值都不是区间的一部分。例如：</p>

<pre><code>for name in names[..&lt;2] {

 print("\(name)")

}

// 打印结果是：

//Anna

//Alex
</code></pre>

<p>单侧区间可以在其他上下文中使用，不仅仅作为下标使用。你不能够省略区间的首值，因为区间遍历的开端是不明显的，你可以省略掉一个区间中的尾值，因为区间本身具有延伸的特性，确保你在循环里有一个结束循环的条件。你可以检查一下某个单侧区间是否包含一个特殊值，就像下面的代码一样。</p>

<pre><code>let range = ...5

range.contains(7) //false

range.contains(4) //true

range.contains(-1) //true
</code></pre>

<h3 id="">逻辑运算符</h3>

<p>逻辑运算符的操作对象是逻辑布尔值(<code>true or false</code>)，Swift基于C语言的三种逻辑表达式。</p>

<ul>
<li><p>逻辑非(<code>!a</code>)</p></li>
<li><p>逻辑与(<code>a &amp;&amp; b</code>)</p></li>
<li><p>逻辑或 (<code>a || b</code>)</p></li>
</ul>

<h4 id="">逻辑非运算符</h4>

<p>逻辑非运算符是对一个布尔值取反，就是让<code>true</code>变成<code>false</code>，让<code>false</code>变成<code>true</code>。</p>

<p>逻辑非运算符是一个前置运算符，它往往出现在操作值的前面，不需要添加空格。它可以被读作<code>非a</code>，下面是一个例子：</p>

<pre><code>let allowedEntry = false

if !allowedEntry {

 print("ACCESS DENIED")

}

//打印结果是ACCESS DENIED
</code></pre>

<p>表达语句<code>if !allowedEntry</code>可以被读作“如果不允许进入”。下面一行代码只有在“不允许进入”是<code>true</code>，也就是说只有在<code>allowedEntry</code>是<code>false</code>的时候执行。</p>

<p>在这个例子中，小心的选择布尔常量和变量有助于我们保持代码的可读性，并且避免了双重逻辑非运算符，或者混乱的逻辑语句。</p>

<h4 id="">逻辑与运算符</h4>

<p>逻辑表达式与运算(<code>a &amp;&amp; b</code>)表示的是只有等式两边同时为<code>true</code>的时候，才为<code>true</code>，否则就是<code>false</code>。</p>

<p>如果有任意一个为<code>false</code>，那么整个表达式就是<code>false</code>。事实上，如果第一个表达式为<code>false</code>的时候，第二个表达式就不执行了，因为这个时候已经不能保证整个表达式是<code>true</code>了，这被称为短路计算。</p>

<p>下面的例子中，只有当两个判断是中的值都为<code>true</code>的时候，<code>if</code>语句才会执行。</p>

<pre><code>let enterDoorCode = true

let passedRetainScan = false

if enterDoorCode &amp;&amp; passedRetainScan {

 print("Welcome!")

} else {

 print("ACCESS DENIED")

}

//打印结果是ACCESS DENIED
</code></pre>

<h4 id="">逻辑或运算符</h4>

<p>逻辑或运算符是由两个内置运算符<code>|</code>组成。你可以用它来表达两个表达式中任意一个为<code>true</code>，那么整个表达式都是<code>true</code>。</p>

<p>和上面的逻辑与运算符一样，逻辑运算符或也有一个短路的计算。当左边的表达式的值为<code>true</code>的时候，运算符右边的值就不被计算了，因为不论它是<code>true</code>或者<code>false</code>都不会影响真个表达式的值。</p>

<p>在下面的例子中，第一个布尔值(<code>hasDoorKey</code>)是<code>false</code>，但是第二个值<code>knowsOverridePassword</code>是<code>true</code>。因为其中一个值为<code>true</code>，那么整个表达时的值就是<code>true</code>，通道是被允许的。</p>

<pre><code>let hasDoorKey = false

let knowsOverridePassword = true

if hasDoorKey || knowsOverridePassword {

 print("Welcome!")

} else {

 print("ACCESS DENIED")

}

//打印结果是Welcome!
</code></pre>

<h4 id="">混合逻辑运算</h4>

<p>你可以把多个逻辑运算符放到一起，创建一个混合逻辑表达式。</p>

<pre><code>if enterDoorCode &amp;&amp; passedRetainScan || hasDoorKey || passedRetainScan {

 print("Welcome!")

} else {

 print("ACCESS DENIED")

}

//打印结果是Welcome!
</code></pre>

<p>上面的这个例子中使用了多个逻辑运算符<code>&amp;&amp;</code>和  <code>||</code>创建了一个混合逻辑运算符。然而，表达式<code>&amp;&amp;</code>和  <code>||</code>都只能操作两个值，所以这实际上是三个简单的逻辑表达式连续执行的结果，这个例子可以被解读为：</p>

<p>如果我们输入了正确的密码并且通过了视网膜的扫描，或者我们有一把有效的钥匙，或者我们知道怎么在紧急情况下重置密码，那么就被允许进入。</p>

<p>在<code>enterDoorCode</code>和<code>passedRetainScan</code> 以及<code>hasDoorKey</code> 因为 enterDoorCode 和 passedRetainScan的值都是false,固逻辑运算符<code>&amp;</code> 值为<code>false</code> 逻辑运算符<code>||</code> <code>hasDoorKey</code>也为<code>false</code>。但是紧急情况重置密码的权限是<code>true</code>。所以整个混合表达式的值就是<code>true</code>。</p>

<p>注意：</p>

<p><code>swift</code>的逻辑运算符<code>&amp;&amp;</code>和  <code>||</code>都是左结合式，它意味着混合表达式是由多个逻辑表达式以及计算的。他们在计算的时候优先计算左边的表达式。</p>

<h3 id="">显式括号</h3>

<p>为了使表达式易读懂，我们有时候会在合适的地方用括号来包含一个逻辑，当然这个括号可能是非必要的。在上个关于门权限的例子中，我们把第一部分用括号括起来了，这样看起来逻辑更加明确：</p>

<pre><code>if (enterDoorCode &amp;&amp; passedRetainScan) || hasDoorKey || knowsOverridePassword {

 print("Welcome!")

} else {

 print("ACCESS DENIED")

}

//打印结果是Welcome!
</code></pre>

<p>通过这个括号我们能清楚的看到在整个逻辑表达式中前两个值是逻辑表达式中独立的一部分。虽然最终的输出结果没有变化，但是整个逻辑表达式显得更容易读懂。可读性有时候比简洁性更加重要，为了使你的代码逻辑更加清晰在合适的地方加上括号吧。</p>

<p>更多swift4.1翻译请查看<a href='https://github.com/iOSDevelopShareTeam/SwiftTourv4.1' >github</a>。</p>

<p>QQ技术交流群：214541576 </p>

<p>微信公众号：shavekevin</p>

<p>开发者头条：<img src='http://upload-images.jianshu.io/upload_images/904675-7fcb401ae0833b31.jpg?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240'  alt="image" title="" /></p>

<p>热爱生活，分享快乐。好记性不如烂笔头。多写，多记，多实践，多思考。</p>]]></description><link>http://shavekevin.com/2018/07/03/swift4-1-basic-operators/</link><guid isPermaLink="false">50b5c48f-8759-47f6-b87e-8e4a577f7aeb</guid><category><![CDATA[swift]]></category><dc:creator><![CDATA[shavekevin]]></dc:creator><pubDate>Tue, 03 Jul 2018 00:42:59 GMT</pubDate></item><item><title><![CDATA[Swift4.1 第二章 The Basics]]></title><description><![CDATA[<h1 id="swift">Swift语言开发指南</h1>

<h2 id="">基础部分</h2>

<p>Swift是一门新的开发语言，它可以在iOS、macOS watchOS以及tvOS系统环境下进行应用的开发。</p>

<p>Swift提供了它自己的<code>C</code>和<code>Objective-C</code>语言的所有基本数据类型。包括用于描述整数的Int，描述浮点型的<code>Double</code>和<code>Float</code>，描述布尔值的<code>Bool</code>以及描述文本数据的<code>String</code>。Swift也提供了三个主要的集合类型。比如集合类型中描述的的数组、集合、和字典。</p>

<p>和C语言一样，Swift用变量来存储和引用一个已经被声明过名字的值。Swift同样能够使用不可变数据类型的值。这些不能改变的值被称为常量，它比C语言中的常量更加强大。当你在使用不可变的值的时候，常量能够使你的代码变得安全整洁。</p>

<p>除了一些熟悉的类型之外，Swift还提供了<code>Objective-C</code>没有的高级类型，比如说：元组。元组能够让你创创建和传递一组数据。函数中返回多个值的时候你可以用元组作为单个复合值来接收。</p>

<p>Swift也提供了一些可选类型，它能够处理值缺失的情况。可选值的意思是说：这里有一个值，它等于<code>X</code>或者<code>它没有值</code>。使用可选类型的之后就像使用<code>Objective-C</code>中的空指针一样，但是它的使用不仅仅局限于类，可以是任意类型。和<code>Objective-C</code>中的空指针相比来说，可选类型可不仅仅是安全和更具表现力那么简单，它们是Swift最强大功能中的核心。</p>

<p>Swift是一门安全类型的语言。这意味着这门语言可以帮你弄明白你所使用的值是什么类型的。如果你的代码中需要的是<code>String</code>,当你用<code>Int</code>来给它赋值的时候，类型安全会阻止你这么做。同样的，如果你意外的将可选字符串传递给非可选字符串那么类型安全会阻止你这么做。类型安全可以帮你在开发过程中尽早的捕获和修正错误。</p>

<h3 id="">常量和变量</h3>

<p>常量和变量都需要用一个别名(比如说<code>maximumNumberOfLoginAttempts</code>或者<code>welcomeMessage</code>)以及一个特殊的数据类型的值(比如说数字<code>10</code>和字符串<code>hello</code>)来来进行关联.常量的值一旦被初始化设置了之后就不能发生改变了，而变量则可以在未来给它赋不同的值。</p>

<h3 id="">常量和变量的声明</h3>

<p>常量和变量在使用之前都是要经过声明的。你可以用关键词<code>let</code>来修饰一个常量，用<code>var</code>关键词来修饰一个变量。下面的例子是通过对常量和变量的使用来模拟一个用户尝试登录次数的场景。</p>

<pre><code>let maximumNumberOfLoginAttempts = 10  
var currentLoginAttempts  = 0  
</code></pre>

<p>这段代码可以被解读为：
声明一个新的名字为<code>maximumNumberOfLoginAttempts</code>的常量，它的值为10。然后声明一个新的名为<code>currentLoginAttempts</code>的变量，它的初始值为0.
在这个例子中，最大允许尝试登录次数被声明为一个常量，因为最大的尝试登录次数不能被修改。当前的尝试登录次数被声明为一个变量，因为这个次值会随着尝试登录失败的次数的增加而增加。
你可以连续声明几个常量或者变量在同一行中，他们之间用逗号隔开。</p>

<pre><code>var x = 0.0,y = 0.0,z = 0.0  
</code></pre>

<p>注意：
如果你的代码中使用的值是不会发生改变的那么用<code>let</code>关键字来声明。如果声明的值是需要发生改变的那么用<code>var</code>关键字来声明。</p>

<h3 id="">类型标注</h3>

<p>在声明常量和变量的时候你可以为它提供一个类型标注，它可以很清楚的告诉编辑器常量和变量以什么样的数据类型被存储。在常量和变量添加类型标注的时候在命名之后加一个冒号以以及空格，然后加上类型的名称。</p>

<p>下面的这个例子里给出一个叫做<code>welcomeMessage</code>变量的类型标注，它表明了这个变量被存储为一个<code>String</code>类型的值。</p>

<pre><code>var welcomeMessage: String  
</code></pre>

<p>在声明的时候冒号的意思是“...类型的”，因此上面的代码可以这样来理解：</p>

<p>声明一个类型为<code>String</code>的变量<code>welcomeMessage</code>。</p>

<p>类型字符串的意思是可以存储任意值的字符串，可以把它理解为有存储功能的"事物类型"。</p>

<p>变量<code>welcomeMessage</code>可以被赋值为任意的字符串而不报错。</p>

<pre><code>welcomeMessage = "Hello"  
</code></pre>

<p>你可以在一行定义多个同一种类型的变量，用逗号隔开在最后一个变量后面添加冒号和类型标注。</p>

<pre><code>var red,green,blue:Double  
</code></pre>

<p>注意：
在实际开发中我们很少需要给常量/变量来写类型标注，如果在定义常量/变量的时候给了一个初始值，那么Swift可以帮我们推断出常量/变量的类型。具体请参考<code>类型和安全判断</code>。在上面定义的变量<code>welcomeMessage</code>中，我们没有给它赋初值，因此变量<code>welcomeMessage</code>的类型是从类型标注里判断的，而不是从它初始值判断的。</p>

<h3 id="">常量和变量的命名</h3>

<p>常量和变量的名字可以包含任意字符串，当然也包括了<code>Unicode</code>
字符。</p>

<pre><code>let π = 3.14159  
let 你好 = "你好世界"  
let dogcow = "dogcow"  
</code></pre>

<p>常量和变量的命名不能包含空格字符，数学符号，箭头，保留的(或者非法的)Unicode码位，连线与制表符。也不能够用数字开头，尽管数字可能在名称的其他地方可以使用。</p>

<p>一旦你为常量和变量声明一个确定的类型之后，你不能够再声明一个相同名字类型的常量/变量，并且也不能够改变它所存储的类型，当然你也不能够对常量和变量的互换操作。</p>

<p>注意：
如果你需要使用和Swift保留关键字相同的名称作为常量和变量名。你可以使用关键字反引号(`)将关键字包起来作为它的名字，不到万不得已的时候，建议你不要使用关键字来作为常量和变量的名字。</p>

<p>你可以改变一个变量的值为另外一种相同类型的值，在下面的例子中，<code>friendWelcome</code>的值从<code>hello！</code>变为<code>Bonjour!</code>。</p>

<pre><code>var friendWelcome = "hello !"  
friendWelcome = "Bonjour ！"  
// friendWelcome  is now "Bonjour！"
</code></pre>

<p>和变量不同的是常量在初始化设置以后就不能够发生改变了。当你改变常量值的时候，编译器会报错。</p>

<pre><code>let languageName = "Swift"  
languageName = "c++"  
//Cannot assign to value: 'languageName' is a 'let' constant
// 这里是一个编译错误，languageName 不能够被改变.
</code></pre>

<h3 id="">打印出常量和变量</h3>

<p>你可以用<code>print(_:separator:terminator:) </code>这个函数打印出常量和变量的当前值。</p>

<pre><code>print(friendWelcome)  
// 打印出 friendWelcome 的值
</code></pre>

<p><code> print(_:separator:terminator:)</code>函数是一个在全局的指定区域打印出一个甚至更多值的的函数。例如在Xcode里面，<code>print(_:separator:terminator:)</code>是在xcode的控制台输出的。<code>separator</code>和<code>terminator</code>参数都有默认值，因此你当你调用函数的时候可以省略掉它们。函数默认通过换行符来结束当前行。如果不想换行，可以默认添加一个空的字符串来代替，例如：<code>print(someValue, terminator: "").</code>如果想了解更多关于参数的信息，可以查看<a href='https://developer.apple.com/library/content/documentation/Swift/Conceptual/Swift_Programming_Language/Functions.html' #//apple_ref/doc/uid/TP40014097-CH10-ID169">默认参数</a>。</p>

<p>Swift用字符串插值的方式把常量名和变量名当做占位符加入到长字符串中，Swift会把常量和变量的值替换掉这些占位符。把常量和变量名放到括号里面,并在括号前面加上反斜杠来转义。</p>

<pre><code>print("The currenct value of firendlyWelcome is \(friendWelcome)")  
// 打印出当前firendlyWelcome的值
</code></pre>

<p>注意：
字符串插值所有能用的选项在<a href='https://docs.swift.org/swift-book/LanguageGuide/StringsAndCharacters.html' #ID292">这里</a>能找到。</p>

<h3 id="">注释</h3>

<p>把你代码里不用执行的文本用打上注释作为注解或者一个标记来提醒你自己。在swift中，编译器会忽略掉注释的编译。</p>

<p>在swift中注释和在c语言的注释十分相似。单行的注释用<code>//</code>来表示。</p>

<pre><code>// This is a comment.
</code></pre>

<p>多行注释以<code>/*</code>开始，以<code>*/</code>结尾。</p>

<pre><code>/*
 This is also a comment.
 but is written over multiple lines. 
 */
</code></pre>

<p>和C语言中多行注释不同的是，swift中多行注释是可以进行嵌套的。你可以写一个嵌套的多行注释用<code>/*</code>开始 ，然后添加第二个多行注释以<code>/*</code>开始以<code>*/</code>结束。最后用<code>*/</code>来结束第一个多行注释。</p>

<pre><code>/* This is the start of the first multiline comment.
 /* This is the second,nested multiline comment. */
 This is the end of the first mulitiline comment.*/
</code></pre>

<p>注释的多行嵌套使得你在注释大量代码块的时候更加便捷和容易，即使代码里已经存在多行注释也没有影响。</p>

<h3 id="">分号</h3>

<p>和其他编程语言不同的是，在swift中，在代码的结尾，不需要你写分号(;)当然，你也可以按照你的习惯来添加分号。有一种情况是必须要添加分号的，那就是在一行中执行多个语句</p>

<pre><code>let cat = "cat";print(cat)  
// print "cat"
</code></pre>

<h3 id="">整数</h3>

<p>整数就是没有小数的数字，比如说：42和-23.整数可以是有符号的类型(正数，负数和0)，也可以是无符号的类型(正数和0)。</p>

<p><code>swift</code>为我们提供了8位，16位，32位，以及64位的无符号和有符号的整数。这些整数的命名规则和c语言类似。比如说：有无符号的8位整数<code>UInt8</code>和有符号的32位整数<code>Int32</code>。像<code>swift</code>中的其他类型一样,整数类的命名都是大写字母开头。</p>

<h4 id="">整数的范围</h4>

<p>你可以通过访问整数的最大值属性和最小值属性在确定他们的范围。</p>

<pre><code>let minValue = UInt8.min // UInt8的最小值为0  
let maxValue = UInt8.max // UInt8的最大值为2  
print("minValue of UInt8 is \(minValue) and maxValue of UInt8 is \(maxValue)。")  
</code></pre>

<p>这些属性的值表明了这种类型数据只能在规定范围内进行操作(就像上面<code>UInt8</code>的例子),因此同种类型的数据可以在表达式中一起使用。</p>

<h4 id="int">Int</h4>

<p>在大多数情况下，在写代码的过程中我们并不需要指定一个长度给<code>Integer</code>。<code>Swift</code>提供了另外一个整数类型的数据<code>Int</code>，它的长度和原生平台的字节数相同。
1. 在32位的平台上，<code>Int</code>的长度和<code>Int32</code>一致。 <br />
2. 在64位的平台上，<code>Int</code>的长度和<code>Int64</code>一致。</p>

<p>如果你不需要为整型添加特殊的长度处理，用默认的<code>Int</code>来实现代码就行。这可以提高代码的一致性和可复用性。甚至是在32位的平台上，他能够储存在<code>-2</code>，<code>147</code>，<code>483</code>，<code>648</code>和<code>2</code>，<code>147</code>，<code>483</code>，<code>647</code>范围之间的数据，在很多时候这个范围内的数据已经很大了。</p>

<h4 id="uint">UInt</h4>

<p><code>swift</code>也为我们提供了无符号类型的整型数据。<code>UInt</code>和原生平台有着相同长度的字节数。
1. 在32位的平台上，<code>UInt</code>的长度和<code>UInt32</code>一致。 <br />
2. 在64位的平台上，<code>UInt</code>的长度和<code>UInt64</code>一致。</p>

<p>注意：
尽量不要使用<code>UInt</code>,除非你真的需要存储一个和当前原生平台长度相同的字节数的无符号整数时候，如果不是这种情况，建议你最好使用<code>Int</code>，即使你要存储的对象已知是非负的。统一使用<code>Int</code>提高了代码的一致性和可复用性。避免在不同的数据类型进行转换，并且匹配数字的类型进行判断，具体请参考<a href='https://docs.swift.org/swift-book/LanguageGuide/TheBasics.html' #ID322">类型安全和类型推断</a>。</p>

<h4 id="">浮点型数据</h4>

<p>所谓浮点型数据就是带有小数部分的数据。比如：<code>3.14258</code>，<code>0.1</code>和<code>-273.15</code>。</p>

<p>浮点型所代表值得范围要比整型要更大，它能够储存比整型更小或者更大的值。Swift提供了两种有符号的浮点数类型。
1. <code>Double</code>类型代表的64位浮点型数据。 <br />
2. <code>Float</code>类型代表的32位浮点型数据。</p>

<p>注意：
<code>Double</code>类型精度至少为小数点后15位，<code>Float</code>类型的精确度仅仅是小数点后6位。
你可以根据自己编程的需要值的范围选择是<code>Double</code>类型还是<code>Float</code>类型，如果两种条件都满足，优先选择<code>Double</code>。</p>

<h3 id="">类型安全和类型判断</h3>

<p>Swift是一个类型安全的语言，类型安全的语言可以让你清楚的知道你所处理的代码值的类型。如果你代码中需要的是<code>String</code>,那么。你如果给它赋值为<code>Int</code>类型的数据，那么编译器就会报错。</p>

<p>因为<code>Swift</code>是类型安全的语言，所以它在编译的时候会对代码进行类型的检查。这在开发过程中能够帮助你尽可能早的发现和解决问题。</p>

<p>当你在处理不同类型数据的时候，类型检查能够帮助避免一些问题。然而，这并不意味着你在每次声明常量或者变量的时候都需要显示指定类型。如果你没有指定显示类型，那么swift会使用类型判断来为你选择合适的类型。类型判断确保了编译器在编译代码的时候通过检查你赋的值自动推断出表达式的类型。</p>

<p>因为有了类型判断，和c或者c++相比来说，swift很少需要你进行类型声明。常量和变量虽然需要来明确类型，但是大部分工作并不需要你来完成，编译器已经为你完成了。</p>

<p>当你声明一个常量或者变量赋初值的时候类型判断变非常有用。在你声明常量或者变量的时候，赋给它们一个字面量(<code>literal value</code>或者<code>literal</code>)就能够让编译器自己来进行类型判断。(所谓字面量就是直接出现在代码中的值，比如下面例子中的<code>42</code>和<code>3.14159</code>。)</p>

<p>例如：如果你给一个新的常量用字面量的形式给它赋值为<code>42</code>没有声明它的数据类型，Swift能推断出你要给常量赋一个<code>Int</code>类型的数据，因为你在初始化的时候给它赋值了一个像整型的数字。</p>

<pre><code>let meaningOfLife = 42  
// meaningOfLife will be inferred to be of  type Int  这里常量meaningOfLife 会被便以及推断为一个整型
</code></pre>

<p>同理，如果你没有给浮点类型的数据标记类型，那么<code>Swift</code>会默认类型的<code>Double</code></p>

<pre><code>let  pi = 3.14159  
// pi will be inferred to be of type Double 这里常量pi会被默认推断为DoubleL类型的数据。
</code></pre>

<p>当swift在推断浮点型数据的时候，它会默认推断为<code>Double</code>类型而不是<code>Float</code>类型。</p>

<p>如果你在一个表达式中用整型和浮点型混合计算的时候,在上下文中会被<code>swift</code>推断为<code>Double</code>类型。</p>

<pre><code>let anotherPi = 3 + 0.14159  
// anotherPi will be inferred of type Double 这里常量anotherPi会被swift推断为Double类型的数据。
</code></pre>

<p>初始值3没有给显式声明类型，并且在表达式中出现了一个浮点类型的字面量，所以表达式被推断为<code>Double</code>类型。</p>

<h3 id="">数值型字面量</h3>

<p>整数类型的字面量可以被写作：
1. 一个十进制数，没有前缀 <br />
2. 一个二进制数，前缀是<code>0b</code> <br />
3. 一个八进制数，前缀是<code>0o</code> <br />
4. 一个十六进制数，前缀是<code>0X</code> <br />
下面所有的整数型字面量都是代表十进制的值<code>17</code></p>

<pre><code>let decimalInteger = 17  
let binaryInteger  = 0b10001// 二进制的17 2*2*2*2+1  
let octalInteger = 0o21 // 八进制的17 2*8+1*1  
let hexadecimalInteger = 0x11 // 十六进制的17 16*1+1  
</code></pre>

<p>浮点型的字面量可以是十进制(没有前缀)，也可以是十六进制的(带有前缀)。在小数的两遍必须是十进制的值值(或者十六进制有前缀的值)。十进制的浮点数可以有一个可选的指数(exponent)。通常用大写的或者小写的e来表示，十六进制的浮点数，通常用大写或者小写的<code>p</code>来表示。</p>

<p>如果一个十进制的指数为<code>exp</code>，那么这个数相当于基数和<code>10^exp</code>的乘积.
例如：
<code>1.25e2</code>表示的是<code>1.25*10^2</code>或者<code>125.0</code>。
<code>1.25e-2</code>表示的是<code>1.25*10^-2</code>或者<code>0.0125</code>。
如果一个有前缀的十六进制的指数为<code>exp</code>，那么这个数相当于基数和<code>2^exp</code>的乘积。
例如：
<code>0xFp2</code>表示的是<code>15*2^2</code>或者<code>60.0</code>。
<code>0xFp-2</code>表示的是<code>15*2^-2</code>或者<code>3.75</code>。
下面的浮点型的字面量都等价于十进制的值<code>12.1875</code>。</p>

<pre><code>//浮点类型的十进制的12.1875
let decimalDouble = 12.1875  
let exponentDouble = 1.21875e1  
let hexadecimalDouble = 0xC.3p0  
//计算方式：0x代表16进制C代表12后面.3代表小数 所以整数部分应该是应该是12*(16^0)+0.3*(16^-1) 
后面的p代表指数，以2为底。所以完整表达式为（12*(16^0)+0.3*(16^-1)）*(2^0)
</code></pre>

<p>数字类型的字面量添加额外的标记能够使它们看起来更容易阅读。整数和浮点型都可以添加额外的零加上下划线来增强可阅读性，并且不会影响字面量的值。</p>

<pre><code>let paddedDouble = 000123.456  
let oneMillion = 1_000_000  
let justOverOneMillion = 1_000_000.000_000_000_1  
</code></pre>

<h3 id="">数值类型转换</h3>

<p>即使常量和变量你已经知道它们是非负的，你也可以在代码中用<code>Int</code>类型来修饰它们。使用默认整数数据类型保证了常量和变量可以被复用并且能用来匹配整数字面量的类型推断。</p>

<p>当我们做特殊的任务时候才会对整型类型做类型指定，比如：需要处理的外部的长度明确了数据或者为了性能优化、内存占用等等。使用显式指定长度的类型可以及时的发现值的溢出并且还能标记我们在处理的是特殊的数据。</p>

<h4 id="">整数类型转换</h4>

<p>不同类型的整数可以保存不同范围的常量和变量。Int8类型的整数可以存储数据的范围是<code>-128</code>到<code>127</code>。无符号整型的可以存储常量和变量的范围是<code>0</code>到<code>255</code>。如果数字超出了常量和变量对应值的范围，编译的时候会报错。</p>

<pre><code>let cannotBeNegative:UInt8 = -1 //不在UInt8值范围内报错  
let tooBig:Int8 = Int8.max + 1  //超出Int8值范围内报错  
</code></pre>

<p>因为不同数据类型能够存储数据的范围是不一样的，在进行类型转换的时候你必须选择一个合适的数据类型进行转换。这种转换方式能够让你的代码的意图更明显，并且能够防止你在隐式转换中遇到的错误。</p>

<p>在转换一个特殊的数字类型到另一个类型的时候，你需要重新初始化一个你需要的目标数据。在下面的例子中，常量<code>twoThousand</code>是一个<code>UInt16</code>类型的数据，和它一起需要转换的数据类型是<code>UInt8</code>,它们两个不能够直接相加，因为他们两个类型不一样。
我们可以用<code>UInt16(one)</code>来创建一个<code>UInt</code>类型的数据用<code>one</code>的值进行初始化。用初始化的数据类代替原始数据。</p>

<pre><code>let twoThousand:UInt16 = 2_000  
let one:UInt8 = 1  
let twoThousandAndOne = twoThousand + UInt16(one)  
</code></pre>

<p>因为等式两边的类型都是<code>UInt16</code>，所以两者等式操作是被允许的。输出的常量<code>(twoThousandAndOne)</code>的数据类型被<code>swift</code>推断为<code>UInt16</code>，因为它是两个UInt16的常量相加而来的。</p>

<p><code>SomeType(ofInitialValue)</code>调用的是<code>swift</code>构造器并传入一个初始值的默认方法。在语言的内部，UInt16有一个构造器可以接收一个<code>UInt8</code>类型的数据。这个构造的作用是通过已经存在的<code>UInt8</code>类型的数据初始化一个<code>UInt16</code>类型的数据。需要注意的是：你并不能任意的传入值，只有在传入<code>UInt16</code>内部有对应构造器的值。不过你可以扩展现有的类型，让它来接收其他类型的值(包括自定义的类型),具体请参考<a href='https://docs.swift.org/swift-book/LanguageGuide/Extensions.html' >扩展</a>。</p>

<h4 id="">整数和浮点数转换</h4>

<p>在进行整数和浮点数类型转换的时候一定要指定类型。</p>

<pre><code>let three = 3  
let pointOneFourOneFiveNine = 0.14159  
let pi = Double(three) + pointOneFourOneFiveNine  
// pi等于3.14159，被Swift推断为double类型
</code></pre>

<p>在这里，常量<code>three</code>被当做一个<code>Double</code>类型的数据来创建，因此<code>+</code>两边应为相同的类型。如果这里没有进行转化，那么<code>+</code>(加号运算符)是不被允许使用的。
浮点型的数据和整型一样可以互相转换。整型可以使用<code>Double</code>和<code>Float</code>进行初始化。</p>

<pre><code>let integerPi = Int(pi)  
//这里integerPi等于3，被Swift推断为Int类型。
</code></pre>

<p>上面我们使用浮点型数据进行初始化一个整型数据的时候，我们发现浮点值被截断了。这意味着4.75变成了4，-3.9被截断为-3。
注意：
结合数字型的常量和变量的规则和数字字面量的规则是不同的，在字面量3可以和直接和字面量0.14159相加，因为数字型的字面量他们本身值是没有特定类型的。它们的类型只有在编译需要计算值的时候才会被推断。</p>

<h3 id="">类型别名</h3>

<p>类型别名是给已经存在的一个数据类型添加一个可选的名字。你可以用关键字<code>typealias</code>来定义这是一个类型别名。</p>

<p>当你想给现有的类型添加一个有意义的名字的时候，类型别名显得特别的有用。我们假设你正在处理特定长度的外部资源的数据的时候：</p>

<pre><code>typealias AudioSample = UInt16  
</code></pre>

<p>当你定义过一个类型别名的时候，你可以在任意一个地方像使用原始名一样来使用这个别名。</p>

<pre><code>var maxAmplitudeFound = AudioSample.min  
//  maxAmplitudeFound 现在的值是0
</code></pre>

<p>在这里，<code>AudioSample</code>被定义成一个值为<code>UInt16</code>的别名。正因为这是一个别名，所以我们调用<code>AudioSample.min</code>实际上就是调用<code>UInt16.min</code>这个函数，这样为变量<code>maxAmplitudeFound</code>提供了一个初始值<code>0</code>。</p>

<h3 id="">布尔值</h3>

<p><code>Swift</code> 有一个基础的布尔类型叫做<code>Bool</code>，布尔的值是逻辑上的值，因此只有真和假。<code>Swift</code>为我们提供了两个bool常量<code>true</code> 和<code>false</code>。</p>

<pre><code>let  ornagesAndOrange = true  
let  turnipsAndDelicious = false  
</code></pre>

<p><code>ornagesAndOrange</code>和<code>turnipsAndDelicious</code>被推断为<code>Bool</code>因为他们是由<code>Bool</code>字面量初始化来的。就像上面提到的<code>Int</code>和<code>Double</code>类型，当在声明的时候你只需要设置一下他们的值<code>true</code>或<code>false</code>不需要声明常量或者变量的类型。当初始化常量或者变量的时候如果要赋值的类型是已知的，就可以触发便以及的类型推断，这使得Swift的代码更加简洁和易读。</p>

<p>当你在写条件语句比如<code>if</code>语句的时候，布尔值显得更加有用。</p>

<pre><code>if turnipsAndDelicious {  
    print("Mmm, tasty turnips!")
} else {
    print("Eww, turnips are horrible.")
}
// 这里打印的是:Eww, turnips are horrible.
</code></pre>

<p>关于条件语句，比如<code>if</code>语句，可以参考<a href='https://docs.swift.org/swift-book/LanguageGuide/ControlFlow.html' >控制流</a>
如果你在使用Bool值的时候赋了其他类型的值，那么swift因为类型安全就会报错。下面的例子中就会报编译的错误。</p>

<pre><code>let i= 1  
if i {  
// 这个例子不会编译成功，会报错（报错原因是 判断的值不是bool类型）
}
</code></pre>

<p>然而，下面的例子是合法的：</p>

<pre><code>let i = 1  
if i == 1 {  
    // 这个例子能编译成功不报错。因为这里i == 1 是一个判断语句
}
</code></pre>

<p>因为等式<code>i == 1</code>的结果是一个<code>Bool</code>类型的值，所以第二个例子能够通过类型检查。像<code>i == 1</code>这样类型的比较，参考 <a href='https://docs.swift.org/swift-book/LanguageGuide/BasicOperators.html' >基本操作符</a>。
和swift中其他类型安全的例子一样，这个方法避免了一些偶然的错误并且保证了代码的目的总是清晰的。</p>

<h3 id="">元组</h3>

<p>元组(Truples)就是把多个值组合成一个复合值。元组中的成员并不一定是相同类型的数据，它们可以是任意类型的数据。
在这个例子中<code>(404,"Not Found")</code>就是一个代表<code>HTTP</code>状态码的元组。<code>HTTP</code>状态码是当你请求网页的时候，web服务器返回的一个特殊值。如果请求的网页不存在就会返回状态码<code>404 NOT Found</code>。</p>

<pre><code>let http404Error = (404,"Not Found")  
// http404Error的类型是(Int,String)的元组，值是(404, "Not Found")
</code></pre>

<p><code>(404,"Not Found")</code>这个元组把<code>Int</code>和<code>String</code>的值放到一起组合起来表示<code>HTTP</code>请求的状态码的两部分：一个数组和另外一个可读的描述。它被描述为一个类型为<code>(Int,String)</code>的元组。</p>

<p>你可以把任意顺序的类型组合为一个元组，这个元组可以你需要的任意类型的数据。你可以创建一个类型为<code>(Int,Int,Int)</code>或者<code>(String,Bool)</code>或者其他的任何你想创建任意组合的元组。</p>

<p>你可以将元组的内容分解为单独的常量或者变量，然后你就可以正常使用它们了。</p>

<pre><code>let (statusCode, statusMessage) = http404Error  
print("The status code is \(statusCode)")  
//输出的状态码为 404
print("The status message is \(statusMessage)")  
// 输出的状态描述为 Not Found
</code></pre>

<p>如果你只需要元组中的其中一个值，那么分解的时候你可以用<code>_</code>将不需要的部分省略掉。</p>

<pre><code>let  (justTheStatusCode,_) =  http404Error  
print("The status code is \(justTheStatusCode)")  
// 输出的状态码为 404
</code></pre>

<p>此外，你还可以用下标来访问元组中的某个值，元组中的下标从0开始。</p>

<pre><code>print("The status code is \(http404Error.0)")  
//输出的状态码为 404
print("The status message is \(http404Error.1)")  
// 输出的状态描述为 Not Found
</code></pre>

<p>你可以在定义元组的时候给单个元组进行命名</p>

<pre><code>let http200Status = (statusCode:200,description:"OK")
</code></pre>

<p>当元组中的元素命名后，你可以通过名字来获取对应的元素的值。</p>

<pre><code>print("The status code is \(http200Status.statusCode)")  
////输出的状态码为 200
print("The status message is \(http200Status.description)")  
// 输出的状态描述为 OK
</code></pre>

<p>元组在用作函数返回值的时候显得尤为重要。已给获取网页请求状态地方函数可以会返回一个<code>(Int,String)</code>元组来描述网络请求的状态。这和只能返回一个值进行比较来说。一个包含两个不同类型值的元组返回地方信息更有用。了解更多请参考<a href='https://docs.swift.org/swift-book/LanguageGuide/Functions.html' #ID164">函数与返回值</a>。</p>

<p>注意：</p>

<p>元组在组织临时值的时候很有用，它们并不适合用到复杂的数据结构里。如果你的数据结构不是临时的使用，那么请使用类或者结构体而不是用元组。了解更多，请参考<a href='https://docs.swift.org/swift-book/LanguageGuide/ClassesAndStructures.html' >类与结构体</a>。</p>

<h3 id="">可选类型</h3>

<p>当处理值缺失情况的时候，你可以用可选类型来表示。可选类型表明有两种可能性：或者有值，你可以解析这个可选类型来访问这个值。或者这个值不存在。
注意：
在<code>C</code>语言和<code>Objective-C</code>语言中不存在可选类型这个概念。在<code>Objective-C</code>语言中最接近的是一个方法中不要么返回一个对象，要么返回<code>nil</code>，<code>nil</code>表示缺少一个合法的对象。然而，这只对对象起作用，对于结构体，基本的C数据类型以及枚举值都不起作用。对于这些类型，<code>Objective-C</code>方法中一般会返回一个特殊值(比如NSNotFound)来暗示值的缺失。这种方法假设方法的调用者知道并对这些特殊值进行判断。然而，Swift的可选类型让你明白任意类型额值缺失，并不需要一个特殊的常量。</p>

<p>这里有一个关于可选值是怎么被用作当做缺省值的例子，Swift中整型中有一个构造器，作用是将一个<code>String</code>类型的值转换成一个<code>Int</code>类型的值。然而，并不是所有的字符串都能够转化为整型。字符串<code>123</code>可以被转化为整型<code>123</code>，但是字符串<code>Hello,world</code>不能被转化为整型。</p>

<p>下面的例子中使用这种便利构造器将<code>String</code>转换成<code>Int</code>类型。</p>

<pre><code>let possibleNumber = "123"  
let convertedNumber = Int(possibleNumber)  
//这里的convertedNumber 被编译器推断为'Int'类型，或者类型 ‘optional Int’
</code></pre>

<p>因为这个构造器也可能失败，所以它的返回了一个可选类型的<code>(optional)Int</code>而不是一个整型。一个可选的<code>Int</code>类型写作<code>Int?</code>而不是<code>Int</code>。这里的问号包含的值是可选类型，也就是说可能包含Int;类型的值也可能不包含。(不能包含其他的类型，比如说<code>Bool</code>类型的值或者String类型的值，它只能是<code>Int</code>类型，或者不存在这个类型。)</p>

<h3 id="nil">nil</h3>

<p>你可以给一个不存在的可选类型的变量赋值为<code>nil</code>：</p>

<pre><code>var serverResponseCode:Int? = 404  
// serverResponseCode中包含了一个可选的Int类型的值404
serverResponseCode = nil  
// 这里serverResponseCode的值为nil(值不存在)
</code></pre>

<p>注意：
你不能够把<code>nil</code>用在非可选的变量和常量上。如果你的代码中存在常量或者变量值缺省的情况，那么在声明的时候就声明为可选变量或者常量。</p>

<p>如果你不提供可选类型的变量的初始值，那么变量会自动设置为<code>nil</code>。</p>

<pre><code>var surveyAnswer:String?  
//这里surveyAnswer将会被自动设置为nil
</code></pre>

<p>注意：
<code>Swift</code>中的<code>nil</code>和<code>Objective-C</code>中的<code>nil</code>意义不一样。在<code>Objective-C</code>中，nil是一个指向不存在对象的一个指针。在<code>Swift</code>中，<code>nil</code>不是一个指针，它是一个确定类型的值。任何类型的可选状态都可以被设置为<code>nil</code>，不仅仅是对象类型的数据。</p>

<h3 id="if">if语句以及强制解析</h3>

<p>你可以使用if语句和nil来判断比较已给可选值是否包含值。你可以使用(<code>==</code>)以及(<code>!=</code>)等于以及不等于两个操作符来判断一个可选值是否包含值。</p>

<p>如果一个可选类型是有值的，那么它被认为不等于<code>nil</code>。</p>

<pre><code>if convertedNumber != nil {  
    print("convertedNumber contains some integer value.")
}
// 输出convertedNumber contains some integer value.
</code></pre>

<p>当你确定这个可选值确实包含值的时候，你可以在可选的名字后面加一个<code>!</code>来获取值。
这个感叹号表示我知道这个值有可选值，请使用它。这种被称为可选值的强制类型解析。</p>

<pre><code>if convertedNumber != nil {  
    print("convertedNumber has an integer value of \(convertedNumber).")
}
//输出的是 convertedNumber has an integer value of 123.
</code></pre>

<p>更多关于<code>if</code>条件语句的介绍，请参考<a href='https://docs.swift.org/swift-book/LanguageGuide/ControlFlow.html' >控制流</a>。</p>

<p>注意:
使用<code>！</code>来获取一个不存在的值可能会导致运行时的错误。在进行强制类型解析<code>!</code>的时候要注意,确保可选类型一定要包含一个不为<code>nil</code>的值。</p>

<h3 id="">可选绑定</h3>

<p>使用可选绑定来找出一个可选类型是否包含值，如果包含，如果包含就把值赋给一个临时变量或者常量。可选绑定可以用在if和while等条件语句中，这条语句不仅仅可以判断可选类型中是否有值，同时也可以将可选类型中的值赋给一个常量或者变量。关于if和while 语句，。请参考<a href='https://docs.swift.org/swift-book/LanguageGuide/ControlFlow.html' >控制流</a>。</p>

<p>像下面一样用if语句写一个可选绑定：</p>

<pre><code>if let constantName = someOptional {  
    statements
}
</code></pre>

<p>你可以像上面那样使用可选绑定来重写在例子<a href='http://wiki.jikexueyuan.com/project/swift/chapter2/01_The_Basics.html' #optionals">可选类型</a>中列举的<code>possibleNumber</code>例子。</p>

<pre><code>if let actualNumber = Int(possibleNumber) {  
    print("\'\(possibleNumber)\' has an integer value of \(actualNumber)")
} else {
    print("\'\(possibleNumber)\'  could not be converted to an integer")
}
// 输出 123 has an integer value of 123
</code></pre>

<p>这段代码可以被这样来解读：
如果<code>Int(possibleNumber)</code>返回的可选<code>Int</code>包含的一个值，创建一个新的叫做<code>actualNumber</code>的常量并将包含的值赋给它。</p>

<p>如果转换成功，那么<code>actualNumber</code>常量可以在<code>if</code>语句的第一个分支中使用。它已经被可选类型包含的值初始化过，这里就不需要再变量的后面添加！来进行强制解析获取它的值了。在这个例子中，<code>actualNumber</code>只是被用来输出转换的结果。</p>

<p>常量和变量都可以使用可选类型绑定。如果你想在<code>if</code>的第一个分支语句中使用<code>actualNumber</code>,那么你可以把判断条件改为<code>if  var actualNumber</code> 这里就把可选类型中包含的值就被赋值给一个变量而不是一个常量。
你可以在<code>if</code>语句中使用多个可选绑定或者<code>Bool</code>类型来作为条件判断，它们之前用逗号隔开。如果可选类型中任意一个值为<code>nil</code>或者<code>Bool</code>类型中判断结果为<code>false</code>，那么整个if语句的条件判断就会被认为是<code>false</code>。下面例子中的<code>if</code>语句是等价的：</p>

<pre><code>if let firstNumber = Int("4"),let secondNumber = Int("42"),firstNumber &lt; secondNumber &amp;&amp; secondNumber &lt; 100 {  
    print("\(firstNumber) &lt; \(secondNumber) &lt; 100")
}
// 打印的结果是：4 &lt; 42 &lt; 100
if let firstnumber = Int("4") {  
    if  let  secondnumber = Int("42") {
        if firstnumber &lt; secondnumber &amp;&amp; secondnumber &lt; 100 {
            print("\(firstnumber) &lt; \(secondnumber) &lt; 100")
        }
    }
}
// 打印的结果是：4 &lt; 42 &lt; 100
</code></pre>

<p>注意：
在if语句中创建的常量和变量的可选绑定，只能在body中使用。相反，在guard语句中创建的常量和变量的可选绑定，只有在guard语句之外能够取到值，请参参考<a href='https://docs.swift.org/swift-book/LanguageGuide/ControlFlow.html' #ID525">提前退出</a>。</p>

<h3 id="">隐式解析可选类型</h3>

<p>就像上面描述的一样，可选类型暗示了常量和变量可以没有值，可选类型通过if语句来判断是否有值，如果有值的话可以通过可选绑定来进行值的解析。</p>

<p>有时候在程序的结构中，在第一次被赋值以后，可以确定一个可选的类型总是有值。在这种情况下，每次都进行判断和解析可选值是很低效的，因为可以确定这个值是总是存在的。
这种类型的可选状态被定义为隐式解析可选类型<code>implicitly unwrapped optionals</code>。把可选类型(<code>String?</code>)后面的问号改为(<code>String!</code>)叹号来声明一个隐式解析可选类型。</p>

<p>当可选类型在第一次被赋值以后就可以确定一直有值的时候，隐式解析可选类型显得很有用。隐式解析可选类型主要被用在Swift中类的构造器中，请参考<a href='https://docs.swift.org/swift-book/LanguageGuide/AutomaticReferenceCounting.html' #ID55">无主引用以及隐式解析可选属性</a>。</p>

<p>一个隐式解析可选类型实际上就是一个普通的可选类型，但是可以被当做非可选类型来使用，并不需要每次都是用解析来获取可选值。下面的例子中展示了可选类型<code>String</code>和隐式解析可选类型<code>String</code>行为之间的区别。</p>

<pre><code>let  possibleString:String? = "An optional string."  
let forcedString:String = possibleString!  
//需要用感叹号来获取值
let  assumedString:String! = "An implicitly unwrapped optional string."  
let implicitString:String = assumedString  
// 不需要用感叹号就能获取值
</code></pre>

<p>你可以把隐式解析可选类型当做一个可以自动解析的可选类型。你要做的就是在声明的时候把感叹号放到类型的结尾，而不是放到每次取值的可选名字的结尾。</p>

<p>注意：
如果你在隐式可选类型中没有值的时候尝试取值，那么会触发运行时的错误。这和你在没有值的普通可选类型后面添加一个叹号一样。</p>

<p>你仍然可以把隐式解析可选类型当做普通的可选类型来判断它是否包含值:</p>

<pre><code>if assumedString != nil {  
    print(assumedString!)
}
// 打印出 An implicitly unwrapped optional string.
</code></pre>

<p>你可以在单个语句的可选绑定类型中使用隐式解析可选类型来检查分析它的值：</p>

<pre><code>if let definiteString = assumedString {  
    print(definiteString)
}
// 打印出 An implicitly unwrapped optional string.
</code></pre>

<p>注意：</p>

<p>如果一个变量在之后可能会是<code>nil</code>的时候，不要使用隐式解析可选类型。如果你需要在变量的声明周期中判断是否为<code>nil</code>的时候，使用普通可选类型。</p>

<h3 id="">错误处理</h3>

<p>你可以使用错误处理(<code>error handling</code>)来应对程序运行过程中可能会出现的错误情况。</p>

<p>与可选值不一样的是，运用值的存在与缺失来表达函数执行的成功与失败，错误处理可以推断出失败的原因。并可以传播到程序的其他地方。</p>

<p>当一个函数遇到出现错误条件的时候，它能抛出错误。调用函数方法的地方能抛出异常并合理的响应。</p>

<pre><code>func canThrowAnError () throws {  
    // this function may or may not throw an error
}
</code></pre>

<p>一个函数在声明中添加一个<code>throws</code>关键字是的时候来抛出错误消息。当一个函数可能会抛出异常的时候，你应该在表达式中使用前置<code>try</code>关键词。</p>

<p><code>Swift</code>会自动将错误传播到当前的范围直到它们被<code>catch</code>句子来处理。</p>

<pre><code>do {  
    try canThrowAnError()
    // no error was thrown
} catch  {
    // an error was thrown
}
</code></pre>

<p>一个<code>do</code>语句创建了一个新的作用域，它允许错误被传递到一个或者多个<code>catch</code>语句中。
这是一个如何运用错误处理来应对不同错误情况的例子：</p>

<pre><code>do {  
    try makeASandwich()
    eatASandwich()
} catch SandwichError.outOfCleardishes {
    washDishes()
} catch SandwichError.missingTngredients(let ingredients) {
    buyGroceries(ingredients)
}
</code></pre>

<p>在这个例子中，<code>makeASandwich()</code>函数会抛出一个错误消息条件是如果没有干净的盘子或者某个原料的缺失。由于<code>makeASandwich()</code>可能会抛出错误，所以函数调用被包在了<code>try</code>语句中。将函数包在一个<code>do</code>语句中，那么任何被抛出的错误都会被传播到<code>catch</code>语句中。
如果没有错误被抛出，那么<code>eatASandwich()</code>函数会被调用。如果一个匹配<code>SandwichError.outOfCleardishes</code>的错误被抛出，那么会执行<code>washDishes()</code>这个函数，如果异常<code>SandwichError.missingTngredients</code>被捕获到，那么<code>buyGroceries(_:)</code>函数将会被调用，并且会使用<code>catch</code>所捕获到的关联值<code>String</code>作为参数。
抛出，捕获以及传播错误等，会在<a href='http://wiki.jikexueyuan.com/project/swift/chapter2/17_Error_Handling.html' >错误处理</a>的章节中说明。</p>

<h3 id="">断言和先决条件</h3>

<p>断言和先决条件是在运行时进行检查的。你可以用它们来检查在执行后面代码的之前是否一个必要的条件已经满足了。如果断言或者先决条件中的<code>bool</code>值是<code>true</code>的时候，那么代码会像往常一样执行。如果条件判断为<code>false</code>，当前程序的状态是无效的，代码的执行会结束，你的app会被终止。</p>

<p>你使用断言和先决条件来表达你做的假设和你在编码时候的希望执行的方式。你可以将这些包含在代码中。断言帮助你在开发阶段找到错误和不正确的假设，先决条件帮助你在在生产环境中发现存在的问题。</p>

<p>除了在运行时验证你的期望值，断言和先决条件也变成了你代码中一种有用的文档形式。和上面讨论的<a href='http://wiki.jikexueyuan.com/project/swift/chapter2/17_Error_Handling.html' >错误处理</a>不同，断言和先决条件不是用来处理可以恢复或者可以预期的错误。因为一个断言失败表明了程序正在处于一个无效的状态，没有办法来捕获一个失败的断言。</p>

<p>使用断言和先决条件不是一个能够避免出现程序无效状态的编码方法。然而，如果一个无效状态的程序产生快了。断言可以强制检查你的数据和程序状态，使程序按照预测中的被终止，并帮助我们更简单的对这个问题进行调试。一旦遇到无效的状态，程序就会被终止，防止无效的状态对程序造成进一步的伤害。</p>

<p>断言和先决条件的不同之处在于它们什么时候进行检测。断言仅仅在调试的时候运行，但是先决条件不仅仅在调试的时候能运行在生产环境下也能运行。在生产环境下，断言条件不会被评估。这意味着你可以在开发阶段多使用断言，这些断言在生产条件下不会造成影响。</p>

<h3 id="">使用断言进行调试</h3>

<p>你可以用Swift标准库的函数<code>assert(_:_:file:line:)</code>来编写一个断言。
你向这个函数传入一个判断结果为true和false的表达式以及一条错误情况下展示的信息。例如：</p>

<pre><code>let age = -3  
assert(age &gt;= 0,"A person's age can't be less than zero.")  
// 这个断言会失败 因为一个人的年龄不可能小于0（把断言中的语句变为age &lt;= 0,就不会走这个断言）
</code></pre>

<p>在这个例子中，只有<code>age &gt;= 0</code>为<code>true</code>也就是说<code>age</code>的值为非负数的时候，代码才会继续执行。如果age的值为负数，就像上面代码中的一样，那么，<code>age &gt;= 0</code> 为<code>false</code>,断言失败，使得应用被终止运行。</p>

<p>你也可以省略掉断言的提示信息，例如：当断言条件可能会重复执行的时候</p>

<pre><code>assert(age &gt;= 0)  
</code></pre>

<p>如果代码已经被检查过，你可以使用函数<code>assertionFailure(_:file:line:)</code>来表明断言失败了。例如：</p>

<pre><code>if age &gt; 10 {  
    print("You can ride the roller-coaster or the ferris wheel.")
} else if age &gt; 0 {
    print("You can ride the ferris wheel.")
} else {
    assertionFailure("A person's age can't be less than zero.")
}
</code></pre>

<h3 id="">强制执行先决条件</h3>

<p>当一个条件可能为假，但是继续执行下去要求条件必须为真的时候，需要使用先决条件。例如:使用先决条件来判断下标有没有越界，或者来检查是否将一个正确的参数传给了函数。</p>

<p>你可以使用函数<code>precondition(_:_:file:line:)</code>来写一个先决条件。向这个函数传入一个结构为true或者false的表达式以及一条错误条件下显示信息。例如：</p>

<pre><code>// 下标的实现
precondition(index &gt; 0, "Index must be greater than zero.")  
</code></pre>

<p>你可以调用函数<a href='https://developer.apple.com/documentation/swift/1539374-preconditionfailure' >preconditionFailure(_:file:line:)</a>来表明出现了一个错误,例如：<code>switch</code>的进入了<code>default</code>分支，所有的有效输入数据都应该被其他分支所处理而不是默认的<code>default</code>分支。
注意：
如果你使用不检查的模式（-Ounchecked）进行编译，先决条件将不会进行检查。编译器会假设所有的先决条件都是<code>true</code>，这将优化你的代码。然而致命的错误函数<code>(_:file:line:)</code>总是中断执行，无论你进行什么样的优化设置。</p>

<p>在设计原型和早期的开发阶段你可以使用致命的错误函数<code>(_:file:line:)</code>，这个阶段只是对方法的声明，
但是没有具体的实现。你可以在方法<code>fatalError("Unimplemented")</code>进行具体实现。因为<code>fatalError</code>不会像断言和先决条件那样可以被优化，所以你可以确保当代码执行到一个没有实现的方法是的时候，程序会被中断。</p>

<p>更多swift4.1翻译请查看<a href='https://github.com/iOSDevelopShareTeam/SwiftTourv4.1' >github</a>。</p>

<p>QQ技术交流群：214541576 </p>

<p>微信公众号：shavekevin</p>

<p>开发者头条：<img src='http://ww1.sinaimg.cn/large/006mQyr2ly1fqgrkt5gurj30qo1bcq53.jpg'  alt="" title="" /></p>

<p>热爱生活，分享快乐。好记性不如烂笔头。多写，多记，多实践，多思考。</p>]]></description><link>http://shavekevin.com/2018/06/14/swift4-1-the-basics/</link><guid isPermaLink="false">b60f2cc6-d60b-4d59-acd1-2219a8e2f789</guid><category><![CDATA[swift]]></category><dc:creator><![CDATA[shavekevin]]></dc:creator><pubDate>Thu, 14 Jun 2018 08:06:13 GMT</pubDate></item><item><title><![CDATA[Swift4.1第一章]]></title><description><![CDATA[<h1 id="swift">欢迎使用Swift</h1>

<h2 id="swift">关于Swift</h2>

<p>用swift来写代码是一种很棒的方式，不管是手机、电脑客户端服务端或者是其他别的都可以用swift代码来运行。她是一种安全快速 交互式的编程语言，结合了现代优秀编程语言的最佳思维，从更加广泛的苹果工程文化和开源社区中汲取更多的智慧。编译器对性能做了很大的优化，并且她的语言也为开发者做了优化，也就是说在性能和语言优化上，它没有做出妥协。</p>

<p>swift是一门极其友好的新语言。她是一种具有工业品质的一门编程语言，和脚本语言一样具富有表现力和愉悦感。用playground来写swift语言能给你所见即所得的体验，当你写完一段代码的时候，你不需要重新去编译也不需要重新运行一个app就可以看到代码执行的结果。</p>

<p>Swift采用了现代的编程模式来定义大量的常见编程错误</p>

<p>例如：</p>

<p>1.变量在使用之前必须要先初始化</p>

<p>2.数组在越界的时候发生错误</p>

<p>3.整形类型需要检查是否溢出</p>

<p>4.可选类型的变量要确保进行过判断nil处理</p>

<p>5.内存是系统自己管理的</p>

<p>6.对错误的处理允许从意外故障中恢复</p>

<p>为了充分利用现代的硬件，swift代码在不断被编译和优化。语法和标准库的设计基于指导库的原则，这很显然使我们的代码表现的更好。速度和安全的结合使得swift成为从hello world 到整个操作系统实现的最佳选择。</p>

<p>Swift将强大的类型推断和模式匹配与现代化的轻量级的语法相结合，使得我们可以用清晰的表达式来表达复杂的想法。因此，代码不仅仅是容易写，而且易于阅读和维护。</p>

<p>Swift的发展已经有一些年了，并且随着新特性的发展在不断的改进。我们swift的目标是雄心勃勃的。我们迫不及待的看看你用swfit能创造出来什么。</p>

<h2 id="">版本兼容性</h2>

<p>这本书主要讲的是Swift 4.1,默认的版本的Swift是基于Xcode 9.2 版本。不论语言是Swift4 或者Swift3你都可以用Xcode9.2来进行编译。</p>

<p>注意：</p>

<blockquote>
  <p>当你在Swift4 环境下使用swift3的代码的时候，语言的标识应该是3.2.因此，你可以使用像#if swift(>=3.2)这样的条件编译代码块来编写与多个版本兼容的swift代码。</p>
</blockquote>

<p>当你使用Xcode9.2来编译Swift3代码的时候，大部分Swift4的功能是可用的。但是下面的一些特性只在Swift4下是可用的。</p>

<p>子串的操作返回值只能是子串的类型，不能是父串的类型
    @objc 这些隐式的属性被用在更少的地方。
    同一个文件中某个类型的延展，可以访问该类型的私有成员变量。  
一个用Swift4编写的工程可以依赖用Swift3写的代码，反之用Swift3写的工程也可以依赖Swift4的代码。
如果你有一个大的工程是由多个framework组成的，那么将你可以一次性的将代码从Swift3移植到Swift4框架中。</p>

<h2 id="swift">Swift之旅</h2>

<p>大多数人在开始学习一门新的语言的时候，按照惯例都要在打印这句单词<code>Hello, world!</code>在Swift中我们同样也可以用一句单独的话来表达。</p>

<pre><code>print("Hello, world!")  
</code></pre>

<p>如果你之前用C语言或者<code>Objective-C</code>语言写过代码的话，<code>swift</code>中的这个语法对你来说应该是很熟悉的，这一行代码就是一个完整的程序。你不需要引入另外一个框架来实现输入和输出一个字符串的操作。我们在使用一个实体的时候都是在全局作用域中使用的，因此这里我们不需要main函数。当然你也不必在每一个声明之后添加分号来结束。
这个旅行的小册子给你足够的信息关于怎么用swift来完成一系列的编程任务。如果你对一些不明白的话，也不要担心。在剩下的这本书里将会为你详细的介绍关于swift的所有东西。</p>

<p>注意：
使用mac下载这个playground，双击文件在Xcode 中打开 下载地址： <a href='https://developer.apple.com/go/?id=swift-tour' >https://developer.apple.com/go/?id=swift-tour</a></p>

<h2 id="">值的使用</h2>

<p>用<code>let</code>来定义一个常量，用<code>var</code>来定义变量。常量不需要在编译期就暴露出来，你必须一次性给它赋一个特别明确的值。这就意味着你可以使用一个常量来代表一个值。这个值只被赋值一次但是可以被很多地方使用。</p>

<pre><code>var myVariable = 42  
myVariable = 50  
let myConstant = 42  
</code></pre>

<p>一个常量或者变量要想对它进行赋值的时候，你必须得知道需要赋什么类型的值给它们。然而，并不是所有的变量都需要直接定义类型的。当你声明一个变量或者常量的时候给他们一个类型的值，让编译器自己去判断你所定义值的类型。在上面的例子中，编译器会自己判断myVariable 是一个<code>integer</code>类型的常量，因为它是被一个<code>integer</code>类型初始化的。
常量或者变量初始化的值并不一定能提供给我们太多的信息(也可能这个常量或者变量没有赋初始值)，你可以给变量或者常量进行类型的绑定设置，用一个冒号隔开。</p>

<pre><code>let implicitInteger = 70  
let implicitDouble = 70.0  
let explicitDouble:Double = 70  
</code></pre>

<p>小测试：
创建一个值为4 的浮点型常量</p>

<pre><code>let floatValue:Float = 4  
</code></pre>

<p>一个值不能够直接被转化为另外一种类型(隐式转换)，如果真的需要把当前值转化为一个不同的类型，我们需要通过显式转换来获得目标值的类型。</p>

<pre><code>let label = "The width is"  
let width = 94  
let widthLabel = label + String(width)  
</code></pre>

<p>小测试：
如果去掉最后一行将width 转化为String的代码，将会出现什么错误呢？
结果：<code>Binary operator '+' cannot be applied to operands of type 'String' and 'Int'</code></p>

<p>还有一种简单的方式可以把值转化为字符串：将你所要转化的值用圆括号括起来，然后放一个斜杠<code>\</code>到圆括号的前面，例如：</p>

<pre><code>let apples = 3  
let orange = 5  
let appSummary = "I have \(apples) apples. "  
let fruitSummary = "I have \(apples + orange) pieces of fruit. "  
</code></pre>

<p>小测试:
在一句欢迎语中，将一个浮点型的值和一个人名用\（）拼接到一起。</p>

<pre><code>let floatValue = 70.0  
let stringName = "Kevin"  
let stringValue = "My name is " +stringName+ "The Tencent rock price is \(floatValue)"  
</code></pre>

<p>用三个双引号"""来定义一个多行的字符串，引号的缩进规则是相同的，例如：</p>

<pre><code>let quotation = """  
I said "I have \(apples) apples." And then I said "I have \(apples+orange) pieces of fruit."  
"""  
</code></pre>

<p>用<code>[]</code>创建一个数组或者字典，用    <code>[]</code>获取数组的某一个元素或者字典的某一个key，最后一个元素可以用<code>，</code>隔开。</p>

<pre><code>var shoppingList = ["catfish","water","tulips","bluePrint"]  
shoppingList[1] = "bottle of water"  
var occupations = [  
"malcolm":"Caption",  
"Kaylee":"Mechanic",  
]
occupations["Jayne"] = "Public Relations"  
</code></pre>

<p>用初始化方法创建一个空的数组或者字典</p>

<pre><code>let emptyArray = [String]()  
let emptyDictonary = [String:Float]()  
</code></pre>

<p>假设给出值的类型能够被编译器推测出来，你可以像这样写一个空的数组[]或者是一个空的字典[:]-- 例如：你可以像给一个变量赋值或者传递参数一样。</p>

<pre><code>shoppingList = []  
occupations = [:]  
</code></pre>

<h2 id="">控制流程</h2>

<p>用<code>if</code>或者<code>switch</code>来处理一些条件判断，用<code>for-in while</code> 和<code>repeat-while</code> 做循环。判断条件之间的圆括号可以省略，但是语句之间的大括号是不可以省略的。</p>

<pre><code>let individualScores = [75,43,103,87,12]  
var teamScore = 0  
for score in individualScores {  
    if score &gt; 50 {
        teamScore += 3
    }else {
        teamScore += 1
    }
}
print(teamScore)  
</code></pre>

<p>在一个    <code>if</code>条件语句中，判断的条件必须是一个bool类型的值，这就是说像 <code>if score {...}</code>这样写的代码是错误的，因为编译器不会隐式的与零值做比较。
你可以会用<code>if</code>和<code>let</code>这个组合来处理赋值中变量值为空的情况，这些值代表是可选类型的值。一个可选类型的值可能包含一个值或者包含一个nil来表示这个值是空的。在定义值类型后面添加一个？表示这个值是可选类型。</p>

<p>小测试：</p>

<pre><code>var optionalString: String? = "hello"  
print(optionalString == nil)  
var optionalName: String? = "john appleseed"  
var greeting = "hello"  
if let name = optionalName {  
    greeting = "hello,\(name)"
}
</code></pre>

<p>小测试：
将<code>optionalName</code>的值改为nil。那么<code>greeting </code>的值应该是什么呢？将<code>optionalName</code>的值改为nil并在<code>if</code>语句之后添加一个<code>else</code> 此时输出<code>greeting</code> 语句。</p>

<pre><code>var optionalString1: String? = "hello"  
print(optionalString1 == nil)  
var optionalName1: String? = nil  
var greeting1 = "hello"  
if let name1 = optionalName1 {  
    greeting1 = "hello,\(name1)"
}else {
    greeting1 = "this is an errorCode!"
}
</code></pre>

<p>输出结果为：<code>"this is an errorCode!"</code> 如果<code>optionalName</code> 为空的时候<code>if</code>判断中的代码就不会执行，此时执行是<code>else</code>语句.</p>

<p>如果可选类型 值为nil，因为判断条件为false不满足那么代码就会被跳过。反之如果可选类型的值就会被解包并复制给<code>let</code> 声明的常量，解包的值就能够在代码块中（花括号中）使用了。</p>

<p>另外一种处理可选类型的方式是用？？操作符提供一个默认的值。如果可选类型的值为空的时候，默认值就会用来替换可选类型的值。</p>

<pre><code>let nickName: String? = nil  
let fullName:String = "john Appleseed"  
let informalGreeting = "hi \(nickName ?? fullName)"  
</code></pre>

<p>Switches 语句支持任意类型数据的比较操作-他们不局限于基本数据类型的比较，还能用于等式的比较。</p>

<pre><code>let vegetable = "red papper"  
switch vegetable {  
case "celery":  
    print("Add some raisins and make ants on a log.")
case "cucumber","watrcress":  
    print("that would make a good tea sandwich")
case let x where x.hasSuffix("papper"):  
    print("Is it a spicy \(x)?")
default:  
    print("Everything tastes good in soup.")
}
</code></pre>

<p>小测试：
试着移除switch的default case 。出现什么错误了？</p>

<pre><code>Switch must be exhaustive  
</code></pre>

<p>通过上面的操作你可以看到，<code>let</code>被用于接收可变值，然后对常量<code>let</code>进行等式判断。
执行完<code>switch</code>中对应的<code>case</code>之后，程序会结束<code>switch</code>语句的执行，因此，不用像OC一样写完一个<code>switch</code>之后紧跟一个<code>break</code>来结束    <code>switch</code>语句。
你可以用<code>for - in</code>这个语句遍历字典中的键值对。由于字典是无序的，因此他们键值对也是无序的。</p>

<pre><code>let interestingNumbers = [  
    "Prime":[2,3,5,7,11,13],
    "Fibonacci":[1, 1, 2, 3, 5, 8],
    "Square":[1, 4, 9, 16, 25],
]
var largest = 0  
for (_,numbers) in interestingNumbers {  
    for number in numbers {
        if number &gt; largest {
            largest = number
        }
    }
}
print(largest)
</code></pre>

<p>小测试：
添加另外一个变量来找到number中的最大值，同时记录下最大的number是哪个？</p>

<pre><code>var largestTest = 0  
var largestNumber = ""  
for (kindTest,numbersTest) in interestingNumbers {  
    for numberTest in numbersTest {
        if numberTest &gt; largestTest {
            largestTest = numberTest
            largestNumber = kindTest
        }
    }
}
print("the largest is \(largestTest),the lastNumber is \(largestNumber).")  
</code></pre>

<p>用while来重复一段代码，直到条件满足。如果把循环条件写在结尾的话，至少要保证循环走一遍。</p>

<pre><code>var n = 2  
while n &lt; 100 {  
    n *= 2
}
print("the result of n is \(n)")  
var m = 2  
repeat {  
    m *= 2
} while m &lt; 100
print("the result of m is \(m)")  
</code></pre>

<p>你可以使用<code>..&lt;</code>来完成一个在一定范围内循环。</p>

<pre><code>var total = 0  
for i in0 ..&lt;4 {  
total += i  
}
print(total)  
</code></pre>

<p>用<code>..&lt;</code> 来进行循环处理的时候，不包括边界的值。如果你想包括边界的值 那么用<code>...</code>来表达。</p>

<h2 id="">函数和闭包</h2>

<p>用<code>func</code>来声明一个函数。函数调用的时候直接使用函数的名字,如果函数有参数的时候要带上参数。用<code>-&gt;</code>来分割参数名和函数返回值类型。</p>

<pre><code>func greet (person:String,day:String) -&gt;String {  
return "Hello \(person),today is \(day)."  
}
greet(person: "Bob", day: "Tuesday")  
</code></pre>

<p>小测试：
去掉函数的参数<code>day</code>，然后添加另外一个参数今天午饭到问候语中。</p>

<pre><code>func greetPre (person:String,meal:String) -&gt;String {  
    return "Hello,\(person),what will you  have on  \(meal)?"
}
greetPre(person: "Kevin", meal: "Supper")  
</code></pre>

<p>一般来说，函数用它们的形参作为实参的标签。实参可以写在形参前面的标签做自定义，或者用_ 来表示没有实参标签。</p>

<pre><code>func greetAdd(_ person:String, on day:String) -&gt;String {  
return "Hello \(person),today is \(day).  
}
greetAdd("John", on:"Wednesday")  
</code></pre>

<p>我们可以用一个元组来处理返回多个值的情况（复合值），比如:一个函数中有多个返回值。元组中的每一个元素可以用它们的名字或者它们的编号来表示。</p>

<pre><code>func calculateStatistics (scores:[Int]) -&gt; (min: Int, max:Int, sum:Int) {  
    var min = scores[0]
    var max = scores[0]
    var sum = 0
    for score in scores {
        if score &gt; max {
            max = score
        } else if score &lt; min {
            min = score
        }
        sum += score
    }
    return (min, max, sum)

}
let statistics = calculateStatistics(scores: [5,3,100,3,9])  
print(statistics.sum)  
print(statistics.2)  
</code></pre>

<p>函数是可以进行嵌套的，嵌套函数可以对声明在外层函数的参数进行访问。你可以在一个既长又复杂的函数中用函数嵌套来进行组织代码。</p>

<pre><code>func returnFifteen() -&gt;Int {  
    var y = 10
    func add() {
        y += 5
    }
    add()
    return y
}
returnFifteen()  
</code></pre>

<p>函数是第一类对象，这意味着一个函数可以用另外一个函数的返回值作为自己的返回值。</p>

<pre><code>func makeIncrementer() -&gt; ((Int) -&gt;Int) {  
    func addOne(number : Int) -&gt;Int {
        return 1 + number
    }
    return addOne
}
var increment = makeIncrementer()  
increment(7)
</code></pre>

<p>函数也可以用另外一个函数作为它的参数。</p>

<pre><code>func hasAnyMatches(list:[Int], condition:(Int) -&gt;Bool) -&gt;Bool {  
    for item in list {
        if condition(item) {
            return true
        }
    }
    return false
}
func lessThanTen(number: Int) -&gt;Bool {  
    return number &lt; 10
}
var numbers = [20, 9, 7, 12]  
hasAnyMatches(list: numbers, condition: lessThanTen)
</code></pre>

<p>函数是一种特殊的闭包：<code>block</code>内部的代码会延时执行。当闭包创建的时候，闭包中的代码可以对闭包中的变量和函数进行访问，就像之前的嵌套函数一样，闭包也可以在其他不同的空间中执行。你可以定义一个用<code>{}</code>包围起来但是没有名字的闭包，用<code>in</code>来分割函数名和函数的返回值。</p>

<pre><code>numbers.map ({(number:Int) -&gt;Int in  
let result = 3 * number  
return result  
})
</code></pre>

<p>小测试：
重写闭包，当值为奇数时返回0</p>

<pre><code>numbers.map ({(number:Int) -&gt;Int in  
if number % 2 == 0 {  
return 0  
}
return number  
})
</code></pre>

<p>如果你想更简单的写一个闭包，那么下面几种方式。如果一个包的类型已经确定，比如说一个代理的回调，你可以省略参数的类型，它的返回类型或者两者都省略。单语句的闭包隐式返回执行的结果。</p>

<pre><code>let mappedNumbers = numbers.map({number in 3 * number})  
print(mappedNumbers)  
</code></pre>

<p>当你使用参数的时候，你可以用参数所在位置来替代参数的名字，这在短的闭包中特别有高效。当闭包作为函数的最后个参数时，它可以直接跟在圆括号之后。如果闭包是函数仅有的一个参数，你可以直接省略掉圆括号。 </p>

<pre><code>let sortedNumbers = numbers.sorted { $0 &gt; $1 }  
print(sortedNumbers)
</code></pre>

<h2 id="">类与对象</h2>

<p>用<code>class</code>加类名来创建一个类。一个类中属性的声明常量和变量方式是相同，只不过它被声明在一个类的上下文中。同样的，方法和函数的声明写法是一样的。</p>

<pre><code>class Shape {  
    var numberOfSides = 0
    func simpleDescription() -&gt;String {
        return "A shape with \(numberOfSides) sides."
    }
}
</code></pre>

<p>小测试：
用<code>let</code>定义一个常量并定义一个带有参数的方法。</p>

<pre><code>let letConstrant = 5  
func letConstant(parame:Int) -&gt;String {  
    return "B shape with\(letConstrant) sides."
}
</code></pre>

<p>通过在类名后面添加 一个<code>()</code>来创建一个类的实例。使用点语法访问这个实例的属性和方法。</p>

<pre><code>var shape = Shape()  
shape.numberOfSides = 7  
var shapeDescription = shape.simpleDescription()  
print(shape.simpleDescription(),"\n",shape.letConstant(parame:5))  
</code></pre>

<p>上面这个版本的<code>Shape</code>类少了一些重要的东西：当一个实例创建的时候应该有一个初始化方法用<code>init</code>方法来创建一个。</p>

<pre><code>class NamedShape {  
    var numberOfSides:Int = 0
    var name:String
    init(name:String) {
        self.name = name
    }
    func simpleDescription() -&gt; String {
        return "A shape with \(numberOfSides) sides."
    }    
}
</code></pre>

<p>注意观察在初始化方法中是怎么用<code>self</code>来区分出调用的<code>name</code>是参数还是属性。当你创建一个了类的实例的时候，初始化参数就能像函数一样被传递使用了。每一个属性就需要被分配一个值，不管它是声明中(比如说上面的变量<code>numberOfSides</code>)或者是初始化的时候(像上面的<code>name</code>)。
如果你想在对象被销毁前做一些处理工作，可以用<code>deinit</code>来创建一个析构器。
在每个子类的后面往往都跟着它所继承的父类，用冒号隔开。一个类对于继承任何标准的基类并不是必须的，所以你可以选择引用或者省略掉父类。
子类重写父类定义的方法的时候用关键词<code>override</code>来标记。这是为了防止意外被重写。如果没有<code>override</code>关键字，编译器会报错。编译器也会检测在子类override的方法是不是真的重写了父类的某个方法。</p>

<pre><code>class Square:NamedShape {

    var sideLength:Double
    init(sideLength:Double, name:String) {
        self.sideLength = sideLength
        super .init(name: name)
        numberOfSides = 4
    }
    func area() -&gt; Double {
        return sideLength * sideLength
    }

    override func simpleDescription() -&gt; String {
        return "A square with sides of length\(sideLength)."
    }

}
let test  = Square(sideLength: 5.2, name: "my test square")  
test.area()  
test.simpleDescription()  
</code></pre>

<p>小测试：
再定义一个继承自NamedShape的类Circle,在它的初始化方法中有一个半径和name的两个参数。在这个类中实现area和simpleDescription两个方法。</p>

<pre><code>class Circle:NamedShape {

    var radius:Double
    init(radius:Double,name:String) {
        self.radius = radius
        super.init(name: name)
        numberOfSides = 6
    }
    func area() -&gt; Double {
        return Double.pi * radius * radius
    }
    override func simpleDescription() -&gt; String {
        return "The radius of the circle is \(radius)."
    }

}
let testCircle = Circle(radius: 2, name: "the circle")  
testCircle.area()  
testCircle.simpleDescription()  
</code></pre>

<p>属性除了能够做简单的存储之外，还能够添加<code>getter</code>和<code>setter</code>方法。</p>

<pre><code>class EquilateralTriangle:NamedShape {

    var sideLength:Double = 0.0
    init(sideLength:Double,name:String) {
        self.sideLength = sideLength
        super.init(name: name)
        numberOfSides = 3
    }
    var perimter:Double {
        get {
            return 3.0 * sideLength
        }
        set {
            sideLength = newValue / 3.0
        }
    }
    override func simpleDescription() -&gt; String {
        return "An equilteral Triangle with sides of length\(sideLength)."
    }
}
var triangle = EquilateralTriangle (sideLength: 3.1, name: "a triangle")  
print(triangle.perimter)  
triangle.perimter = 9.9  
print(triangle.sideLength)  
</code></pre>

<p>在<code>perimter</code>的<code>setter</code>方法中，新值被隐式的命名为<code>newValue</code>,你可以在<code>setter</code>方法的圆括号内为新值重新命名。
我们注意到对<code>EquilateralTriangle</code>进行初始化的时候有以下三个步骤，</p>

<ul>
<li>对父类声明的属性进行赋值</li>
<li>初始化的时候调用父类的初始化方法</li>
<li>改变在父类定义的属性的值，一些额外的设置工作在调用方法以及 <code>getter</code>和<code>setter</code>方法的时候也会完成。
如果你并不是真的要计算一个属性的值，但是仍然需要添加相应的代码，用willset和didset来操作新值。这段代码会在初始化之外属性值发生任意改变的时候执行。例如：下面的类保证了三角形的每一条边的长度和正方形的每一条边的长度都相等。</li>
</ul>

<pre><code>class TriangleAndSquare {  
    var  triangle:EquilateralTriangle {
        willSet {
            square.sideLength = newValue.sideLength
        }
    }
    var  square:Square {
        willSet {
            triangle.sideLength = newValue.sideLength
        }
    }
    init(size:Double,name:String) {
        square = Square(sideLength: size, name: name)
        triangle = EquilateralTriangle(sideLength: size, name: name)
    }
}

var triangleAndSquare = TriangleAndSquare(size: 10, name: "another test shape")  
print(triangleAndSquare.square.sideLength)  
print(triangleAndSquare.triangle.sideLength)  
triangleAndSquare.square = Square(sideLength: 50, name: "larger square")  
print(triangleAndSquare.triangle.sideLength)
</code></pre>

<p>当值是可选的时候，你可以在一些操作之前添加<code>？</code>例如：使用方法、属性、下标的时候。如果在<code>?</code>之前值已经是<code>nil</code>，那么在<code>？</code>之后的所有操作都可以被忽视掉,整个表达式的结果就是<code>nil</code>。相反的，可选值将被展开(也就是说可选值是有值的)，那么在<code>？</code>之后的代码将会按照展开的值执行。综合以上两种情况，表达式的值是可选值。</p>

<pre><code>let optionalSquare: Square? = Square(sideLength: 2.5, name: "optional square")  
let sideWidth = optionalSquare?.sideLength  
</code></pre>

<h2 id="">枚举和结构体</h2>

<p>使用enum关键字来创建一个枚举，创建方式就像我们创建类和其他数据类型的数据一样，枚举里面可能会包含一些和枚举值相关的方法。</p>

<pre><code>enum Rank:Int {  
    case ace = 1
    case two,three,four,five,six,seven,eight,nine,ten
    case jack,queen,king
    func simpleDescription() -&gt; String {
        switch self {
        case .ace:
            return "ace"
        case .jack:
            return "jack"
        case .queen:
            return "queen"
        case .king:
            return "king"
        default:
            return String(self.rawValue)
        }
    }
}
let  ace = Rank.ace  
let aceRawValue = ace.rawValue  
</code></pre>

<p>小测试：
写一个函数从枚举<code>Rank</code>中取出两个枚举值比下他们的<code>rawValue</code></p>

<pre><code>func compareFromRang(_ a:Rank,_ b:Rank) -&gt; String{  
    if a.rawValue &gt; b.rawValue {
        return "\(a.simpleDescription())'s rawValue is the max than \(b.simpleDescription())'s"
    }else {
        return "\(a.simpleDescription())'s rawValue is the max than \(b.simpleDescription())'s"
    }
}
compareFromRang(Rank.jack,Rank.king)  
</code></pre>

<p>在swift中，枚举的默认值是从0开始，枚举中成员的值是自增的，但是你可以给成员自定义值。在上面的例子中，Ace就被明确的赋值为1，它之后的成员就是按照顺序自增的。你可以使用字符串或者浮点类型的值作为枚举的初始值。使用<code>rawValue</code>这个属性来访问枚举中成员变量的值。
你可以使用<code>init?(rawValue:)</code>这个初始化方法来获取枚举中某个成员的值。如果在<code>Rank</code>这个枚举中能够找到的话，返回相对应的值。如果找不到那么就返回<code>nil</code>。</p>

<pre><code>if  let convertedRank = Rank (rawValue: 3) {  
    let threeDescription = convertedRank.simpleDescription()
    print(threeDescription)

}
</code></pre>

<p>枚举中的每个case值都是实际的值，另外一种写法是给他们自己定义值。事实上，如果这个值没有特殊的意义，你没有必要重新为他们赋值。</p>

<pre><code>enum  Suit {  
    case spades,hearts,diamonds,clubs
    func simpleDescription() -&gt;String {
        switch self {
        case .spades:
            return "spades"
        case .hearts:
            return "hearts"
        case .diamonds:
            return "diamonds"
        case .clubs:
            return "clubs"
        }
    }
}

let hearts = Suit.hearts  
let heartsDescription = hearts.simpleDescription()  
</code></pre>

<p>小练习：
为上面的<code>Suit</code>枚举添加一个<code>color()</code>方法，使得 <code>spades</code>和<code>clubs</code> 返回<code>black</code> <code>hearts</code>和<code>diamonds</code>返回<code>red</code>。</p>

<pre><code>    func colorDescription()-&gt;UIColor {
        switch self {
        case .spades,.clubs:
            return UIColor.black
        case .hearts,.diamonds:
            return UIColor.red
        }
    }
</code></pre>

<p>在枚举外面调用</p>

<pre><code>let heartsColorDescription = hearts.colorDescription()
</code></pre>

<p>上面我们可以看出用两种方式引用了<code>hearts</code>这个枚举成员，当我们<code>hearts</code>这个从常量赋值的时候，枚举中的<code>Suit.hearts</code>需要用全名来引用(也就是说先找到枚举所属，然后使用它的成员变量)，因为常量没有显式的指定类型。在switch中，枚举成员使用它的缩写<code>.hearts</code>来引用，因在<code>suit</code>这个枚举中<code>self</code>是已知的类型。如果值的类型是已知的话可以使用缩写。
如果一个枚举有自己的初始值，这些值在他们声明的时候就已经确定了。那就意味着：不同枚举实例中相同成员变量都有一个相同的值。另外一种为枚举成员变量的值进行关联，那就是在创建枚举成员变量的时候确定的。这意味着枚举成员的关联值可能会不同。你可以把关联值理解为枚举成员的寄存属性。例如：可以考虑一下从服务端来获取日出和日落时间情况的请求。服务端会返回你正确的结果或者错误的描述信息。</p>

<pre><code>enum ServerResponse {  
    case result(String, String)
    case failure(String)
}
let success = ServerResponse.result("6:00 am", "8:09 pm")  
let failure = ServerResponse.failure("Out of cheese.")  
switch success {  
case let .result(sunrise, sunset):  
    print("Sunrise is at \(sunrise) and sunset is at \(sunset).")
case let .failure(message):  
    print("Failure ... \(message)")
}
</code></pre>

<p>小练习：
为上面的枚举<code>ServerResponse</code>添加第三个成员变量,并添加相应的<code>switch</code>语句。
注意一下日出和日落的时间是怎么从<code>ServerResponse</code>中获取到并且和<code>switch</code>中的<code>case</code> 相匹配的。
用<code>struct</code>创建一个结构体，结构体和类有很多相似之处，比如说方法和构造器。他们之间最大的区别就是结构体是用来传值的，而类是传的是引用。</p>

<pre><code>struct Card {  
    var rank:Rank
    var suit:Suit
    func simpleDescription() -&gt; String {
        return "The \(rank.simpleDescription()) of \(suit.simpleDescription())"
    }

}
let threeOfSpades = Card(rank: .three, suit: .spades)  
let threeOfSpadesDescription = threeOfSpades.simpleDescription()  
</code></pre>

<p>小练习：
给<code>Crad</code>添加一个方法，创建一副完整的扑克牌并把<code>rank</code>和<code>suit</code>对应起来。</p>

<h2 id="">协议和扩展</h2>

<p>用<code>protocol</code>来声明一个协议</p>

<pre><code>protocol ExampleProtocol {  
    var simpleDescription:String{get}
    mutating func adjust()
}
</code></pre>

<p>类、枚举和结构体都能够遵循并实现协议。</p>

<pre><code>class SimpleClass:ExampleProtocol {  
    var simpleDescription: String = "A very simple class."
    var anotherProperty:Int = 69105
    func  adjust() {
        simpleDescription += " Now 100% adjusted."
    }

}
var a  = SimpleClass()  
a.adjust()  
let aDescription = a.simpleDescription  
struct SimpleStructure:ExampleProtocol {  
    var simpleDescription: String = "A simple structure"
    mutating func adjust() {
        simpleDescription += "(adjusted)"
    }

}
var b = SimpleStructure ()  
b.adjust()  
let bDescription = b.simpleDescription  
</code></pre>

<p>小练习：写一个枚举遵守上面的这个协议。</p>

<pre><code>enum SimpleEnum:Int,ExampleProtocol {  
    case type,title
    var simpleDescription:String {
        switch self {
        case .type:
            return "A simple  enum  about  type"
        case .title:
            return "A simple  enum  about  title"
        }
    }
    func adjust() {
        print("this is enum")
    }
}
var c = SimpleEnum.type  
c.simpleDescription  
c.adjust()  
</code></pre>

<p>注意在声明<code>SimpleStructure</code>的时候使用关键字<code>mutating</code>来标记一个能改变结构体值的方法。<code>SimpleClass</code>的声明不需要用关键字来标记修改它的方法，因为类里的方法是可以一直发生改变的。
用<code>extension</code>延展来为现有的类添加功能，比如添加一些新的方法和可以用来计算的属性。你可以用延展来添加一个协议给一个在其他地方创建的类，甚至添加的类型可能是引用的一个library或者框架。</p>

<pre><code>extension Int:ExampleProtocol {

    var simpleDescription:String {
        return "The number \(self)"
    }
    mutating func adjust() {
        self += 42
    }
}
print(7.simpleDescription)  
</code></pre>

<p>小练习：
给<code>Double</code>类型的数据添加一个<code>absoluteValue</code>属性</p>

<pre><code>extension Double {  
    var absoluteValue:String {
        return "The Double number is \(self)"
    }
}
print(2.0.absoluteValue)  
</code></pre>

<p>你可以像使用其他命名类型一样来使用协议名，例如：创建一个具有不同类型的数据的集合，这些不同类型的数据都遵循同一个协议。当你要处理的类型是协议的值的时候，协议之外定义的方法是不可用的。</p>

<pre><code>let protocolValue:ExampleProtocol = a  
print(protocolValue.simpleDescription)  
// 打开注释你就会发现报错信息
//print(protocolValue.anotherProperty)
//Value of type 'ExampleProtocol' has no member 'anotherProperty'
</code></pre>

<p>尽管<code>protocolValue</code>这个变量在运行时的时候类型是<code>SimpleClass</code>,编辑器在编译的时候仍然把它的类型当<code>ExampleProtocol</code>。这就意味着你不能访问类在协议方法之外的属性或者方法。</p>

<h2 id="">错误处理</h2>

<p>你可以用任何遵循<code>Error</code>协议类型来表示错误。</p>

<pre><code>enum PrinterError:Error {  
    case outOfPaper
    case noToner
    case onFire
}
</code></pre>

<p>用<code>throw</code>这个关键字来抛出一个错误，并且用<code>throw</code>来标记一个可以抛出错误的一个函数。如果你在一个方法里抛出了一个错误，函数会立即返回并且会调用函数的代码进行处理错误。
处理error的方式有很多种，一种方式是用<code>do-catch</code>。在<code>do</code>这个代码块里面，在你需要标记的抛出错误的代码之前添加<code>try</code>。在<code>catch</code>的代码块里，错误会自动被命名<code>error</code>，当然除非你给错误指定一个新的名字。</p>

<pre><code>do {  
    let printerResponse = try send(job: 1040, toPrinter: "Bi sheng")
    print(printerResponse)
} catch  {
    print(error)
}
</code></pre>

<p>小练习：
改变打印者的名字为“Never Has Toner”,让<code>send(job:Toner)</code>抛出一个错误。</p>

<pre><code>do {  
    let printerResponse = try send(job: 1040, toPrinter: "Never Has Toner")
    print(printerResponse)
} catch  {
    print(error)
}
</code></pre>

<p>你可以自己定义多个<code>catch</code>代码块来处理错误。你可以在<code>catch</code>之后添加一种模式，用法可以和<code>switch</code>中使用成员变量<code>case</code>一样。</p>

<pre><code>do {  
    let printerResponse = try send(job: 1440, toPrinter: "Gutenberg")
    print(printerResponse)
} catch PrinterError.onFire {
    print("I'll just put this over here,with the rest of the fire")
} catch let printferError as PrinterError {
    print("Printer error:\(printferError)。")
} catch {
    print(error)
}
</code></pre>

<p>小练习：
在<code>do</code>代码块中添加抛异常的代码。你要抛出什么样的错误才能让第一个catch接收并处理？第二个 第三个呢？
对<code>send(job:Int,toPrinter printerName:String)</code>作补充就可以得到问题的答案了</p>

<pre><code>func send(job:Int,toPrinter printerName:String) throws-&gt; String {  
    if printerName == "Never Has Toner" {
        throw PrinterError.noToner
    } else if printerName == "Out Of Paper"
    {
        throw PrinterError.outOfPaper
    }
    else if printerName == "On fire" {
        throw PrinterError.onFire
    }
    return "Job sent"
}
</code></pre>

<p>另外一种处理错误的方式是使用<code>try?</code>关键字来转换结果为可选项。如果函数抛出了一个错误，错误将被忽略并且结果会被置为nil。
否则，结果就是一个包含了函数返回值的可选项。(这就是说如果抛出异常那么结果就是nil，如果不抛出异常那么结果就是一个带有一个返回值的可选项。)</p>

<pre><code>let printerSuccess = try?send(job: 1884, toPrinter: "Mergenthaler")  
let printerFailure = try?send(job: 1885, toPrinter:"Never Has Toner")  
</code></pre>

<p>使用<code>defer</code>代码块来表示在函数返回之前，函数执行的代码。这段代码会执行不管函数是否抛出错误。使用<code>defer</code>你可以将设置和清除的代码写在里面，尽管它们执行的时机可能完全不同。</p>

<pre><code>var fridgeIsOpen = false  
let fridgeContent = ["milk","eggs","leftovers"]  
func fridgeContains(_ food:String) -&gt; Bool {  
    fridgeIsOpen = true
    defer {
        fridgeIsOpen = false
    }
    let result = fridgeContent.contains(food)
    return result

}
fridgeContains("banana")  
print(fridgeIsOpen)  
</code></pre>

<h2 id="">泛型</h2>

<p>在尖括号里写一个名字来创建一个泛型函数或者泛型的类型。</p>

<pre><code>func makeArray&lt;Item&gt;(repeating item:Item, numberOfTimes:Int) -&gt; [Item] {  
    var result = [Item]()
    for _ in 0 ..&lt; numberOfTimes {
        result.append(item)
    }
    return result
}
makeArray(repeating: "knock", numberOfTimes: 4)  
</code></pre>

<p>你也可以创建泛型的函数和方法，在类、枚举和结构体里都可以使用。</p>

<pre><code>// Reimplement the Swift standard library's optional type
enum OptionalValue&lt;Wrapped&gt; {  
    case none
    case some(Wrapped)
}
var possibleInteger:OptionalValue&lt;Int&gt; = .none  
possibleInteger = .some(100)  
</code></pre>

<p>在类型名字的后面使用<code>where</code>来指定对类型的一系列的需求，例如：限定一个类型来执行一个协议，限定两个类型是相同的,指定一个类有一个特定的父类。</p>

<pre><code>func anyCommonElements&lt;T:Sequence,U:Sequence&gt;(_ lhs:T,_ rhs:U)-&gt;Bool  
    where T.Iterator.Element:Equatable,
    T.Iterator.Element ==
    U.Iterator.Element {
        for lhsItem in lhs {
            for rhsItem in rhs {
                if lhsItem == rhsItem {
                    return true
                }
            }
    }
    return false
}
anyCommonElements([1,2,3], [3])  
</code></pre>

<p>小练习：
修改<code>anyCommonElements(:)</code>函数来创建一个函数，返回一个数组。内容是两个序列里的公共元素。</p>

<pre><code>func anyCommonArrayElements&lt;T:Sequence,U:Sequence&gt;(_ lhs:T,_ rhs:U)-&gt;Array&lt;Any&gt;  
    where T.Iterator.Element:Equatable,
    T.Iterator.Element ==
    U.Iterator.Element {
        var  array = Array&lt;Any&gt;()
        for lhsItem in lhs {
            for rhsItem in rhs {
                if lhsItem == rhsItem {
                   array.append(lhsItem)
                }
            }
        }
        return array
}
anyCommonArrayElements([1,2,3], [2,3,4,5])  
</code></pre>

<p>上面我们所写的<code>&lt;T:Equatable&gt;</code>和<code>&lt;T&gt;...where T:Equatable</code> 是等价的。</p>]]></description><link>http://shavekevin.com/2018/05/25/swift4-chapter-one/</link><guid isPermaLink="false">69268d4e-c5bd-44a1-ba2d-3d7dc9299322</guid><category><![CDATA[swift]]></category><dc:creator><![CDATA[shavekevin]]></dc:creator><pubDate>Fri, 25 May 2018 09:41:41 GMT</pubDate></item><item><title><![CDATA[iOS面试旗开得胜之横扫千军篇(三)]]></title><description><![CDATA[<blockquote>
  <p>本文问题来自<a href='http://www.shavekevin.com/2017/12/17/mianshiwenti-deshengpian/' >iOS面试旗开得胜之问题篇</a> 中的横扫千军之战胜篇</p>
</blockquote>

<h2 id="7afnetworking">7.AFNetworking你使用过是哪几个版本？他们有什么区别？使用过程中应该注意哪些问题？</h2>

<p>       
AFNetworking支持HTTP请求和基于REST的网络服务（包括GET、POST、 PUT以及DELETE等），支持ARC。AFNetworking项目中还包含一些列单元测试。 <br />
       
AFNetworking 2.0开始使用NSURLConnection的基础API ，以及较新基于NSURLSession的API的选项。 <br />
       
AFNetworking 3.0现已完全基于NSURLSession的API，删除了了对 NSURLConnection的封装内容 <br />
       
这是因为NSURLSession能够完全替代NSURLConnection，并且具有很多优点:
- 支持后台运行的网络任务
- 暂停、停止、重启网络任务，不需要自己封装NSOperation
- 支持断点续传，异步下载
- 支持上传，异步上传
- 获取下载、上传的进度
注意:
       
3.0版本最低支持版本是从iOS7 <br />
       </p>

<h4 id="">废弃的类</h4>

<p>废弃对NSURLConnection的支持
被删除的类：
- AFURLConnectionOperation
- AFHTTPRequestOperation
- AFHTTPRequestOperationManager
       
用以替代的是下面的类:
- AFURLSessionManager
- AFHTTPSessionManager
       
进行修改的类：
- UIImageView+AFNetworking
- UIWebView+AFNetworking.h
- UIButton+AFNetworking.h
       </p>

<h5 id="afhttprequestoperationmanagerafhttpsessionmanager">如果你之前的开发是基于AFHTTPRequestOperationManager的网络请求现在你应该转到AFHTTPSessionManager下去进行.</h5>

<h5 id="uikit">UIKit的迁移</h5>

<p>图片下载已经被重构，以遵循AlamofireImage架构与新的AFImageDownloader类。这个类的图片下载职责的代理人是UIButton与UIImageView的类目，并且提供了一些方法，在必要时可以自定义。类别中，下载远程图片的实际方法没有改变。
       
UIWebView的类目被重构为使用AFHTTPSessionManager作为其网络请求。 <br />
       
UIAlertView的类目被废弃 <br />
从AFNetworking 3.0后UIAlertView的类目因过时而被废弃。并没有提供UIAlertController类目的计划，因为这是应用程序应处理的逻辑，而不是这个库。
       
下面进行新旧对比：
       
AFNetwork 2.x  </p>

<pre><code>AFHTTPRequestOperationManager *manager = [AFHTTPRequestOperationManager manager];  
 //设置网络请求超时时间
       [manager.requestSerializer willChangeValueForKey:@"timeoutInterval"];
       manager.requestSerializer.timeoutInterval = 30.0f;
       [manager.requestSerializer didChangeValueForKey:@"timeoutInterval"];
       [manager.requestSerializer setValue:@"application/x-www-form-urlencoded;" forHTTPHeaderField:@"Content-Type"];
       [self addHeaderParams:manager];
       manager.responseSerializer.acceptableContentTypes = [NSSet setWithObjects:@"application/json",@"text/html",nil];
       [manager POST:self.requestURL
       parameters:self.requestParams
       success:^(AFHTTPRequestOperation *operation, id responseObject) {
       
       (@"success-POST:%@",responseObject);
       
       }
       failure:^(AFHTTPRequestOperation *operation, NSError *error) {
       DebugLog(@"failurePOST:%@",error.description)
       }];
</code></pre>

<p>       
AFNetworking 3.x  </p>

<pre><code>AFHTTPSessionManager *manager = [AFHTTPSessionManager manager];  
       [manager.requestSerializer willChangeValueForKey:@"timeoutInterval"];
       manager.requestSerializer.timeoutInterval = 30.0f;//30.0f
       [manager.requestSerializer didChangeValueForKey:@"timeoutInterval"];
       [manager.requestSerializer setValue:@"application/x-www-form-urlencoded;" forHTTPHeaderField:@"Content-Type"];
       [self addHeaderParams:manager];
       manager.responseSerializer.acceptableContentTypes = [NSSet setWithObjects:@"application/json",@"text/html",nil];
       
       [manager POST:self.requestURL parameters:self.requestParams progress:nil success:^(NSURLSessionDataTask * _Nonnull task, id  _Nullable responseObject) {
       
       NSLog(@"success-POST:%@",responseObject);
       
       } failure:^(NSURLSessionDataTask * _Nullable task, NSError * _Nonnull error) {
       
       }];
</code></pre>

<p>       
即：每次开启一个网络请求时，首先新建一个AFHTTPSessionManager，然后将相关的requestSerializer和reponseSerializer赋值；最后发起相应的GET/POST等请求。
       
如果是直接采用NSURLSession来请求网络，写法如下：</p>

<pre><code>       NSURLSession *session =  [NSURLSession
       sessionWithConfiguration:
       [NSURLSessionConfiguration defaultSessionConfiguration]
       delegate:nil
       delegateQueue:[NSOperationQueue mainQueue]];
       
       NSURLSessionDataTask *dataTask = [session dataTaskWithRequest:request
       completionHandler:completionHandler];
       
       [dataTask resume];
</code></pre>

<p>       即：新建一个Session（多个请求要用共享的SessionManager/Session），然后新建task，激活task，完成网络请求。
       </p>

<h5 id="">共享原因：</h5>

<p>       共享的Session将会复用TCP的连接，而每次都新建Session的操作将导致每次的网络请求都开启一个TCP的三次握手,共享会提升网络速度
       
AFNetworking 3.x 提供的post方法：  </p>

<pre><code>       [manager POST: parameters: constructingBodyWithBlock: progress: success: failure:]
       
       [manager POST: parameters: progress: success: failure:]
</code></pre>

<p>       不建议使用的方法：</p>

<pre><code>       [manager POST: parameters: success: failure:];
       [manager POST: parameters: constructingBodyWithBlock: success: failure:]
</code></pre>

<h4 id="afnetworking">AFNetworking缓存</h4>

<p>AFNetworking实际上使用了两个独立的缓存机制： <br />
       
(1)AFImagecache：一个提供图片内存缓存的类，2.x时继承自NSCache，3.x不再使用NSCache。AFImagecache3.x之前存在于UIImageView+AFNetwork，之后存在于AFAutoPurgingImageCache中。
       
(2)NSURLCache：仍使用原生缓存机制：NSURLCache。NSURLConnection’s默认的URL缓存机制，用于存储NSURLResponse对象：一个默认缓存在内存，通过配置可以缓存到磁盘的类。NSURLCache对每个NSURLRequest对象都会遵守缓存策略（NSURLRequestCachePolicy）。
       
注意：NSCache与NSURLCache没有任何关系
       
3.0之前的缓存方法：存在于AFURLConnectionOperation类文件中。 <br />
       </p>

<pre><code>- (void)setCacheResponseBlock:(NSCachedURLResponse * (^)(NSURLConnection *connection, NSCachedURLResponse *cachedResponse))block;
</code></pre>

<p>3.0之后：在类AFURLSessionManager中  </p>

<pre><code>- (void)setDataTaskWillCacheResponseBlock:(nullable NSCachedURLResponse * (^)(NSURLSession *session, NSURLSessionDataTask *dataTask, NSCachedURLResponse *proposedResponse))block;
</code></pre>

<p>苹果系统缓存存储策略：</p>

<pre><code>{
       NSURLCacheStorageAllowed,  //默认，可以存在内存（重启设备清除），可以存储磁盘（代码清除）
       NSURLCacheStorageAllowedInMemoryOnly,
       NSURLCacheStorageNotAllowed,
       } NSURLCacheStoragePolicy;
</code></pre>

<p>请求缓存策略：</p>

<pre><code>{
       NSURLRequestUseProtocolCachePolicy = 0, //默认策略
       NSURLRequestReloadIgnoringLocalCacheData = 1,//忽略本地缓存，从源加载
       NSURLRequestReloadIgnoringLocalAndRemoteCacheData = 4, // 忽略本地&amp;服务器缓存，从源加载
       NSURLRequestReloadIgnoringCacheData = NSURLRequestReloadIgnoringLocalCacheData,
       NSURLRequestReturnCacheDataElseLoad = 2,  //先从缓存加载，如果没有缓存，从源加载
       NSURLRequestReturnCacheDataDontLoad = 3,  //离线模式，加载缓存数据（无论是否过期），不从源加载
       NSURLRequestReloadRevalidatingCacheData = 5 // 存在的缓存数据先确认有效性，无效的话从源加载
       };
       typedef NSUInteger NSURLRequestCachePolicy;
</code></pre>

<p>清除所有的URL缓存Response：</p>

<pre><code>       [[NSURLCache sharedURLCache] removeAllCachedResponses];
</code></pre>

<h2 id="8">8.谈谈你对算法的理解，在工作中你都应用了哪些算法来解决问题</h2>

<h2 id="">基础算法</h2>

<h4 id="">快速排序算法</h4>

<p>快速排序是由东尼·霍尔所发展的一种排序算法。在平均状况下，排序 n 个项目要Ο(n log n)次比较。在最坏状况下则需要Ο(n2)次比较，但这种状况并不常见。事实上，快速排序通常明显比其他Ο(n log n) 算法更快，因为它的内部循环（inner loop）可以在大部分的架构上很有效率地被实现出来。
       
快速排序使用分治法（Divide and conquer）策略来把一个串行（list）分为两个子串行（sub-lists）。
       
算法步骤：
       
1 从数列中挑出一个元素，称为 “基准”（pivot）， <br />
2 重新排序数列，所有元素比基准值小的摆放在基准前面，所有元素比基准值大的摆在基准的后面（相同的数可以到任一边）。在这个分区退出之后，该基准就处于数列的中间位置。这个称为分区（partition）操作。 <br />
3 递归地（recursive）把小于基准值元素的子数列和大于基准值元素的子数列排序。 <br />
 递归的最底部情形，是数列的大小是零或一，也就是永远都已经被排序好了。虽然一直递归下去，但是这个算法总会退出，因为在每次的迭代（iteration）中，它至少会把一个元素摆到它最后的位置去。
<img src='http://shavekevin.com/content/images/2018/Mar/----.gif'  alt="快速排序.gif" />
       </p>

<h4 id="">堆排序算法</h4>

<p>堆排序（Heapsort）是指利用堆这种数据结构所设计的一种排序算法。堆积是一个近似完全二叉树的结构，并同时满足堆积的性质：即子结点的键值或索引总是小于（或者大于）它的父节点。
堆排序的平均时间复杂度为Ο(nlogn) 。
       
 算法步骤：
1.创建一个堆H[0..n-1] <br />
2.把堆首（最大值）和堆尾互换 <br />
3. 把堆的尺寸缩小1，并调用shift_down(0),目的是把新的数组顶端数据调整到相应位置 <br />
4. 重复步骤2，直到堆的尺寸为1 <br />
<img src='http://shavekevin.com/content/images/2018/Mar/-----.gif'  alt="堆排序算法.gif" />
       </p>

<h4 id="">归并排序</h4>

<p>归并排序（Merge sort，台湾译作：合并排序）是建立在归并操作上的一种有效的排序算法。该算法是采用分治法（Divide and Conquer）的一个非常典型的应用。
       
算法步骤：
1. 申请空间，使其大小为两个已经排序序列之和，该空间用来存放合并后的序列 <br />
2. 设定两个指针，最初位置分别为两个已经排序序列的起始位置 <br />
3. 比较两个指针所指向的元素，选择相对小的元素放入到合并空间，并移动指针到下一位置 <br />
4. 重复步骤3直到某一指针达到序列尾 <br />
5. 将另一序列剩下的所有元素直接复制到合并序列尾 <br />
<img src='http://shavekevin.com/content/images/2018/Mar/-----1.gif'  alt="归并算法.gif" />
       </p>

<h4 id="">二分查找算法</h4>

<p>二分查找算法是一种在有序数组中查找某一特定元素的搜索算法。搜素过程从数组的中间元素开始，如果中间元素正好是要查找的元素，则搜素过程结束；如果某一特定元素大于或者小于中间元素，则在数组大于或小于中间元素的那一半中查找，而且跟开始一样从中间元素开始比较。如果在某一步骤数组为空，则代表找不到。这种搜索算法每一次比较都使搜索范围缩小一半。折半搜索每次把搜索区域减少一半，时间复杂度为Ο(logn) 。
       </p>

<h2 id="">加密算法原理</h2>

<h4 id="md5">MD5加密</h4>

<p>MD5加密是最常用的加密方法之一，是从一段字符串中通过相应特征生成一段32位的数字字母混合码。 <br />
       
       
MD5主要特点是 不可逆，相同数据的MD5值肯定一样，不同数据的MD5值不一样（也不是绝对的，但基本是不能一样的）。MD5算法还具有以下性质：</p>

<p>1、压缩性：任意长度的数据，算出的MD5值长度都是固定的。 <br />
2、容易计算：从原数据计算出MD5值很容易。 <br />
3、抗修改性：对原数据进行任何改动，哪怕只修改1个字节，所得到的MD5值都有很大区别。 <br />
4、弱抗碰撞：已知原数据和其MD5值，想找到一个具有相同MD5值的数据（即伪造数据）是非常困难的。 <br />
5、强抗碰撞：想找到两个不同的数据，使它们具有相同的MD5值，是非常困难的。 <br />
MD5虽然说是不可逆的，但是由于有网站<a href='http://www.cmd5.com/' 的存在，专门用来查询MD5码'>http://www.cmd5.com的存在，专门用来查询MD5码</a> 所以有的简单的MD5码是可以在这里搜到源码的。为了让MD5码更加安全 涌现了很多其他方法 如加盐。 盐要足够长足够乱 得到的MD5码就很难查到。 <br />
       </p>

<h4 id="sha1">SHA1加密</h4>

<p>安全哈希算法（Secure Hash Algorithm）主要适用于数字签名标准（Digital Signature Standard DSS）里面定义的数字签名算法（Digital Signature Algorithm DSA）。对于长度小于2^64位的消息，SHA1会产生一个160位的消息摘要。当接收到消息的时候，这个消息摘要可以用来验证数据的完整性。在传输的过程中，数据很可能会发生变化，那么这时候就会产生不同的消息摘要。
       
SHA1有如下特性：不可以从消息摘要中复原信息；两个不同的消息不会产生同样的消息摘要。  </p>

<h4 id="hmac">HMAC加密</h4>

<p>此加密方法需要先生成密钥，然后再对密码进行MD5和HMAC加密，数据库中需要存放当时使用的密钥和密码加密后的密文，在用户登陆时 再次对填入的密码用密钥进行加密 并且还要加上当前时间（精确到分钟） 再次HMAC加密，服务器里也会拿出以前存放的密文加上时间再次加密。所以就算黑客在中途截取了密码的密文 也在能在1分钟只能破译才能有效，大大加强了安全性。服务器为了考虑到网络的延迟一般会多算一种答案，如23分过来的密码 他会把23分和22分的都算一下和用户匹配只要对上一个就允许登陆。
       </p>

<h4 id="base64">base64加密</h4>

<p>在MIME格式的电子邮件中，base64可以用来将binary的字节序列数据编码成ASCII字符序列构成的文本。使用时，在传输编码方式中指定base64。使用的字符包括大小写字母各26个，加上10个数字，和加号“+”，斜杠“/”，一共64个字符，等号“=”用来作为后缀用途。
       
完整的base64定义可见RFC 1421和RFC 2045。编码后的数据比原始数据略长，为原来的4/3。
       </p>

<h4 id="">对称加密算法</h4>

<p>优点：算法公开、计算量小、加密速度快、加密效率高、可逆
缺点：双方使用相同钥匙，安全性得不到保证
现状：对称加密的速度比公钥加密快很多，在很多场合都需要对称加密，相较于DES和3DES算法而言，AES算法有着更高的速度和资源使用效率，安全级别也较之更高了，被称为下一代加密标准
nECB ：电子代码本，就是说每个块都是独立加密的nCBC ：密码块链，使用一个密钥和一个初始化向量 (IV)对数据执行加密转换 <br />
ECB和CBC区别：CBC更加复杂更加安全，里面加入了8位的向量（8个0的话结果等于ECB）。在明文里面改一个字母，ECB密文对应的那一行会改变，CBC密文从那一行往后都会改变。 <br />
       </p>

<h4 id="rsa">RSA加密</h4>

<p>RSA非对称加密算法 <br />
非对称加密算法需要两个密钥：公开密钥（publickey）和私有密钥（privatekey）公开密钥与私有密钥是一对，如果用公开密钥对数据进行加密，只有用对应的私有密钥才能解密；如果用私有密钥对数据进行加密，那么只有用对应的公开密钥才能解密
特点：非对称密码体制的特点：算法强度复杂、安全性依赖于算法与密钥，但是由于其算法复杂，而使得加密解密速度没有对称加密解密的速度快，对称密码体制中只有一种密钥，并且是非公开的，如果要解密就得让对方知道密钥。所以保证其安全性就是保证密钥的安全，而非对称密钥体制有两种密钥，其中一个是公开的，这样就可以不需要像对称密码那样传输对方的密钥了
基本加密原理：
(1)找出两个“很大”的质数：P &amp; Q
(2)N = P * Q
(3)M = (P – 1) * (Q – 1)
(4)找出整数E，E与M互质，即除了1之外，没有其他公约数
(5)找出整数D，使得E*D除以M余1，即 (E * D) % M = 1
       
经过上述准备工作之后，可以得到：E是公钥，负责加密D是私钥，负责解密N负责公钥和私钥之间的联系加密算法，假定对X进行加密(X ^ E) % N = Yn根据费尔马小定义，根据以下公式可以完成解密操作(Y ^ D) % N = X
但是RSA加密算法效率较差，对大型数据加密时间很长，一般用于小数据。常用场景：分部要给总部发一段报文，先对报文整个进行MD5得到一个报文摘要，再对这个报文摘要用公钥加密。然后把报文和这个RSA密文一起发过去。总部接收到报文之后要先确定报文是否在中途被人篡改，就先把这个密文用私钥解密得到报文摘要，再和整个报文MD5一下得到的报文摘要进行对比 如果一样就是没被改过。
       </p>

<h2 id="9reactnativeweexwebnativeapphybridapp">9.谈谈你对React Native 、weex、Web、NativeAPP 和Hybrid APP 的看法</h2>

<h4 id="nativeapp">Native APP开发模式</h4>

<ul>
<li>这种模式指的是用原生 API 开发APP，就是说用Objective-C、swift 开发iOS。
<ul><li>优点：
<ul><li>用户体验最佳，优质的用户界面，华丽的交互。</li>
<li>不同平台提供不同体验。</li>
<li>节省带宽成本。</li>
<li>可访问本地资源，打开速度快。</li></ul></li>
<li>缺点：
<ul><li>维护多个版本的成本比较高。</li>
<li>开发成本比较大。</li>
<li>盈利需要与第三方分成。</li>
<li>AppStore 审核机制比较麻烦。
       </li></ul></li></ul></li>
</ul>

<h4 id="webapp">Web App（网页应用）</h4>

<ul>
<li>指使用Html开发的移动端网页App,类似微信小程序，整个App都是网页。
<ul><li>优点:
<ul><li>用户不需要安装，不会占用手机内存。</li>
<li>开发成本低，维护更新简单。</li>
<li>跨平台好。</li></ul></li>
<li>缺点:
<ul><li>用户体验不好，不能离线，必须联网。</li>
<li>现在纯web的App 貌似不能上AppStore。
       </li></ul></li></ul></li>
</ul>

<h4 id="hybridapp">Hybrid APP</h4>

<ul>
<li>混合开发模式，原生Api+Html共同开发，比如iOS,用html写好界面，用UIWebView展示。
       
<ul><li>多 View 混合型
   这种模式主要特点是将webview作为Native中的一个view组件,当需要的时候在独立运行显示,也就是说主体是Native,web技术只是起来一些补充作用.`这种模式几乎就是原生开发,没有降低什么难度,到了16年几乎已经没人使用了
       </li>
<li>单View混合型
这种模式是在同一个view内,同时包括Native view和webview(互相之间是层叠的关系),比如一些应用会用H5来加载百度地图作为整个页面的主体内容,然后再webview之上覆盖一些原生的view,比如搜索什么的.这种模式开发完成后体验较好,但是开发成本较大,一般适合一些原生人员使用
       </li></ul></li>
<li><p>Web主体型</p>

<p>这种模式算是传统意义上的Hybrid开发,很多Hybrid框架都是基于这种模式的,比如PhoneGap,AppCan,Html5+等
这种模式的一个最大特点是,Hybrid框架已经提供各种api,打包工具,调试工具,然后实际开发时不会使用到任何原生技术,实际上只会使用H5和js来编写,然后js可以调用原生提供的api来实现一些拓展功能。往往程序从入口页面,到每一个功能都是h5和js完成的。
       
理论上来说,这种模式应该是最佳的一种模式(因为用H5和js编写最为快速,能够调用原生api,功能够完善),但是由于一些webview自身的限制,导致了这种模式在性能上损耗不小,包括在一些内存控制上的不足,所以导致体验要逊色于原生不少。
       
当然了,如果能解决体验差问题,这种模式应当是最优的(比如由于iOS对H5支持很好,iOS上的体验就很不错)。
       </p></li>
<li><p>多主体共存型（灵活型）</p>

<p>这种模式的存在是为了解决web主体型的不足,这种模式的一个最大特点是,原生开发和h5开发共存,也就是说,对于一些性能要求很高的页面模块,用原生来完成,对于一些通用型模块,用h5和js来完成.</p>

<p>这种模式通用有跨平台特性,而且用户体验号,性能高,不逊色与原生,但是有一个很大的限制就是,采用这种模式需要一定的技术前提
也就是说这种模式不同于web主体型可以直接用第三方框架,这种模式一般是一些有技术支持的公司自己实现的,包括H5和原生的通信,原生API提供,容器的一些处理全部由原生人员来完成,所以说,使用这种技术的前提是得有专业的原生人员(包括Android,iOS)以及业务开发人员(原生开发负责功能,前端解决简单通用h5功能)
       
当然了,如果技术上没有问题,用这种方案开发出来的App体验是很好的,而且性能也不逊色原生,所以是一种很优的方案</p></li>
<li>Hybrid app 基本原理：通过JSBridge,H5页面可以调用Native的api,Native也可调用H5页面的方法或者通知H5页面回调，如图：
<img src='http://shavekevin.com/content/images/2018/Mar/Hybrid-----.jpg'  alt="Hybrid基本原理图.jpg" />
       </li>
</ul>

<h4 id="reactnative">React Native</h4>

<ul>
<li><p>Facebook发起的开源的一套新的APP开发方案,使用JS+部分原生语法来实现功能。初次学习成本较高,但是在入门后,经过良好的封装也能够实现大部分的跨平台。而且体验很好。</p></li>
<li><p>优点：</p>

<ul><li>虽然说开发成本大于Hybrid模式,但是小于原生模式,大部分代码可复用。</li>
<li>性能体验高于Hybrid,不逊色与原生。</li>
<li>开发人员单一技术栈,一次学习,跨平台开发。</li>
<li>社区繁荣,遇到问题容易解决。</li></ul></li>
<li><p>缺点：</p>

<ul><li>虽然可以部分跨平台,但并不是Hybrid中的一次编写,两次运行那种,而是不同平台代码有所区别。</li>
<li>开发人员学习有一定成本。无法像Hybrid模式一样平滑。
       </li></ul></li>
</ul>

<h3 id="reactnativeweex">React Native 和 Weex 的原理。</h3>

<ul>
<li>React Native原理其实跟Weex差不多，底层也会把React转换为原生API。</li>
<li>React Native和Weex区别在于跨平台上面，Weex只要写一套代码，React Native需要iOS,安卓都写，说明React Native底层解析原生API是分开实现的，iOS一套，安卓一套。
       </li>
<li>React Native会在一开始生成OC模块表，然后把这个模块表传入JS中，JS参照模块表，就能间接调用OC的代码。
相当于买了一个机器人（OC），对应一份说明书（模块表），用户（JS）参照说明书去执行机器人的操作。
       </li>
</ul>

<h4 id="reactnativehttpswwwjianshucomp5cc61ec04b39">了解更多 React Native 请<a href='https://www.jianshu.com/p/5cc61ec04b39' >点击这里</a></h4>

<h4 id="weexhttpweexapacheorgcnwiki">了解更多 Weex 请<a href='http://weex.apache.org/cn/wiki/' >点击这里</a></h4>

<h4 id="">没有说哪种方式最好，只能根据需求以及时机来选择符合当前业务的开发方式是最好的。</h4>

<p>QQ技术交流群：214541576 </p>

<p>微信公众号：shavekevin</p>

<p>开发者头条：
<img src='http://shavekevin.com/content/images/2018/Mar/iOS------1.jpg'  alt="" /></p>

<p>热爱生活，分享快乐。好记性不如烂笔头。多写，多记，多实践，多思考。    </p>]]></description><link>http://shavekevin.com/2018/03/21/mianshihengsaoqianjun-partthree/</link><guid isPermaLink="false">a044aac8-8699-4d9b-b366-4f637ac01508</guid><category><![CDATA[面试胸有成竹]]></category><dc:creator><![CDATA[shavekevin]]></dc:creator><pubDate>Wed, 21 Mar 2018 01:15:29 GMT</pubDate></item><item><title><![CDATA[iOS面试旗开得胜之横扫千军篇(二)]]></title><description><![CDATA[<blockquote>
  <p>本文问题来自<a href='http://www.shavekevin.com/2017/12/17/mianshiwenti-deshengpian/' >iOS面试旗开得胜之问题篇</a> 中的横扫千军之战胜篇</p>
</blockquote>

<h2 id="5">5.请你谈谈你对视频播放器/直播的理解。如果封装一个视频播放器你会怎么做？封装中遇到哪些问题？你是怎么解决的？</h2>

<h4 id="">视频播放器/直播的理解：</h4>

<ul>
<li><p>流媒体协议：
常用的流媒体协议有以下几种：</p></li>
<li><p>实时传输协议 RTP 与 RTCP
RTP(Real-time Transport Protocol) 是用于 Internet 上针对多媒体数据流的一种传输协议。RTP 由两个紧密连接部分组成：  </p></li>
<li>RTP：传送具有实时属性的数据。</li>
<li>RTP 控制协议（RTCP）：监控服务质量并传送正在进行的会话参与者的相关信息。</li>
</ul>

<p>RTP 协议是建立在 UDP 协议上的。RTP 本身并没有提供按时发送机制或其它服务质量（QoS）保证，它依赖于低层服务去实现这一过程。 RTP 并不保证传送或防止无序传送，也不确定底层网络的可靠性。 RTP 实行有序传送， RTP 中的序列号允许接收方重组发送方的包序列，同时序列号也能用于决定适当的包位置，例如：在视频解码中，就不需要顺序解码。</p>

<p>实时传输控制协议（Real-time Transport Control Protocol,RTCP）是实时传输协议（RTP）的一个姐妹协议。RTCP为RTP媒体流提供信道外控制。RTCP定期在流多媒体会话参加者之间传输控制数据。RTCP的主要功能是为RTP所提供的服务质量提供反馈。RTCP收集相关媒体连接的统计信息，例如：传输字节数，传输分组数，丢失分组数，时延抖动，单向和双向网络延迟等等。网络应用程序可以利用RTCP所提供的信息试图提高服务质量，比如限制信息流量或改用压缩比较小的编解码器。RTCP本身不提供数据加密或身份认证，其伴生协议SRTCP（安全实时传输控制协议）则可用于此类用途。</p>

<ul>
<li>实时流协议RTSP
RTSP（Real Time Streaming Protocol）是由Real Networks和Netscape共同提出的。该协议定义了一对多应用程序如何有效地通过IP网络传送多媒体数据。RTSP提供了一个可扩展框架，使实时数据，如音频与视频的受控、点播成为可能。数据源包括现场数据与存储在剪辑中的数据。该协议目的在于控制多个数据发送连接，为选择发送通道，如UDP、多播UDP与TCP提供途径，并为选择基于RTP上发送机制提供方法。</li>
</ul>

<p>RTSP（Real Time Streaming Protocol）是用来控制声音或影像的多媒体串流协议，并允许同时多个串流需求控制，传输时所用的网络通讯协定并不在其定义的范围内，服务器端可以自行选择使用TCP或UDP来传送串流内容，它的语法和运作跟HTTP 1.1类似，但并不特别强调时间同步，所以比较能容忍网络延迟。而前面提到的允许同时多个串流需求控制（Multicast），除了可以降低服务器端的网络用量，更进而支持多方视讯会议（Video Conference）。 因为与HTTP1.1的运作方式相似，所以代理服务器《Proxy》的快取功能《Cache》也同样适用于RTSP，并因RTSP具有重新导向功能，可视实际负载情况来转换提供服务的服务器，以避免过大的负载集中于同一服务器而造成延迟。</p>

<h4 id="rtsprtp">RTSP 和RTP的关系 ：</h4>

<p>RTP不象http和ftp可完整的下载整个影视文件，它是以固定的数据率在网络上发送数据，客户端也是按照这种速度观看影视文件，当影视画面播放过后，就不可以再重复播放，除非重新向服务器端要求数据。</p>

<p>RTSP与RTP最大的区别在于：RTSP是一种双向实时数据传输协议，它允许客户端向服务器端发送请求，如回放、快进、倒退等操作。当然，RTSP可基于RTP来传送数据，还可以选择TCP、UDP、组播UDP等通道来发送数据，具有很好的扩展性。它时一种类似与http协议的网络应用层协议。</p>

<ul>
<li>实时消息传输协议RTMP/RTMPS</li>
<li>RTMP(Real Time Messaging Protocol)实时消息传送协议是Adobe Systems公司为Flash播放器和服务器之间音频、视频和数据传输 开发的开放协议。
它有三种变种：
（1)工作在TCP之上的明文协议，使用端口1935。
（2)TMPT封装在HTTP请求之中，可穿越防火墙。
（3)RTMPS类似RTMPT，但使用的是HTTPS连接。</li>
</ul>

<p>RTMP协议(Real Time Messaging Protocol)是被Flash用于对象,视频,音频的传输.这个协议建立在TCP协议或者轮询HTTP协议之上.。 <br />
      RTMP协议就像一个用来装数据包的容器,这些数据既可以是AMF格式的数据,也可以是FLV中的视/音频数据.一个单一的连接可以通过不同的通道传输多路网络流.这些通道中的包都是按照固定大小的包传输的。
      
      RTMP视频播放的特点：
      
      （1）RTMP协议是采用实时的流式传输，所以不会缓存文件到客户端，这种特性说明用户想下载RTMP协议下的视频是比较难的；
      
      （2）视频流可以随便拖动，既可以从任意时间点向服务器发送请求进行播放，并不需要视频有关键帧。相比而言，HTTP协议下视频需要有关键帧才可以随意拖动。
      
      （3）RTMP协议支持点播/回放（通俗点将就是支持把flv,f4v,mp4文件放在RTMP服务器，客户端可以直接播放），直播（边录制视频边播放）。
      
      RTMP环境的架设：
      
      因为该协议是adobe公司开发的，所以最初服务器端架设的环境是FMS(Flash Media Server)，该软件为收费软件，价格昂贵。后来，开源软件red5的推出，使rtmp协议的架设成本大大缩小，但是在性能方面不如fms的稳定。此外，wowza虽然是收费的，但价格比较适中。
      
      - 微软媒体服务器协议MMS
      
      MMS（Microsoft Media Server Protocol）是用来访问并流式接收Window Media服务器中.asf文件的一种协议。MMS协议用于访问Windows Media发布点上的单播内容。MMS是连接Windows Media单播服务的默认方法。若观众在Windows Media Player中键入一个URL以连接内容，而不是通过超级链接访问内容，则他们必须是MMS协议引用该流。MMS的预设端口是1755。
      
      - HLS
      
       HTTP Live Streaming（HLS）是苹果公司(Apple Inc.)实现的基于HTTP的流媒体传输协议，可实现流媒体的直播和点播，主要应用在iOS系统，为iOS设备（如iPhone、iPad）提供音视频直播和点播方案。HLS点播，基本上就是常见的分段HTTP点播，不同在于，它的分段非常小。
       
       相对于常见的流媒体直播协议，例如RTMP协议、RTSP协议、MMS协议等，HLS直播最大的不同在于，直播客户端获取到的，并不是一个完整的数据流。HLS协议在服务器端将直播数据流存储为连续的、很短时长的媒体文件（MPEG-TS格式），而客户端则不断的下载并播放这些小文件，因为服务器端总是会将最新的直播数据生成新的小文件，这样客户端只要不停的按顺序播放从服务器获取到的文件，就实现了直播。由此可见，基本上可以认为，HLS是以点播的技术方式来实现直播。由于数据通过HTTP协议传输，所以完全不用考虑防火墙或者代理的问题，而且分段文件的时长很短，客户端可以很快的选择和切换码率，以适应不同带宽条件下的播放。不过HLS的这种技术特点，决定了它的延迟一般总是会高于普通的流媒体直播协议。
       
       - 编解码
       
       编解码器（codec）指的是一个能够对一个信号或者一个数据流进行变换的设备或者程序。这里指的变换既包括将 信号或者数据流进行编码（通常是为了传输、存储或者加密）或者提取得到一个编码流的操作，也包括为了观察或者处理从这个编码流中恢复适合观察或操作的形式的操作。编解码器经常用在视频会议和流媒体等应用中。
       
       - H.264
       是由ITU-T视频编码专家组（VCEG）和ISO/IEC动态图像专家组MPEG）联合组成的联合视频组（JVT，Joint Video Team）提出的高度压缩数字视频编解码器标准。这个标准通常被称之为H.264/AVC（或者AVC/H.264或者H.264/MPEG-4 AVC或MPEG-4/H.264 AVC）而明确的说明它两方面的开发者。
       
       - 优势：
       1．低码率（Low Bit Rate）：和MPEG2和MPEG4 ASP等压缩技术相比，在同等图像质量下，采用H.264技术压缩后的数据量只有MPEG2的1/8，MPEG4的1/3。
       2．高质量的图像：H.264能提供连续、流畅的高质量图像（DVD质量）。
       3．容错能力强：H.264提供了解决在不稳定网络环境下容易发生的丢包等错误的必要工具。
       4．网络适应性强：H.264提供了网络抽象层（Network Abstraction Layer），使得H.264的文件能容易地在不同网络上传输（例如互联网，CDMA，GPRS，WCDMA，CDMA2000等）。
       
       - 特点：
       1．更高的编码效率：同H.263等标准的特率效率相比，能够平均节省大于50%的码率。
       2．高质量的视频画面：H.264能够在低码率情况下提供高质量的视频图像，在较低带宽上提供高质量的图像传输是H.264的应用亮点。
       3．提高网络适应能力：H.264可以工作在实时通信应用（如视频会议）低延时模式下，也可以工作在没有延时的视频存储或视频流服务器中。
       4．采用混合编码结构：同H.263相同，H.264也使用采用DCT变换编码加DPCM的差分编码的混合编码结构，还增加了如多模式运动估计、帧内预测、多帧预测、基于内容的变长编码、4x4二维整数变换等新的编码方式，提高了编码效率。
       5．H.264的编码选项较少：在H.263中编码时往往需要设置相当多选项，增加了编码的难度，而H.264做到了力求简洁的“回归基本”，降低了编码时复杂度。
       6．H.264可以应用在不同场合：H.264可以根据不同的环境使用不同的传输和播放速率，并且提供了丰富的错误处理工具，可以很好的控制或消除丢包和误码。
       7．错误恢复功能：H.264提供了解决网络传输包丢失的问题的工具，适用于在高误码率传输的无线网络中传输视频数据。
       8．较高的复杂度：264性能的改进是以增加复杂性为代价而获得的。据估计，H.264编码的计算复杂度大约相当于H.263的3倍，解码复杂度大约相当于H.263的2倍。
       
       - FFmpeg
       是一套可以用来记录、转换数字音频、视频，并能将其转化为流的开源计算机程序。采用LGPL或GPL许可证。它提供了录制、转换以及流化音视频的完整解决方案。它包含了非常先进的音频/视频编解码库libavcodec，为了保证高可移植性和编解码质量，libavcodec里很多code都是从头开发的。
       
       - 功能
       多媒体视频处理工具FFmpeg有非常强大的功能包括视频采集功能、视频格式转换、视频抓图、给视频加水印等。ffmpeg视频采集功能非常强大，不仅可以采集视频采集卡或USB摄像头的图像，还可以进行屏幕录制，同时还支持以 RTP 方式将视频流传送给支持 RTSP 的流媒体服务器，支持直播应用。
       
       - AAC
       全称Advanced Audio Coding，是一种专为声音数据设计的文件压缩格式。与MP3不同，它采用了全新的算法进行编码，更加高效，具有更高的“性价比”。利用AAC格式，可使人感觉声音质量没有明显降低的前提下，更加小巧。苹果ipod、诺基亚手机支持AAC格式的音频文件。相对于mp3，AAC格式的音质更佳，文件更小。
       - 优点
       1、提升的压缩率：可以以更小的文件大小获得更高的音质。
       2、支持多声道：可提供最多48个全音域声道。
       3、更高的解析度：最高支持96KHz的采样频率。
       4、提升的解码效率：解码播放所占的资源更少
       - 不足
       AAC属于有损压缩的格式，与时下流行的APE、FLAC等无损格式相比音质存在“本质上”的差距。加之，传输速度更快的USB3.0和16G以上大容量MP3正在加速普及，也使得AAC头上“小巧”的光环不复存在。
       
       - HLS分段生成策略及m3u8索引文件
       m3u8，是HTTP Live Streaming直播的索引文件。m3u8基本上可以认为就是.m3u格式文件，区别在于，m3u8文件使用UTF-8字符编码。</p>

<pre><code>       #EXTM3U                     m3u文件头，必须放在第一行
       #EXT-X-MEDIA-SEQUENCE       第一个TS分片的序列号
       #EXT-X-TARGETDURATION       每个分片TS的最大的时长
       #EXT-X-ALLOW-CACHE          是否允许cache
       #EXT-X-ENDLIST              m3u8文件结束符
       #EXTINF                     extra info，分片TS的信息，如时长，带宽等
</code></pre>

<p>       - HLS的分段策略，基本上推荐是10秒一个分片，当然，具体时间还要根据分好后的分片的实际时长做标注。通常来说，为了缓存等方面的原因，在索引文件中会保留最新的三个分片地址，以类似“滑动窗口”的形式，进行更新。
       </p>

<h4 id="">直播现在大部分都是引用第三方服务的。简单封装播放器如下：</h4>

<pre><code>#import &lt; UIKit/UIKit.h &gt;
#import &lt; MediaPlayer/MediaPlayer.h &gt;
#import &lt; AVKit/AVKit.h &gt;
       
@interface ZrMoviePlayer : UIView
       
@property (nonatomic,strong) MPMoviePlayerController *moviePlayer;//视频播放控制器
       
/**
*  播放资源地址
*/
@property (nonatomic,strong) NSString *videoUrl;
       
/**
*  播放资源缩略图
*/
@property (nonatomic,strong) UIImageView *caver;
       
/**
*  初始化
*
*  @param frame frame
*  @param url   videoUrl
*
*  @return self
*/
-(instancetype)initWithFrame:(CGRect)frame videoUrl:(NSString *)url;
       
/**
*  播放资源缩略图
*
*  @param url picUrl
*/
- (void)showCoverWithUrl:(NSString *)url;
       
@end
</code></pre>

<p>.m</p>

<pre><code>#import "ZrMoviePlayer.h"
       
@interface ZrMoviePlayer ()
       
@end
       
@implementation ZrMoviePlayer
       
-(instancetype)initWithFrame:(CGRect)frame videoUrl:(NSString *)url{
       
       self.videoUrl = url;
       self = [super initWithFrame:frame];
       
       if (self) {
       
       /** [用户手动点击播放 默认暂停]*/
       //[self.moviePlayer play];
       
       [selfaddNotification];
       
       /** 获取缩略图 */
       //[self thumbnaiImageRequest];
       
       [self addGest];
       }
       return self;
}
       
/**
*  添加手势
*/
-(void)addGest {
       
       UITapGestureRecognizer *tapGesture = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(moviePicCoverTapGesture)];
       [self addGestureRecognizer:tapGesture];
}
       
/**
*  移除缩略图 播放视频
*/
-(void)moviePicCoverTapGesture {
       
       [self.caver removeFromSuperview];
       [self.moviePlayer play];
}
       
- (void)dealloc{
       
 /** 移除所有通知监控 */
       [[NSNotificationCenter defaultCenter]removeObserver:self];
}
#pragma mark -- 私有方法`       
/**
*  获得本地文件路径
*
*  @return 文件路径
*/
- (NSURL *)getFileUrl{
       
       NSString *urlStr = [[NSBundle mainBundle]pathForResource:@"" ofType:nil];
       return [NSURL URLWithString:urlStr];
}
       
/**
*  取得网络文件路径
*
*  @return 文件路径
*/
- (NSURL *)getNetworkUrl{
       
       NSString *urlStr = _videoUrl;
       urlStr = [urlStr stringByAddingPercentEscapesUsingEncoding:NSUTF8StringEncoding];
       return [NSURL URLWithString:urlStr];
}
       
/**
*  创建媒体播放器
*
*  @return moviePlayer
*/
- (MPMoviePlayerController *)moviePlayer{
       
       if (!_moviePlayer) {
       
       NSURL *url = [self getNetworkUrl];
       _moviePlayer = [[MPMoviePlayerController alloc]initWithContentURL:url];
       _moviePlayer.view.frame = self.bounds;
       _moviePlayer.view.autoresizingMask = UIViewAutoresizingFlexibleWidth|UIViewAutoresizingFlexibleHeight;
       [self addSubview:_moviePlayer.view];
       }
       
       return _moviePlayer;
}
       
/**
*  获取视频缩略图
*/
- (void)thumbnaiImageRequest{
       
       /** 获取13.0s、21.5s的缩略图 */
       [self.moviePlayer requestThumbnailImagesAtTimes:@[@13.0,@21.5] timeOption:MPMovieTimeOptionNearestKeyFrame];
}
       
#pragma mark - 控制器通知
       
/**
*  添加通知监控多媒体控制器状态
*/
- (void)addNotification{
       
       NSNotificationCenter *notificationCenter = [NSNotificationCenter defaultCenter];
       
       [notificationCenter addObserver:self
       selector:@selector(mediaPlayerPlaybackFinished:)
       name:MPMoviePlayerPlaybackDidFinishNotification
       object:self.moviePlayer];
       
       [notificationCenter addObserver:self
       selector:@selector(mediaPlayerPlaybackStateChange:)
       name:MPMoviePlayerPlaybackStateDidChangeNotification
       object:self.moviePlayer];
       
       [notificationCenter addObserver:self
       selector:@selector(mediaPlayerThumbnailRequestFinished:)
       name:MPMoviePlayerThumbnailImageRequestDidFinishNotification
       object:self.moviePlayer];
}
       
/**
*  播放状态改变，注意播放完成时的状态是暂停
*
*  @param notify 通知对象
*/
- (void)mediaPlayerPlaybackStateChange:(NSNotification *)notify{
       
       switch (self.moviePlayer.playbackState) {
       
       case MPMoviePlaybackStatePlaying:
       
       NSLog(@"正在播放");
       //            [[NSNotificationCenter defaultCenter] postNotificationName:MoviePlayNotification object:nil];
       
       break;
       case MPMoviePlaybackStatePaused:
       
       NSLog(@"暂停播放");
       //            [[NSNotificationCenter defaultCenter] postNotificationName:MoviePauseOrStopNotification object:nil];
       
       break;
       case MPMoviePlaybackStateStopped:
       
       NSLog(@"停止播放");
       //            [[NSNotificationCenter defaultCenter] postNotificationName:MoviePauseOrStopNotification object:nil];
       
       break;
       default:
       
       NSLog(@"播放状态：%ld",(long)self.moviePlayer.playbackState);
       break;
       }
}
       
- (void)mediaPlayerPlaybackFinished:(NSNotification *)notify{
       
       NSLog(@"播放完成");
}
       
/**
*  缩略图请求完成，此方法每次截图成功后都会调用一次
*
*  @param notify 通知对象
*/
- (void)mediaPlayerThumbnailRequestFinished:(NSNotification *)notify{
       
       NSLog(@"视频截图成功");
       
       UIImage *image = notify.userInfo[MPMoviePlayerThumbnailImageKey];
       // 保存图片到相册（首次调用会请求用户获访问相册权限）
       UIImageWriteToSavedPhotosAlbum(image, nil, nil, nil);
}
       
/**
*  播放资源缩略图
*
*  @param url picUrl
*/
- (void)showCoverWithUrl:(NSString *)url {
       UIImageView *caver = [[UIImageView alloc] init];
       _caver = caver;
       [caver sd_setImageWithURL:[NSURL URLWithString:url]placeholderImage:[UIImage imageNamed:@"videoPlaceHolder"]];
       [self addSubview:caver];
       
       UIImageView *pause = [[UIImageView alloc] initWithFrame:CGRectMake(0, 0, 100, 100)];
       pause.image = [UIImage imageNamed:@"zr_pause"];
       [caver addSubview:pause];
       
       caver.sd_layout
       .leftSpaceToView(self,0)
       .topSpaceToView(self,0)
       .rightSpaceToView(self,0)
       .bottomSpaceToView(self,0);
       
       pause.center = CGPointMake(self.center.x, self.center.y-20);
}
       
@end
</code></pre>

<p>       
<a href='https://mp.weixin.qq.com/s/1Y2hbg7SVPdFitl7kOO99A' ><strong>这篇文章</strong></a>讲解了 <strong>得到</strong> 和 <strong>喜马拉雅FM</strong> 的播放器的实现。
       
喜马拉雅FM的<a href='https://pan.baidu.com/s/1WokAFkYj3Z5JK4HL-d-7Zg' ><strong>头文件点击下载</strong></a> 密码: u4uu</p>

<h2 id="6jsonxml">6.谈谈你对JSON XML的理解。</h2>

<ul>
<li>JSON是什么？
       
<ul><li>JSON 指的是 JavaScript 对象表示法（JavaScript Object Notation）。</li>
<li>JSON 是轻量级的文本数据交换格式。</li>
<li>JSON 独立于语言（JSON 使用 JavaScript 语法来描述数据对象，但是 JSON 仍然独立于语言和平台。JSON 解析器和 JSON 库支持许多不同的编程语言。）</li>
<li>JSON 具有自我描述性，更易理解。
       </li>
<li>JSON的规则很简单： 对象是一个无序的 “ ‘名称/值’ 对” 集合。一个对象以“{”（左括号）开始，“}”（右括号）结束。每个“名称”后跟一个“:”（冒号）；“‘名称/值’ 对”之间使用“,”（逗号）分隔。
       </li></ul></li>
<li><p>什么是 XML?</p>

<ul><li>XML 指可扩展标记语言（EXtensible Markup Language）。</li>
<li>XML 是一种标记语言，很类似 HTML。</li>
<li>XML 的设计宗旨是传输数据，而非显示数据。</li>
<li>XML 标签没有被预定义。您需要自行定义标签。</li>
<li>XML 被设计为具有自我描述性。
       </li></ul></li>
<li>JSON 与 XML 的互相比较
<ul><li>简单的数据
XML</li></ul></li>
</ul>

<pre><code>&lt;person&gt;  
&lt;name&gt;xiaoMing&lt;/name&gt;  
&lt;age&gt;20&lt;/age&gt;  
&lt;/person&gt;  
</code></pre>

<p>JSON  </p>

<pre><code>{
"name":"xiaoMing",  
 "age":20
}
</code></pre>

<ul>
<li>复杂的数据
XML  </li>
</ul>

<pre><code>&lt;section&gt;  
&lt;title&gt;BOOK&lt;/title&gt;  
&lt;signing&gt;  
&lt;author name="author-1"/&gt;  
&lt;book title="book1" price="$11"/&gt;  
&lt;/signing&gt;  
&lt;signing&gt;  
&lt;author name="author-2"/&gt;  
&lt;book title="book2" price="$22"/&gt;  
&lt;/signing&gt;  
&lt;/section&gt;  
</code></pre>

<p>JSON  </p>

<pre><code>"section":{  
       "title":"BOOK",
       "signing":[
       {
       "author":
       {
       "name":"author-1"
       },
       "book":
       {
       "title":"book1",
       "price":"$11"
       }
       },
       {
       "author":
       {
       "name":"author-2"
       },
       "book":
       {
       "title":"book2",
       "price":"$22"
       }
       }]
       }
</code></pre>

<ul>
<li>JSON和XML的优缺点
XML <br />
<ul><li>优点
（1）格式统一，符合标准；
（2）容易与其他系统进行远程交互，数据传输比较方便。
 - 缺点
（1）XML文件庞大，文件格式复杂，传输占带宽；
（2）服务器端和客户端都需要花费大量代码来解析XML，导致服务器端和客户端代码变得异常复杂且不易维护；
（3）客户端不同浏览器之间解析XML的方式不一致，需要重复编写很多代码；
（4）服务器端和客户端解析XML花费较多的资源和时间。
       
JSON  </li></ul></li>
<li>优点
(1)数据格式比较简单，易于读写，格式都是压缩的，占用带宽小；</li>
</ul>

<p>(2)易于解析，客户端JavaScript可以简单的通过eval_r()进行JSON数据的读取；</p>

<p>(3)支持多种语言，包括ActionScript, C, C#, ColdFusion, Java, JavaScript, Perl, PHP, Python, Ruby等服务器端语言，便于服务器端的解析；</p>

<p>(4)在PHP世界，已经有PHP-JSON和JSON-PHP出现了，偏于PHP序列化后的程序直接调用，PHP服务器端的对象、数组等能直接生成JSON格式，便于客户端的访问提取；</p>

<p>(5)因为JSON格式能直接为服务器端代码使用，大大简化了服务器端和客户端的代码开发量，且完成任务不变，并且易于维护。
       
- 缺点
(1)没有XML格式这么推广的深入人心和喜用广泛，没有XML那么通用性；
(2)JSON格式目前在Web Service中推广还属于初级阶段。
       </p>

<p>QQ技术交流群：214541576 </p>

<p>微信公众号：shavekevin</p>

<p>开发者头条：
<img src='http://shavekevin.com/content/images/2018/Mar/iOS------1.jpg'  alt="" /></p>

<p>热爱生活，分享快乐。好记性不如烂笔头。多写，多记，多实践，多思考。    
       </p>]]></description><link>http://shavekevin.com/2018/03/21/mianshi-hengsaoqianjun-parttwo/</link><guid isPermaLink="false">f1bf5024-a8a5-4fa5-a42e-cb5a9eba0908</guid><category><![CDATA[面试胸有成竹]]></category><dc:creator><![CDATA[shavekevin]]></dc:creator><pubDate>Wed, 21 Mar 2018 01:14:28 GMT</pubDate></item><item><title><![CDATA[iOS面试旗开得胜之横扫千军篇(一)]]></title><description><![CDATA[<blockquote>
  <p>本文问题来自<a href='http://www.shavekevin.com/2017/12/17/mianshiwenti-deshengpian/' >iOS面试旗开得胜之问题篇</a> 中的横扫千军之战胜篇</p>
</blockquote>

<h2 id="1tableview">1.谈谈 tableview 的重用机制。</h2>

<ul>
<li><p>为什么要“重用”？iPhone 重用机制是为了实现大量数据显示而采用的一种节省内存的机制。如果一个 tableview 有几百个 cell，这个内存消耗无疑是很恐怖的。再加上 cell 中还有 image 之类的资源。很容易出现 memory warning 甚至 crash ！！！</p></li>
<li><p>重用代码</p></li>
</ul>

<pre><code>static NSString *CellTableIdentifier = @"cell ";  
UITableViewCell *cell = [tableView  dequeueReusableCellWithIdentifier: CellTableIdentifier];  
if (cell == nil) {  
cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:CellTableIdentifier];  
}
</code></pre>

<p>关键函数</p>

<pre><code>- (__kindof UITableViewCell *)dequeueReusableCellWithIdentifier:(NSString *)identifier
forIndexPath:(NSIndexPath *)indexPath;  
</code></pre>

<ul>
<li><p>文档说明如下：
Returns a reusable table-view cell object for the specified reuse identifier and adds it to the table. <br />
它返回的是一个受 identifier 管理定位的可重用的 tableViewCell。</p></li>
<li><p>那么它是如何重用的？
举例：假设系统启动的时候，tableView 可以显示 7 个 tableViewCell ，并且都有 tag 值，是 0 - 6。咱们把 tableView 向上滑动，那么 tag 为 0 的 Cell 将会移动到 tag 为 6 的 Cell 下面。重新设置属性。为 1 的 Cell 会移动到 0 的 Cell 下面。。。  这就是 “可重用”。
但是有的同学会问了：经常会有 cell 重叠的情况发生， 这个很容易理解，因为 Cell 滑出界面并被放入重用队列时， cell 中的内容不会消失，可以通过下面代码解决。</p></li>
</ul>

<pre><code>- (void)layoutSubviews {
[super layoutSubviews];
/*这里面对属性的值进行更改*/
}
</code></pre>

<h2 id="2">2.静态库的原理是什么？你有没有自己写过静态编译库，遇到了哪些问题？</h2>

<ul>
<li>库本质上讲是一种可执行的二进制格式，可以载入内存中执行。是程序代码的集合，共享代码的一种方式。</li>
<li>静态库是闭源库，不公开源代码，都是编译后的二进制文件，不暴露具体实现。</li>
<li>静态库 一般都是以 .a 或者 .framework 形式存在。</li>
<li>静态库编译的文件比较大，因为整个函数库的数据都会被整合到代码中，这样的好处就是编译后的程序不需要外部的函数库支持，不好的一点就是如果改变静态函数库，就需要程序重新编译。多次使用就有多份冗余拷贝。</li>
<li>使用静态库的好处：模块化分工合作、可重用、避免少量改动导致大量的重复编译链接。</li>
<li>一般公司都会有核心开发团队和普通开发团队，然后公司的核心业务由核心开发团队写成静态库然后让普通开发团队调用，这样就算普通开发团队离职也带不走公司的核心业务代码。一般核心开发团队是不会离职的。</li>
<li>有静态库自然就有动态库了。这里所谓的静态和动态是相对编译期和运行期的。静态库在程序编译时会被链接到代码中，程序运行时将不再需要改静态库，而动态库在编译时不会被链接到代码中，只有程序运行时才会被载入，所以 hook 别人程序或者说做插件都是运用了 runtime 机制，然后动态库注入修改的。</li>
<li>制作 .a 文件时候，要注意 CPU 架构的支持，i386、X86_64、 armv7、armv7s。 查看可以通过命令 “ lipo -info 静态库名称” 。模拟器 .a 文件和真机 .a 文件合并可以通过 "lipo -create 模拟器静态库1名 真机静态库2名 -output 新静态库名称"</li>
</ul>

<h5 id="">一些坑</h5>

<ul>
<li>命名不要太随意，毕竟是被别人拿过去用的要能看懂。</li>
<li>framework中用到了NSClassFromString，但是转换出来的class 一直为nil。解决方法：在主工程的【Other Linker Flags】需要添加参数【-ObjC]即可。</li>
<li>如果Xcode找不到框架的头文件，你可能是忘记将它们声明为public了。 解决方法：进入target的Build Phases页，展开Copy Headers项，把需要public的头文件从Project或Private部分拖拽到Public部分。</li>
<li>尽量不要用 xib 。由于静态框架采用静态链接，linker会剔除所有它认为无用的代码。不幸的是，linker不会检查xib文件，因此如果类是在xib中引用，而没有在O-C代码中引用，linker将从最终的可执行文件中删除类。这是linker的问题，不是框架的问题（当你编译一个静态库时也会发生这个问题）。苹果内置框架不会发生这个问题，因为他们是运行时动态加载的，存在于iOS设备固件中的动态库是不可能被删除的。
有两个解决的办法：</li>
</ul>

<p>1、 让框架的最终用户关闭linker的优化选项，通过在他们的项目的Other Linker Flags中添加-ObjC和-all_load。</p>

<p>2、 在框架的另一个类中加一个该类的代码引用。例如，假设你有个MyTextField类，被linker剔除了。假设你还有一个MyViewController，它在xib中使用了MyTextField，MyViewController并没有被剔除。你应该这样做： <br />
在MyTextField中：
+(void)forceLinkerLoad_ {}
在MyViewController中：
+(void)initialize {     [MyTextField forceLinkerLoad<em>]; }
他们仍然需要添加-ObjC到linker设置，但不需要强制all</em>load了。
第2种方法需要你多做一点工作，但却让最终用户避免在使用你的框架时关闭linker优化（关闭linker优化会导致object文件膨胀）。</p>

<h2 id="3httptcpipsocket">3.谈谈你对HTTP 、TCP、 IP、socket 协议的理解。</h2>

<h4 id="http">HTTP</h4>

<ul>
<li><a href='https://baike.baidu.com/item/%E8%B6%85%E6%96%87%E6%9C%AC%E4%BC%A0%E8%BE%93%E5%8D%8F%E8%AE%AE' >超文本传输协议</a>（HTTP，HyperText Transfer Protocol)
HTTP 协议对应应用层，HTTP 协议是基于 TCP 连接的。HTTP 链接就是所谓的短连接，即客户端向服务器发送一次请求，服务器端响应后连接即会断掉。  </li>
<li>HTTP 1.0中，客户端的每次请求都要求建立一次单独的连接，在处理完本次请求后，就自动释放连接。
HTTP 1.1则可以在一次连接中处理多个请求，并且多个请求可以重叠进行，不需要等待一个请求结束后再发送下一个请求。  </li>
<li>因为 HTTP 是“短连接”，所以要保持客户端程序的在线状态，需要不断的向服务器发起连接请求。一般的做法就是不需要获取任何数据，客户端也保持每隔一段固定的时间向服务器发送一次“保持连接”的请求，服务器在收到该请求后对客户端进行回复，表示知道客户端“在线”。若服务器长时间无法收到客户端的请求，则认为客户端“下线”，若客户端长时间无法收到服务器的回复，则认为网络已经断开。</li>
</ul>

<h4 id="tpcip">TPC/IP</h4>

<ul>
<li><p>TPC/IP 协议是传输层协议，主要解决数据如何在网络中传输。“IP”代表网际协议，TCP和UDP使用该协议从一个网络传送数据包到另一个网络。把IP想像成一种高速公路，它允许其它协议在上面行驶并找到到其它电脑的出口。TCP和UDP是高速公路上的“卡车”，它们携带的货物就是像HTTP，文件传输协议FTP这样的协议等。
你应该能理解，TCP和UDP是FTP，HTTP和SMTP之类使用的传输层协议。虽然TCP和UDP都是用来传输其他协议的，它们却有一个显著的不同：TCP提供有保证的数据传输，而UDP不提供。这意味着TCP有一个特殊的机制来确保数据安全的不出错的从一个端点传到另一个端点，而UDP不提供任何这样的保证。
下面的图表试显示不同的TCP/IP和其他的协议在最初OSI模型中的位置：
<img src='http://shavekevin.com/content/images/2018/Mar/TCP-IP-------OSI-------1.jpg'  alt="TCP:IP和其他的协议在OSI模型中的位置.jpg" /></p></li>
<li><p>TCP 标志位，有以下6种标示
1、SYN(synchronous建立联机) <br />
2、ACK(acknowledgement 确认) <br />
3、PSH(push传送) <br />
4、FIN(finish结束) <br />
5、RST(reset重置) <br />
6、URG(urgent紧急) <br />
Sequence number(顺序号码) <br />
Acknowledge number(确认号码)</p></li>
<li><p>客户端 TCP 状态迁移：
CLOSED->SYN<em>SENT->ESTABLISHED->FIN</em>WAIT<em>1->FIN</em>WAIT<em>2->TIME</em>WAIT->CLOSED</p></li>
<li><p>服务器TCP状态迁移：
CLOSED->LISTEN->SYN收到->ESTABLISHED->CLOSE<em>WAIT->LAST</em>ACK->CLOSED</p></li>
<li><p>各个状态的意义如下：
1、LISTEN - 侦听来自远方TCP端口的连接请求； <br />
2、SYN-SENT -在发送连接请求后等待匹配的连接请求； <br />
3、SYN-RECEIVED - 在收到和发送一个连接请求后等待对连接请求的确认； <br />
4、ESTABLISHED- 代表一个打开的连接，数据可以传送给用户； <br />
5、FIN-WAIT-1 - 等待远程TCP的连接中断请求，或先前的连接中断请求的确认； <br />
6、FIN-WAIT-2 - 从远程TCP等待连接中断请求； <br />
7、CLOSE-WAIT - 等待从本地用户发来的连接中断请求； <br />
8、CLOSING -等待远程TCP对连接中断的确认； <br />
9、LAST-ACK - 等待原来发向远程TCP的连接中断请求的确认； <br />
10、TIME-WAIT -等待足够的时间以确保远程TCP接收到连接中断请求的确认； <br />
11、CLOSED - 没有任何连接状态；  </p></li>
<li>TCP/IP 三次握手
1、第一次握手：建立连接时，客户端A发送SYN包（SYN=j）到服务器B，并进入SYN<em>SEND状态，等待服务器B确认。 <br />
2、第二次握手：服务器B收到SYN包，必须确认客户A的SYN（ACK=j+1），同时自己也发送一个SYN包（SYN=k），即SYN+ACK包，此时服务器B进入SYN</em>RECV状态。 <br />
3、第三次握手：客户端A收到服务器B的SYN＋ACK包，向服务器B发送确认包ACK（ACK=k+1），此包发送完毕，客户端A和服务器B进入ESTABLISHED状态，完成三次握手。 完成后，客户端和服务器开始传送数据。 <br />
4、由于TCP连接是全双工的，因此每个方向都必须单独进行关闭。这个原则是当一方完成它的数据发送任务后就能发送一个FIN来终止这个方向的连接。收到一个 FIN只意味着这一方向上没有数据流动，一个TCP连接在收到一个FIN后仍能发送数据。首先进行关闭的一方将执行主动关闭，而另一方执行被动关闭。</li>
</ul>

<p>CP的连接的拆除需要发送四个包，因此称为四次挥手(four-way handshake)。客户端或服务器均可主动发起挥手动作，在socket编程中，任何一方执行close()操作即可产生挥手操作。</p>

<p>（1）客户端A发送一个FIN，用来关闭客户A到服务器B的数据传送。
（2）服务器B收到这个FIN，它发回一个ACK，确认序号为收到的序号加1。和SYN一样，一个FIN将占用一个序号。
（3）服务器B关闭与客户端A的连接，发送一个FIN给客户端A。
（4）客户端A发回ACK报文确认，并将确认序号设置为收到序号加1。
- 深入理解TCP连接的释放：
由于TCP连接是全双工的，因此每个方向都必须单独进行关闭。这原则是当一方完成它的数据发送任务后就能发送一个FIN来终止这个方向的连接。收到一个 FIN只意味着这一方向上没有数据流动，一个TCP连接在收到一个FIN后仍能发送数据。首先进行关闭的一方将执行主动关闭，而另一方执行被动关闭。
TCP协议的连接是全双工连接，一个TCP连接存在双向的读写通道。 <br />
简单说来是 “先关读，后关写”，一共需要四个阶段。以客户机发起关闭连接为例：
1.服务器读通道关闭 <br />
2.客户机写通道关闭 <br />
3.客户机读通道关闭 <br />
4.服务器写通道关闭 <br />
关闭行为是在发起方数据发送完毕之后，给对方发出一个FIN（finish）数据段。直到接收到对方发送的FIN，且对方收到了接收确认ACK之后，双方的数据通信完全结束，过程中每次接收都需要返回确认数据段ACK。
详细过程：
第一阶段   客户机发送完数据之后，向服务器发送一个FIN数据段，序列号为i；
1.服务器收到FIN(i)后，返回确认段ACK，序列号为i+1，关闭服务器读通道； <br />
2.客户机收到ACK(i+1)后，关闭客户机写通道； <br />
（此时，客户机仍能通过读通道读取服务器的数据，服务器仍能通过写通道写数据）
第二阶段 服务器发送完数据之后，向客户机发送一个FIN数据段，序列号为j；
3.客户机收到FIN(j)后，返回确认段ACK，序列号为j+1，关闭客户机读通道； <br />
4.服务器收到ACK(j+1)后，关闭服务器写通道。 <br />
这是标准的TCP关闭两个阶段，服务器和客户机都可以发起关闭，完全对称。
FIN标识是通过发送最后一块数据时设置的，标准的例子中，服务器还在发送数据，所以要等到发送完的时候，设置FIN（此时可称为TCP连接处于半关闭状态，因为数据仍可从被动关闭一方向主动关闭方传送）。如果在服务器收到FIN(i)时，已经没有数据需要发送，可以在返回ACK(i+1)的时候就设置FIN(j)标识，这样就相当于可以合并第二步和第三步。</p>

<ul>
<li>TCP的 TIME<em>WAIT 和 CLOSE</em>WAIT 状态
CLOSE<em>WAIT： <br />
发起TCP连接关闭的一方称为client，被动关闭的一方称为server。被动关闭端未发出FIN的TCP状态是CLOASE</em>WAIT。出现这种状况一般都是由于server端代码的问题，如果你的服务器上出现大量CLOSE<em>WAIT，应该要考虑检查代码。
TIME</em>WAIT： <br />
根据TCP协议定义的3次握手断开连接规定,发起socket主动关闭的一方 socket将进入TIME<em>WAIT状态。TIME</em>WAIT状态将持续2个MSL(Max Segment Lifetime),在Windows下默认为4分钟，即240秒。TIME<em>WAIT状态下的socket不能被回收使用. 具体现象是对于一个处理大量短连接的服务器,如果是由服务器主动关闭客户端的连接，将导致服务器端存在大量的处于TIME</em>WAIT状态的socket， 甚至比处于Established状态下的socket多的多,严重影响服务器的处理能力，甚至耗尽可用的socket，停止服务。</li>
</ul>

<h4 id="socket">socket</h4>

<ul>
<li><p>socket连接就是所谓的长连接，理论上客户端和服务器端一旦建立起连接将不会主动断掉；但是由于各种环境因素可能会是连接断开，比如说：服务器端或客户端主机down了，网络故障，或者两者之间长时间没有数据传输，网络防火墙可能会断开该连接以释放网络资源。所以当一个socket连接中没有数据的传输，那么为了维持连接需要发送心跳消息~~具体心跳消息格式是开发者自己定义的。</p></li>
<li><p>套接字(socket)是通信的基石，是支持TCP/IP协议的网络通信的基本操作单元。它是网络通信过程中端点的抽象表示，包含进行网络通信必须的五种信息：连接使用的协议，本地主机的IP地址，本地进程的协议端口，远地主机的IP地址，远地进程的协议端口。</p></li>
<li><p>我们平时说的最多的socket是什么呢，实际上socket是对TCP/IP协议的封装，Socket本身并不是协议，而是一个调用接口(API)，通过Socket，我们才能使用TCP/IP协议。 实际上，Socket跟TCP/IP协议没有必然的联系。Socket编程接口在设计的时候，就希望也能适应其他的网络协议。所以说，Socket的出现 只是使得程序员更方便地使用TCP/IP协议栈而已，是对TCP/IP协议的抽象，从而形成了我们知道的一些最基本的函数接口，比如create、 listen、connect、accept、send、read和write等等。网络有一段关于socket和TCP/IP协议关系的说法比较容易理解：
“TCP/IP只是一个协议栈，就像操作系统的运行机制一样，必须要具体实现，同时还要提供对外的操作接口。这个就像操作系统会提供标准的编程接口，比如win32编程接口一样，TCP/IP也要提供可供程序员做网络开发所用的接口，这就是Socket编程接口。”
实际上，传输层的TCP是基于网络层的IP协议的，而应用层的HTTP协议又是基于传输层的TCP协议的，而Socket本身不算是协议，就像上面所说，它只是提供了一个针对TCP或者UDP编程的接口。socket是对端口通信开发的工具,它要更底层一些.</p></li>
<li><p>Socket是一个针对TCP和UDP编程的接口，你可以借助它建立TCP连接等等。而TCP和UDP协议属于传输层 。
而http是个应用层的协议，它实际上也建立在TCP协议之上(HTTP是轿车，提供了封装或者显示数据的具体形式；Socket是发动机，提供了网络通信的能力)。</p></li>
</ul>

<p>Socket是对TCP/IP协议的封装，Socket本身并不是协议，而是一个调用接口（API），通过Socket，我们才能使用TCP/IP协议。Socket的出现只是使得程序员更方便地使用TCP/IP协议栈而已，是对TCP/IP协议的抽象，从而形成了我们知道的一些最基本的函数接口。</p>

<ul>
<li><p>短连接：
连接->传输数据->关闭连接
HTTP是无状态的，浏览器和服务器每进行一次HTTP操作，就建立一次连接，但任务结束就中断连接。 <br />
也可以这样说：短连接是指SOCKET连接后发送后接收完数据后马上断开连接。</p></li>
<li><p>长连接：
连接->传输数据->保持连接 -> 传输数据-> 。。。 ->关闭连接。
长连接指建立SOCKET连接后不管是否使用都保持连接，但安全性较差。</p></li>
<li><p>http 的长连接：
HTTP也可以建立长连接的，使用Connection:keep-alive，HTTP 1.1默认进行持久连接。HTTP1.1和HTTP1.0相比较而言，最大的区别就是增加了持久连接支持(貌似最新的 http1.0 可以显示的指定 keep-alive),但还是无状态的，或者说是不可以信任的。</p></li>
<li><p>长连接和短连接的使用环境：
长连接多用于操作频繁，点对点的通讯，而且连接数不能太多情况，。每个TCP连接都需要三步握手，这需要时间，如果每个操作都是先连接，再操作的话那么处理速度会降低很多，所以每个操作完后都不断开，次处理时直接发送数据包就OK了，不用建立TCP连接。例如：数据库的连接用长连接， 如果用短连接频繁的通信会造成socket错误，而且频繁的socket 创建也是对资源的浪费。
而像WEB网站的http服务一般都用短链接，因为长连接对于服务端来说会耗费一定的资源，而像WEB网站这么频繁的成千上万甚至上亿客户端的连接用短连接会更省一些资源，如果用长连接，而且同时有成千上万的用户，如果每个用户都占用一个连接的话，那可想而知吧。所以并发量大，但每个用户无需频繁操作情况下需用短连好。
总之，长连接和短连接的选择要视情况而定。</p></li>
<li><p>发送接收方式：
1、异步 <br />
报文发送和接收是分开的，相互独立的，互不影响。这种方式又分两种情况：
(1)异步双工：接收和发送在同一个程序中，由两个不同的子进程分别负责发送和接收
(2)异步单工：接收和发送是用两个不同的程序来完成。
2、同步 <br />
报文发送和接收是同步进行，既报文发送后等待接收返回报文。 同步方式一般需要考虑超时问题，即报文发出去后不能无限等待，需要设定超时时间，超过该时间发送方不再等待读返回报文，直接通知超时返回。
在长连接中一般是没有条件能够判断读写什么时候结束，所以必须要加长度报文头。读函数先是读取报文头的长度，再根据这个长度去读相应长度的报文。</p></li>
</ul>

<h2 id="4ios">4.谈谈你对iOS中沙盒机制的理解。</h2>

<ul>
<li><p>iOS 的沙盒机制（sandbox）
每个iOS应用都有自己的应用沙盒，应用沙盒就是文件系统目录。
1.每个应用程序的活动范围都限定在自己的沙盒里 <br />
2.不能随意跨越自己的沙盒去访问别的应用程序沙盒中的内容 <br />
（iOS8已经部分开放访问extension）
3.在访问别人沙盒内的数据时需要访问权限。</p></li>
<li><p>沙盒的组成：
1、Documents： <br />
Documents中一般保存应用程序本身产生文件数据，例如游戏进度，绘图软件的绘图等， iTunes备份和恢复的时候，会包括此目录。 <br />
注意：在此目录下不要保存从网络上下载的文件，否则app无法上架！（曾试过 plist 存小文件扔进去  上架通过）
2、Library： <br />
（1）、Caches：
此目录用来保存应用程序运行时生成的需要持久化的数据，这些数据一般存储体积比较大，又不是十分重要，比如网络请求数据等。这些数据需要用户负责删除。iTunes同步设备时不会备份该目录。</p></li>
</ul>

<p>（2）、Preferences：
此目录保存应用程序的所有偏好设置，iOS的Settings(设置)应用会在该目录中查找应用的设置信息。iTunes同步设备时会备份该目录
在Preferences/下不能直接创建偏好设置文件，而是应该使用NSUserDefaults类来取得和设置应用程序的偏好.</p>

<p>3、tmp： <br />
此目录保存应用程序运行时所需的临时数据，使用完毕后再将相应的文件从该目录删除。应用没有运行时，系统也可能会清除该目录下的文件。iTunes同步设备时不会备份该目录</p>

<ul>
<li>手机越狱后跟沙盒的关系
不同的越狱方案对沙盒会有所不同的影响，iOS 越狱不代表沙盒（sandbox）的移除，但通常都会使其变得更“虛弱”。</li>
</ul>

<p>越狱之后，原本的一些私密文件，比如短信资料(/var/mobile/Library/SMS/sms.db)，AppStore安装的App仍旧没有访问权限。但是，越狱后App能获得应用文件夹(/var/mobile/Aplications/)的读写权限，就是说，我能够遍历找到你的支付宝、招行、QQ、微信的App里面的用户资料，这仍旧会导致很大的安全问题。即使在AppStore上线的App，也能轻易根据当前是否越狱来作出不同的动作，Apple审核检查不到这一点。</p>

<ul>
<li>越狱本身的方法就是透过iOS现有的漏洞进行越狱，代表越狱是有风险性的情况发生，不过不可就认为越狱不安全，在越狱后iOS沙盒并没有被破坏，使用者虽然获取了root权限，并不表示就可让App都能够拥有root权限，可自动将指令写入至系统资料夹中，不过越狱后，在iOS上发现的漏洞或BUG，越狱界都会有开发者在第一时间内发布修补漏洞，先前就有不少案例。</li>
</ul>

<p>在越狱后我们能够享有各种不同的插件，可让iOS设备实现不同功能，这些插件我们都会透过Cydia一些公认安全的插件源（如Bigboss、ModMyi）进行下载安装，这些软体源就像是AppStore会有审核机制、会员注册与付费购买机制，在上面所安装的插件都有一定的高信任与安全，其中比较好用的插件也都必须付费才能够使用，毕竟这些都是由不同开发者所开发出来的产品，如开发者没有收入来源，为何还要继续维护与更新下去呢？有些插件会由作者自己架设插件源免费提供分享的也有，不过也延伸出了有些越狱用户贪图免费，透过底下几种方式来安装，导致造成插件功能不正常、Cydia不稳定或是造成出错问题发生。</p>

<p>QQ技术交流群：214541576 </p>

<p>微信公众号：shavekevin</p>

<p>开发者头条：
<img src='http://shavekevin.com/content/images/2018/Mar/iOS------1.jpg'  alt="" /></p>

<p>热爱生活，分享快乐。好记性不如烂笔头。多写，多记，多实践，多思考。         
       
       
       
       </p>]]></description><link>http://shavekevin.com/2018/03/21/mianshi-hengsaoqianjun-partone/</link><guid isPermaLink="false">3b03bb5f-77e2-4957-ab55-68577ed91551</guid><category><![CDATA[面试胸有成竹]]></category><dc:creator><![CDATA[shavekevin]]></dc:creator><pubDate>Wed, 21 Mar 2018 01:02:00 GMT</pubDate></item><item><title><![CDATA[每周一算法2018.02.09]]></title><description><![CDATA[<blockquote>
  <p>Can Place Flowers</p>
</blockquote>

<p>Suppose you have a long flowerbed in which some of the plots are planted and some are not. However, flowers cannot be planted in adjacent plots - they would compete for water and both would die.</p>

<p>Given a flowerbed (represented as an array containing 0 and 1, where 0 means empty and 1 means not empty), and a number n, return if n new flowers can be planted in it without violating the no-adjacent-flowers rule.</p>

<p>Example 1: <br />
Input: flowerbed = [1,0,0,0,1], n = 1 <br />
Output: True <br />
Example 2: <br />
Input: flowerbed = [1,0,0,0,1], n = 2 <br />
Output: False <br />
Note: <br />
The input array won't violate no-adjacent-flowers rule. <br />
The input array size is in the range of [1, 20000]. <br />
n is a non-negative integer which won't exceed the input array size.</p>

<h4 id="">描述：</h4>

<p>假设你有一个花园,有些地是可以种花的,有些不可以。要求是你种的花不能够相邻,也就是说花与花之间是有间隙的,如果种的过于密集.他们可能会因为竞争水资源活不了。
给定过一个花园的数组，其中的元素包括0和1，0表示空地，1表示已经种了都花。给定一个花的数目，问，能否种的下这么都花，保证花不会死。
例：
输入： flowerbed = [1,0,0,0,1], n = 1
输出：True </p>

<p>输入: flowerbed = [1,0,0,0,1], n = 2
输出: False</p>

<p>注意：
1.输入的数组遵循种花的规则 <br />
2.输入的数组长度在1~20000之间 <br />
3.n是一个整数，不会超过输入数组的长度</p>

<h4 id="">分析：</h4>

<p>可以种花的数组有以下特点
00x 这个地方 第一位0 可以种花 <br />
000 这个地方 中间的0 可以种花 <br />
x00 这个地方  最后的0 可以种花 <br />
然后遍历整个数组即可</p>

<ul>
<li>解题思路：主要是找到能种花的场景，如果当前位置能够种花，那么这设置为1，将最大的值做-1操作。循环结束，判断最后n的值与0进行比较。(或者定义一个常量对其做+1操作，然后与n进行对比)关键判断在于 左右两边是否种花。(贪心算法：从左向右遍历数组，将满足条件的0设置为1，然后将值与n比较)</li>
</ul>

<h4 id="java">Java</h4>

<pre><code>class Solution {  
    public boolean canPlaceFlowers(int[] flowerbed, int n) {
       for (int i = 0; i &lt; flowerbed.length &amp;&amp; n &gt; 0; i++) {
            if (n == 0) return true;
            if (flowerbed[i] == 0) {
                int next = (i == flowerbed.length - 1 ? 0 : flowerbed[i + 1]);
                int pre = (i == 0 ? 0 : flowerbed[i - 1]);
                if (next + pre == 0) {
                    flowerbed[i] = 1;
                    --n;
                }
            }
        }
        return n &lt;= 0;
    }
}
</code></pre>

<h4 id="c">C++:</h4>

<pre><code>class Solution {  
public:  
    bool canPlaceFlowers(vector&lt;int&gt;&amp; flowerbed, int n) {

        int length = flowerbed.size();
        for (int i = 0; i &lt; length &amp;&amp; n &gt; 0; i++) {
            if (flowerbed[i] == 0) {
                int left = i - 1 &gt; 0 ? i - 1 : 0;
                int right = i + 1 &lt; length - 1 ? i + 1 : length - 1;
                if (flowerbed[left] == 0 &amp;&amp; flowerbed[right] == 0) {
                    flowerbed[i] = 1;
                    --n;
                }
            }
        }
        return n &lt;= 0;
    }
};
</code></pre>

<h4 id="swift">Swift:</h4>

<pre><code>   func canPlaceFlowers(_ flowerbed: [Int], _ n: Int) -&gt; Bool {

        var flowerbed = flowerbed
        var n = n
        if flowerbed.count == 0 {return false}
        let arrayLength = flowerbed.count
        for i in 0 ..&lt; arrayLength {
            if n &gt; 0 {
                if flowerbed[i] == 0 {
                    let left = i - 1 &gt; 0 ? i - 1 : 0
                    let right = i + 1 &lt; arrayLength - 1 ? i + 1 : arrayLength - 1
                    if flowerbed[left] == 0 &amp;&amp; flowerbed[right] == 0 {
                        flowerbed[i] = 1
                        n -= 1
                    }
                }
            }
        }
        return n &lt;= 0
    }
</code></pre>

<h4 id="python">Python:</h4>

<pre><code>class Solution(object):  
    def canPlaceFlowers(self, flowerbed, n):
        """
        :type flowerbed: List[int]
        :type n: int
        :rtype: bool
        """
        for i, v in enumerate(flowerbed):
            if v: continue
            if i &gt; 0 and flowerbed[i - 1]: continue
            if i &lt; len(flowerbed) - 1 and flowerbed[i + 1]: continue
            n -= 1
            flowerbed[i] = 1
        return n&lt;=0
</code></pre>

<h4 id="">什么是贪心算法？</h4>

<h6 id="">词条来自维基百科</h6>

<p>贪心法，又称贪心算法、贪婪算法、或称贪婪法，是一种在每一步选择中都采取在当前状态下最好或最优（即最有利）的选择，从而希望导致结果是最好或最优的算法。比如在旅行推销员问题中，如果旅行员每次都选择最近的城市，那这就是一种贪心算法。
贪心算法在有最优子结构的问题中尤为有效。最优子结构的意思是局部最优解能决定全局最优解。简单地说，问题能够分解成子问题来解决，子问题的最优解能递推到最终问题的最优解。
贪心算法与动态规划的不同在于它对每个子问题的解决方案都做出选择，不能回退。动态规划则会保存以前的运算结果，并根据以前的结果对当前进行选择，有回退功能。
贪心法可以解决一些最优化问题，如：求图中的最小生成树、求哈夫曼编码……对于其他问题，贪心法一般不能得到我们所要求的答案。一旦一个问题可以通过贪心法来解决，那么贪心法一般是解决这个问题的最好办法。由于贪心法的高效性以及其所求得的答案比较接近最优结果，贪心法也可以用作辅助算法或者直接解决一些要求结果不特别精确的问题。</p>

<h4 id="">什么是动态规划？</h4>

<h6 id="">词条来自维基百科</h6>

<p>动态规划（英语：Dynamic programming，简称DP）是一种在数学、管理科学、计算机科学、经济学和生物信息学中使用的，通过把原问题分解为相对简单的子问题的方式求解复杂问题的方法。
动态规划常常适用于有重叠子问题[1]和最优子结构性质的问题，动态规划方法所耗时间往往远少于朴素解法。
动态规划背后的基本思想非常简单。大致上，若要解一个给定问题，我们需要解其不同部分（即子问题），再合并子问题的解以得出原问题的解。
通常许多子问题非常相似，为此动态规划法试图仅仅解决每个子问题一次，从而减少计算量：一旦某个给定子问题的解已经算出，则将其记忆化存储，以便下次需要同一个子问题解之时直接查表。这种做法在重复子问题的数目关于输入的规模呈指数增长时特别有用。</p>

<p>本题来自:leetCode</p>

<p><a href='https://leetcode.com/problems/can-place-flowers/description/' >https://leetcode.com/problems/can-place-flowers/description/</a></p>]]></description><link>http://shavekevin.com/2018/02/24/canplaceflowers-20180209/</link><guid isPermaLink="false">6e14f09e-d5ec-44a8-95ce-a0c8e88ceaa9</guid><category><![CDATA[算法与数据结构基础]]></category><dc:creator><![CDATA[shavekevin]]></dc:creator><pubDate>Sat, 24 Feb 2018 02:42:34 GMT</pubDate></item></channel></rss>