基本配置
4盘位 + J1900处理器 + 4G RAM + 16G MSATA SSD + 200w电源 + 单口网卡
这是蜗牛星际的C款(单口),也有双口,就是贵了一点,有条件的可以直接上C双。拿到手就是用U盘做引导安装黑群晖。
硬件准备
软件准备
打包下载:百度网盘; 提取码: bvpf
将U盘插入电脑,运行PE盘制作工具,右下角选择“安装PE到U盘”;
如图设置,点击“立即安装进U盘”;
PE盘制作工具生成了两个分区,EFI为PE引导分区,“微PE工具箱”为文件区,将黑群晖引导镜像(img)、DiskImg、DiskGenius(PE版)放入U盘内。
这里介绍一种洗白方案,需要花钱,淘宝自行购买。不想洗白的可以跳过!!!
在windows下操作:
安装OSFMount,完成后运行OSFMount 下载途径 提取码:6eyz;
点击左下角-Mount new,选择下载的img镜像(img镜像目录必须是英文或着数字,不能是中文);
选择Partition 0,点击OK;
再把下面Read-only drive的选项去勾后点击OK;
在OSFMount软件里双击镜像后打开grub目录,编辑grub.cfg文件,建议使用Notepad++编辑grub.cfg文件
根据自己的要求填写正版的SN码和Mac地址,就可以全洗白了
修改完成后点击Dismount all & Exit
将制作好的PE U盘插入蜗牛星际背部的USB接口,通电开机,有键盘的话,可以按下F7选择PE U盘启动(没有键盘也能操作的);
使用DiskGenius,将自带16G SSD内所有分区删除并保存;
启动 DiskImg,驱动器选择机器内置SSD,浏览选择镜像写入(路径、文件名不能有任何中文字符);
写入完成后,使用DiskGenius(PE自带版本太旧了,一定要用复制进去的DiskGenius),浏览系统内置SSD引导分区文件(ESP),将grub.cfg文件复制到U盘(之后洗白要用);
设备关机,拔出U盘。
NAS主机连接网线并启动(此时显示器上显示Happy Hacking);
Mac上安装Synology Assistant(群晖助手),保持在同一局域网并搜索,搜出结果后,最后选择联机;
选中NAS主机安装DSM系统,浏览找到pat格式,依照提示安装即可;
这里不要把自动更新打开,也不要开启任何反馈计划;
初始化DSM完成,进入桌面后,关机。
如果上面的步骤,你有全洗白的话,到此应该是安装黑裙成功,并洗白了。开机体验,最大的缺点就是噪音挺大的。
NAS主机(249元) + 河南->上海顺丰运费(46元)
一块西部数据2T红盘(330元,咸鱼收)
黑裙洗白(30元)
更换了电源风扇(12元)
|
|
最终的树形结构:
|
|
现在用Rust来实现一个?
|
|
在上一篇我们已经提到了,上面的代码Rust肯定不会让我们编译通过。已经提示我们使用&
、Box
、Rc
。他们都是指针类型,都是指向内存上某一个位置。
&
在Rust里面称作borrow类型。只是引用内存上某个位置的值,并不拥有所指的值。如果我们想使用&
来修复这个问题,我们需要注意一个问题就是生命周期,borrow类型生命周期取决owner的生命周期。
|
|
Box
是一种智能指针,零运行时开销。拥有它所指向的数据。我们为什么称做智能的,是因为当执行过边界,它将drop掉它所指向的数据然后drop本身。不需要手动管理内存!!!
|
|
Rc
是另外一种智能指针,是reference count
的简称。用于记录数据结构被引用的次数。当引用的数字减小到0时,就自行清理。如果在一个线程,对于同一个数据有多个owner的时候,我们选用Rc
。对于多线程,我们有Arc
(atomic reference count)
|
|
上面的三个方法都可以解决之前的问题,那我们该怎么选择呢,这取决于自己的使用场景。
紧接着我们又面临的问题就是无法实例化之前定义的tree结构。为什么呢?现有的数据结构,left和right子树都是BoxNone
,但在Rust中有Option
:
|
|
现在我们就可以创建我们第一个tree:
现在将代码做一些优化:
|
|
|
|
那为什么在Python就能完美运行呢?Python在运行的时候为树对象动态分配内存,将所有内容包装在PyObject中,类似Rc。
遇到类似的情况我们该怎么处理和选择呢?我们需要了解所有可能的解决方案,如果可以,我们就远离使用智能指针,坚持简单借用。如果不能,我们就选择侵入性更小的一个。
]]>原文链接:https://stackoverflow.com/questions/25296195/why-are-recursive-struct-types-illegal-in-rust
我们来看一个例子吧:
|
|
编译会出错:
出错的意思:Person是无限大小的。
在Rust中该怎么修复呢?上面也提示了使用Box
, Rc
, &
。 将Person
放进一个Box
,然后就正常work了。
这背后究竟是为什么呢?
我们都知道Rust在编译的时候就需要知道一个类型究竟该分配多少内存。如果一个类型的内存不知道是多少的话,比如说上面的recursive就是其中一种,需要无限的空隙间,Rust就会在编译阶段直接报错。
但是Box
是知道空间大小的,上面的例子中就在一个递归中插入一个box。Susan有一个mother,father和partner,他们每一个都有一个mother,father,partner……而Box使用一个指针,这个指针是固定大小动态内存分配的。
在structs
,enums
(tuples)中的数据是直接内联存储在struct值的内存中的。给定一个struct:
|
|
现在我们来计算该大小:size_of::
这个size将会变得无限的大:
而Box<T>
是一个指针,有固定的大小的,所以(u8, Option
在Rust的官方文档References and Borrowing [https://doc.rust-lang.org/book/second-edition/ch04-02-references-and-borrowing.html] 用到了三种不同类型的string变量:String,&String 和 &str
首先看一下str 和 String之间的区别:String是一个可变的、堆上分配的UTF-8的字节缓冲区。而str是一个不可变的固定长度的字符串,如果是从String解引用而来的,则指向堆上,如果是字面值,则指向静态内存。
|
|
上面的a, b 是&str,不是String,&str更像一个固定的数组,String像一个可变的数组。
String保留了一个len()和capacity(),但str只有一个len()。
&str 是 str的一个的borrowed 类型,可以称为一个字符串切片,一个不可变的string。
&String 是String的borrowed类型,这只不过是一个指针类型,可以传递而不放弃ownership。事实上,一个&String可以当做是&str。
foo()可以使用string slice或者borrowed String类型。
如果我们想修改字符串的内容,只需要传递一个可变引用就行了。
|
|
|
|
String后面接上N个&str
如果只想要一个字符串的只读视图,或者&str作为一个函数的参数,那就首选&str。如果想拥有所有权,想修改字符串那就用String吧。
]]>刚到上海,顶着火辣辣的太阳,跟DIY同学一起找房子,开始两个月离公司很远,每天上班还要坐地铁,我还是挺适应坐地铁1小时上班,让我想起了北京的日子,每天坐一个小时的地铁去中关村。后来公司搬家了,从航运搬到了杨浦国正,离公司更近了。每天上下班习惯了踩单车,在复旦邯郸路等红绿灯还被送外卖的小哥干翻了两次,大家的生活都不容易,我屁股摔得很疼,也不去计较。
平时周末也就宅在家里,爱去菜市场买点菜,做几道菜,自己吃着合口味就行。我也发现一个奇怪的现象,每每炒两个菜的时候,一个菜会很淡一个菜会很咸。后来,总结出要是自己不会炒的菜,就尽量偏咸一点,这样吃起来不那么难吃(꒦꒦) )
没有规律地跑步,最长的坚持是连续坚持了46天,那些日子,感觉自己的颈椎不是那么疼了。跑步一定程度上能缓解我的颈椎。
一年前,我顶多算是一个代码的搬运工,ctr+c和ctr+v比较多,自己在写代码上面没有太多的追求,可能自己当初的追求就是实现功能,缺少了一些多实现功能的思考。来了你站后,跟着json工作了半年多,虽然我跟他在工作上交流相处是有一些问题,我不喜欢他强势的方式,但是他对我的帮助也很大,学到的路子是野了一点,渐渐地追求代码的质量,逼迫自己在vim下编程,遇到问题怎么定位问题,解决问题发现困难怎么绕开困难等等。
接触写一些静态强类型的语言,这是一个对自己思维的考验,从一开始的不适应到后来的适应。想更多地接触底层的东西,自己也买了一些书籍【有的还没有看完】。接触一些关于架构设计的东西,一个系统为什么要这样设计,这样设计的业务场景,能不能做优化。如果在面试中,回答出为什么也是加分项哟!
年初的定的计划,技术相关的继续落实中….(刷题的感觉)
入职你站,无疑看到很多小姐姐,晚上无意间跟小姐姐聊了一下。想起了,公司的那啥活动,记得她是旁边小组的成员,对她的印象就是气质。后来群里下发了活动照片,那是最没有怂的一次——截图发群里求联系,简单粗暴,总之感谢大佬帮助!!!后面啥的。。。(我不编了)
最后,也是最重要的,就是感谢父母亲戚朋友,对自己的要求就是做好本职工作提升实力,多领点工钱!
]]>如果你拥有和保存比特币,就需要通过客户端,通常这个客户端就是钱包。关于钱包的解释就不多介绍了,简单的来说就是连接区块的一个入口。像成熟的比特币、以太坊就有很多钱包工具。比特币钱包实际上是不包含比特币的,比特币钱包是由私钥和公钥所组成的数据库。比特币本身是存储在区块链中。用户用私钥来签名交易,从而证明他们有这笔交易。
对于钱包而言,最重要的就是私钥。一般钱包需要访问你的用户资产,就会要求输入私钥。
一个钱包包含的信息有:
钱包中的地址生成过程:
上面的过程是单向的,反过来是不的行。
钱包根据不同的维度还分为多种。
根据访问钱包数据类型,钱包分类如下:
根据同步数据类型,钱包分类如下:
拥有了一个钱包,获得自己的地址。就像银行账户,有了它别人就可以给你转账。很多钱包软件可以帮你生成自己的地址。此外钱包还是可以自己开发的,这儿就不多介绍了。
刚刚也说了,对于钱包最核心的就是私钥。私钥是一个大于零,小于1.158 * 10^77的一个任意数字,挑中了就是你的。然后通过椭圆曲线函数,生成对应的公钥,再经过一系列的hash计算得到地址。
如果一个人公开了他的比特币地址,我们就可以在Blockchain.info 网站输入比特币地址查询到他的资产和交易记录,我们只能看到他的资产,却没法通过地址反推出他的私钥。私钥就可以认为是银行账号的密码。如果私钥丢失了,我们就没法找到会这部分资产。
前面就提到了,比特币本身是存储在区块链中。可见,钱不是存在钱包里。钱包这个软件只是可以显示余额,完成交易【唯一记录就是私钥,交易需要私钥签名】。
钱的数量存在区块链上,如果你下载一个比特币的客户端,同步以后有大约100多G的数据。里面包含了从第一笔交易到现在快10年的所有数据。钱并不存在在任何地方,钱的余额存在无数的电脑共同维护的一个账本里。
钱包创建后,我想恢复怎么办?就需要助记词。我在创建钱包的时候,钱包就帮我生成了一串12个单词的句子。说这句话要抄下来,不要截屏保存。每个单词都有一个自己的编号,这些编号按照复杂的算法拼起来就可以还原出私钥。
助记词生成的过程:
128 位熵产生 12 位单词的助记词,助记词产生 512 位的种子。什么是种子呢?种子就是能生成主密钥的数据,钱包所有的数据都衍生自这个种子。通常某些钱包还会包含一个密码,这个密码是进入钱包的凭证。它仅仅是是一个保护私钥的密码,丢了存私钥的文件,有密码也没有用了。
!注:BIP39(Bitcoin Improvement Proposal 39)定义了2048个单词的列表。
选择正确的钱包很重要,如果往不支持该token的钱包里转token,可能会导致token再也找不回来。
区块链的英文名字叫BlockChain,从字面上理解是不是可以拆成Block和Chain。从Wikipedia上的解释是一些记录的列表打包在一起,称为区块,这些区块通过密码学的知识将他们连接在一起。
学计算机的同学可能都知道什么是链表,链表其实是一个数据结构,是一种线性表,并不会按线性的顺序存储数据,而是在每一个节点里存下一个节点的指针。
区块链的底层就是使用了链表数据结构,这个数据块是有顺序的,当前的数据块总能找到上一个数据块。
链表中有头(首)节点,区块链中的首块称为创世区块。在区块链中,每一个区块(链表中数据块)有两个基本结构:Head和Body。
先看区块的结构图:
以比特币分析:
其中头部的CURRENT和PREVIOUS就是一串哈希值。NONCE和TARGET跟POW共识机制挖矿有关。
比特币目前的版本VERISON是0x20000000,相当于block的第五个版本。用一个位数表示一个独立的功能,以区分未来的软分叉。
写代码的人都知道哈希是一种算法,输入一个东西(字符串,图片,视频等等),最后变成一个有固定长度的哈希值,这个值是唯一的,输入的内容稍微改了一点,哈希值都会改变。从这个哈希也很难推导出原始数据。在比特币中用的哈希算法是SHA256。
比如说PHP中
|
|
不难看出,稍微改了一个字符,输出的哈希值就是完全不一样,完全没有规律。
在上面提到了区块头部的CURRENT和PREVIOUS就是一串哈希值。每个区块的ID从他的区块头80个字节数据两次SHA256哈希得到。在比特币中,对于这个ID有要求,符合条件的ID才是合法的。区块头的哈希值必须小于一个数。每一个新的区块的长达64字节的id前面都用零补全,一个合法的区块id是长这样子的:0000000000000000003c19cdbebe2df5c7f82558e2c80a0c7341e25072b732a2
数据区块的产生就是要计算出新的区块Id,其实就是挖矿的过程。挖矿的机器,只要找到一个幸运数字填到Nonce的位置,使得区块头的数据两次哈希后的值以18个以上0位开头,就表明区块生成,符合条件。不断的尝试幸运数据,知道符合条件。
此外,比特币还会动态的调整调整难度,这样做主要为了保证大约每10分钟产生一个区块。然而全网算力是变化的,如果想要新区块的产生保持都基本这个速率,难度值必须根据全网算力的变化进行调整。难度的调整是在每个完整节点中独立自动发生的。每2016个区块,所有节点都会按统一的公式自动调整难度,也就是说,如果区块产生的速率比10分钟快则增加难度,比10分钟慢则降低难度。这个公式为:
比特币POW的目标值(target)计算公式如下:
目标值的最大值为一个恒定值:0x00000000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF
难度系数越调越高(目标值越来越小),导致了采矿越来越难。
当计算出符合条件的区块ID后,就开始向全网广播这个新区块,从而获得奖励,目前的奖励是12.5个比特币,每4年减半。这个12.5个比特币是比特网络上唯一没有发款人只有收款人的交易,新比特币就这样凭空诞生了。
如果两个人同时向区块写入数据,也就是说有两个区块加入,因为他们的前一个区块是同一个,这就形成了分叉。
|
|
目前的规则是,新节点总是采用最长的那条区块链。如果区块链有分叉,将看哪个分支在分叉点后面,先达到6个新区块(称为”六次确认”)。按照10分钟一个区块计算,一小时就可以确认。
其实添加新的区块是需要算理的,最终拥有最大算力的一方获胜。如果有人掌握了51%以上的算力就能控制区块链。
区块链是分布式的数据库,对于数据,哪怕修改了很小的一部分,数据的哈希就变了,区块头就变了。在目前来看,还是安全的,可行的。
]]>于是就有了这个笔记:
先看看项目的结构:
|
|
我相信这样的结构大家一定很熟悉,除了admin.html
和src文件夹下面的Admin.vue
、admin.js
,还有一些api,pages,vuex等文件夹,就是最常见的一个vue-cli初始化的项目结构。
我想要就是新增一个后台管理界面的入口admin.html,其他能够共用的还是共用,进入正题:
打开 ~\build\webpack.base.conf.js
,找到entry,添加多入口:
这样运行编译的时候,每一个入口都会对应一个chunk。
打开 ·~\build\webpack.dev.conf.js· ,在plugins下找到HtmlWebpackPlugin
,在其后面添加对应的多页,并为每个页面添加Chunk配置如下:
|
|
打开~\config\index.js
,找到build下的index: path.resolve(__dirname, '../dist/index.html')
,在其后添加多页:
|
|
webpack.prod.conf.js
打开~\build\webpack.prod.conf.js
,在plugins下找到HtmlWebpackPlugin
,在其后面添加对应的多页,并为每个页面添加Chunk配置:
|
|
恩,没有了,就不修改什么项目结构了,过程越复杂越容易出错。上面webpack的配置简单能看懂。Enjoy~~~
]]>这一年,从杭州到上海,来上海有多种原因,最终还是来了。中间在六月份的时候提出离职,最后七月初离开的。离开DXY也有一点点埋怨,这个之前也写了一篇文章,第一次抹黑一家我工作过的公司,我希望也是最后一次。写那个文章的时候,可能当初真的不是懂事,后来来慢慢地就明白了一些道理,这就是成长的经历吧。来到上海也差不多快半年了,工作的内容跟以前也不大一样,学到了不少东西,这里就不展开细说了,要说可能说两天都说不完。以前的工作也算是比较单调,做来做去就是那些东西,感觉也没有什么新意,时间长了自然感觉到厌倦,那种的环境待久了真的不是很适合自己的成长,走出来也是一种自我的觉醒。现在的工作谈不上多么的有趣,但总能从中悟出一些东西,还是值得的。平时也没有什么加班,晚上回到家,也能写点自己的相关。反正都是瞎折腾嘛。
来到上海后,生活的节奏比以前快多了,之前在北京生活过,知道什么样的生活节奏比较快,从杭州到上海,自然能感觉到。杭州其实是很美的城市,适宜居住,当然了现在的房价也很高。也许有一天还是会回到杭州。在杭州的时候,生活的压力也不是很大,工作日都是在外面或者公司吃饭,周末了有闲情的话就自己煮饭。到了上海,吃了一些日子的外卖,厌倦了外卖,就每天自己做饭,想吃什么就做什么,唯二的缺点就是自己长胖了和每天要花一个小时在做饭上面。快年底的时候,开始锻炼了,每天坚持跑步锻炼了。总得来说,生活还是很单调的!
这个是我最不想说的部分。在泡妹子方面我确实不是很擅长。年初的时候认识了她,现在这样的处境,也不知道该说什么,我能做的还会继续做下去。毕竟我上过董路老师的车,他发的几百趟车,我好歹也上了100+趟,也学到了一些东西,希望能付诸实践!也祝自己顺利!
开篇也说了下半年的计划都没有实现,定太多的计划真的不是很实际。上半年为什么能完成,也是一点点的完成的。今后还是慢慢来吧,每一天充实起来。最后就是希望能继续锻炼身体。
]]>这个小工具的初衷,解决实际问题还是很重要的。
说这些都是没用,那就跟来做一下吧。
项目托管在github上,地址:dyike/CTEmail。记得来star哟!!!
首先,稍微知道怎么操作Python,像我这种不会写代码都能操作,你一定也可以。其次是到Plotly——这是一个可视化数据的工具有点类似于HighCharts,不过支持多种语言,很强大了。先熟悉一下, 然后注册一个账号,后面会用到。本文着重数据生成图表图片。邮件服务配置查看README
将项目clone下来,熟悉项目的结构,里面不到两百行代码,简单粗暴。
|
|
在send.py
文件中配置自己的邮箱账号,密码,邮件标题,邮件模板路径和发送到的邮箱。
邮件模板的默认路径是./content/
,会自动读取该路径下的html文件。
|
|
默认是配置QQ的STMP发送服务(stmp.qq.com),端口是25。你也可以配置163.gmail等等,在初始化CTEmail()配置相应的配置即可。
项目中提供了一个默认的模板,你可以根据你的实际需求定制的模板,content
文件夹下面还有图片资源,我们生成的图表的图片资源也是在该文件夹下面。
需要注意的是,html文件中,将img标签用
模板中需要注意的一点:也是非常重要的一点就是:html中多个
快要结束了,本文的却重点来了,不要慌,也很简单。就是使用Plotly,关于使用离线(本地)模式还是在线模式,看自己实际需求。我这里说在线的。因为我用的是定时脚本,我只能调用在线的API生成图片保存到本地content
文件夹下面。
安装Plotly
|
|
get_img.py
文件,文件名可以重命名,里面需要的配置你的认证信息credentials,信息在https://plot.ly/settings/api
中查看。有两种方式:第一种如下
设置username和api_key。
|
|
或者在安装完成后,在~/.plotly/.credentials
文件中配置你的账号信息。
看到的信息大致如下:修改对应的即可。
get_img.py
文件
|
|
注意上面拼接模板文件内容的时候使用了换行符"\n"
,为什么这样使用,一简单粗暴,二为了引起重视【这里有坑】。
执行上面的脚本文件python get_img.py > ./content/index.html
这样就可以将模板文件写入到content
目录下的index.html
执行python send.py
邮件就可以发送邮件,将上面的几个命令写入到shell脚本中,更新方便快捷。
其他图表的生成也可以参考官方文档的介绍。
放在最后的不是不重要,解决实际问题才是更重要,欢迎来CTEmailStar!!!
]]>就是定义一个创建对象的接口,让子类是实例化具体的类,工厂方法就是让类的实例化,延迟到子类中。这是属于创建类模式。
用模板方法的方式创建对象来解决,父类定义所有标准通用行为,然后将创建细节放在子类中实现并输出给客户端。工厂方法模式主要四个要素:
工厂模式是一种典型的解耦模式,当需要系统有比较好的扩展性时,可以考虑工厂模式,不同的产品用不同的实现工厂来组装。
|
|
还是先来说说概念吧,简单工厂模式(simple factory pattern)又称为静态工厂方法模式,是属于创建型模式。可以根据参数的不同返回不同类的实例,说通俗一点就是一家工厂,能够生产轮胎,还能生产齿轮等等,只需要跟工厂说一声(传参数),就能生产出来了。一般情况下被创建的实例都是具有共同的父类。
什么时候使用?比如在计算器中,有加法运算,有减法运算,有乘法运算,有除法运算等等。各个运算都是一种运算操作(operator),我们只是修改部分属性从而让他们具备了不同的运算能力。如果我们希望我们在使用这个计算器的时候,我们不需要知道具体是怎么计算的,就只需要得到正确的结果,此时,不是不就可以使用简单工厂模式。
下面直接看源码:
|
|
|
|
为了分析上面的代码,将代码分为四块:
第一块
主要用来实现类的自动加载,注册加载composer自动生成的class loader。
第二块
主要来实例化服务容器,Laravel的一些基本服务的注册,核心组件注册等等,当然了也包括容器本身的注册。在注册的过程中服务容器会在对应的属性中记录注册的内容,方便于在程序运行期间提供对应的服务。这部分内容可以称为程序启动准备阶段。
第三块
处理请求,用户发送的请求入口文件是index.php,从生成
Illuminate\Http\Request
实例,交给handle()进行处理。将该$request实例绑定到第二步生成的$app容器上。并发送相应
第四块
请求的后期清理处理工作,请求结束并进行回调。
先看看bootstrap\app.php
源码:里面有注释
|
|
关于Illuminate\Foundation\Application.php
文件查看源码
setBasePath()
设置注册应用的基础路径,并在容器中绑定这些基础基础路径。
registerBaseBindings()
|
|
主要绑定容器实例本身,服务容器中设置了一个静态变量$instance
,该变量是在Illuminate\Container\Container.php
中定义,Application
类继承了Container
类,在Container
类中可以通过public static function getInstance()
获取服务容器实例。服务容器实例还绑定不同的服务容器别名,记录在$instances
共享实例数组(在Container
类中)
|
|
registerBaseServiceProviders()
服务提供者的注册是非常重要的,因为它给服务容器添加各种服务。这里只是注册了最基本的三个服务:
|
|
在容器里注册一个服务提供者的方法:public function register($provider, $options = [], $force = false)
源码阅读分析:
getProvider($provider)
进行判断,如果服务提供者存在则获取这个实例对象。resolveProvider($provider)
进行实例对象。markAsRegistered($provider)
。bootProvider($provider)
。registerCoreContainerAliases()
$aliases
数组变量中定义了整个框架的核心服务别名,在解析的过程中需要根据实例化的类或者接口名查找服务别名,然后通过服务别名获取具体的服务。
这里放一张图进行小小的总结:
为什么要服务容器?服务容器实例化后,就可以通过服务容器自动实例化对象了,可以参考上一篇的服务容器。index.php
中Kernel类就是通过服务容器自动创建完成的。
在bootstrap\app.php
文件中就注册了三个服务,其中包括了这个核心类接口,在注册服务时,服务名一般是接口。注册的服务是具体的类名,这一般是通过反射基础来实例化的,并通过反射机制解决构造函数的依赖关系,参考上篇的服务容器有讲解。
这里说的核心类是指App\Http\Kernel
类,这个类只是定义了$middleware
,$middlewareGroups
和 $routeMiddleware
是哪个数组属性。这个类是继承Illuminate\Foundation\Http\Kernel
类的,不妨看看这个类中的构造函数,不难看出这个构造函数是存在依赖关系,一个是Illuminate\Contracts\Foundation\Application
,还有一个是Illuminate\Routing\Router
。他们在服务容器初始化的时候都进行了实例化。
capture()
在程序启动准备工作完成了之后,就开始请求的实例化。对于请求就是客户端的发送的一个请求报文。这个对应着Illuminate\Http\Request
类的实例对象。请求实例的创建是通过Illuminate\Http\Request
类中的capture()
函数完成的。
|
|
|
|
handle()
完成了请求实例化自然需要对请求实例进行处理,最终返回响应。请求处理是通过Illuminate\Foundation\Http\Kernel.php
中handle()
进行的,处理是handle中的sendRequestThroughRouter()
方法实现的,通过路由请求实例。而enableHttpMethodParameterOverride()
方法会是使能请求拒绝,被使能后在请求过程中添加CSRF保护,服务端发送一个CSRF令牌给客户端,也就是一个cookie,在客户端发送POST请求需要将该令牌发送给服务端,否则拒绝处理该请求。
直接看源码:
有6个步骤:环境检测、配置加载、异常处理、Facade注册、服务提供者注册、启动服务,通过bootstrap()
方法完成准备工作,会调用服务容器$app实例中bootstrapWith()
函数,进而通过$this->make($bootstrapper)->bootstrap($this)
make方法完成每个准备类的初始化工作,然后调用准备类的bootstrap
方法实现准备工作。
|
|
Route::get()
以为是一个Route类,其实不然,只是通过别名实现的。
|
|
主要分要两个步骤:完成外观自动加载类的实例化并将外观别名数组添加到实例中,然后完成外观自动加载类中自动加载函数的添加。
在laravel中有两个别名,一个是容器核心别名,定义在Application类中,而存储在中Application实例的$aliases属性中,另一个是外观别名,定义在app.php配置文件中,程序运行后存储在AliasLoader类实例中的$aliases属性中。
那像Route::get()
是怎么调用的呢?程序首先需要加载类Route,注册了外观别名,那么自动加载栈的第一个函数是AliasLoader类的load()函数,此函数会查找外观别名对应的类名,也就是Illuminate\Support\Facades\Route
类,加载这个类,执行get()方法,但是这个类中并没有此静态方法。这个类继承Illuminate\Support\Facades\Facade
,也没有get()方法,但是有一个__callStatic()
魔术方法,然后调用一个getFacadeAccessor()
静态方法,每一个具体的外观类都需要这个静态方法,该方法就是返回别名类所对应的在服务容器中的名称,对于Illuminate\Support\Facades\Route
返回的就是“router”,接着通过服务容器获取对应的实例对象,这里对应的Illuminate\Routing\Router
类的实例,通过static::$app[$name]
实现,最终调用这个类中的get()方法。
|
|
register()
函数,还有一个boot()
函数根据需求实现,主要用于启动服务,不是必须的,不实现会在父类中统一处理。对于实现boot()函数的服务提供者,会通过BootProviders类进行统一管理调用。要实现boot()也比较简短,只需要调用服务容器中的boot()函数即可。
|
|
在Laravel程序中有中间件的概念,就是对请求的处理,首先是经过中间的处理,然后经过路由的处理,最终到控制器生成响应。这个过程中基本是以装饰者模式的思想进行的。
看看app/Http/Kernel.php源码:
|
|
回到刚才看到的路由分发回调函数
|
|
路由的信息都会保存在一个Illuminate\Routing\Router
类实例中,而这个类实例存储在kernel类中。
查到请求对应的路由后,请求传递给对应的路由去处理
|
|
进而交给相应的controller去处理。这部分,首先根据控制器类名通过服务容器进行实例化,再通过调用控制的实例对应的方法来生成响应的主体部分(并非最终的响应)。经过一系列的处理之后生成响应。响应是封装在Illuminate\Http\Response
实例中的。
$response->send()
|
|
$kernel->terminate($request, $response)
程序终止,完成终止中间件的调用
|
|
什么东西都是有始有终的,至此请求生命周期结束。
]]>请求到响应整个执行过程,可以分为四个阶段:程序启动准备阶段,请求实例化阶段,请求处理阶段,响应发送和终止程序。
准备阶段:完成一些文件自动加载,服务容器实例化,基础服务提供者注册和Kernel类的实例化。
请求实例化阶段:将请求信息以对象的实行进行存储。
请求处理阶段:准备请求处理环境,完成环境和配置加载等6大东西。通过中间件处理通过路由和控制器处理,生成相应。
请求终止阶段:将响应发送给客户端并终止程序。
使用laravel也有一段时间了,现在应该向深入理解laravel去看看一些源码了。laravel之所以新颖,使用了大量PHP新语法,包含命名空间(组件化开发的条件),匿名函数,反射机制,还有后期静态绑定,trait等等。
当你深入去挖掘源码的时候,laravel框架中使用的都是大家熟知的东西,并没有多么的高深。那就耐着性子一层一层地往下扒吧!
服务容器是laravel框架中相当核心的东西,提供了整个框架运行需要的服务。服务是什么呢?通俗地讲就是系统运行中需要的比如对象,配置信息之类的东西。服务容器就是承载这些东西的,在程序运行过程中动态的给系统提供服务(资源)。
服务容器提供的东西比较多,在这些功能中,需要注意的问题就是解决依赖实现解耦。说到这里,我们可能都听说过控制反转(Inversion Of Control)——IOC容器。服务容器也可以是IOC容器。控制反转模式是解决系统组件之间的相互依赖关系的一种模式。那什么是依赖?怎么解决依赖?不妨看一个例子。
我们就拿出去春游儿来谈吧,出去玩有很多方式,比如开车出去,乘坐火车出去。
|
|
不难看出上面的代码,两个组件之间就产生的依赖,Traveller
的初始化依赖与Car
,如果需求改动了,实例化的交通工具不是car是其他的,就需要不断改变实例化的对象,你说这样的代码怎样?我们不应该在Traveller
中固定交通工具的初始化,而是交给外部去实现,将这种依赖关系通过动态注入的方式实现,这就是IOC模式的思想。
于是乎,我们将交通工具的实例化提取出来外部管理,这样做,也体现出面向对象的设计的一个原则——将经常变化的部分提取出去,与固定不变的部分进行分离。这里我们使用工厂模式来实现。
|
|
现在看,Traveller
跟trafficTool
之间没有依赖关系了,但是却变成Traveller
跟TrafficToolFactory
之间的依赖了,如果我们再碰到需求,需要修改工厂模式,这样的代码还是不易于维护。
控制反转模式也叫做依赖注入模式,控制反转是将组件之间的依赖关系从程序内部提到外部的容器中,而依赖注入是将组建的依赖通过外部以参数或其他形式注入。两种说法一个意思。
|
|
这儿就是一个依赖注入的过程,Traveller
类的构造函数依赖一个外部具有Visit
接口的实例,在实例化Traveller
时,传入一个$trafficTool
实例,即通过依赖注入的方式解决依赖问题。
但是还是有一个小问题,这儿是我们是通过手动的方式注入依赖,依赖注入需要通过接口来限制,而不能随便开放。你还有完没完?其实通过IOC容器就可以实现自动依赖注入。
|
|
在这个实现过程中,没有用new关键字来实例化对象,不需要关注对象的依赖关系,只需要在容器填充的过程中理顺接口与实现类之间的关系以及实现类与依赖接口之间的关系。这里的实例化是通过反射的机制完成的。
下篇继续以Laravel的源码进行分析。
在Laravel中,服务容器是由Illuminate\Container\Container
类实现的,实现了服务容器的核心功能,而Illuminate\Foundation\Application
类继承了该类,主要实现服务容器的初始配置和功能扩展。
在bootstrap\app.php
中,$app就是服务容器的创建,然后在index.php中,通过require_once bootstrap\app.php
就生成了服务容器。
服务容器生成了之后,里面除了最基本的好像什么也没有,那是不行的,首先需要向容器中填充服务,也即是服务绑定。那是怎么绑定的呢?可以简单地理解为键值对的概念,根据一个“key”就能找到对应的服务。难怪里面有一些数组属性。对于不同的绑定需要在服务容器中不同的绑定函数来实现,主要包括回调函数服务绑定和实例对象服务绑定。回调函数服务绑定就是一个回调函数,而实例对象服务绑定的就是一个实例对象。
回调函数服务绑定还分为普通绑定和单例绑定。普通绑定就是每次生成该服务的实例对象时还会生成一个新的实例对象,也就是说在生命周期中可以同时生成多个这种实例对象,而单例绑定在生成一个实例对象后,如果再次生成就会返回第一次生成的实例对象也就是在程序生命周期中只能生成一个这样的实例的对象,这个就是设计模式中的单例模式。
说了这么多,现在我们来一个简单的测试。
|
|
|
|
|
|
在bootstrap\app.php
中,
|
|
上面的服务容器通过三种不同的方式绑定服务,一种是普通模式绑定回调函数,另一种是单例模式绑定回调函数,还有一种是绑定实例对象。绑定后在服务容器增加内容:
|
|
不难看出,回调函数服务绑定是在$bindings中记录的,其key为绑定的服务名称,value是回调函数和模式标识。如果是普通模式,则share为false,如果是单例模式则为true。实例对象服务绑定在$instances中记录,key为服务名称,value为实例对象。
说到这里其实还有一个形式的绑定,就是绑定具体类名称,本质上也是绑定回调函数的方式,只是回调函数是服务容器根据提供的参数自动生成的。绑定服务的时候可以通过类名或者接口名作为服务,而服务是类名。
]]>在“装饰模式”中很好的提现了开放关闭原则,即类应该对扩展开放对修改关闭。装饰者模式可以让我们在不对原来代码的修改的情况下对类进行扩展。现在我们举一个例子,就好比给生产好的手机进行包装,我们在对手机进行包装的过程不会去对手机进行任何修改,只管用盒子将盒子包装好就行。
装饰者模式,用另一种表达方式就是“对原有的物体进行装饰,给原有的物体添加上新的装饰品”。这里的例子就是,手机是被装饰者,包装盒就是装饰品。这样的话,可以看出装饰者模式就是“动态地将责任附加到对象上。若要扩展功能,装饰着提供了比继承更有弹性的替代方案。”
在“装饰者模式”中所使用的装饰就是变化的部分,也就是Decorator是变化的部分对应着我们的包装盒,因为对手机进行包装的过程就是包装盒变化的过程,也就是为手机装饰的过程。而手机就可以看做是组件。需要注意的是,所谓的装饰者不仅仅是给组件添加的新的装饰品。一个装饰者对象就是添加该装饰后的组件,也就是说装饰者=旧组件 + 新装饰品。
PhoneInterface.php
|
|
Decorator.php
|
|
DecoratorPack.php
|
|
ApplePhone.php
和SmartisanPhone.php
|
|
|
|
test.php
|
|
|
|
在设计模式中有不同的设计原则,其中一条就是”将可能会变化的代码独立出来,不要和不变得代码混在一起“。
现在举个例子就是,我们周末出去游玩,我们出去可以乘坐多种交通工具,这个时候就可以选择”策略模式“来实现。简单分析一下,这个场景中的乘坐的交通工具是多种多样的,可以骑自行车,可以坐地铁,可以自己开车。
根据上面的设计思路,,我们可以对”乘坐交通工具“的策略进行提取。使用StrategyInterface
接口来规定策略,使用不同的出行方式对外都有一个统一的接口。在此就是乘坐交通工具,不同的出行方式有不同的”乘坐交通工具“的策略。
对于出行(outing)的定义就是不变的部分。这个类中定义了出行的方式,以及是否改变策略,其中依赖于乘坐交通工具的接口。
策略模式
的具体实现:StrategyInterface.php
|
|
Outing.php
|
|
OutingByCar.php
和OutingBySubway.php
|
|
|
|
最后,我们来测试一下
|
|
运行的结果:
|
|
func NewScanner(r io.Reader) *Scanner
返回一个新的Scanner从r读取。split函数默认为ScanLines。
func ScanBytes(data []byte, atEOF bool) (advance int, token []byte, err error)
是scaner的一个分割函数,将每一个字节作为一个字符返回。
func ScanLines(data []byte, atEOF bool) (advance int, token []byte, err error)
ScanLines是一个Scanner的拆分函数,它返回每行文本,删除任何尾随的行尾标记。
返回的行可能为空。行结束标记是一个可选的回车,后跟一个强制换行。在正则表达式符号中,它是\ r?\ n
。
最后一个非空行的输入将被返回,即使它没有换行符。
func ScanRunes(data []byte, atEOF bool) (advance int, token []byte, err error)
func ScanWords(data []byte, atEOF bool) (advance int, token []byte, err error)
拆分函数,删除空格,返回空格分割的文字,永远不会返回一个空字符串。
空间定义由unicode.IsSpace
设定。
func (s *Scanner) Err() error
func (s *Scanner) Bytes() []byte
func (s *Scanner) Text() string
func (s *Scanner) Scan() bool
func (s *Scanner) Buffer(buf []byte, max int)
func (s *Scanner) Split(split SplitFunc)
SplitFunc 有四个:ScanBytes、ScanLines、ScanRunes、ScanWords。
func NewReader(rd io.Reader) *Reader
创建一个reader,其中buffer的Size是默认大小。
其实就是调用func NewReaderSize(rd io.Reader, size int) *Reader
func (b *Reader) Reset(r io.Reader)
Reset放弃所有缓冲数据,重置所有状态和切换从r读取的缓冲读取器。
func (b *Reader) Peek(n int) ([]byte, error)
Peek返回下一个n字节,而不推进读取器。
如果Peek返回少于n个字节,它也返回一个错误,解释为什么读取短。
如果n大于b的缓冲区大小,错误是ErrBufferFull。
func (b *Reader) Discard(n int) (discarded int, err error)
Discard跳过接下来的n个字节,返回丢弃的字节数。
如果Discard跳过少于n个字节,它也返回一个错误。
如果0 <= n <= b.Buffered(),Discarding能够从底层的io.Reader读取。
func (b *Reader) Read(p []byte) (n int, err error)
func (b *Reader) ReadByte() (byte, error)
func (b *Reader) UnreadByte() error
func (b *Reader) ReadRune() (r rune, size int, err error)
func (b *Reader) UnreadRune() error
func (b *Reader) Buffered() int
func (b *Reader) ReadSlice(delim byte) (line []byte, err error)
func (b *Reader) ReadLine() (line []byte, isPrefix bool, err error)
func (b *Reader) ReadBytes(delim byte) ([]byte, error)
func (b *Reader) ReadString(delim byte) (string, error)
func (b *Reader) WriteTo(w io.Writer) (n int64, err error)
func NewWriterSize(w io.Writer, size int) *Writer
func NewWriter(w io.Writer) *Writer
func (b *Writer) Reset(w io.Writer)
func (b *Writer) Flush() error
func (b *Writer) Available() int
func (b *Writer) Buffered() int
func (b *Writer) Write(p []byte) (nn int, err error)
func (b *Writer) WriteByte(c byte) error
func (b *Writer) WriteRune(r rune) (size int, err error)
func (b *Writer) WriteString(s string) (int, error)
func (b *Writer) ReadFrom(r io.Reader) (n int64, err error)
宏观上讲:
今年要完成的就是
要想完成这些,需要的合理地、高效地使用时间,其实每天有很多时间是浪费的,边缘时间充分利用的话还是可以做很多的事,为什么之前没能使用起一些边缘时间,还不是因为没有一个明确的计划。比如我要在一周里面看完一本书。
微观上谈:
每天我7点就起床了,其实公司上班是弹性的,朝九晚六朝十晚七,就目前看,也真的没必要每天八点半就往公司赶,十五分钟就走到公司了。为了充分利用时间我每天应该9点出门去公司,这样每天早上就有至少一个半小时的时间看书,中午休息的一个小时,如果不休息的话,我觉得这一个小时还是可以看书的。下班回来也有三四个小时,这段时间可以用来写一些代码,逛逛社区,玩玩游戏,撩撩妹子,总之可以细化一些任务出来。下班回来前可以锻个练。
周末的话,天儿好的时候,一定要出门溜溜了,去年来杭州半年达成的成就就是——周末全宅家里了。春天来了,应该出去走走。充分考虑到杭州好像是每周都是要下雨的,如果碰巧赶上下雨,就在家看书?也不啊,小区后面不是电影院也可以看看有没有好看的电影,总之周末至少有一天时间是玩的。如果真的是高效的话,劳逸结合真是相当必要的。
写了这么多,至少现在清晰了一些计划和思路,不写了去学习算法去了,一周时间搞定那本算法的书。
]]>先祝大家圣诞快乐,其次就是向大家拜个早年,鸡年大吉。
2016年,元旦的时候,在学校的跨年晚会上,感叹,就这么进入本命年了。这一年,好多不太顺利。往好处想想,这些都是好事,有些事情经历了总是好的。最大的感触,就是自己跟身边的人还是有很大的差距的。
我不知道去年我是怎么决定考研的,做这个决定后悔吗?后悔,也不后悔。一分为二地看,现在看来,这已经不是很重要了。后来也算明白了一个道理,考研考上了跟没有考上,对你的未来未必有多大的联系,所以不要在意结果,注重过程,自己能开心就好。
考研结束了,就寻思着要实习,毕竟大四了,应该找一份正规一点的实习工作,不想总像之前的那样折腾打杂的,那样也学不到太多的东西。这个时候就找胡波大大求一份实习,坦率的讲,面试感觉很不好,胡波也给我一些鼓励。后来差不多三月份去渣浪实习了,开始接触PHP了,实习的三个月里,学了一些自己之前不知道的东西,这三个多月也是吊儿郎当的,其实不应该这样。由于之前考研,也没有参加什么校招之类的,在三四月份的时候,在关注春招,还面腾讯的测试岗,问啥啥不会,就是那种感觉。后来又面360的游戏平台的开发,那次面试还是学到了不少。不管面试的结果怎么样,我面完了都会总结。面多了就有感觉了,大家问的题目都是类似的。最喜欢在渣浪的日子,抛出问题总能找到能一起讨论的人。还有就是大家都喜欢自黑,往死黑的那种,那种感觉很爽,我不知道这算不算一种团队文化。后来的故事就不多说了,总之还认识许多渣浪的朋友,大家有缘江湖再见呗。
啊,大学四年就这么结束了,还记得当初从家拖着两个箱子去学校报道的时候。冬去春来,年复一年又一年,四年都过得这么快,更别说这一年过得这么快了。毕业前给自己定的规划就是:毕业后,在北京呆一两年,然后回南方。就这个事,跟我爸不知道在电话里唠叨了多少次了。别的同学其实也是这样,有的留在北京有的选择回家有的选择继续学习读研出国,总之大家都有一个自己的目标,大家都在为自己的目标奋斗。只有到毕业的时候,才觉得在学校的日子是多么的珍贵,幸好没有哭得稀里哗啦的。
毕业本来是留在北京的,房子都找好了,后来在入职上有点拖拉,而且还不是很保险,不能一直吊在一棵树上嘛。毕业后半个月开始寻找新的工作,朋友帮我找了好几家,去面的也只有两家。后来想了想要么就回到南方吧,反正早晚要回去的,还不如现在就回去。果断退掉了房子(虽然被扣了不少钱,呆北京我也没有多少钱可以交房租了),然后跟朋友挤了一晚上,然后就去杭州面试了,新的生活新的环境还是算可以。没有想到这么快就来到了南方了,而且也没有太大的准备。现在在花厂上班,好好对待第一份工作。
从去年考研的时候,就决定要搞技术,毕竟技术是硬实力,到哪儿都会有碗饭吃。差不多那个时候开始七七八八就开始写PHP了。回首这一年自己折腾的东西挺多的。坑挖得比较多,但挖得都不深。有的时候选择不一,所以刨的坑比较多。既然刨了那么多坑还是写出来吧。
今年买的书,技术方面的书比较多,自从工作了之后,发现自身暴露出的问题还是挺多的,在买书上面也不再那么吝啬了,相信付出总是有回报的。
周末常常自己做饭,尝试了一些新花样,好多失败了。反正自己能做的也有两道拿手的菜,还有自己做的饭不至于那么的难吃,哈哈哈!
年初制定的一些目标,实现了一部分,也有一些没有实现越来越远。总之,本命年过得不太顺,自己得到了一些成长。
2016,即便不好,不还是过过来了嘛,2017年会更好,技术有进步,去实现自己的小想法。
2017年,不挖坑,深挖坑!写好每一段代码,看好每一本书,做好每一道菜。
]]>首先有几点需要说明的是:Programming iOS 9是一本很不错的书,纸质书很厚,看完真的是需要毅力的。
这个笔记谈不上翻译,当然目前很大一部分是借鉴http://wdxtub.com/ 这个blog里的笔记【这个blog文章我常看】,我只是在上面作了一些修改。
为什么这么说呢?因为我操作的环境是在swift3+Xcode8+iOS10。
我只是业余的、业余的、业余的学习iOS开发,如果里面什么错误,欢迎指正!欢迎iOS大牛带我飞。
CGRect:
Swift 2:
let frame = CGRectMake(0, 0, 20, 20)
Swift 3:
let frame = CGRect(x: 0, y: 0, width: 20, height: 20)CGPoint
Swift 2:
let point = CGPointMake(0, 0)
Swift 3:
let point = CGPoint(x: 0, y: 0)CGSize
Swift 2:
let size = CGSizeMake(20, 20)
Swift 3:
let size = CGSize(width: 20, height: 20)UIColor
Swift 2:
let color = UIColor.redColor()
Swift 3:
let color = UIColor.red“NS”
Swift 2:
NSTimer
NSData
NSError
Swift 3:
Timer
Data
ErrorUserDefaults
Swift 2:
NSUserDefaults.standardUserDefaults().//something
Swift 3:
UserDefaults.standard.//something
假设有一个Superview和一个subview,subview是被嵌入了10个points。如图所示:
实现上图的代码:
关于bounds的属性是一个view在自己的坐标系中的矩形尺寸(frame是在superview的坐标系的)。
效果图如下:
我们通常都是如此使用bounds的用法。当你需要往一个view里面放东西的时候,无论是手动绘制们还是放一个subview,通常都要使用view的bounds。
当你改变一个view的bounds,他的frame也会对应改变,frame的改变是基于其中心点的(中心点不会变)。
在上面的代码增加两行:
效果图如下:就是v2基于中心点不变,height和width属性值都增加了20个points,结果就是完全覆盖了紫色的view。
还可以变着花样来:
再增加两行代码:
效果图如下:
不难看出view向原点移动方向的反方向进行了移动,这个因为一个view 的原点是与其frame的左上角一致。
其实我们可以发现:改变view的bounds size是会影响frame的Size,反之亦然,唯一不变的是view的center,。这个属性,跟frame的属性一样,这表示一个subview的位置是在其Superview的坐标系中的位置。通过下面的代码是可以获取的:
改变 view 的 bounds 不会影响其 center,改变一个 view 的 center 不会影响其 bounds。所以其实一个 view 的 bounds 和 center 就可以确定其在 superview 中的位置,frame 可以看作是一个由 bounds 和 center 组成的表达式的简便写法而已。注意有些情况下 frame 会没有什么意义,但是 bounds 和 center 总是有效的,所以建议多用 bounds 和 center 的组合,也比较容易理解。
以下方法是可以进行不同view之间的坐标转换:
如果第二参数为nil,那么就取window的值。比如:如果v2是v1的subview,那么要把v2放到v1的中心,就用:
注意,通过改变 center 来设置 view 的位置时,如果高或宽不是偶数,那么可能会导致 misaligned。可以通过打开模拟器的 Debug -> Color Misaligned Images 来进行检测。一个简单的方法是调整好位置之后调用 makeIntegralInPlace 来设置 view 的 frame。
设备屏幕是没有 frame 的,但是有 bounds。Main window 也没有 superview,不过其 frame 被设置为屏幕的 bounds,如:
|
|
在绝大数的情况下,window 坐标系就是 screen 坐标系。现在的iOS中坐标系和手机是否选择是有关的,有如下两个属性:
UIScreen 的 coordinateSpace 属性
这个坐标空间会旋转,就是高和宽在设备旋转时会呼唤,(0.0, 0.0)是这个app本身的左上方 。
UIScreen的fixedCoordinateSpace 属性
这个坐标空间不会变化,就是物理上的左上角,从用户来看,这里的 (0.0, 0.0) 可能是 app 本身的任何一个角。
可以用下面的方法来对不同坐标空间进行转换:
假设界面中有一个 UIView v,我们想知道它的实际设备坐标,可以用下面的代码:
|
|
一个 view 的 transform 属性改变这个 view 是如何被绘制的,实际上就是一个 CGAffineTransform类的 3x3 矩阵。所有的变换都是以这个view的center做基准的,下面的具体实例:
效果图入下:旋转了45度
注意,这里的view的center和bounds都没有变,但是frame的数值已经没有意义,因为现在它的尺寸是能够覆盖当前view的最小矩形,并不会随着view的旋转而旋转。
|
|
效果如下:
view的bounds仍然不收影响,因为subview仍然绘制在相对于Superview的位置。也就是说这个两个view在水平方向一起拉伸。
代码:
效果图如下:
再变,代码只需要改动如下:
效果图如下:
还有一种方法能实现上图的效果:
继续增加下面的代码:
效果图如下:
再来一个变换:
效果图如下:
界面上的每个 view 都有一个 traitCollection 属性,值是一个 UITraitCollection,包含下面四个属性:
水平和竖直都是 .Regular -> iPad
水平是 .Compact 竖直是 .Regular -> iPhone 在垂直方向,或者 iPad 的分屏应用
水平和竖直都是 .Compact -> iPhone 在水平方向(iPhone 6/6s/7 plus除外)
水平是 .Regular 竖直是 .Compact -> iPhone 6/6s/7 Plus 在水平方向
当应用运行时如果 trait collection 发生改变,会调用 traitCollectionDidChange 方法。
假设superview的bounds变化,其subview的bounds和center是不会变的,实际应用中,我们可能更需要subview根据Superview的变化而变化,这就是Layout。
Layout的主要执行方式:
Manual layout(手动layout)
superview在被更改尺寸会发送layoutSubview消息,如果你新建自己的子类,并且重写layoutSuperview就可以手动更改,这个很麻烦,但是可以做任何你想做的事。
Autoresizing
Autoresizing是iOS6之前的方式,主要通过自己的autoresizingMask属性来变化。
Authlayout
iOS6中引入的,取决于view的constraints(NSLayoutConstraint的实例)。Autolayout是在layoutSubview的幕后实现,不需要代码也能实现复杂的layoutSubview功能。
通常不会用到手动 layout,autoresizing 基本也是自动的,autolayout 主要在 xCode 的编辑器中进行设定。在代码中创建的 view 默认使用 autoresizing 而不是 autolayout。
]]>