随着拥有多个硬线程CPU(超线程、双核)的普及,多线程和异步操作等并发程序设计方法也受到了更多的关注和讨论。本文主要是想与园中各位高手一同探讨一下如何使用并发来最大化程序的性能。
多线程和异步操作的异同
多线程和异步操作两者都可以达到避免调用线程阻塞的目的,从而提高软件的可响应性。甚至有些时候我们就认为多线程和异步操作是等同的概念。但是,多线程和异步操作还是有一些区别的。而这些区别造成了使用多线程和异步操作的时机的区别。
异步操作的本质
所有的程序最终都会由计算机硬件来执行,所以为了更好的理解异步操作的本质,我们有必要了解一下它的硬件基础。 熟悉电脑硬件的朋友肯定对DMA这个词不陌生,硬盘、光驱的技术规格中都有明确DMA的模式指标,其实网卡、声卡、显卡也是有DMA功能的。DMA就是直接内存访问的意思,也就是说,拥有DMA功能的硬件在和内存进行数据交换的时候可以不消耗CPU资源。只要CPU在发起数据传输时发送一个指令,硬件就开始自己和内存交换数据,在传输完成之后硬件会触发一个中断来通知操作完成。这些无须消耗CPU时间的I/O操作正是异步操作的硬件基础。所以即使在DOS这样的单进程(而且无线程概念)系统中也同样可以发起异步的DMA操作。
线程的本质
线程不是一个计算机硬件的功能,而是操作系统提供的一种逻辑功能,线程本质上是进程中一段并发运行的代码,所以线程需要操作系统投入CPU资源来运行和调度。
异步操作的优缺点
因为异步操作无须额外的线程负担,并且使用回调的方式进行处理,在设计良好的情况下,处理函数可以不必使用共享变量(即使无法完全不用,最起码可以减少共享变量的数量),减少了死锁的可能。当然异步操作也并非完美无暇。编写异步操作的复杂程度较高,程序主要使用回调方式进行处理,与普通人的思维方式有些初入,而且难以调试。
多线程的优缺点
多线程的优点很明显,线程中的处理程序依然是顺序执行,符合普通人的思维习惯,所以编程简单。但是多线程的缺点也同样明显,线程的使用(滥用)会给系统带来上下文切换的额外负担。并且线程间的共享变量可能造成死锁的出现。
适用范围
在了解了线程与异步操作各自的优缺点之后,我们可以来探讨一下线程和异步的合理用途。我认为:当需要执行I/O操作时,使用异步操作比使用线程+同步I/O操作更合适。I/O操作不仅包括了直接的文件、网络的读写,还包括数据库操作、Web Service、HttpRequest以及.Net Remoting等跨进程的调用。
而线程的适用范围则是那种需要长时间CPU运算的场合,例如耗时较长的图形处理和算法执行。但是往往由于使用线程编程的简单和符合习惯,所以很多朋友往往会使用线程来执行耗时较长的I/O操作。这样在只有少数几个并发操作的时候还无伤大雅,如果需要处理大量的并发操作时就不合适了。
“/”应用程序中的服务器错误。
未能加载文件或程序集“XXXXXX”或它的某一个依赖项。试图加载格式不正确的程序。
说明: 执行当前 Web 请求期间,出现未经处理的异常。请检查堆栈跟踪信息,以了解有关该错误以及代码中导致错误的出处的详细信息。
异常详细信息: System.BadImageFormatException: 未能加载文件或程序集“XXXXXX”或它的某一个依赖项。试图加载格式不正确的程序。
源错误:
执行当前 Web 请求期间生成了未经处理的异常。可以使用下面的异常堆栈跟踪信息确定有关异常原因和发生位置的信息。
程序集加载跟踪: 下列信息有助于确定程序集“XXXXXX”未能加载的原因。
警告: 程序集绑定日志记录被关闭。 要启用程序集绑定失败日志记录,请将注册表值 [HKLM\Software\Microsoft\Fusion!EnableLog] (DWORD)设置为 1。 注意: 会有一些与程序集绑定失败日志记录关联的性能损失。 要关闭此功能,请移除注册表值 [HKLM\Software\Microsoft\Fusion!EnableLog]。
当使用 Visual Studio 发布网站时,可能会遇到上述黄屏报错,原因之一是引用的 dll 路径不正确,可以用 Release 模式生成一次看看;原因之二是应用程序的位数与服务器的不匹配。
一般来说用“Any CPU”的方式没有问题,但遇到上述报错的百度网友都很轻松地通过修改 IIS 应用程序池的“启用 32 位应用程序”来解决这个问题,原因是他们的服务器是 64 位操作系统。当我这次在 32 位操作系统的服务器上遇到这个问题的时候真的是手足无措了,然后一阵乱配置,偶尔还能成功跑起来,于是仔细对比了发布到服务器上的文件,发现只有 bin 目录下的 dll 文件有区别,而导致这些区别的原因是我在发布时把“Release - Any CPU”换成了“Debug - Any CPU”,所以一时找不到更好的解决办法的朋友不妨也试试这个方法,只是会有一点点担心性能问题。有更好的解决方案的朋友也不要忘了联系我。
这里仅记录我个人的安装过程,仅供参考。
安装前的准备
手动备份:数据库、浏览器收藏夹、FTP 站点、Navicat 连接、远程桌面、RaiDrive(点登录)、RSS源、虚拟机镜像、“下载”目录、“桌面”目录、开机启动程序、iPhone 备份(C:\Users\[用户名]\Apple\MobileSync\Backup)、浏览器插件、自动备份中的差异化文件。
推荐的 Edge 扩展(登录账号会自动同步):Charset、Feedbro、IP Address and Domain Information、Kimi 浏览器助手、JSONView、Pretty Js、User-Agent Switcher and Manager、Wappalyzer、凌风云网盘助手、图片助手(ImageAssistant) 批量图片下载器、猫抓、网页更新提醒
桌面图标和开始菜单截图。桌面如果是手动排序图标的,先按分类归入文件夹,以方便系统恢复后按分类摆放。
备份:桌面、Chrome 用户目录、聊天记录、快捷回复、项目、hosts 文件。
像 QQ、TIM、微信、旺旺、千牛 等即时聊天工具,最好将数据文件目录拷贝一份(包含图片视频等),再打开软件登录帐号进行记录备份(一般不含图片视频等)。这样,在重装系统后,如果还原拷贝的聊天文件不能供聊天软件使用,可以用备份数据恢复。
备份剪映目录,否则重装后需要重新下载素材/字体等,位置:C:\用户\AppData\Local\JianyingPro\User Data\
备份到移动硬盘或 NAS
排查以上未提及的新软件或新设置是否有需要处理或备份的。
清空回收站
确定系统盘所在硬盘和分区和大小,避免在格式化时选错硬盘分区。
外星人电脑开机按 F2 进入 BIOS,F12 选择启动设备。
设置
这台电脑(右键)- 属性 - 高级系统设置 - 高级 - 性能设置 - 高级 - 更改 - 去掉“自动管理所有驱动器的分布文件大小”前的勾,选择 SSD 分区的分布文件为系统管理的大小,点设置,其它分区无分布文件,点设置 - 确定
以上是在 SSD 空间足够的情况下,如果不足,那么可以分配到其它分区。
Windows 更新、并在高级选项中开启(更新 Windows 时接收其它 Microsoft 产品的更新)
安装驱动
目前的 Windows Update 已经可以安装大部分驱动了,如果设备管理器中还有警示图标,或者有些硬件功能不能启用,那么可以前往对应品牌的官网下载,如联想驱动安装工具、戴尔驱动程序,其它可使用驱动精灵。
安装 Office
安装 Office 批量授权版
立即安装即可,不要更改路径。
恢复数据
Chrome 目录(C:\Users\[用户名]\AppData\Local\Google\Chrome\User Data\Default\)
QQ 记录(默认路径:C:\Users\[用户名]\Documents\Tencent Files)
微信记录(默认路径:C:\Users\[用户名]\Documents\WeChat Files),微信记录可能会不能使用
视情况删除非系统盘的 \Program Files、\Program Files (x86)、\Users\xoyozo\AppData 等
应用程序
安装 Edge。
先安装 SQL Server,再安装 Visual Studio(如欲同时安装低版本 VS,先安装低版本,再安装高版本)
安装 Adobe
安装其它软件
替换 hosts,若出现安全软件警告,信任即可。
激活 Windows、Office、Adobe 等:
嗯
细节
Win10 任务栏单击“文件资源管理器”打开“此电脑”的设置方法:打开任意文件资源管理器窗口,菜单 - 查看 - 选项,将“打开文件资源管理器时打开”改为“此电脑”
Win10 任务栏日期显示星期:控制面板 - 更改日期、时间和数字格式 - 其他设置 - 日期,将短日期改为:yyyy/M/d ddd
随系统启动程序的目录(所有用户):C:\ProgramData\Microsoft\Windows\Start Menu\Programs\StartUp
随系统启动程序的目录(单个用户):C:\用户\[用户名]\AppData\Roaming\Microsoft\Windows\「开始」菜单\程序\启动,(即:C:\Users\[用户名]\AppData\Roaming\Microsoft\Windows\Start Menu\Programs\Startup)
自带的微软五笔已经提供五笔拼音混打了,所以抛弃QQ五笔了
设置 Navicat 自动运行。
设置电源相关
SQL Server 按需设置启动/禁用:SQL Server 配置管理器 - SQL Server 服务 - 右侧双击选择实例 - 切换到“服务”选项卡 - 启动模式
先上代码
DataClassesXDataContext db = new DataClassesXDataContext();
// 使用 let 关键字
DateTime dt = DateTime.Now;
var q1 = (from z in db.dtZone
let adCount = db.dtAD.Count(ad => ad.iZone == z.ID)
select new
{
z.ID,
adCount,
}).Take(10).ToList();
Response.Write((DateTime.Now - dt) + "<br />");
// 先 Select 后 Take
dt = DateTime.Now;
var q2 = (from z in db.dtZone
select new
{
z.ID,
adCount = db.dtAD.Count(ad => ad.iZone == z.ID),
}).Take(10).ToList();
Response.Write((DateTime.Now - dt) + "<br />");
// 先 Take 后 Select
dt = DateTime.Now;
var q3 = (from z in db.dtZone
select new { z.ID }).Take(10).ToList().Select(z => new
{
z.ID,
adCount = db.dtAD.Count(ad => ad.iZone == z.ID),
}).ToList();
Response.Write((DateTime.Now - dt) + "<br />");
在一个有 365 行记录的 dtZone 表和 5700 行记录的 dtAD 的数据库中进行嵌套查询(远程),同样取 10 条 dtZone 记录及对应的 dtAD 数,结果表明前两种执行时间相差无几,第二种略快,但第三种由于进行了多次查询,消耗的时间接近于前两种的 10 倍。结果仅供参考。
编者按:今天腾讯万技师同学的这篇技术总结必须强烈安利下,目录清晰,层次分明,每个接口都有对应的简介、系统要求、实例、核心代码以及超实用的思维发散,帮你直观把这些知识点get起来。以现在HTML 5的势头,同志们,你看到的这些,可都是钱呐。
十二年前,无论多么复杂的布局,在我们神奇的table面前,都不是问题;
十年前,阿捷的一本《网站重构》,为我们开启了新的篇章;
八年前,我们研究yahoo.com,惊叹它在IE5下都表现得如此完美;
六年前,Web标准化成了我们的基础技能,我们开始研究网站性能优化;
四年前,我们开始研究自动化工具,自动化测试,谁没玩过nodejs都不好意思说是页面仔;
二年前,各种终端风起云涌,响应式、APP开发都成为了我们研究的范围,CSS3动画开始风靡;
如今,CSS3动画、Canvas、SVG、甚至webGL你已经非常熟悉,你是否开始探寻,接下来,我们可以玩什么,来为我们项目带来一丝新意?
没错,本文就是以HTML5 Device API为核心,对HTML5的一些新接口作了一个完整的测试,希望能让大家有所启发。
目录:
一、让音乐随心而动 – 音频处理 Web audio API
二、捕捉用户摄像头 – 媒体流 Media Capture
三、你是逗逼? – 语音识别 Web Speech API
四、让我尽情呵护你 – 设备电量 Battery API
五、获取用户位置 – 地理位置 Geolocation API
六、把用户捧在手心 – 环境光 Ambient Light API
七、陀螺仪 Deviceorientation
八、Websocket
九、NFC
十、震动 - Vibration API
十一、网络环境 Connection API
一、让音乐随心而动 – 音频处理 Web audio API
简介:
Audio对象提供的只是音频文件的播放,而Web Audio则是给了开发者对音频数据进行分析、处理的能力,比如混音、过滤。
系统要求:
ios6+、android chrome、android firefox
实例:
http://sy.qq.com/brucewan/device-api/web-audio.html
核心代码:
var context = new webkitAudioContext();
var source = context.createBufferSource(); // 创建一个声音源
source.buffer = buffer; // 告诉该源播放何物
createBufferSourcesource.connect(context.destination); // 将该源与硬件相连
source.start(0); //播放
技术分析:
当我们加载完音频数据后,我们将创建一个全局的AudioContext对象来对音频进行处理,AudioContext可以创建各种不同功能类型的音频节点AudioNode,比如
1、源节点(source node)
我们可以使用两种方式加载音频数据:
<1>、audio标签
var sound, audio = new Audio();
audio.addEventListener('canplay', function() {
sound = context.createMediaElementSource(audio);
sound.connect(context.destination);
});
audio.src = '/audio.mp3';
<2>、XMLHttpRequest
var sound, context = createAudioContext();
var audioURl = '/audio.mp3'; // 音频文件URL
var xhr = new XMLHttpRequest();
xhr.open('GET', audioURL, true);
xhr.responseType = 'arraybuffer';
xhr.onload = function() {
context.decodeAudioData(request.response, function (buffer) {
source = context.createBufferSource();
source.buffer = buffer;
source.connect(context.destination);
}
}
xhr.send();
2、分析节点(analyser node)
我们可以使用AnalyserNode来对音谱进行分析,例如:
var audioCtx = new (window.AudioContext || window.webkitAudioContext)();
var analyser = audioCtx.createAnalyser();
analyser.fftSize = 2048;
var bufferLength = analyser.frequencyBinCount;
var dataArray = new Uint8Array(bufferLength);
analyser.getByteTimeDomainData(dataArray);
function draw() {
drawVisual = requestAnimationFrame(draw);
analyser.getByteTimeDomainData(dataArray);
// 将dataArray数据以canvas方式渲染出来
};
draw();
3、处理节点(gain node、panner node、wave shaper node、delay node、convolver node等)
不同的处理节点有不同的作用,比如使用BiquadFilterNode调整音色(大量滤波器)、使用ChannelSplitterNode分割左右声道、使用GainNode调整增益值实现音乐淡入淡出等等。
需要了解更多的音频节点可能参考:
https://developer.mozilla.org/en-US/docs/Web/API/Web_Audio_API
4、目的节点(destination node)
所有被渲染音频流到达的最终地点
思维发散:
1、可以让CSS3动画跟随背景音乐舞动,可以为我们的网页增色不少;
2、可以尝试制作H5酷酷的变声应用,增加与用户的互动;
3、甚至可以尝试H5音乐创作。
看看google的创意:http://v.youku.com/v_show/id_XNTk0MjQyNDMy.html
二、捕捉用户摄像头 – 媒体流 Media Capture
简介:
通过getUserMedia捕捉用户摄像头获取视频流和通过麦克风获取用户声音。
系统要求:
android chrome、android firefox
实例:
捕获用户摄像头 捕获用户麦克风
http://sy.qq.com/brucewan/device-api/camera.html
http://sy.qq.com/brucewan/device-api/microphone-usermedia.html
核心代码:
1、摄像头捕捉
navigator.webkitGetUserMedia ({video: true}, function(stream) {
video.src = window.URL.createObjectURL(stream);
localMediaStream = stream;
}, function(e){
})
2、从视频流中拍照
btnCapture.addEventListener('touchend', function(){
if (localMediaStream) {
canvas.setAttribute('width', video.videoWidth);
canvas.setAttribute('height', video.videoHeight);
ctx.drawImage(video, 0, 0);
}
}, false);
3、用户声音录制
navigator.getUserMedia({audio:true}, function(e) {
context = new audioContext();
audioInput = context.createMediaStreamSource(e);
volume = context.createGain();
recorder = context.createScriptProcessor(2048, 2, 2);
recorder.onaudioprocess = function(e){
recordingLength += 2048;
recorder.connect (context.destination);
}
}, function(error){});
4、保存用户录制的声音
var buffer = new ArrayBuffer(44 + interleaved.length * 2);
var view = new DataView(buffer);
fileReader.readAsDataURL(blob); // android chrome audio不支持blob
… audio.src = event.target.result;
思维发散:
1、从视频拍照自定义头像;
2、H5视频聊天;
3、结合canvas完成好玩的照片合成及处理;
4、结合Web Audio制作有意思变声应用。
三、你是逗逼? – 语音识别 Web Speech API简介:
1、将文本转换成语音;
2、将语音识别为文本。
系统要求:
ios7+,android chrome,android firefox
测试实例:
http://sy.qq.com/brucewan/device-api/microphone-webspeech.html
核心代码:
1、文本转换成语音,使用SpeechSynthesisUtterance对象;
var msg = new SpeechSynthesisUtterance();
var voices = window.speechSynthesis.getVoices();
msg.volume = 1; // 0 to 1
msg.text = ‘识别的文本内容’;
msg.lang = 'en-US';
speechSynthesis.speak(msg);
2、语音转换为文本,使用SpeechRecognition对象。
var newRecognition = new webkitSpeechRecognition();
newRecognition.onresult = function(event){
var interim_transcript = '';
for (var i = event.resultIndex; i < event.results.length; ++i) {
final_transcript += event.results[i][0].transcript;
}
};
测试结论:
1、Android支持不稳定;语音识别测试失败(暂且认为是某些内置接口被墙所致)。
思维发散:
1、当语音识别成为可能,那声音控制将可以展示其强大的功能。在某些场景,比如开车、网络电视,声音控制将大大改善用户体验;
2、H5游戏中最终分数播报,股票信息实时声音提示,Web Speech都可以大放异彩。
四、让我尽情呵护你 – 设备电量 Battery API简介:
查询用户设备电量及是否正在充电。
系统要求:
android firefox
测试实例:
http://sy.qq.com/brucewan/device-api/battery.html
核心代码:
var battery = navigator.battery || navigator.webkitBattery || navigator.mozBattery || navigator.msBattery;
var str = '';
if (battery) {
str += '<p>你的浏览器支持HTML5 Battery API</p>';
if(battery.charging) {
str += '<p>你的设备正在充电</p>';
} else {
str += '<p>你的设备未处于充电状态</p>';
}
str += '<p>你的设备剩余'+ parseInt(battery.level*100)+'%的电量</p>';
} else {
str += '<p>你的浏览器不支持HTML5 Battery API</p>';
}
测试结论:
1、QQ浏览器与UC浏览器支持该接口,但未正确显示设备电池信息;
2、caniuse显示android chrome42支持该接口,实测不支持。
思维发散:
相对而言,我觉得这个接口有些鸡肋。
很显然,并不合适用HTML5做电池管理方面的工作,它所提供的权限也很有限。
我们只能尝试做一些优化用户体验的工作,当用户设备电量不足时,进入省电模式,比如停用滤镜、摄像头开启、webGL、减少网络请求等。
五、获取用户位置 – 地理位置 Geolocation简介:
Geolocation API用于将用户当前地理位置信息共享给信任的站点,目前主流移动设备都能够支持。
系统要求:
ios6+、android2.3+
测试实例:
http://sy.qq.com/brucewan/device-api/geolocation.html
核心代码:
var domInfo = $("#info");
// 获取位置坐标
if (navigator.geolocation) {
navigator.geolocation.getCurrentPosition(showPosition,showError);
}
else{
domInfo.innerHTML="抱歉,你的浏览器不支持地理定位!";
}
// 使用腾讯地图显示位置
function showPosition(position) {
var lat=position.coords.latitude;
var lon=position.coords.longitude;
mapholder = $('#mapholder')
mapholder.style.height='250px';
mapholder.style.width = document.documentElement.clientWidth + 'px';
var center = new soso.maps.LatLng(lat, lon);
var map = new soso.maps.Map(mapholder,{
center: center,
zoomLevel: 13
});
var geolocation = new soso.maps.Geolocation();
var marker = null;
geolocation.position({}, function(results, status) {
console.log(results);
var city = $("#info");
if (status == soso.maps.GeolocationStatus.OK) {
map.setCenter(results.latLng);
domInfo.innerHTML = '你当前所在城市: ' + results.name;
if (marker != null) {
marker.setMap(null);
}
// 设置标记
marker = new soso.maps.Marker({
map: map,
position:results.latLng
});
} else {
alert("检索没有结果,原因: " + status);
}
});
}
测试结论:
1、Geolocation API的位置信息来源包括GPS、IP地址、RFID、WIFI和蓝牙的MAC地址、以及GSM/CDMS的ID等等。规范中没有规定使用这些设备的先后顺序。
2、初测3g环境下比wifi环境理定位更准确;
3、测试三星 GT-S6358(android2.3) geolocation存在,但显示位置信息不可用POSITION_UNAVAILABLE。
六、把用户捧在手心 – 环境光 Ambient Light简介:
Ambient Light API定义了一些事件,这些时间可以提供源于周围光亮程度的信息,这通常是由设备的光感应器来测量的。设备的光感应器会提取出辉度信息。
系统要求:
android firefox
测试实例:
http://sy.qq.com/brucewan/device-api/ambient-light.html
核心代码:
这段代码实现感应用前当前环境光强度,调整网页背景和文字颜色。
var domInfo = $('#info');
if (!('ondevicelight' in window)) {
domInfo.innerHTML = '你的设备不支持环境光Ambient Light API';
} else {
var lightValue = document.getElementById('dl-value');
window.addEventListener('devicelight', function(event) {
domInfo.innerHTML = '当前环境光线强度为:' + Math.round(event.value) + 'lux';
var backgroundColor = 'rgba(0,0,0,'+(1-event.value/100) +')';
document.body.style.backgroundColor = backgroundColor;
if(event.value < 50) {
document.body.style.color = '#fff'
} else {
document.body.style.color = '#000'
}
});
}
思维发散:
该接口适合的范围很窄,却能做出很贴心的用户体验。
1、当我们根据Ambient Light强度、陀螺仪信息、当地时间判断出用户正躺在床上准备入睡前在体验我们的产品,我们自然可以调整我们背景与文字颜色让用户感觉到舒适,我们还可以来一段安静的音乐,甚至使用Web Speech API播报当前时间,并说一声“晚安”,何其温馨;
2、该接口也可以应用于H5游戏场景,比如日落时分,我们可以在游戏中使用安静祥和的游戏场景;
3、当用户在工作时间将手机放在暗处,偷偷地瞄一眼股市行情的时候,我们可以用语音大声播报,“亲爱的,不用担心,你的股票中国中车马上就要跌停了”,多美的画面。
参考文献:
https://developer.mozilla.org/en-US/docs/Web/API
http://webaudiodemos.appspot.com/
http://www.w3.org/2009/dap/
原文:http://mathewsanders.com/designing-adaptive-layouts-for-iphone-6-plus/
翻译:叮当当咚当当小胖妞呀
从IOS6开始,苹果公司就一直建议我们使用自适应布局,但是迄今为止,我发现大家都在回避这个问题,考虑的最多的仍然是固定布局。
iPhone 6的上市让人们很难再去逃避自适应布局这个问题,四种屏幕尺寸(如果要支持iPad就要上升到5种)、三种分辨率和转向似乎让着手于自适应布局变得更加简单。
在文章的最后,你应该能流畅使用 storyboards、 约束(constraints)和 size class特性,这三个是Apple为开发和制作自适应布局提供的Xcode工具。
所以下载Xcode6,给自己来杯饮料,留上一个小时的时间,让我们开始吧!
- Storyboards
- 模拟尺寸
- 约束
- 头部和尾部空间
- 横向空间约束
- 同等宽度约束
- 纵横比约束
- 约束管理
- 添加约束
- 检查和编辑现有约束
- 删除约束
- 布局问题和冲突
- 错位视图警告
- 约束缺失错误
- Size Classes
- 宽度和高度特性
- 我们的目标…
- 为通用的size class添加约束
- 为iPhone纵向布局添加约束
- 为iPhone横向布局添加约束
- 编辑助手:设备预览
- 为iPad布局添加约束
- 使用布局和间隔视图
- 布局视图样例
- 间隔视图样例
- 下一步
Storyboards
Xcode中,storyboards可以让你在屏幕上拖动和放置原生对象(例如按钮、图片、文本框和标签)并且定义每个屏幕之间如何连接。
在Xcode专业术语中,可以看到、摸到、互动的用户界面元素,我们称之为 视图(按钮、图片、文本框、标签等等)。
包含并管理所有那些视图的屏幕被称为 视图控制器,我倾向于交替使用这些术语。
当我们在一个storyboard上添加视图控制器的时候,它的纵横比其实跟任何设备都不匹配,事实上,它是一个完美的600pt*600pt的正方形:
这提醒了我们,storyboard屏幕并不代表特定硬件上的屏幕,而是 任何IOS设备的抽象表现。
模拟尺寸
也许你不介意在600pt*600pt的画布上操作,但是在实际大小上操作可能会更有帮助。
我们可以非常方便的在Xcode里改变视图控制器模拟的尺寸和转向:
约束
向你们展示约束的最好方法是实例。
我们先创建一个简单的包含一个屏幕的storyboard(模拟4英寸iPhone的尺寸),再添加两个相邻的正方形。
当我们在竖屏中运行app的时候,这两个正方形完全按照我们的布局排列,当我们变成横屏时,它们仍然忠于自己的坐标:
当我们使用iPhone 6模拟器运行项目的时候,我们可以再一次看到,我们的布局并没有自适应不同的屏幕尺寸,屏幕右边多出了可见的空白区域。
这是由于没有约束,这些对象仍然会按照我们在storyboard上创建的x,y坐标和面积来定位。
所以让我们开始添加约束,看看会对它们有些什么影响。
头部和尾部空间
我们在蓝色方块前添加10pt的头部空间约束,在红色方块后添加10pt的尾部空间约束。
当我们运行项目的时候,约束把蓝色方块固定在屏幕左侧,把红色方块固定在右侧。
横向空间约束
我们在两个方块之间再添加一个10pt的横向空间约束,就像上一个约束一样,这样会让两个方块之间有一个固定的空间。
当我们运行项目的时候,会发现一个意料之外的结果:我们所有的约束都满足了,但是为了满足所有约束,其中一个方块被拉伸了。
这似乎告诉我们这样一件事:除非被告知,否则IOS会尽量让大多数对象都保持“自然”大小。
同等宽度约束
我们添加一个同等宽度约束。
当运行项目的时候,我们可以看到两个方块宽度的增量相同。
纵横比约束
最后,对两个方块添加纵横比约束。
这个约束保证了宽度和高度的比永远是相同的。
当我们运行app的时候,我们可以看到每个方块的宽度在增加,高度也同时在增加。
用iPhone 6模拟器运行项目,覆盖我们之前添加过的约束,你可以看到这两个正方形是如何利用可用空间来拉伸的。
约束管理
现在你已经看到了我们是如何使用约束的,下面我们来介绍一下具体操作。
提示:尽量少使用约束
添加约束的一个要点是试着挑战自己,越少使用约束越好。
虽然我并不认为添加的约束数量能够很大程度上影响IOS的性能,但是越少的使用约束,你的布局就会越好维护。
添加约束
在storyboard中添加约束有三个方法:
- 约束弹窗也许最好的方法就是选择一个或多个对象,从其中一个弹窗中添加约束。我认为这是个很好的方法,因为可以看到现有约束的范围。如果有一条约束不可用,可能就意味着你需要两个或更多对象来实施这条约束。
- ctrl + 拖动你可以按住ctrl键,同时拖动到一个对象上(可以是相邻对象、父级对象或者是自身),会出现一个浮层告诉你可以被添加的约束有哪些。这是我最喜欢的添加约束的方法,因为你不需要把你的鼠标移动到大老远的底部再回到对象。
- 菜单/绑定你也可以选择一个或多个对象,在菜单中点击 editor -> pin来选择约束,这其实是最没有效率的方法,但是如果你发现自己不停的重复添加一种约束,也许为一个或多个选项添加绑定更合理一点。
检查和编辑现有约束
检查视图中应用的约束最简单的办法就是选择视图,切换到Size检查面板。
或者在文档大纲层次面板中切换constant数值。
无论是哪种方法,选择约束可以让你直接更新它。
删除约束
同样的,约束也可以通过高亮+点击删除按钮来删除。
要删除一个视图的所有约束,或者屏幕中的所有约束,在布局问题弹窗中有一条捷径:
布局问题和冲突
当我们在storyboard中添加约束的时候,Xcode总会给出一些关于这些约束的警告或者错误。
有时候这些问题可以忽略不计,但是更多的时候,它们必须被解决。
查看约束警告和错误最好在文档大纲面板中,每个视图控制器会有一个红色或者黄色的icon来表示是不是有布局问题:
错位视图警告
一个比较常见的警告是错位视图警告,如果约束规定这个视图应该在的地方,和这个视图实际在的地方不匹配,那么这种警告随时会出现。
当你转换模拟尺寸或者不小心用鼠标移动了一个视图,这些警告也会出现。
点击一个错位的视图,屏幕上会出现一个虚线框,告诉你约束规定了这个视图应该在的位置。
如果点击警告,会出现一个带有几个选项的浮层,你可以选择让Xcode更新约束来匹配storyboard上的位置,也可以让Xcode更新框架来移动或者拉伸视图,使它们回到约束规定的位置。
缺失约束错误
让我们回到一开始蓝方块和红方块的例子,我们没有添加过一个关于纵向位置的约束,但当我们运行app的时候看起来完全没问题,因为IOS会认为纵向位置就是我们在storyboard上放置的位置,不过为了避免一些意料之外的行为,我们最好还是规定一下。
在 顶部布局向导添加顶部空间约束可以消除一些说不定道不明的约束和错误。
Size Classes
为了在大多数基础布局或者更高级的布局上使用约束,你需要同时使用size classes。
Size classes是一种能够告诉你物理设备宽度和高度的特性。
当我们在storyboard的屏幕上添加对象或者约束时,只有设备符合当前的宽高特性,对象才会显现,约束才会被应用。
这是一个非常有用的方法,它让我们确定了不同设备和转向下布局的变化。
高度和宽度特性
使用 紧凑(compact)、 任意(any)和 正常(regular)这三种的组合,可以让我们确定一些范围内的设备布局。
虽然不能涉及每一个变化(比如不能确定iPhone 6 Plus的竖向模式,也不能让iPad的转向消失),但是大多数的变化都可以确定。
我们说过,介绍size classes最简单的方法是实例,所以让我们看一个例子。
我们的目标…
作为例子,我们做一个Instagram详情页的简单版本,可以适应大屏和转向。
我们的目标是创建一个布局,它可以机智的适应更大的iPhone 6设备(我说的机智,是指只有照片会放大,其他的元素如头像照片仍然不变),并且当设备横屏的时候,图片和元数据会并肩排列,而不是像竖屏时的上下排列。
为通用的size class添加约束
跟之前的例子不同,这次我们不使用模拟尺寸度量,我们使用通用size class的抽象方形布局,这样会使我们记住,为这个size class添加的任何视图和约束,只能是那些添加在任意屏幕尺寸和转向上的视图和约束。
我们需要用到这些约束:
除了必要的空间、间隙和纵横比约束,我们还需要让某些对象排列在顶部或者底部。
现在,在你的storyboard上添加视图和约束吧。做这步的时候,我的storyboard长这样:
注意,现在我们有了所有转向上所需要的元素,但是我没有费心完成每个转向上的布局,下一步我们才会做!
为iPhone纵向布局添加约束
现在,我们把size class切换到 紧凑宽度和 正常高度,这个尺寸特性的组合适用于任何竖屏下的iPhone设备。
如果要切换size classes,点击storyboard中下方的size class标签,选择你想要看到的宽高组合。
注意,当你切换size class时,视图控制器的尺寸会更新来显示新的抽象布局。
最后,你可以重新定位、拉伸视图和添加竖屏上的约束了。这里有一些我添加到竖向布局上的约束:
这是我做到这步时,我的storyboard的样子:
在这个阶段,你可以用任何的iPhone模拟器(3.5、4、4.7或者5.5英寸屏幕)来运行你的项目,看看竖屏情况下的布局是否合理,虽然如果转向成横屏时,布局看上去会一团糟。
注意,我们没有固定图片的宽度,我们只是简单地把图片的左右边缘固定在屏幕两侧。当变为更大的iPhone 6屏幕尺寸时,图片会被拉伸,又因为我们添加了一个固定图片纵横比的约束,所以高度也会同时增加。
为iPhone横向布局添加约束
现在切换size class到 任意宽度和 紧凑高度,这种尺寸特性的组合适用于任何横屏下的iPhone设备,并且会为了满足横屏视图下的目标更新布局。
这次,图片的顶部、左边和底部边缘都被固定在父级视图的边缘。
这个局部另一个有意思的地方是,在评论标签上添加的尾部空间约束。
当图片的约束使得图片变大的时候,这个标签上的约束会使得自身变窄。为了适应这种视图下的文本,文本高度需要增加到两行——的确如此!
下图是我做到这步时storyboard的样子。注意,在文档大纲中,一些约束会稍稍渐隐,这些约束是在竖向视图中我们添加的,它们仍然存在,但是当我们选中当前的宽高特性时,它们不会被应用。
也要注意,当你转换宽高特性时,storyboard会更新以便显示你为那些特性增加的约束。太棒了!
现在运行项目,我们会发现当转向改变时,我们有了一个既适用于横屏又适用于竖屏的布局,更棒的是在转向时,会有一个完美的过渡动画。
我把整个过渡动画放慢,这样你们能看的更加清楚。注意动画中也有层级关系,正由于此,即使在正常布局下视图并没有重叠,我们也需要考虑如何安排视图的层级关系……
编辑助手:设备预览
Xcode另外一个很有用的东西:设备预览助手。你不再需要无数次运行项目来检查你的布局是否正确,你只需要使用编辑助手storyboard预览,选择你想要的所有设备和转向就可以了。
这并不是完美的(比如导航条的颜色在这个例子中缺失了),但是它却是一个很好地选择,它可以让你把你自己的语言切换到双倍长度伪语言,可以在你的storyboard中重复任何文本。
这是我storyboard上的预览助手,它展示了一个3.5英寸横屏和4英寸竖屏的iPhone,你可以看到我还需要再加工加工来适应更长的标签……-_-
为iPad布局添加约束
现在,切换size classes到 正常宽度和 正常高度,这是iPad横屏和竖屏的特性。
就像之前一样,我们重新定位、拉伸我们的视图。对于iPad布局,我决定使用固定尺寸的图片(所以它不会像在iPhone上那样切换横竖屏时会改变大小),元数据都显示在图片底部。
下图是我在重新排列视图并添加了约束之后的storyboard:
到现在为止,我们为某些特别的size class特性添加了约束,但是现在我们要尝试些不同的东西。
由于iPad上会有多余的空间,我们不要仅仅只是重排视图,让我们来加一点额外的内容。对于这个例子来说,我们再加两张图片(也许是在这张主照片之前和之后拍摄的照片)。
这是我的storyboard更新之后的样子:
我原本可以手动输入这些次级图片的宽高,但我没有,取而代之的是我让它们的尺寸变成主图片的某个比例。
这样做的好处是如果我们决定改变主图片的尺寸,布局的其他部分会自动的计算并且更新次级图片的宽高,而不用我们手动输入。
这是一张我截的图,我改变了主图片的宽度约束,可以看到其他的部分也随之更新了。
我没有在iPad模拟器上运行项目并截屏,不过这里有一些横屏和竖屏的效果图:
使用布局和间隔视图
Xcode中有些约束我们可以随时运用,但是对于一些些常见的布局,我们需要一点帮助。
布局视图样例
要把一个视图的边缘固定在父级视图的边缘,或者把它固定在父级视图的正中心是件很简单的事情,~~但是我没有发现简便的方法,让视图固定在离正中心有一段固定距离的地方。~~感谢Jeff Nouwen指出,在把视图固定在中间后,你可以改变constant值来达到你的期望。
理论上来说,我们需要的是一个‘ 中心排列导向’,即任何视图都能水平和垂直居中与父级视图中:
为了实现这个目标,通过现在我们可以使用的约束,我们把我们的视图嵌入一个外层视图(这个外层视图没有背景色,只是为了帮助我们布局而存在),可以把它定位在父级视图的中间。
通过这个方法,你的约束看起来可能像这样:
现在我比较喜欢让我的布局层级尽量平。另外一个不使用外层视图的方法,我叫它 布局视图。
使用这种方法,我倾向于在storyboard中添加一个颜色明亮的视图,然后把它的hidden
属性设置为true
,你仍然可以在storyboard布局中看到它(虽然稍稍渐隐),但是运行模拟器的时候它不会出现。
现在你在你的视图和布局视图中可以添加 居中排列(align center to center)。
间隔视图样例
之前我们看到了我们是如何固定视图边缘到父级视图边缘的间隙,使得当父级视图变大时,间隙约束也会让视图变大。
但是如果我们想要视图仍然保持原来的大小,在父级视图变大时,视图之间的间隙也变大呢?
理论上来说,我们想要某种类似 同等宽度约束的约束,只不过同等宽度约束是作用在视图上,我们想让它作用在约束上。
此时我们需要添加 间隔视图来帮助我们布局,但由于我们不想在运行app时看到它们,我们会把它们的hidden
属性设置为true
。
使用这项技术的方法,是为每一个你想要等比增长的间隙添加一个间隔视图,把其他视图的边缘固定在间隔视图的边缘上,然后为所有的间隔视图添加 同等宽度约束。
下一步
现在希望你们已经完全有信心开始探索应用约束的布局了。
不要指望能即刻成功,Xcode是一个非常复杂的工具,除了storyboard和约束之外它还有很多其他功能,有些功能刚开始使用会觉得很神秘和困惑。就像其他复杂的工具一样,在你熟悉它的怪癖之前你还需要一段适应时间,但是我建议你尽早的去熟悉它,因为我预计(至少我希望)下一代设计师会更多地使用Xcode这样的工具而非Photoshop。
如果你是一位创建设计规范的设计师,并想让IOS开发者遵循你的规范,你应该开始考虑和你的设计团队沟通的最好方法。也许你会创建一个视觉语言来描述你想要应用的约束。
我个人认为,设计师应该更多地承担一些他们创建工作上的责任,而不是让一个前端开发人员重新创建设计意图,而设计师只是管理storyboard文件。
如果你发现这个教程上有任何难以理解的地方,或者有错误的敌方,请让我知道,我会尽快更新这篇文章。