夏一

人生是不断成就完善的过程。

周末

WebRTC 视频对话

今天聊一下WebRTC。很多开发者,可能会觉得有些陌生,或者直接感觉繁杂。因为WebRTC在iOS上的应用,只是编译都让人很是头痛。这些话,到此为止,以防让了解者失去信心。我们只传播正能量,再多的困难都是可以解决的。本博客会不断更新增加内容,不要怕长。首先看一下定义。WebRTC,名称源自网页实时通信(WebReal-TimeCommunication)的缩写,是一个支持网页浏览器进行实时语音对话或视频对话的技术,是谷歌2010年以6820万美元收购GlobalIPSolutions公司而获得的一项技术。2011年5月开放了工程的源代码,在行业内得到了广泛的支持和应用,成为下一代视频通话的标准。

WebRTC实现了基于网页的视频会议,标准是WHATWG协议,目的是通过浏览器提供简单的javascript就可以达到实时通讯(Real-Time Communications(RTC))能力。

WebRTC(Web Real-TimeCommunication)项目的最终目的主要是让Web开发者能够基于浏览器(Chrome\FireFox\...)轻易快捷开发出丰富的实时多媒体应用,而无需下载安装任何插件,Web开发者也无需关注多媒体的数字信号处理过程,只需编写简单的Javascript程序即可实现,W3C等组织正在制定Javascript 标准API,目前是WebRTC1.0版本,Draft状态;另外WebRTC还希望能够建立一个多互联网浏览器间健壮的实时通信的平台,形成开发者与浏览器厂商良好的生态环境。同时,Google也希望和致力于让WebRTC的技术成为HTML5标准之一,可见Google布局之深远。 

WebRTC提供了视频会议的核心技术,包括音视频的采集、编解码、网络传输、显示等功能,并且还支持跨平台:windows,linux,mac,android。

架构组件介绍

(1) Your Web App

Web开发者开发的程序,Web开发者可以基于集成WebRTC的浏览器提供的webAPI开发基于视频、音频的实时通信应用。 

(2)Web API

面向第三方开发者的WebRTC标准API(Javascript),使开发者能够容易地开发出类似于网络视频聊天的web应用,最新的标准化进程可以查看这里。 

这些API可分成Network Stream API、 RTCPeerConnection、Peer-to-peerData API三类,详细的API说明可以看这里。

Network Stream API

MediaStream:MediaStream用来表示一个媒体数据流。

MediaStreamTrack在浏览器中表示一个媒体源。

RTCPeerConnection

RTCPeerConnection:一个RTCPeerConnection对象允许用户在两个浏览器之间直接通讯。

RTCIceCandidate :表示一个ICE协议的候选者。

RTCIceServer:表示一个ICE Server。

Peer-to-peer Data API

DataChannel:数据通道( DataChannel)接口表示一个在两个节点之间的双向的数据通道 。

(3)WebRTC Native C++ API

本地C++ API层,使浏览器厂商容易实现WebRTC标准的Web API,抽象地对数字信号过程进行处理。

(4)Transport / Session

传输/会话层

会话层组件采用了libjingle库的部分组件实现,无须使用xmpp/jingle协议

a. RTP Stack协议栈

Real Time Protocol

b. STUN/ICE

可以通过STUN和ICE组件来建立不同类型网络间的呼叫连接。

c. Session Management

一个抽象的会话层,提供会话建立和管理功能。该层协议留给应用开发者自定义实现。

(5)VoiceEngine

音频引擎是包含一系列音频多媒体处理的框架,包括从视频采集卡到网络传输端等整个解决方案。

PS:VoiceEngine是WebRTC极具价值的技术之一,是Google收购GIPS公司后开源的。在VoIP上,技术业界领先,后面的文章会详细了解

a. iSAC

Internet Speech Audio Codec

针对VoIP和音频流的宽带和超宽带音频编解码器,是WebRTC音频引擎的默认的编解码器

采样频率:16khz,24khz,32khz;(默认为16khz)

自适应速率为10kbit/s ~ 52kbit/;

自适应包大小:30~60ms;

算法延时:frame + 3ms

b.iLBC

Internet Low Bitrate Codec

VoIP音频流的窄带语音编解码器

采样频率:8khz;

20ms帧比特率为15.2kbps

30ms帧比特率为13.33kbps

标准由IETF RFC3951和RFC3952定义

c.NetEQ for Voice

针对音频软件实现的语音信号处理元件

NetEQ算法:自适应抖动控制算法以及语音包丢失隐藏算法。使其能够快速且高解析度地适应不断变化的网络环境,确保音质优美且缓冲延迟最小。

是GIPS公司独步天下的技术,能够有效的处理由于网络抖动和语音包丢失时候对语音质量产生的影响。

PS:NetEQ也是WebRTC中一个极具价值的技术,对于提高VoIP质量有明显效果,加以AEC\NR\AGC等模块集成使用,效果更好。

d.Acoustic Echo Canceler (AEC)

回声消除器是一个基于软件的信号处理元件,能实时的去除mic采集到的回声。

e.Noise Reduction (NR)

噪声抑制也是一个基于软件的信号处理元件,用于消除与相关VoIP的某些类型的背景噪声(嘶嘶声,风扇噪音等等… …)

(6)VideoEngine

WebRTC视频处理引擎

VideoEngine是包含一系列视频处理的整体框架,从摄像头采集视频到视频信息网络传输再到视频显示整个完整过程的解决方案。

a. VP8

视频图像编解码器,是WebRTC视频引擎的默认的编解码器

VP8适合实时通信应用场景,因为它主要是针对低延时而设计的编解码器。

PS:VPx编解码器是Google收购ON2公司后开源的,VPx现在是WebM项目的一部分,而WebM项目是Google致力于推动的HTML5标准之一

b. Video Jitter Buffer

视频抖动缓冲器,可以降低由于视频抖动和视频信息包丢失带来的不良影响。

c. Image enhancements

图像质量增强模块

对网络摄像头采集到的图像进行处理,包括明暗度检测、颜色增强、降噪处理等功能,用来提升视频质量


视频

WebRTC的视频部分,包含采集、编解码(I420/VP8)、加密、媒体文件、图像处理、显示、网络传输与流控(RTP/RTCP)等功能。

视频采集---video_capture

源代码在webrtc\modules\video_capture\main目录下,包含接口和各个平台的源代码。

在windows平台上,WebRTC采用的是dshow技术,来实现枚举视频的设备信息和视频数据的采集,这意味着可以支持大多数的视频采集设备;对那些需要单独驱动程序的视频采集卡(比如海康高清卡)就无能为力了。

视频采集支持多种媒体类型,比如I420、YUY2、RGB、UYUY等,并可以进行帧大小和帧率控制。

视频编解码---video_coding

源代码在webrtc\modules\video_coding目录下。

WebRTC采用I420/VP8编解码技术。VP8是google收购ON2后的开源实现,并且也用在WebM项目中。VP8能以更少的数据提供更高质量的视频,特别适合视频会议这样的需求。

视频加密--video_engine_encryption

视频加密是WebRTC的video_engine一部分,相当于视频应用层面的功能,给点对点的视频双方提供了数据上的安全保证,可以防止在Web上视频数据的泄漏。

视频加密在发送端和接收端进行加解密视频数据,密钥由视频双方协商,代价是会影响视频数据处理的性能;也可以不使用视频加密功能,这样在性能上会好些。

视频加密的数据源可能是原始的数据流,也可能是编码后的数据流。估计是编码后的数据流,这样加密代价会小一些,需要进一步研究。

视频媒体文件--media_file

源代码在webrtc\modules\media_file目录下。

该功能是可以用本地文件作为视频源,有点类似虚拟摄像头的功能;支持的格式有Avi

另外,WebRTC还可以录制音视频到本地文件,比较实用的功能。

视频图像处理--video_processing

源代码在webrtc\modules\video_processing目录下。

视频图像处理针对每一帧的图像进行处理,包括明暗度检测、颜色增强、降噪处理等功能,用来提升视频质量。

视频显示--video_render

源代码在webrtc\modules\video_render目录下。

在windows平台,WebRTC采用direct3d9和directdraw的方式来显示视频,只能这样,必须这样。

网络传输与流控

对于网络视频来讲,数据的传输与控制是核心价值。WebRTC采用的是成熟的RTP/RTCP技术。

音频

WebRTC的音频部分,包含设备、编解码(iLIBC/iSAC/G722/PCM16/RED/AVT、NetEQ)、加密、声音文件、声音处理、声音输出、音量控制、音视频同步、网络传输与流控(RTP/RTCP)等功能。

音频设备---audio_device

源代码在webrtc\modules\audio_device\main目录下,包含接口和各个平台的源代码。

在windows平台上,WebRTC采用的是Windows Core Audio和WindowsWave技术来管理音频设备,还提供了一个混音管理器。

利用音频设备,可以实现声音输出,音量控制等功能。

音频编解码---audio_coding

源代码在webrtc\modules\audio_coding目录下。

WebRTC采用iLIBC/iSAC/G722/PCM16/RED/AVT编解码技术。

WebRTC还提供NetEQ功能---抖动缓冲器及丢包补偿模块,能够提高音质,并把延迟减至最小。

另外一个核心功能是基于语音会议的混音处理。

声音加密--voice_engine_encryption

和视频一样,WebRTC也提供声音加密功能。

声音文件

该功能是可以用本地文件作为音频源,支持的格式有Pcm和Wav。

同样,WebRTC也可以录制音频到本地文件。

声音处理--audio_processing

源代码在webrtc\modules\audio_processing目录下。

声音处理针对音频数据进行处理,包括回声消除(AEC)、AECM(AECMobile)、自动增益(AGC)、降噪(NS)、静音检测(VAD)处理等功能,用来提升声音质量。

网络传输与流控

和视频一样,WebRTC采用的是成熟的RTP/RTCP技术。


好了,这么多的概念内容之后我要切入正题,iOS才是我们真正关心的。下面就进入我们关心的内容。

Webrtc的ios框架编译1.WebRTC的iOS框架的选择

目前两个比较活跃的开源WebRTC实现.

  • Google WebRTC:

项目地址是: https://code.google.com/p/webrtc/

  • Ericsson Research OpenWebRTC:

项目地址是: https://github.com/EricssonResearch/openwebrtc

我们戴维营教育为了给学生实战项目中运用WebRTC视频通话技术,选择Google的WebRTC项目来构建iOSApp的开发框架,因为目前Chrome浏览器和FireFox浏览器的WebRTC支持都是采用该项目.那么问题就来了,既然浏览器里都支持了WebRTC,那我们再去移植编译它到iOS平台干嘛呢,直接用webview 不行? 对,还不行!Apple在这方面已经严重拖后腿了.不过他有他牛逼的Facetime技术,可以随时随地的视频通话,但是他不开源,所以我们只能垂涎了.故还是老老实实的移植WebRTC吧.非常幸运的是,Google的Chromium项目开发者已经实现了其WebRTC的Objective-C的一套API了.

不过,丑话还是说在前头好,要从零开始集成WebRTC到我们的App中中,简直就是噩梦;因为WebRTC项目和Chromium项目有一定的关联依赖关系,而且这些项目都是跨平台的大项目,采用了Google自己的一套编译系统,相对我们日常的IDE来说要复杂的多.如果我们需要得到一个WebRTC的库或者框架,我们就需要忘记XcodeIDE和Interface Builder这些高科技,我们要切换到终端环境下用命令行下的黑科技来征服这一切.

2.开始WebRTC源码下载

前提条件:

  • 我现在用的Macbook,8G内存,运行OS X 10.9.5.

  • 安装最新的git和subversions并确保其可正常工作.

  • Xcode 6.1.1 和 Command Line Tools.

  • 中国大陆用户额外要求,快速的VPN,或者快速的shadowsocks服务.(FQ和给git和svn以及curl设置代理等).

2.1 创建一个编译目录

我们创建一个目录专门来存放项目编译工具和项目代码仓库等.确保该目录所在磁盘可用空间至少有8~10G.打开系统的终端工具进入到Shell:

wuqiong:~ apple$mkdir -p $HOME/opensource/webrtc_build/2.2 下载Chromium的depot工具

在执行下面命令之前,请确保你已经连上快速VPN已经FQ了,或者你已经给git单独配置了有效的socksFQ代理,如果你这些都不是问题,就当我没说.

wuqiong:~ apple$cd $HOME/opensource/webrtc_build/wuqiong:webrtc_build apple$git clone https://chromium.googlesource.com/chromium/tools/depot_tools.git


这是一套Google用来编译Chromium或者WebRTC的构建工具,在我们后续的编译过程中也将使用它.为了命令行使用方便,我们把这些工具的路径加入到系统环境变量PATH中去:

wuqiong:webrtc_build apple$echo"export PATH=$PWD/depot_tools:$PATH" >$HOME/.bash_profile

然后需要关闭当前终端重新开启一个来上面设置的环境变量生效.或者在现在终端执行入门命令在当前终端里加载生效:

wuqiong:webrtc_build apple$source$HOME/.bash_profile2.3 下载WebRTC的源码

在我们的编译工作目录webrtc_build下创建一个webtrtc子目录来存放代码,请执行下面命令:

wuqiong:webrtc_build apple$ mkdirwebrtc wuqiong:webrtc_build apple$ cdwebrtc

在上面的检查工作没错之后,我们就需要开始把WebRTC项目的代码仓库下载一份到本地来.由于其仓库之大,大约一共需要下载6G+的东西.所以这一步非常需要有耐心.而且需要有稳定无障碍的互联网.执行如下命令然后吧:

wuqiong:webrtc apple$ gclient config--name src http://webrtc.googlecode.com/svn/trunk wuqiong:webrtcapple$ echo "target_os = ['ios']" >>.gclient wuqiong:webrtc apple$ gclient sync--force

FQ快的去喝咖啡,慢的去约妹子吧.办完事情之后回来如果上面的命令都一切顺利,我们就可以往下走去开始编译了.(为了方便大家,我已经把webrtc_build目录打包备份,这样大家可以省去大量的代码下载时间.打包文件有5G,正在寻找网盘存放,随后公布.)

2.4 编译WebRTC.framework

到了这一步,源码应该已经下载好了.这些源码可以编译为好几个平台,OS X, Linux, Windows, Android,iOS等.这里我们只需要编译iOS平台的WebRTC,并制作成一个iOS的开发框架.这里我们不能用Xcode工具,因为这些项目压根就不支持XCode.我们需要在终端命令行环境下去搞定这一切!

首先,为了我们装逼玩黑武器,我们需要在webrtc的项目代码目录下创建一个脚本,这个脚本就是我为了简化命令的复杂度和提高使用的方便性专门编写的一个一键框架编译脚本,这个脚本就是今天的核心黑科技了.先创建一个空文件,然后赋予执行权限:

wuqiong:webrtc apple$ touchbuild_webrtc.sh wuqiong:webrtc apple$ chmod +xbuild_webrtc.sh

然后用编辑器打开编辑刚刚创建的脚本文件,把如下脚本粘贴进去之后保存并关闭:


1 #!/bin/bash
  2 # Script to build WebRTC.framework for iOS
  3 # Copyright (C) 2015 戴维营教育  - All Rights Reserved
  4 # Last revised 28/1/2015
  5 #
  6
  7 function build_iossim_ia32() {
  8     echo "*** building WebRTC for the ia32 iOS simulator";
  9     export GYP_GENERATORS="ninja";
10     export GYP_DEFINES="build_with_libjingle=1 build_with_chromium=0 libjingle_objc=1 OS=ios target_arch=ia32";
11     export GYP_GENERATOR_FLAGS="$GYP_GENERATOR_FLAGS output_dir=out_ios_ia32";
12     export GYP_CROSSCOMPILE=1;
13     pushd src;
14     gclient runhooks;
15     ninja -C out_ios_ia32/Release-iphonesimulator iossim AppRTCDemo;
16
17     echo "*** creating iOS ia32 libraries";
18     pushd out_ios_ia32/Release-iphonesimulator/;
19     rm -f  libapprtc_signaling.a;
20     popd;
21     mkdir -p out_ios_ia32/libs;
22     libtool -static -o out_ios_ia32/libs/libWebRTC-ia32.a out_ios_ia32/Release-iphonesimulator/lib*.a;
23     strip -S -x -o out_ios_ia32/libs/libWebRTC.a -r out_ios_ia32/libs/libWebRTC-ia32.a;
24     rm -f out_ios_ia32/libs/libWebRTC-ia32.a;
25     echo "*** result: $PWD/out_ios_ia32/libs/libWebRTC.a";
26
27     popd;
28 }
29
30 function build_iossim_x86_64() {
31     echo "*** building WebRTC for the x86_64 iOS simulator";
32     export GYP_GENERATORS="ninja";
33     export GYP_DEFINES="build_with_libjingle=1 build_with_chromium=0 libjingle_objc=1 OS=ios target_arch=x64 target_subarch=arm64";
34     export GYP_GENERATOR_FLAGS="$GYP_GENERATOR_FLAGS output_dir=out_ios_x86_64";
35     export GYP_CROSSCOMPILE=1;
36     pushd src;
37     gclient runhooks;
38     ninja -C out_ios_x86_64/Release-iphonesimulator iossim AppRTCDemo;
39
40     echo "*** creating iOS x86_64 libraries";
41     pushd out_ios_x86_64/Release-iphonesimulator/;
42     rm -f  libapprtc_signaling.a;
43     popd;
44     mkdir -p out_ios_x86_64/libs;
45     libtool -static -o out_ios_x86_64/libs/libWebRTC-x86_64.a out_ios_x86_64/Release-iphonesimulator/lib*.a;
46     strip -S -x -o out_ios_x86_64/libs/libWebRTC.a -r out_ios_x86_64/libs/libWebRTC-x86_64.a;
47     echo "*** result: $PWD/out_ios_x86_64/libs/libWebRTC.a";
48
49     popd;
50 }
51
52 function build_iosdevice_armv7() {
53     echo "*** building WebRTC for armv7 iOS devices";
54     export GYP_GENERATORS="ninja";
55     export GYP_DEFINES="build_with_libjingle=1 build_with_chromium=0 libjingle_objc=1 OS=ios target_arch=armv7";
56     export GYP_GENERATOR_FLAGS="$GYP_GENERATOR_FLAGS output_dir=out_ios_armv7";
57     export GYP_CROSSCOMPILE=1;
58     pushd src;
59     gclient runhooks;
60     ninja -C out_ios_armv7/Release-iphoneos AppRTCDemo;
61
62     echo "*** creating iOS armv7 libraries";
63     pushd out_ios_armv7/Release-iphoneos/;
64     rm -f  libapprtc_signaling.a;
65     popd;
66     mkdir -p out_ios_armv7/libs;
67     libtool -static -o out_ios_armv7/libs/libWebRTC-armv7.a out_ios_armv7/Release-iphoneos/lib*.a;
68     strip -S -x -o out_ios_armv7/libs/libWebRTC.a -r out_ios_armv7/libs/libWebRTC-armv7.a;
69     echo "*** result: $PWD/out_ios_armv7/libs/libWebRTC.a";
70
71     popd;
72 }
73
74 function build_iosdevice_arm64() {
75     echo "*** building WebRTC for arm64 iOS devices";
76     export GYP_GENERATORS="ninja";
77     export GYP_DEFINES="build_with_libjingle=1 build_with_chromium=0 libjingle_objc=1 OS=ios target_arch=arm64 target_subarch=arm64";
78     export GYP_GENERATOR_FLAGS="$GYP_GENERATOR_FLAGS output_dir=out_ios_arm64";
79     export GYP_CROSSCOMPILE=1;
80     pushd src;
81     gclient runhooks;
82     ninja -C out_ios_arm64/Release-iphoneos AppRTCDemo;
83
84     echo "*** creating iOS arm64 libraries";
85     pushd out_ios_arm64/Release-iphoneos/;
86     rm -f  libapprtc_signaling.a;
87     popd;
88     mkdir -p out_ios_arm64/libs;
89     libtool -static -o out_ios_arm64/libs/libWebRTC-arm64.a out_ios_arm64/Release-iphoneos/lib*.a;
90     strip -S -x -o out_ios_arm64/libs/libWebRTC.a -r out_ios_arm64/libs/libWebRTC-arm64.a;
91     echo "*** result: $PWD/out_ios_arm64/libs/libWebRTC.a";
92
93     popd;
94 }
95
96 function combine_libs() 
97 {
98     echo "*** combining libraries";
99     lipo  -create   src/out_ios_ia32/libs/libWebRTC.a \
100             src/out_ios_x86_64/libs/libWebRTC.a \
101             src/out_ios_armv7/libs/libWebRTC.a \
102             src/out_ios_arm64/libs/libWebRTC.a \
103             -output libWebRTC.a;
104     echo "The public headers are located in $PWD/src/talk/app/webrtc/objc/public/*.h";
105 }
106
107 function create_framework() {
108     echo "*** creating WebRTC.framework";
109     rm -rf WebRTC.framework;
110     mkdir -p WebRTC.framework/Versions/A/Headers;
111     cp ./src/talk/app/webrtc/objc/public/*.h WebRTC.framework/Versions/A/Headers;
112     cp libWebRTC.a WebRTC.framework/Versions/A/WebRTC;
113
114     pushd WebRTC.framework/Versions;
115     ln -sfh A Current;
116     popd;
117     pushd WebRTC.framework;
118     ln -sfh Versions/Current/Headers Headers;
119     ln -sfh Versions/Current/WebRTC WebRTC;
120     popd;
121 }
122
123 function clean() 
124 {
125     echo "*** cleaning";
126     pushd src;
127     rm -rf out_ios_arm64 out_ios_armv7 out_ios_ia32 out_ios_x86_64;
128     popd;
129     echo "*** all cleaned";
130 }
131
132 function update()
133 {
134     gclient sync --force
135     pushd src
136     svn info | grep Revision > ../svn_rev.txt
137     popd
138 }
139
140 function build_all() {
141     build_iossim_ia32 && build_iossim_x86_64 && \
142     build_iosdevice_armv7 && build_iosdevice_arm64 && \
143     combine_libs && create_framework;
144 }
145
146 function run_simulator_ia32() {
147     echo "*** running webrtc appdemo on ia32 iOS simulator";
148     src/out_ios_ia32/Release-iphonesimulator/iossim src/out_ios_ia32/Release-iphonesimulator/AppRTCDemo.app;
149 }
150
151 function run_simulator_x86_64() {
152     echo "*** running webrtc appdemo on x86_64 iOS simulator";
153     src/out_ios_x86_64/Release-iphonesimulator/iossim -d 'iPhone 6' -s '8.1'  src/out_ios_x86_64/Release-iphonesimulator/AppRTCDemo.app;
154 }
155
156 function run_on_device_armv7() {
157     echo "*** launching on armv7 iOS device";
158     ideviceinstaller -i src/out_ios_armv7/Release-iphoneos/AppRTCDemo.app;
159     echo "*** launch complete";
160 }
161
162 function run_on_device_arm64() {
163     echo "*** launching on arm64 iOS device";
164     ideviceinstaller -i src/out_ios_arm64/Release-iphoneos/AppRTCDemo.app;
165     echo "*** launch complete";
166 }
167
168 #运行命令行参数中第一个参数所指定的Shell函数
169 $@

这个编译脚本除了可以编译WebRTC项目自带的AppRTCDemo应用外,还可以编译出WebRTC.framework.

执行如下命令来编译我们所需要的全部:

wuqiong:webrtc apple$ ./build_webrtc.sh build_all

等上面命令完成之后,我们所需要的WebRTC框架就在当前目录下了.可以用ls命令查看之:

wuqiong:webrtc apple$ ls WebRTC.framework build_webrtc.sh  libWebRTC.a      srcwuqiong:webrtc apple$ 

第一个WebRTC.framework就是我们需要的框架了! 到此,我们的编译任务就完成了!不是吧..就这么简单?不是说起来超级麻烦吗?呵呵,装逼结束.繁琐的部分已经封装到了shell脚本里头去了.如果有兴趣可以去研究一下这个脚本.

2.5 WebRTC.framework的依赖.

如果项目使用了该框架,那么编译的时候需要在项目的Build Phases中添加如下库和框架:

  • libstdc++.6.dylib

  • libsqlite3.dylib

  • libc++.dylib

  • libicucore.dylib

  • Security.framework

  • CFNetwork.framework

  • GLKit.framework

  • AudioToolbox.framework

  • AVFoundation.framework

  • CoreAudio.framework

  • CoreMedia.framework

  • CoreVideo.framework

  • CoreGraphics.framework

  • OpenGLES.framework

  • QuartzCore.framework

重要提示

目前Google官方代码中在ARMv7平台有VP8视频编码的stackoverflow问题。

以iOS为例,建议直接使用CocoaPods上面的东西,名字是libjingle_peerconnection。

使用这个库里面的东西并不是很麻烦,但要搞清楚里面的逻辑却挺头大的,因为Google提供了一个例子,AppRTCDemo,里面由于使用了很多Google自己的一个架构,把整个Demo搞得剧复杂无比,不过搞清楚原理后,只需要使用几个简单的API就可以实现WebRTC的通信。

WebRTC要工作起来,需要一下几个方面

1. 双方建立PeerConnection

2. 一端创建和发出Offer,另一端接收Offer后响应Answer

3. PeerConnection的两端交换SDP信息

建立PeerConnection需要ICE信息,里面提供的网络打洞穿墙等路径数据,

RTCIceServer *iceServer = [[RTCICEServer alloc] initWithURI:[NSURLURLWithString:YOUR_SERVER]     username:USERNAME_OR_EMPTY_STRINGpassword:PASSWORD_OR_EMPTY_STRING]]; RTCPeerConnectionFactory *pcFactory = [[RTCPeerConnectionFactory alloc] init]; RTCPeerConnection *peerConnection = [pcFactory peerConnectionWithICEServers:iceServers                                                 constraints:nildelegate:self];

一端创建Offer

RTCMediaConstraints *constraints = [RTCMediaConstraints alloc]initWithMandatoryConstraints: @[ [[RTCPair alloc]initWithKey:@"OfferToReceiveAudio"value:@"true"], [[RTCPair alloc]initWithKey:@"OfferToReceiveVideo"value:@"true"] ] optionalConstraints:nil]; [peerConnection createOfferWithConstraints:constraints];

这之后的动作都是通过peerConnection的delegate来完成的createOffer之后,会触发didCreateSessionDescription方法,可以在这个方法中来设置localDescription

- (void)peerConnection:(RTCPeerConnection*)peerConnectiondidCreateSessionDescription:(RTCSessionDescription*)sdp error:(NSError *)error {[peerConnection setLocalDescription:sdp] }

localDescription设置成功后,会触发didSetSessionDescriptionWithError的方法

- (void)peerConnection:(RTCPeerConnection*)peerConnection didSetSessionDescriptionWithError:(NSError *)error{ if (peerConnection.signalingState ==RTCSignalingHaveLocalOffer) { // 通过SignalingChannel发送Offer之类的信息 } }

当另一端收到Offer时,要设置remoteDescription

RTCSessionDescription *remoteDesc =[[RTCSessionDescription alloc]initWithType:@"answer" sdp:sdp]; [peerConnectionsetRemoteDescription:remoteDesc];

剩下的一些delegate都是跟ICE的candidate相关的,处理在建立PeerConnection时的P2P连接信息

- (void)peerConnection:(RTCPeerConnection*)peerConnection gotICECandidate:(RTCICECandidate *)candidate {// 获得Candidate信息,通过signaling channel发送给另一端 }

当通过signalingchannel收到candidate信息后,添加到peerConnection,让P2P来处理打洞信息

RTCICECandidate *candidate = [[RTCICECandidate alloc]initWithMid:SDP_MID index:SDP_M_LINE_INDEXsdp:SDP_CANDIDATE]; [self.rtcPeerConnectionaddICECandidate:candidate];

最后一个是媒体数据处理,当peerConnection收到媒体数据时,会触发另一个方法

- (void)peerConnection:(RTCPeerConnection *)peerConnectionaddedStream:(RTCMediaStream *)stream { // Createa new render viewwith asizeof your choice RTCEAGLVideoView*renderView = [[RTCEAGLVideoView alloc]initWithFrame:CGRectMake(100,100)]; [stream.videoTracks.lastObjectaddRenderer:self.renderView]; // RTCEAGLVideoView is a subclass ofUIView, so renderView // can be inserted into your view hierarchywhere it suits your application. }

总共这些方法就能够简单的让WebRTC工作起来,但还需要很多别的处理,比如iOS的默认speaker播放声音,音视频编解码,chrome用的v8,iOS用的是H264,还有很多问题要等待一一处理。今天先到这里后续再更新谢谢大家。





蓝牙模块

现在可穿戴设备越来越多,特别是applewatch出来之后,各种的watch都推出了。这样连接就显得非常重要了,目前主流的连接还是蓝牙。因此蓝牙这方面的技能也是大家需要拓展的小知识。今天趁有点时间把这方面的知识整理一下。方便大家相互学习查阅。

说到蓝牙,首先要说一下蓝牙2.0,在苦逼的2.0时代,只能使用MFI认证的蓝牙模块设计你的蓝牙产品。因为其它未经认证的是不能被检测到的。除非用户越狱。那么什么是MFI认证呢?意思是(Make Foripod/ipad/iphone),只有少数的硬件厂商才有苹果的MFI认证。但试问自己做的产品要有多牛才能让用户为此越狱呢。所以一个MFI认证很多的iOS软件开发公司望而却步。

还有一点需要注意的是iOS是的蓝牙从出生开始就是对文件传输是绝对的拒绝的。原因很简单,因为非越狱苹果上面听歌不都是要花钱的么,如果你用蓝牙把歌曲传给别人,那不是侵权了么(当然,在TC没有侵权一说),所以,苹果在硬件上面就把你传输文件给限制了。苹果之所以给他们装上蓝牙不过是让你练练蓝牙耳机啊,蓝牙音箱之类的。

但是,在蓝牙4.0出来之后(注意,硬件上要4s以上,系统要ios6以上才能支持4.0),苹果开放了BLE通道,真是屌丝的福音啊,我们这些没有MFI认证的蓝牙设备终于可以连接非越狱的苹果了,所以也可以开发蓝牙应用啦。但是,这得是蓝牙4.0才有的,也就是说爱疯4也不支持哦,所以完全兼容还是没可能。

蓝牙4.0的出现突然之间让我们拥有了很多。我们可以畅快地开发自己蓝牙产品啦。

接下来我们进入正题。蓝牙开发一般分为以下五步。

1建立中心角色

#import

CBCentralManager *manager;

manager = [[CBCentralManager alloc]initWithDelegate:selfqueue:nil];

2扫描外设(discover)

[manager scanForPeripheralsWithServices:niloptions:options];

3连接外设(connect)

- (void)centralManager:(CBCentralManager *)centraldidDiscoverPeripheral:(CBPeripheral *)peripheraladvertisementData:(NSDictionary*)advertisementDataRSSI:(NSNumber*)RSSI

{

if([peripheral.name isEqualToString:BLE_SERVICE_NAME]){

[selfconnect:peripheral];

}

s);

}

-(BOOL)connect:(CBPeripheral *)peripheral{

self.manager.delegate =self;

[self.managerconnectPeripheral:peripheraloptions:[NSDictionarydictionaryWithObject:[NSNumbernumberWithBool:YES]forKey:CBConnectPeripheralOptionNotifyOnDisconnectionKey]];

}

4扫描外设中的服务和特征(discover)

- (void)centralManager:(CBCentralManager *)centraldidConnectPeripheral:(CBPeripheral *)peripheral

{

NSLog(@"Did connect to peripheral: %@", peripheral);

_testPeripheral = peripheral;

[peripheral setDelegate:self]; 
//查找服务

[peripheral discoverServices:nil];

}


- (void)peripheral:(CBPeripheral *)peripheraldidDiscoverServices:(NSError*)error

{

NSLog(@"didDiscoverServices");

if(error)

{

NSLog(@"Discovered services for %@ with error: %@",peripheral.name, [error localizedDescription]);

if([self.delegaterespondsToSelector:@selector(DidNotifyFailConnectService:withPeripheral:error:)])

[self.delegateDidNotifyFailConnectService:nilwithPeripheral:nilerror:nil];

return;

}

for(CBService *service in peripheral.services)

{

//发现服务

if([service.UUID isEqual:[CBUUIDUUIDWithString:UUIDSTR_ISSC_PROPRIETARY_SERVICE]])

{

NSLog(@"Service found with UUID: %@",service.UUID); 
//查找特征

[peripheral discoverCharacteristics:nilforService:service];

break;

       }

    }

}

- (void)peripheral:(CBPeripheral *)peripheraldidDiscoverCharacteristicsForService:(CBService *)serviceerror:(NSError*)error

{

if(error)

{

NSLog(@"Discovered characteristics for %@ with error: %@",service.UUID, [error localizedDescription]);

[selferror];

return;

}

NSLog(@"服务:%@",service.UUID);

for(CBCharacteristic *characteristic inservice.characteristics)

{

//发现特征

if([characteristic.UUID isEqual:[CBUUIDUUIDWithString:@"xxxxxxx"]]) {

NSLog(@"监听:%@",characteristic);
//监听特征

[self.peripheralsetNotifyValue:YESforCharacteristic:characteristic];

         }

    }

}

5与外设做数据交互(读 与 写)


- (void)peripheral:(CBPeripheral *)peripheraldidUpdateValueForCharacteristic:(CBCharacteristic *)characteristicerror:(NSError*)error

{

if(error)

{

NSLog(@"Error updating value for characteristic %@ error: %@",characteristic.UUID, [error localizedDescription]);

self.error_b = BluetoothError_System;

[selferror];

return;

}

//   NSLog(@"收到的数据:%@",characteristic.value);

[selfdecodeData:characteristic.value];

}


NSData*d2 = [[PBABluetoothDecode sharedManager]HexStringToNSData:@"0x02"];

[self.peripheral writeValue:d2 forCharacteristic:characteristictype:CBCharacteristicWriteWithoutResponse];

今天就先到这里,大家有什么新的增加知识点,再一起研究。博客对于我们搞开发的人来说是个好工具。可以自己整理复习一些知识,也可以让阅读者学到一些知识。所以在微博无处不在的时代,我们仍然畅快地书写长篇的博客。同志们加油!


Swift高级

接下来我再看一下,Swift高级。语法,可能会被认为是简单的、零散的,但是我们学习的重点是,将这些简单的、零散的语法熟练掌握,应用到功能的实现中去。这才是我们想要的。







//  main.swift
//  Swift-SeniorGrammer
//
//  Created by CE on 16/7/8.
//  Copyright © 2016年 vcchou. All rightsreserved.
//

import Foundation

print("Hello, World!")

//一、函数
//1.定义函数
//无参无返回值的函数
func eat() {
   print("eating")
}
//函数调用
eat()

//2.定义一个有参数无返回值的函数
//func 函数名(参数名称:参数类型。。。)
func drink(a: Int,b: Int) {
    print(a +b)
}
drink(10, b: 20)


//3.定义一个有参数和返回值的函数
//func 函数名(参数名称:参数类型。。。) ->返回值类型
func relax(a: Int,b: Int) ->Int {
    return a -b
}
var result1 = relax(30, b: 20)
print(result1)

//4.可以让元组作为函数的返回值
func run(a: Int,b: Int) ->(a1:Int,b1:Int) {
    return (a *10,b * 10)
}
var r = run(10, b: 20)
print(r)
print(r.a1) //访问到元组里的具体元素
print(r.b1)


//5.多参量函数
//函数可以有多种不同类型的参数
//函数一
func greet(personName: String) ->String {
    return"hello" + personName
}
//函数二
func greetAgain(personName: String) ->String {
    return"hello again" + personName
}
//实现多参量函数
func sayHello(personName: String,alreadyGreet: Bool) ->String{
    ifalreadyGreet {
       return greetAgain(personName)
    } else{
       return greet(personName)
    }
}
print(sayHello("尼古拉斯赵四", alreadyGreet: false))


//6.函数的外部参数
//主要是为了指明每个参数所代表的意义
func login(userName para1: String,password para2: String) {
    print("用户名是\(para1),密码是 \(para2)")
}
login(userName: "小明", password: "123")

//忽略参数
func jump(para1: String,_ para2: String) {
   print("\(para1) \(para2)")
}
jump("123", "456")

//7.函数的默认参数值
func fly(a: Int,b: Int = 10) ->Int {
    return a *b
}
var f = fly(20)
var f1 = fly(20, b: 30)
print(f1)

//8.定义可辨参数的函数(通过...使得函数的参数个数是动态的)
func sum(numbers: Int...) ->Int {
    var result =0
    for i innumbers {
       result += i
    }
   
    returnresult
}
var sumResult = sum(1,3,5,2,7,4)
print(sumResult)

//9.函数的常量参数和变量参数
//默认情况下,函数的参数都是let类型
func swim(var a: Int,b: Int) ->Int {
    a = a +10
    returna
}
var s = swim(10, b: 0)
print(s)

//10.函数的输入输出参数
//使用inout关键字来定义函数的输入输出参数,只有变量才能作为函数的输入输出参数
func change(inout a: Int,inout b: Int) {
    let temp =a
    a = b
    b =temp
}
var c1 = 10
var c2 = 15
change(&c1, b: &c2)

print("c1 is \(c1),c2 is \(c2)")

//11.函数类型的使用
//加法
func add(a:Int,b:Int) ->Int {
    return a +b
}
//减法
func sub(a:Int,b:Int) ->Int {
    return a -b
}
//共同特点是:(Int,Int) ->Int
//函数类型作为类型标注使用
var res1: (Int,Int) ->Int = add
var finalRes1 = res1(2,3)
print(finalRes1)

//函数类型作为参数类型
func hehe(a: (Int,Int) ->Int,b: Int,c: Int) ->Int {
    returna(b,c)
}
var h = hehe(sub, b: 10, c: 30)
print(h)

//函数类型可以作为函数返回类型
func test(a: Int,b: Int) ->(Int,Int) ->Int {
    if a + b> 10 {
       return sub
    } else{
       return add
    }
}
var t = test(5, b: 4)
print(t(100,200))


//二 闭包

//swift中的闭包是自包含的代码块 可以在代码块中传递和使用  类似oc中的block
//闭包可以捕捉和存储所在上下文任意的常量和变量的引用 即包裹这些常量和变量  在swift中会自动管理捕获的过程中涉及到所有的内存操作

//1.闭包表达式语法
//{(参数:参数类型……)->返回值类型 in
//
//       //执行语句
//
//}


//2.sort函数
//是系统提供的一个排序函数  用于对数组中的元素进行排序 并不会修改原来数组

let names = ["Jack","Rose","Xaioming","Zhangsan"];
//比较大小
func prepare(para1:String,para2:String)->Bool{

    return para1< para2

}

//实际上试讲自定义的比较函数整个作为参数传递给了sort函数
var newNames = names.sort()

print(newNames)

//3.使用闭包语法写  在oc中用到block的地方 在swift中就是闭包

newNames = names.sort({(para1:String,para2:String)->Boolin


return para1 > para2

})


print(newNames)


//4.根据上下文来推断类型
//在swift中系统可以自动的推断参数和返回值的类型
newNames = names.sort({para1,para2 in

    return para1> para2

})

print(newNames)


//5.单表达式闭包隐式返回  参数类型 return都隐藏达到隐式返回的目的  只有单行表达式时可以隐藏
newNames = names.sort({para1,para2 in

    para1 >para2

})


print(newNames)



//尾随闭包



//三、枚举
//在c中我们的枚举  将枚举名和一个整型的值相对应 在swift中枚举变得更加灵活  枚举名不再只跟整形值相对应 也不再给每个枚举名提供一个值 如果有值值的类型 可能是字符串 字符  整型 或者 浮点型

//1.定义一个枚举
//使用enum关键字
enum Direction{

   //枚举成员值
    //case关键字表示新的一行 成员值将被定义
    casewest
    caseeast
    casenorth
    casesouth
   
}


//多个成员值可以定义在同一条语句中 用逗号隔开
enum Aniaml {

    casedog,pig,cat,elephant

}



//2.使用枚举值
var direction = Direction.east
//枚举再被初次初始化之后 获取另外的值时 可以直接使用(.)调用
direction = .south

print(direction)


//3.匹配枚举值和swich语句
switch direction {

case.north:
   print("north")
case.south:
   print("south")
   
case.west:
   print("west")
   
case.east:
   print("east")
   
}


//四、类和结构体
//1.定义
//类 使用class关键字
class person {
    //定义属性
    var name =""
    var age =0

}

//结构体 是哦用struct关键字
struct student {

    //定义属性
    var number =20
    var grade =5

}

//2.类的实例化
//person类
var per = person()

//给类的属性赋值
per.name = "张三"
per.age = 3
print("姓名:\(per.name),年龄:\(per.age)")

//student的实例化
var stu = student()
//给结构体属性进行赋值
stu.grade = 7
stu.number = 50
print("年级:\(stu.grade),人数:\(stu.number)")


//3.类和结构体的混合编程


struct Hand{

    var number =0

}


class Child {
   
    var name =""
    var age =0
   //将结构作为类型标注  当类中包含自定的类型时 需要将包含定义的类型初始化
    var hands:Hand = Hand()
   
}

//实例化Child
var child = Child()
//属性赋值
child.name = "旺财"
child.age = 12
child.hands.number = 2;
print("小孩\(child.name),今年\(child.age)岁,有\(child.hands.number)只手")


//4.逐一构造器
//结构体具有逐一构造器 但是类没有 因为结构体是值类型 类是引用类型

struct Dog {

    var name =""
    var age =0

}
//逐一构造器 实例化的同时 对属性进行赋值

var dog = Dog(name: "大黄", age: 3)


//5.值类型
//特点 当赋值给变量或常量或者函数的时候 是通过拷贝的形式 拷贝之后是两个并存独立的文件
var dog1 = dog
print(dog1.name)

dog.name = "小黄"
print(dog.name)

//6.引用类型
//类是引用类型  引用不是独立的 当引用类型给变量或常量或函数复制时 只是做了指针的指向内存中并没有拷贝
class Cat {

    var name =""
    var age =0

}

var cat = Cat()
cat.name = "小猫"

var cat1 = cat
print(cat1.name)

cat.name = "大猫"
print(cat.name)
print(cat1.name)


//类和结构体的区别
//相同点 1.定义的方式是一样的  2.都有各自的属性 3.实例化的方式一样  4.属性赋值的方式也是一样的

//不同点 1.关键字不同  类class 结构体struct 2.实例化时 结构体有另外的逐一构造器 但类没有  3.结构体是值类型 类是引用类型

//联系 可以将结构体作为类型标注在类中使用


//7.实例方法和类方法的使用
class Tiger {

    var name =""
    var age =0
   //创建实例方法
    funceat(){
   
       print("practice makes prfect")
       //在实例方法中self表示当前类的对象
       self.name  = "tiger"
    }
   //self还可以用来区分属性和形参
    funcrun(name:String){
   
       self.name = name
    }
    //创建类方法可以通过关键字static或class关键字来标明
   //在实例方法前面加上一个static或class关键字就成了类方法
    static funcbark(){
       
       print("barking")
       
    }

}

//调用类方法和实例方法
//实例化
var tiger = Tiger()
//调用实例化方法:通过对象的调用
tiger.eat()
//调用类方法:通过类名调用
Tiger.bark()

//相同点和不同点


//8.在值类型(结构体)中修改属性
struct student1{

    //定义属性
    var number =100
    var grade =3
   //在值类型中定义实例方法 并在实例方法中修改结构体的属性的话 需要使用关键字mutating
    mutatingfunc study(){
   
    self.number= 60
       print(":\(self.number)")
   
    }
   //在实例方法中可以直接对self进行修改
    mutatingfunc play(){
       //使用逐一构造器
       self = student1(number: 30, grade: 8)
       print("学生人数:\(self.number),年级:\(self.grade)")
   
    }

}

var stu1 = student1()
stu1.play()
stu1.study()

//9.类的继承
class Car{

    var carName= ""
    var price =0.0
   
   //可以在父类的实例化方法中添加final关键字 表示 设置此方法不被子类重写
//    final funcrun(){
     func run(){
   
       print("奔跑吧,皮卡丘")
   
    }
   
}

//通过类的继承 子类可以继承父类所有的方法和属性
//和oc一样 swift中不许多继承
class AutoCar:Car{
   //给子类添加一个新的属性
    var factory= "山东蓝翔"

}

var autoCar = AutoCar()
autoCar.carName = "兰博基尼"
autoCar.price = 1.0;
autoCar.run()

class Bicycle:Car{
   //如果要在子类中重写父类的方法 需要用override关键字进行表明
    overridefunc run() {
       print("奔跑吧,呵呵")
    }
   
}

var bicycle = Bicycle()
bicycle.run()


//五 构造函数 (init函数或者初始化函数)

class Bird {

    var name =""
    var age =0
    letwight:Double
   //创建构造方法
   //构造方法的作用为当类被实例化时 第一个调用的方法
   init(){
   print("bird")
   
   //swift中的构造方法可以给常量赋值
       self.wight = 2.0
   
    }
   //带有内部参数的构造方法
   init(name:String){
    self.name =name
    self.wight =3.0
 
    }

   //带有外部参数的构造方法
    init(namepara1:String,age para2:Int){
       self.name = para1
       self.age = para2
       self.wight = 4.0

    }
   //隐藏外部参数的构造方法
   init(para1:String,_para2:Int){
       self.name = para1
       self.age = _para2
       self.wight = 4.0

    }

}


//创建bird对象
var bird1 = Bird.init()
var bird2 = Bird.init(name:"喜鹊")
var bird3 = Bird.init(name:"",age:3)
//var bird4 = Bird.init("麻雀",3)


class FlyBird:Bird{

   //在子类中重写父类的构造方法
   
    overrideinit(){
   
   super.init()
   
   
    }
   
    //析构函数
    //只适用于类当一个类的实例被释放后 析构器会被立即调用 析构器使用关键字deinit来标明
   deinit{
   
   print("析构器被调用")
   
   
    }
}

//实例化
var flyBird:FlyBird? = FlyBird()
print(flyBird)

//将flyBird对象置为nil 才会被调用
flyBird = nil

//六 扩展
//使用extension关键字定义扩展

extension Int {
    //扩展属性
    var a: Int{
       //扩展之后进行的操作
       return self * self
    }

}
//通过点直接调用属性
print(5.a)

extension Bird {

    //扩展属性
    varbirdColor:String {
       
       return self.name + "是白色的" //self指的还是bird对象
       
    }
    //扩展方法
    funcsingSong(){
   
   print("\(self.name)在唱歌")
   
    }

}

//通过扩展向父类中提添加的属性和方法 都可以传递给子类
var bird11 = FlyBird()

bird11.name = "鹦鹉"
print(bird11.birdColor)
print(bird11.singSong())





Swift基础

Swift已经更新到3.0啦,也算该成熟了,所有对于之前认为版本没确定,学了等于再学的同志可以开始啦!不然就要落后啦!

对于Swift的学习主要是官方文档,官方文档讲的也非常的详细,这里我就不再赘述。

详见,Swift官方文档

这里直接上最现实的最通俗的代码。个人学习和总结,希望大家指正。



//  main.swift
//  Swift-BasicGrammer
//
//  Created by CE on 16/7/8.
//  Copyright © 2016年 vicchou. All rightsreserved.
//

import Foundation

//普通打印
print("Hello, World!")
print("任何一门语言的学习开始于 \("Hello, World!")")

//注释:添加单行语句的注释用//,添加多行语句或者段语句的注释使用
//分号:可写可不写,一般情况下不加分号,但是,当同一行有多条语句的时候必须添加分号
//标记:在OC中,标记使用#pragma mark - xxxx,在Swift中,标记使用的是//MARK: -xxxxxx

//在Swift中,工程没有主函数,类似于脚本语言,但实质上,是一种动态编译型语言,代码的执行顺序是从上往下依次执行

//1.声明变量,使用var关键字
var a = 10
a = 20
print(a)

//2.声明常量,使用let关键字
let b = 20
//b = 30
print(b)

//3.类型标注
//为了在声明常量或者变量的时候,通过添加类型标注的方式来说明当前常量或者变量所代表的类型
var c: Int = 10
var d: Double = 2.2
//在Swift中,字符和字符串都用双引号
var e: Character = "e"
var f: String = "hello"

//4.常量和变量的命名

//var 1a = 10
//var - = 10
//var let = 10

//可以使用的命名方式
let 二狗子 = "二狗子"
let 🐶 = "三狗子"
var π = 3.14

//5.数据类型转换
var a1 = Int("10")
var b1 = String(10)
print("\(a1!)~~~\(b1)")

//6.声明布尔类型
var bool = true //只有true和false
var bool2: Bool = false

//7.元组变量
//元组是Swift提供的一种新的数据结构,将多个元素组合在一起叫做元组,在元祖中,元素的数据类型可以互不相同
var c1 = (4,"hello") //方式一
print(c1.1) //通过索引的方式访问元组元素

var(key1,key2) = (10,"abc") //方式二
print(key1)
print(key2) //通过映射的方式访问元组元素

let c2 = (name:"小芳",age:18,score:100) //方式三
print("有个姑娘叫 \(c2.name),今年 \(c2.age) 岁,考试成绩 \(c2.score)")//通过key值的方式访问元组元素

//8.可选类型
//可选类型也是Swift提供的一种新的类型,分为有值和无值,在Swift中,系统不会自动的帮我们初始化常量或者变量
//?和!,用?设置一个常量或者变量为可选类型,如果可选类型已经被初始化,变量或者常量的值则为这个初始化的值,如果可选类型没有被初始化的话,变量则为nil,这个过程叫做封包

//!,在变量或者常量后面添加!表示将可选类型转化为一般类型,这个过程叫做解包或者拆包
//可选类型就是为了处理值缺失的情况
var d1: Int? = 10
print(d1!)
var d2: String? = "hello"
print(d2!)

//9.if语句
if d1 == nil {
   print("没有值")
} else {
   print("有值")
}

//10.可选绑定
//使用可选绑定来判断可选类型是否包含值,如果有值则将值赋给另外一个变量或者常量
if let x = d1 {
   print(x)
}

//11.使用断言
//使用代码的方式实现断点可以达到的效果
//使用assert来添加断言,如果函数里面表达式为真,则没影响,如果表达式为假,则程序会崩溃到这个地方,作用就是进行辅助调试程序
let e1 = 10
assert(e1 < 100)

//错误处理
//抛出异常(throws)--->捕捉异常(添加关键字try try? try!)

//12.运算符

//赋值运算符
let a3 = 10
let b3 = 20

//算数运算符
print(a3 + b3)
print(a3 - b3)
print(a3 * b3)
print(a3 / b3)

//求余运算符
//在Swift中,可以使用浮点型进行求余运算
print(3.1 % 2.2)

//自增自减运算符
var i = 10
print(i++)
print(++i)

//复合赋值运算符
var c3 = 3
c3 += 1 //c3 = c3 + 1
print(c3)

//比较运算符
//返回值是布尔值
print(a3 >= b3)
print(a3 == b3)
print(a3 <= b3)

//三目运算符
var apple = 2
var banana = false
var total = banana ? 5 : 2
print(total)

//空合运算符
//形式:a ??b,表示对可选类型a进行空判断,如果a有值则对a进行解封并返回a的值,如果a无值则返回默认值b,空合运算符是三目运算符的升级版,以更优雅的方式诠释了封包和解封这两种行为
var choiceColor: String? = "red"
var defaultColor = "blue"
var useColor = choiceColor ?? defaultColor
print(useColor)
//使用空合运算符的必要条件:1.a必须是可选类型 2.a和b的数据类型必须一致

//逻辑运算符
//与
let answer1 = true
let answer2 = false
print(answer1 && answer2) //一假为假,全真为真
//或
print(answer1 || answer2) //一真则真,全假为假
//非
print(!answer2)

//区间运算符
//1...5表示1,2,3,4,5
for i in 1...5 {
    print("第\(i) 次 say hello")
}
//半开区间
for i  in 1..<5 {
    print("第\(i) 次 say world")
}

//13.字符串
//字符串的初始化
var str1: String = "" //方式一
var str2: String = String() //方式二

//判断字符串是否为空
print(str1.isEmpty)
print(str2.isEmpty)

//判断字符串是否相等
if str1 == str2 {
   print("相等")
}

//进行字符串的拼接
var str3 = "hello"
var str4 = "everybody"
var str5 = str3 + str4
print(str5)

//进行字符串的遍历
for c in str5.characters {
   print(c)
}

//在字符串中插入字符
str3.insert("a", atIndex: str3.startIndex) //在最前面插入字符
str3.insert("b", atIndex: str3.endIndex) //在最后面插入一个字符
str3.insert("c", atIndex:str3.endIndex.advancedBy(-2))//在字符串末尾向前数第几位插入字符
str3.insert("d", atIndex: str3.startIndex.successor())//在正向第二位插入字符
str3.insert("f", atIndex: str3.endIndex.predecessor())//在倒数第二位插入字符
print(str3)

//获取字符串中字符的个数
print(str3.characters.count)

//通过字符串中的索引获取对应的字符
for i in str3.characters.indices {
   //通过索引获取字符
   print(str3[i])
}

//删除字符串中指定位置的字符
str3.removeAtIndex(str3.startIndex)
str3.removeAtIndex(str3.endIndex.predecessor())
//删除字符串中指定范围的字符串
var range = str3.startIndex..
str3.removeRange(range)
print(str3)

//字符串的比较
var str6 = "abc"
var str7 = "bcd"
if str6 <= str7 {
   print("相同")
} else {
   print("不相同")
}

//判断前缀和后缀
var str8 = "hello world"
//判断前缀
print(str8.hasPrefix("h"))
//判断后缀
print(str8.hasSuffix("f"))

//14.数组
//创建空数组
var arr1 = [Int]() //方式一
var arr2 = Array() //方式二

//判断数组是否为空
print(arr1.isEmpty)

//创建带有默认值的数组
//count:数组元素个数,repeatedValue:默认的元素值
var arr3 = [Int](count: 10, repeatedValue: 5)
var arr4 = Array(count: 20, repeatedValue: 10)

//两个数组合并,两个数组里面的数据类型必须一致,相加后两个数组合并为一个数组
var arr5 = arr3 + arr4
print(arr5)

//方式三:用字面量的方式创建数组
var arr6 = [1,2,3,4]

//访问数组
print(arr6[3])

//修改数组元素
arr6[0] = 10
print(arr6)

//15.集合
//创建集合
var set1 = Set() //空集合 //方式一
//向集合中存放数据,集合和数组的区别:数组元素是有序的,集合的元素是无序的
set1.insert(10)
set1.insert(20)
set1.insert(30)
set1.insert(40)
set1.insert(50)
print(set1)

//删除集合中的某个元素
set1.remove(20)
print(set1)

//遍历集合
for i in set1.sort() {
   print(i)
}

//方式二:用字面量的方式创建集合
var set2: Set = [10,11,12,13]
var set3: Set = ["a","b","c","d"]

//集合的运算
var set4: Set = [1,2,3,4,5]
var set5: Set = [3,5,6,7,8]
//获取两个集合的交集
var set45 = set4.intersect(set5)
print(set45)
//合并两个集合,并且相同的元素只保留一份,即只保留一份交集
var set54 = set4.union(set5)
print(set54)

//获取除了交集之外set4剩下的部分
var set6 = set4.subtract(set5)
print(set6)

//获取除过交集之外剩下的所有元素
var set7 = set4.exclusiveOr(set5)
print(set7)

//判断两个集合是否相等
if set6 == set7 {
   print("相同")
} else {
   print("不相同")
}

//判断集合之间的关系
//判断一个集合是否是另外一个集合的子集
var set8: Set = [1,2,3,4]
print(set8.isSubsetOf(set4))

//判断一个集合是否包含另一个集合
print(set4.isSupersetOf(set8))

//判断一个集合是否是另外一个集合的子集并且两个集合不相等
print(set4.isStrictSubsetOf(set8))

//判断一个集合是否包含另外一个集合并且两个集合不相等
print(set4.isStrictSupersetOf(set8))

//16.字典
//创建空字典
var dic1 = [Int: String]() //方式一
//通过字面量的方式创建字典
var dic2 = [1:"a",2:"b",3:"c"] //方式二

//访问字典中的元素
print(dic2[2]!)
//修改字典中的元素
dic2[3] = "f"
print(dic2)

//字典的遍历
//方式一
for(key,value) in dic2 {
   print("\(key) 对应着 \(value)")
}
//方式二:字典的有序遍历
for key in dic2.keys.sort() {
   //获取到key对应的value
    var value =dic2[key]
   print("\(key) 对应着 \(value!)")
   
}

//17.循环控制语句
//for in
//_在Swift中表示被忽略
for _ in 1...10 {
    print("helloworld")
}

//while
var w = 0
while w < 10 {
   print("hahahhaahh")
   
    w++
}

//repeat while 相当于c中的do while
var r = 0
repeat {
   print("胖狗子")
    r++
} while r < 10

//swich语句
//Swift中的switch语句和c中的不同,当匹配的case分支中的代码执行完毕之后,不会接着向下执行下面的case语句
var somePoint = (2,2)
switch somePoint {
case (1,1):
   print("(1,1)")
case (2,_):
   print("(2,2)")
case (3,3):
   print("(3,3)")
default:
   print("default")
}

//元组值的绑定
switch somePoint {
case (let x,3):
   print(x)
default:
   print("没有匹配的值")
}

//where条件语句的使用,用于增加额外的条件
switch somePoint {
case let(x,y) where x == y:
   print("\(x)  \(y)")
default:
   print("default")
}

//fallthrough(贯穿),添加贯穿之后,会接着执行下面的代码
var somePoint2 = (2,2)
switch somePoint2 {
case (2,1):
   print("2,1")
case (2,2):
   print("2,2")
    //添加贯穿
   fallthrough
case (2,3):
   print("2,3")
default:
   print("default")
}