springboot jar包解压、重新压缩打包方法

 jar, spring-boot  springboot jar包解压、重新压缩打包方法已关闭评论
5月 112020
 

springboot在产生jar包后,有时需要修改其中的文件并重新打包,比如上传到服务器后,发现配置文件里某个配置没改,可以使用下面的方法:

以一个测试springboot 包  spingboot-ouu-test.jar 为例

因为有重新打包的需求,建议jar包放在一个空的文件夹下执行下面的命令:

  • 解压spingboot-ouu-test.jar

jar -xvf spingboot-ouu-test.jar

常见的配置文件如:application.properties在BOOT-INF/classes/目录下.

  • 修改文件后, 重新打包spingboot-ouu-test.jar,

jar  -cvfM0 spingboot-ouu-test.jar .           //(M后面是数字0,如果只使用参数cf,java -jar spingboot-ouu-test.jar启动springboot jar项目时将提示“没有主清单属性”)

 

附:

jar -tvf spingboot-ouu-test.jar   //查看jar包文件结构

jar命令常用参数:

c :创建一个 jar 包
t :显示 jar 包里面的内容
x :解压 jar 包
u :添加文件到 jar包
f :指定 jar 包的文件名
v :在 CMD 显示详细执行过程(报告)
m :指定 manufest.mf 文件(该文件可以对jar包及其内容做设置)
0 :打包 jar包 时不压缩
M :不产生 manufest.mf 文件,覆盖 m 参数的设置
i :为打包的 jar包 创建索引文件
c :进入某目录后再执行 jar 命令,相当于 cd 进入目录然后不带 c 参数执行 jar命令

 

Linux里使用shell中的$(( ))、$( )、“与${ }及区别

 linux, shell  Linux里使用shell中的$(( ))、$( )、“与${ }及区别已关闭评论
4月 072020
 
转一篇linux下关于“$(( ))、$( )、“与${ }”使用及区别的好文章。来自:https://blog.csdn.net/number_0_0/article/details/73291182
命令替换

在bash中,$( )` `(反引号)都是用来作命令替换的。
命令替换与变量替换差不多,都是用来重组命令行的,先完成引号里的命令行,然后将其结果替换出来,再重组成新的命令行。

exp 1

  1. $ echo today is $(date “+%Y-%m-%d”)
  2. today is 2014-07-01
$( )与``

在操作上,这两者都是达到相应的效果,但是建议使用$( ),理由如下:

  • ``很容易与”搞混乱,尤其对初学者来说。
  • 在多层次的复合替换中,``必须要额外的跳脱处理(反斜线),而$( )比较直观。
  • 最后,$( )的弊端是,并不是所有的类unix系统都支持这种方式,但反引号是肯定支持的。

exp 2

  1. # 将cmd1执行结果作为cmd2参数,再将cmd2结果作为cmd3的参数
  2. cmd3 $(cmd2 $(cmd1))
  3. # 如果是用反引号,直接引用是不行的,还需要作跳脱处理
  4. cmd3 `cmd2 \`cmd1\“
${ }变量替换

一般情况下,$var${var}是没有区别的,但是用${ }会比较精确的界定变量名称的范围

  1. $ A=B
  2. $ echo ${A}B
  3. BB

取路径、文件名、后缀
先赋值一个变量为一个路径,如下:
file=/dir1/dir2/dir3/my.file.txt

命令 解释 结果
${file#*/} 拿掉第一条 / 及其左边的字符串 dir1/dir2/dir3/my.file.txt
${file##*/} 拿掉最后一条 / 及其左边的字符串 my.file.txt
${file#*.} 拿掉第一个 . 及其左边的字符串 file.txt
${file##*.} 拿掉最后一个 . 及其左边的字符串 txt
${file%/*} 拿掉最后一条 / 及其右边的字符串 /dir1/dir2/dir3
${file%%/*} 拿掉第一条 / 及其右边的字符串 (空值)
${file%.*} 拿掉最后一个 . 及其右边的字符串 /dir1/dir2/dir3/my.file
${file%%.*} 拿掉第一个 . 及其右边的字符串 /dir1/dir2/dir3/my

记忆方法如下:

  • # 是去掉左边(在键盘上 # 在 $ 之左边)
  • % 是去掉右边(在键盘上 % 在 $ 之右边)
  • 单一符号是最小匹配;两个符号是最大匹配
  • *是用来匹配不要的字符,也就是想要去掉的那部分
  • 还有指定字符分隔号,与*配合,决定取哪部分

取子串及替换

命令 解释 结果
${file:0:5} 提取最左边的 5 个字节 /dir1
${file:5:5} 提取第 5 个字节右边的连续 5 个字节 /dir2
${file/dir/path} 将第一个 dir 提换为 path /path1/dir2/dir3/my.file.txt
${file//dir/path} 将全部 dir 提换为 path /path1/path2/path3/my.file.txt
${#file} 获取变量长度 27

根据状态为变量赋值

命令 解释 备注
${file-my.file.txt} 若 $file 没设定,则使用 my.file.txt 作传回值 空值及非空值不作处理
${file:-my.file.txt} 若 $file 没有设定或为空值,则使用 my.file.txt 作传回值 非空值时不作处理
${file+my.file.txt} 若$file 设为空值或非空值,均使用my.file.txt作传回值 没设定时不作处理
${file:+my.file.txt} 若 $file 为非空值,则使用 my.file.txt 作传回值 没设定及空值不作处理
${file=txt} 若 $file 没设定,则回传 txt ,并将 $file 赋值为 txt 空值及非空值不作处理
${file:=txt} 若 $file 没设定或空值,则回传 txt ,将 $file 赋值为txt 非空值时不作处理
${file?my.file.txt} 若 $file 没设定,则将 my.file.txt 输出至 STDERR 空值及非空值不作处理
${file:?my.file.txt} 若 $file没设定或空值,则将my.file.txt输出至STDERR 非空值时不作处理

tips:
以上的理解在于, 你一定要分清楚 unset 与 null 及 non-null 这三种赋值状态. 一般而言, : 与 null 有关, 若不带 : 的话, null 不受影响, 若带 : 则连 null 也受影响.

数组

  1. A=“a b c def” # 定义字符串
  2. A=(a b c def) # 定义字符数组
命令 解释 结果
${A[@]} 返回数组全部元素 a b c def
${A[*]} 同上 a b c def
${A[0]} 返回数组第一个元素 a
${#A[@]} 返回数组元素总个数 4
${#A[*]} 同上 4
${#A[3]} 返回第四个元素的长度,即def的长度 3
A[3]=xyz 则是将第四个组数重新定义为 xyz
$(( ))与整数运算

bash中整数运算符号

符号 功能
+ – * / 分别为加、减、乘、除
% 余数运算
& | ^ ! 分别为“AND、OR、XOR、NOT”

在 $(( )) 中的变量名称,可于其前面加 $ 符号来替换,也可以不用。

  1. $ a=5;b=7;c=2
  2. $ echo $((a+b*c))
  3. 19
  4. $ echo $(($a+$b*$c))
  5. 19

进制转换
$(( ))可以将其他进制转成十进制数显示出来。用法如下:
echo $((N#xx))
其中,N为进制,xx为该进制下某个数值,命令执行后可以得到该进制数转成十进制后的值。

  1. $ echo $((2#110)) # 二进制转十进制
  2. 6
  3. $ echo $((16#2a)) # 十六进制转十进制
  4. 42
  5. $ echo $((8#11)) # 八进制转十进制
  6. 9

(( ))重定义变量值

  1. $ a=5;b=7
  2. $ ((a++));echo $a
  3. 6
  4. $ ((a–));echo $a
  5. 5
$ ((a<b));echo $? 0

使用(( ))作整数测试时,不要跟[ ]的整数测试搞混乱了。

swift下类和结构体的比较(相同点与不同点,什么情况下使用)

 swift  swift下类和结构体的比较(相同点与不同点,什么情况下使用)已关闭评论
3月 122020
 

网上看到的一篇好文章,清楚明了的解释了swift下类与结构体的相同点和不同点,并说明了该在什么情况下使用,推荐! 来自: https://juejin.im/post/5b57e75ef265da0f87593513

结构体是构建代码所用的一种通用且灵活的构造体。我们可以使用完全相同的语法规则来为类和结构体定义属性(变量,常量)和添加方法。从而扩展类和结构体的功能。

与其他编程语言所不同的是,Swift并不要求你为自定义类和结构去创建独立的接口和实现文件。你所要做的是在一个单一文件中定义一个类或者结构体,系统将会自动生成面向其他代码的外部接口。

注: 通常一个的势力被称为对象。在Swift中,类和结构体的关系要比在其他语言中更加密切,本章中所讨论的部分功能都可以在类和结构体上。因此主要使用实例。内容包含类和结构体对比结构体和枚举是值类型类是引用类型类和结构体的选择字符串、数组和字典类型的赋值和复制行为


类和结构体对比

Swift中的类和结构体有很多共同点:

  • 定义属性用于储存值
  • 定义方法用于提供功能
  • 定义下标操作通过下标语法可以访问它们的值
  • 定义构造器用于生成初始化值
  • 通过扩展以增加默认实现的功能
  • 遵循协议以提供某种标准功能

与结构体相比,类还有如下的附加功能:

  • 继承允许一个类继承另一个类的特征
  • 类型转换允许在运行时检查和解释一个类实例的类型
  • 析构器允许一个类实例释放任何其所被分配的资源
  • 引用计数允许对一个类的多次引用

注: 结构体总是通过被复制的方式在代码中传递,不使用引用计数。

定义语法

类和结构体有着类似的定义方式。通过关键字classstruct来分别表示类和结构体,并在一对大括号中定义它们的具体内容:

class SomeClass {
	// 在这里定义类
}
struct SomeStructure {
	// 在这里定义结构体
} 

注: 在每次定义一个新类或者结构体的时候,实际上是定义了一个新的Swift类型。因此请使用UpperCamelCase这种命名方式命名(如SomeClassSomeStructure等),已便符合标准Swift类型的大写命名风格(如StringIntBool)。相反的,请使用lowerCamelCase这种方式为属性和方法命名(如:framerateincrementCount),以便和类型名区分。

以下是定义结构体和定义类的示例:

struct Resolution {
	var width = 0
	var height = 0
}
class VideoMode {
	var resolution = Resolution()
	var interlaced = false
	var frameRate = 0.0
	var name: String?
} 

在上面的示例中我们定义了一个名为Resolution的结构体,用来描述一个显示器的像素分辨率。这个结构体包含了两个名为widthheight的存储属性。存储属性是被捆绑和存储在类或结构体中的常量或变量。这两个属性被初始化为整数0的时候,它们会被推断为Int类型。

在上面的示例中我们还定义了一个名为VideoMode的类,用来描述一个视频显示器的特定模式。这个类包含了四个变量存储属性。第一个是分辨率,它被初始化为一个新的Resolution结构体的实例,属性类型被推断为Resolution。新的VideoMode实例同时还会初始化其他三个属性,它们分别是,初始化为falseinterlaced,初始值为0.0frameRate,以及可选值为Stringnamename属性会被自定赋值nil,为可选类型。

类和结构体实例

Resolution结构体和VideoMode类的定义仅描述了什么是ResolutionVideoMode。它们并没有描述一个特定的分辨率(resolution)和视频模式(video mode)。为了描述一个特定的分辨率或者视频模式,我们需要生成一个它们的实例。

let someResolution = Resolution()
let someVideoMode = VideoMode() 

结构体和类都使用构造器语法来生成新的实例。构造器语法的最简单形式是在结构体或者类的类型名称后跟随一对空括号,如Resolution()VideoMode()。通过这种方式所创建的类或者结构体实例,其属性均会被初始化为默认值。

属性访问

通过使用点语法,你可以访问实例的属性。其语法规则则是,实例名后面紧跟属性名:

print("The width of someResolution is \(someResolution.width)")
// 打印 "The width of someResolution is 0"

print("The width of someVideoMode is \(someVideoMode.resolution.width)")
// 打印 "The width of someVideoMode is 0"

someVideoMode.resolution.width = 1280
print("The width of someVideoMode is now \(someVideoMode.resolution.width)")
// 打印 "The width of someVideoMode is now 1280" 

注: 与OC语言不通的是,Swift允许直接设置结构体属性的子属性。上面的最后一个例子,就是直接设置了someVideoModeresolution属性的width这个子属性,以上操作并不需要重新为整个resolution属性设置新值。

结构体类型的成员逐一构造器

所有结构体都有一个自动生成的成员逐一构造器,用于初始化新结构体实例中成员的属性。新实例中各个属性的初始值可以通过属性的名称传递到成员逐一构造器之中,如下:

let vga = Resolution(width: 640, height: 480) 

与结构体不同,类实例没有默认的成员逐一构造器

结构体和枚举是值类型

值类型被赋予给一个变量、常量或者传递给一个函数的时候,其值会被拷贝

在Swift中,所有的基本类型:整数,浮点数,布尔值,字符串,数组,字典都是值类型,并且在底层都是以结构体的形式所实现。所有的结构体和枚举类型都是值类型。这意味着他们的实例,以及实例中所包含的任何值类型属性,在代码中传递都会被复制。

let hd = Resolution(width: 1920, height: 1080)
var cinema = hd 

在以上示例中,声明了一个名为hd的常量,其值为一个初始化为全高清视频分辨率(1920 像素宽,1080 像素高)的Resolution的实例。

下面,为了符合数码影院的放映需求(2048 像素宽,1080 像素高),cinemawidth 属性需要作如下修改:

cinema.width = 2048 

这里,将会显示 cinemawidth 属性确已改为了 2048

print("cinema is now  \(cinema.width) pixels wide")
// 打印 "cinema is now 2048 pixels wide" 

然而,初始的 hd 实例中 width 属性还是 1920

print("hd is still \(hd.width) pixels wide")
// 打印 "hd is still 1920 pixels wide" 

证明将hd赋值给cinema的时候,实际是将hd中所有储存的值进行拷贝,然后将拷贝的数据存储到新的cinema实例中。由于两者相互独立,因此将 cinemawidth 修改为 2048 并不会影响 hd 中的 width 的值。

枚举也遵循相同的行为准则:

enum CompassPoint {
	case North, South, East, West
}
var currentDirection = CompassPoint.West
let rememberedDirection = currentDirection
currentDirection = .East
if rememberedDirection == .West {
	print("The remembered direction is still .West")
}
// 打印 "The remembered direction is still .West" 

类是引用类型

与值类型不同,引用类型在被赋予到一个变量、常量挥着被传递到一个函数时,其值不会被拷贝。因此,引用的是已存在的实例本身而不是其拷贝。使用VideoMode举例如下:

let tenEighty = VideoMode()
tenEighty.resolution = hd
tenEighty.interlaced = true
tenEighty.name = "1080i"
tenEighty.frameRate = 25.0

let alsoTenEighty = tenEighty
alsoTenEighty.frameRate = 30.0

print("The frameRate property of tenEighty is now \(tenEighty.frameRate)")
// 打印 "The frameRate property of theEighty is now 30.0" 

以上示例中,声明了一个名为tenEighty的常量,其引用了一个VideoMode了的新实例。在之前的示例中,这个视频模式被赋予了HD分辨率(1920*1080)的一个拷贝(即 hd 实例)。同时设置为interlaced,命名为“1080i”。最为帧率为25.0帧每秒。然后,tenEighty 被赋予名为 alsoTenEighty 的新常量,同时对 alsoTenEighty 的帧率进行修改。

因为类是引用类型,所以 tenEightalsoTenEight 实际上引用的是相同的 VideoMode 实例。换句话说,它们是同一个实例的两种叫法。

通过查看 tenEightyframeRate 属性,我们会发现它正确的显示了所引用的 VideoMode 实例的新帧率,其值为 30.0

注: tenEightyalsoTenEighty 被声明为常量而不是变量。然而你依然可以改变 tenEighty.frameRatealsoTenEighty.frameRate,因为 tenEightyalsoTenEighty 这两个常量的值并未改变。它们并不“存储”这个 VideoMode 实例,而仅仅是对 VideoMode 实例的引用。所以,改变的是被引用的 VideoModeframeRate 属性,而不是引用 VideoMode 的常量的值。

恒等于运算符

因为类是引用类型,有可能有多个常量和变量在幕后同时引用同一个类实例。(对于结构体和枚举来说,这并不成立。因为它们作为值类型,在二笔赋予到常量、变量或者传递到函数时,其值总是被拷贝。)

Swift里有两个恒等于符号:等价于(===不等价于!== 来判定两个常量是否引用同一个类实例。

if tenEighty === alsoTenEighty {
	print("tenEighty and alsoTenEighty refer to the same Resolution instance.")
}
//打印 "tenEighty and alsoTenEighty refer to the same Resolution instance." 

注: 等于(==等价于(===)意义不同:

等于表示两个实例的值相等相同 等价于表示两个类类型的常量或者变量引用同一个类实例。

指针

在OC中,指针是来引用内存中的地址。一个引用某个引用类型实例的Swift常量或者变量,与OC的指针类似,但并不直接指向某个内存地址,也不要求使用(*)来表明你在创建一个引用。Swift中的这些引用于其他的常量或者变量的定义相同。

类和结构体的选择

在我们的代码中,我们可以使用类和结构体来定义我们的自定义数据类型。

然而,结构体实例总是通过值传递,类实例总是通过引用传递。这意味两者使用不同的任务。当你在考虑一个工程项目的数据和功能的时候,你需要决定每个数据结构是定义成类还是结构体。

按照通用准则,当符合一条或多条一下条件时,可以考虑结构体:

  • 该数据结构的主要目的是用来封装少量相关简单数据值
  • 有理由预计该数据结构的实例在被赋值或传递时,封装的数据将会被拷贝而不是被引用。
  • 该数据结构中存储的值类型属性,也应该被拷贝,而不是被引用。
  • 该数据结构不需要去继承另一个既有类型的属性或者行为。

字符串、数组、字典类型的赋值与复制行为

Swift中,许多基本类型如StringArrayDictionary类型均以结构体的形式实现。这意味着被赋值给新的常量,或者被传入函数或方法中时,它们的值会被拷贝。

OC中NSStringNSArrayNSDictionary类型均以类的形式实现,而并非结构体。它们在被赋值或者被传入函数或者方法时,不会发生值拷贝,而是传递现有实例的引用。

 

Swift自动布局SnapKit的使用介绍

 swift  Swift自动布局SnapKit的使用介绍已关闭评论
3月 042020
 
网上看到的一篇介绍snapkit使用的文章,推荐:https://www.jianshu.com/p/2bad53a2a180, 转发下:

简介

SnapKit,一个经典的Swift版的第三方库,专门用于项目的自动布局,目前在github上的stars就高达9340颗星,这是一个不小的数字,亦足以证明它存在的非凡意义和作用。作者认为,在iOS开发(swift)中,它是用于项目最优秀的自动布局的必选库之一。它的作者仍然是写Objective-C的第三方库Masonry的大牛 – @Robert Payne,开门见山,本文将详细介绍介绍SnapKit的详细使用和安装,相信对于初入门该库的开发者或许会有一定的帮助。

Snapkit的安装(CocoaPods)

环境配置要求:

  • iOS 8.0 / Mac OS X 10.11+
  • Xcode 8.0+
  • Swift 3.0+

安装

在已经安装CocoaPods的前提下, 即可以进行下列步骤。

  • 在你的项目工程里的Podfile文件里面添加

 

source 'https://github.com/CocoaPods/Specs.git'

platform :ios, '10.0'

use_frameworks!

target '这里是你的工程名称' do
    pod 'SnapKit', '~> 3.0'
end
  • 老生常谈,运行CocoaPods的如下命令

 

pod install

到此,不出意外的话,你已经将SnapKit集成到你的项目中了。然后,就开始讲怎么使用它了。

Snapkit的布局使用

1、 实现一个宽高为100,居于当前视图的中心的视图布局,示例代码如下

 

import UIKit
import SnapKit

class ViewController: UIViewController {
    override func viewDidLoad() {
        super.viewDidLoad()
     
        let testView = UIView()
        testView.backgroundColor = UIColor.cyan
        view.addSubview(testView)
        testView.snp.makeConstraints { (make) in
            make.width.equalTo(100)         // 宽为100
            make.height.equalTo(100)        // 高为100
            make.center.equalTo(view)       // 位于当前视图的中心
        }
        
    }
}

更简洁的写法可以

 

import UIKit
import SnapKit

class ViewController: UIViewController {
    override func viewDidLoad() {
        super.viewDidLoad()
     
        let testView = UIView()
        testView.backgroundColor = UIColor.cyan
        view.addSubview(testView)
        testView.snp.makeConstraints { (make) in
            make.width.height.equalTo(100)    // 链式语法直接定义宽高
            make.center.equalToSuperview()    // 直接在父视图居中
        }
        
    }
}

效果图

TestPicture1

2、View2位于View1内, view2位于View1的中心, 并且距离View的边距的距离都为20

 

import UIKit
import SnapKit

class ViewController: UIViewController {
    override func viewDidLoad() {
        super.viewDidLoad()
     
         // 黑色视图作为父视图
         let view1 = UIView()
         view1.frame = CGRect(x: 0, y: 0, width: 300, height: 300)
         view1.center = view.center
         view1.backgroundColor = UIColor.black
         view.addSubview(view1)
         
         // 测试视图
         let view2 = UIView()
         view2.backgroundColor = UIColor.magenta
         view1.addSubview(view2)
         view2.snp.makeConstraints { (make) in
              make.top.equalToSuperview().offset(20)      // 当前视图的顶部距离父视图的顶部:20(父视图顶部+20)
              make.left.equalToSuperview().offset(20)     // 当前视图的左边距离父视图的左边:20(父视图左边+20)
              make.bottom.equalToSuperview().offset(-20)  // 当前视图的底部距离父视图的底部:-20(父视图底部-20)
              make.right.equalToSuperview().offset(-20)   // 当前视图的右边距离父视图的右边:-20(父视图右边-20)
         }
        
    }
}

更简洁的写法

 

import UIKit
import SnapKit

class ViewController: UIViewController {
    override func viewDidLoad() {
        super.viewDidLoad()
     
         // 黑色视图作为父视图
         let view1 = UIView()
         view1.frame = CGRect(x: 0, y: 0, width: 300, height: 300)
         view1.center = view.center
         view1.backgroundColor = UIColor.black
         view.addSubview(view1)
         
         // 测试视图
         let view2 = UIView()
         view2.backgroundColor = UIColor.magenta
         view1.addSubview(view2)
         view2.snp.makeConstraints { (make) in
            make.edges.equalToSuperview().inset(UIEdgeInsets(top: 20, left: 20, bottom: 20, right: 20))
         }
        
    }
}

效果图

TestPicture2

3、布局一个视图view2, 让它的水平中心线小于等于另一个视图view2的左边,可以这样布局

 

import UIKit
import SnapKit

class ViewController: UIViewController {
    override func viewDidLoad() {
        super.viewDidLoad()
     
         // 黑色视图作为父视图
         let view1 = UIView()
         view1.frame = CGRect(x: 0, y: 0, width: 300, height: 300)
         view1.center = view.center
         view1.backgroundColor = UIColor.black
         view.addSubview(view1)
        
         // 测试视图
         let view2 = UIView()
         view2.backgroundColor = UIColor.magenta
         view1.addSubview(view2)
         view2.snp.makeConstraints { (make) in
            // 让顶部距离view1的底部为10的距离
            make.top.equalTo(view1.snp.bottom).offset(10)
            // 设置宽、高
            make.width.height.equalTo(100)
            // 水平中心线<=view1的左边
            make.centerX.lessThanOrEqualTo(view1.snp.leading)
         }
        
    }
}

效果图

TestPicture

视图的属性说明

通过上面的大致简单布局我们对SnapKit有了一个基本的了解,那么, 它的布局属性是怎么来的呢?和原生的布局类有什么关联? 下面看一个SnapKit的布局属性表

从表中,我们知道,Snapkit的布局属性都是源自于系统的NSLayoutAttribute,那么,NSLayoutAttribute是个什么呢?其实,它在swift中是一个枚举,内部列举了很多布局属性诸如top、left、leading、centerX等,Snapkit的布局属性与它们都存在一一的对应关系。

Snapkit 的 greaterThanOrEqualTo 属性

如果想让视图View2的左边>=父视图View1的左边, 这时我们就可以用到greaterThanOrEqualTo

 

import UIKit
import SnapKit

class ViewController: UIViewController {
    override func viewDidLoad() {
        super.viewDidLoad()
        
         // 黑色视图作为父视图
         let view1 = UIView()
         view1.frame = CGRect(x: 0, y: 0, width: 300, height: 300)
         view1.center = view.center
         view1.backgroundColor = UIColor.black
         view.addSubview(view1)
        
         // 测试视图
         let view2 = UIView()
         view2.backgroundColor = UIColor.magenta
         view1.addSubview(view2)
         view2.snp.makeConstraints { (make) in
            // 让顶部距离view1的底部为10的距离
            make.top.equalTo(view1.snp.bottom).offset(10)
            // 设置宽、高
            make.width.height.equalTo(100)
            // 水平中心线<=view1的左边
            make.left.greaterThanOrEqualTo(view1)
            
            // 或者, 和上面一行代码一样的效果
//            make.left.greaterThanOrEqualTo(view1.snp.left)
         }
    }
}

效果图

TestPicture

其实,greaterThanOrEqualTo这个属性有点多余,比如上面这行代码 make.left.greaterThanOrEqualTo(view1) , 我们可以换成 make.left.equalToSuperview()make.left.equalTo(view1.snp.left), 效果是一样的,也就是说,一般情况下 >=<= 我们都可以直接用equalTo来代替!

SnapKit的greaterThanOrEqualTo和lessThanOrEqualTo联合使用

当我们想要让某个视图的width或height大于等于某个特定的值,小于等于某个特定的值的时候,一般而言,Snapkit会以greaterThanOrEqualTo为准,这里举一个width的例子,为了方便,这里指贴出了viewDidLoad中的代码(其他没必要)

 

 // 黑色视图作为父视图
 let view1 = UIView()
 view1.frame = CGRect(x: 0, y: 0, width: 300, height: 300)
 view1.center = view.center
 view1.backgroundColor = UIColor.black
 view.addSubview(view1)
    
 // 测试视图
 let view2 = UIView()
 view2.backgroundColor = UIColor.magenta
 view1.addSubview(view2)
 view2.snp.makeConstraints { (make) in
    make.width.lessThanOrEqualTo(300)
    make.width.greaterThanOrEqualTo(200)
    make.height.equalTo(100)
    make.center.equalToSuperview()
 }

接着,我们来看一下效果图

Test6

很明显,最后的宽度是以make.width.greaterThanOrEqualTo(200)为标准的,也可以这样的,在同时使用两者的情况下,greaterThanOrEqualTo的优先级略比lessThanOrEqualTo的优先级高。值得一提的是, 在上面的例子中,如果我们只设置make.width.lessThanOrEqualTo(300),那么view2是不会显示出来的,因为view2不知道你要表达的是要显示多少,小于等于300,到底是100还是200呢?(这里指针对width和height)所以它不能确定这个约束的值,但是,如果我们单独设置make.width.greaterThanOrEqualTo(200),那么就和上面的效果一样,因为它会以200为标准布局约束!

lessThanOrEqualTo的用于上、下、左、右

如果我们想要视图view2的左边 <= view1.left + 10, 那么就可以直接用到lessThanOrEqualTo布局了,看下面这个例子

 

 // 黑色视图作为父视图
 let view1 = UIView()
 view1.frame = CGRect(x: 0, y: 0, width: 300, height: 300)
 view1.center = view.center
 view1.backgroundColor = UIColor.black
 view.addSubview(view1)
    
 // 测试视图
 let view2 = UIView()
 view2.backgroundColor = UIColor.magenta
 view1.addSubview(view2)
 view2.snp.makeConstraints { (make) in
    make.left.lessThanOrEqualTo(20)     // <= 父视图的左边+20
    make.right.equalTo(-40)             // = 父视图的右边-40
    make.height.equalTo(100)
    make.center.equalToSuperview()
 }

效果图

Test7

Snapkit布局的灵活性

  • Snapkit布局灵活性很强, 我们看下面的例子, 他们的效果是一样的

 

make.left.equalToSuperview().offset(10)
make.left.equalTo(10)
make.left.equalTo(view1.snp.left).offset(10)
  • 设置视图的大小(width,height),他们效果是一样的

 

make.width.height.equalTo(100)
或
make.width.equalTo(100)
make.height.equalTo(100)
或
make.size.equalTo(CGSize(width: 100, height: 100))
  • 优先级(priority)

我们来看一下Snapkit的优先级设置, 优先级都是附加在约束链的末尾处,看下面的使用方法

 

// 黑色视图作为父视图
let view1 = UIView()
view1.frame = CGRect(x: 0, y: 0, width: 300, height: 300)
view1.center = view.center
view1.backgroundColor = UIColor.black
view.addSubview(view1)
    
// 测试视图
let view2 = UIView()
view2.backgroundColor = UIColor.magenta
view1.addSubview(view2)
view2.snp.makeConstraints { (make) in
    make.width.equalTo(100).priority(666)
    make.width.equalTo(250).priority(999)
    make.height.equalTo(111)
    make.center.equalToSuperview()
}

效果图

priorityimage

从上面我们可以知道, 我们设置了两个优先级:make.width.equalTo(100).priority(666) make.width.equalTo(250).priority(999), 那运行结果是一个哪个为准呢?显然是以优先级为 999的为准,因为 priority(999)>priotity(666), 所以在使用Snapkit的过程中,有时我们可以使用优先级priority来设置我们的约束, 另外,值得一提的是,SnapKit的优先级最大值只能是 1000, 如果优先级的数值超过1000,则运行时就会Crash!这里要尤其注意。

</br>

  • 更新约束(引用约束)

</br>

我们可以通过保存某一个约束布局来更新相应的约束,或者保存一组约束布局到一个数组中更新约束, 具体看下面代码

 

// 保存约束(引用约束)
var updateConstraint: Constraint?
    
override func viewDidLoad() {
    super.viewDidLoad()
    
    // 黑色视图作为父视图
    let view1 = UIView()
    view1.frame = CGRect(x: 0, y: 0, width: 300, height: 300)
    view1.center = view.center
    view1.backgroundColor = UIColor.black
    view.addSubview(view1)

    // 测试视图
    let view2 = UIView()
    view2.backgroundColor = UIColor.magenta
    view1.addSubview(view2)
    view2.snp.makeConstraints { (make) in
        make.width.height.equalTo(100)  // 宽高为100
        self.updateConstraint = make.top.left.equalTo(10).constraint   // 距离父视图上、左为10
    }
    
    let updateButton = UIButton(type: .custom)
    updateButton.backgroundColor = UIColor.brown
    updateButton.frame = CGRect(x: 100, y: 80, width: 50, height: 30)
    updateButton.setTitle("更新", for: .normal)
    updateButton.addTarget(self, action: #selector(updateConstraintMethod), for: .touchUpInside)
    view.addSubview(updateButton)
}
    
// 更新约束
func updateConstraintMethod() {
    self.updateConstraint?.update(offset: 50)   // 更新距离父视图上、左为50
}
testgif1
  • 更新约束(snp.updateConstraints)

说起这个updateConstraints, 我也懵逼过,那么它到底有何作用呢?又怎么用呢?它和一开始就使用的makeConstraints又有什么明确的区别呢?请继续往下看

说明1:如果你这是更新某个约束或某几个约束的常量值,你就可以使用updateConstraints而不是makeConstraints

说明2:这个也是苹果推荐用来添加或更新约束的方式

说明3:这个方法可以调用多次,会相应setNeedsUpdateConstraints, 在控制器中,可以写在override func updateViewConstraints()方法里面(当然也可以写在你想要什么时候更新的点击事件里面)

 

import UIKit
import SnapKit

class ViewController: UIViewController {

    lazy var blackView = UIView()
    
    override func viewDidLoad() {
        super.viewDidLoad()
        
        blackView.backgroundColor = UIColor.black
        view.addSubview(blackView)
        blackView.snp.makeConstraints { (make) in
            
            // 四个约束确定位置和大小
            make.width.equalTo(100)
            make.height.equalTo(150)
            make.top.equalTo(10)
            make.centerX.equalToSuperview()
        }
 
    }
    
    override func updateViewConstraints() {
        
        blackView.snp.updateConstraints { (make) in
            // 更新距离父视图顶部的约束(从 10 ---> 300 )
            make.top.equalTo(300)
        }
        
        // 根据苹果,调用父类应该放在末尾
        super.updateViewConstraints()
    }
}

注意: 从上面的代码中我们很明确地知道, blackView通过widthheighttopcenterX确定了它本身的大小和位置, 但是, 在 run 出来之后,它的top改变了距离, 从 10 变成了 300,其他三个约束保持不变, 见下图效果:

test10

显而易见, 除了top约束, 其他都没有改变! 也就是说,约束被更新(相当于系统升级一样,是一个道理)

现在,我们通过UIButton的点击事件来证明一下制作约束makeConstraintsupdateConstraints具体的区别在哪里?

 

lazy var blackView = UIView()
    
override func viewDidLoad() {
    super.viewDidLoad()
    
    blackView.backgroundColor = UIColor.black
    view.addSubview(blackView)
    blackView.snp.makeConstraints { (make) in
        
        make.width.equalTo(100)
        make.height.equalTo(150)
        make.top.equalTo(50)
        make.centerX.equalToSuperview()
    }
    
    let btn = UIButton(type: .custom)
    btn.backgroundColor = UIColor.brown
    btn.frame = CGRect(x: 100, y: 200, width: 60, height: 30)
    btn.addTarget(self, action: #selector(buttonAction), for: .touchUpInside)
    view.addSubview(btn)
 
}
    
// 点击更新/制作约束
func buttonAction() {
    
    blackView.snp.makeConstraints { (make) in
        make.width.height.equalTo(20)
        make.top.equalTo(300)
    }
    
}

先看效果图

点击事件发生前(图1):

test11

点击事件发生后(图2)

test12

图3

test14

图4

test13

上面,我们知道,视图 blackView一开始是由四个约束确定位置和大小,在点击事情响应后,我们又给 blackView 制作(记住,是制作,而不是重做,两者有明确的区别)了3个约束,分别是 widthheighttop, 那么这样做问题出现在哪里呢? 第一, 点击事情发生前(图1), 在点击事件发生后(见图2), 我们发现,blackViewwidthheight约束改变了,但是 top却没有改变,还是原来的距离父视图顶部 50 的距离, 原因在于,我们在原来的约束基础上又添加了多余的约束, 也就是说,约束从4个变成了7个(见图3左边constraints), 这样就产生了约束不明确,进而导致snapkit的警告(见图4), 这样布局显然是不可取的,在项目中这样做极其危险,甚至可能会导致异常奔溃!!!!

现在, 我们该将点击事件中的约束布局从makeConstraints改变成updateConstraints来试试两者有什么区别(下面只添加了点击事件的代码,其他事重复的就不多此一举了)

 

func buttonAction() {
    // 注意这里是updateConstraints, 而不是makeConstraints
    blackView.snp.updateConstraints { (make) in
        make.width.height.equalTo(20)
        make.top.equalTo(300)
    }
    print("这里试试snapkit有没有报警告")
}

接着看点击事件后的效果图

图5

test5

图6

test6

图7

test7

发现没有,在将makeConstraints改变成updateConstraints之后,约束还是4个,snapkit没有报警告,点击事件中的widthheighttop全部起了作用,而这就是两者的本质区别makeConstraints是制作约束,在原来的基础上再添加另外的约束,也就是画蛇添足,约束增加,视图布局就有不确定性,从而有些约束起作用,有些不起作用(如上面的top),snapkit报警告!!!而updateConstraints是更新约束,改变原有约束,约束不会增加,没经过updateConstraints处理的保持原有约束,经过处理就更新约束,约束不会减少,snapkit不会产生警告,这是正常标准的更新约束的正确方式!!!

</br>

  • 重做约束(remakeConstraints)

重做约束的本质就是:去掉已有的所有约束, 重新做约束,记住,是做约束, 也就是说, 使用了remakeConstraints后,重做的约束必须要能确定相应视图的大小位置, 之前makeConstraints的约束已经不会存在了,完全销毁!!!

 

// 点击更新/制作约束
func buttonAction() {
    
    // 注意这里是 remakeConstraints
    blackView.snp.remakeConstraints { (make) in
        make.width.height.equalTo(20)
        make.top.equalTo(300)
    }
    
    print("这里试试snapkit有没有报警告")
}

效果图

图(1)

test18

图(2)

test19

图(3)

test20

我们看到, blackView重做了约束, 之前的约束不起任何作用,由于它在重做约束后只有 3 个约束分别是 widthheighttop, 但是这里有一个问题,就是这 3 个约束只能确定大小,无法确定视图的位置, 所以在水平方向上或者左右缺少一个布局条件, 故 blackView整体视图的x紧靠左边(默认)! 另外我们发现, 在图(3)中,右上角出现了一个感叹号“!”, 那是因为告诉你缺少了一个约束条件:x-xcode-debug-views://7f81fcbc7900: runtime: Layout Issues: Horizontal position is ambiguous for UIView.

小结

通过以上学习,我们或深或浅地学习了布局三方库SnapKit的使用, 我相信,只要将上述布局会使用,并且懂得布局的原则和道理,基本上就可以“高枕无忧”了,至于涉及动态布局、动画布局等知识,后续有时间会更新文档。

</br>

 

swift内存管理中WEAK 和 UNOWNED介绍与使用

 swift  swift内存管理中WEAK 和 UNOWNED介绍与使用已关闭评论
1月 062020
 

网上看到的一篇关于swift 的内存管理的weak和unowned介绍和使用的文章,觉得写的挺好,分享下。文章来源地址: onevcat

 

不管在什么语言里,内存管理的内容都很重要,所以我打算花上比其他 tip 长一些的篇幅仔细地说说这块内容。

Swift 是自动管理内存的,这也就是说,我们不再需要操心内存的申请和分配。当我们通过初始化创建一个对象时,Swift 会替我们管理和分配内存。而释放的原则遵循了自动引用计数 (ARC) 的规则:当一个对象没有引用的时候,其内存将会被自动回收。这套机制从很大程度上简化了我们的编码,我们只需要保证在合适的时候将引用置空 (比如超过作用域,或者手动设为 nil 等),就可以确保内存使用不出现问题。

但是,所有的自动引用计数机制都有一个从理论上无法绕过的限制,那就是循环引用 (retain cycle) 的情况。

什么是循环引用

虽然我觉得循环引用这样的概念介绍不太应该出现在这本书中,但是为了更清晰地解释 Swift 中的循环引用的一般情况,这里还是简单进行说明。假设我们有两个类 A 和 B, 它们之中分别有一个存储属性持有对方:

class A {
    let b: B
    init() {
        b = B()
        b.a = self
    }

    deinit {
        print("A deinit")
    }
}

class B {
    var a: A? = nil
    deinit {
        print("B deinit")
    }
}

在 A 的初始化方法中,我们生成了一个 B 的实例并将其存储在属性中。然后我们又将 A 的实例赋值给了 b.a。这样 a.b 和 b.a 将在初始化的时候形成一个引用循环。现在当有第三方的调用初始化了 A,然后即使立即将其释放,A 和 B 两个类实例的 deinit 方法也不会被调用,说明它们并没有被释放。

var obj: A? = A()
obj = nil
// 内存没有释放

因为即使 obj 不再持有 A 的这个对象,b 中的 b.a 依然引用着这个对象,导致它无法释放。而进一步,a 中也持有着 b,导致 b 也无法释放。在将 obj 设为 nil 之后,我们在代码里再也拿不到对于这个对象的引用了,所以除非是杀掉整个进程,我们已经永远也无法将它释放了。多么悲伤的故事啊..

在 Swift 里防止循环引用

为了防止这种人神共愤的悲剧的发生,我们必须给编译器一点提示,表明我们不希望它们互相持有。一般来说我们习惯希望 “被动” 的一方不要去持有 “主动” 的一方。在这里 b.a 里对 A 的实例的持有是由 A 的方法设定的,我们在之后直接使用的也是 A 的实例,因此认为 b 是被动的一方。可以将上面的 class B 的声明改为:

class B {
    weak var a: A? = nil
    deinit {
        print("B deinit")
    }
}

在 var a 前面加上了 weak,向编译器说明我们不希望持有 a。这时,当 obj 指向 nil 时,整个环境中就没有对 A 的这个实例的持有了,于是这个实例可以得到释放。接着,这个被释放的实例上对 b 的引用 a.b 也随着这次释放结束了作用域,所以 b 的引用也将归零,得到释放。添加 weak 后的输出:

A deinit
B deinit

可能有心的朋友已经注意到,在 Swift 中除了 weak 以外,还有另一个冲着编译器叫喊着类似的 “不要引用我” 的标识符,那就是 unowned。它们的区别在哪里呢?如果您是一直写 Objective-C 过来的,那么从表面的行为上来说 unowned 更像以前的 unsafe_unretained,而 weak 就是以前的 weak。用通俗的话说,就是 unowned 设置以后即使它原来引用的内容已经被释放了,它仍然会保持对被已经释放了的对象的一个 “无效的” 引用,它不能是 Optional 值,也不会被指向 nil。如果你尝试调用这个引用的方法或者访问成员属性的话,程序就会崩溃。而 weak 则友好一些,在引用的内容被释放后,标记为 weak 的成员将会自动地变成 nil (因此被标记为 @weak 的变量一定需要是 Optional 值)。关于两者使用的选择,Apple 给我们的建议是如果能够确定在访问时不会已被释放的话,尽量使用 unowned,如果存在被释放的可能,那就选择用 weak

我们结合实际编码中的使用来看看选择吧。日常工作中一般使用弱引用的最常见的场景有两个:

  1. 设置 delegate 时
  2. 在 self 属性存储为闭包时,其中拥有对 self 引用时

前者是 Cocoa 框架的常见设计模式,比如我们有一个负责网络请求的类,它实现了发送请求以及接收请求结果的任务,其中这个结果是通过实现请求类的 protocol 的方式来实现的,这种时候我们一般设置 delegate 为 weak

// RequestManager.swift
class RequestManager: RequestHandler {

    @objc func requestFinished() {
        print("请求完成")
    }

    func sendRequest() {
        let req = Request()
        req.delegate = self

        req.send()
    }
}

// Request.swift
@objc protocol RequestHandler {
    optional func requestFinished()
}

class Request {
    weak var delegate: RequestHandler!;

    func send() {
        // 发送请求
        // 一般来说会将 req 的引用传递给网络框架
    }

    func gotResponse() {
        // 请求返回
        delegate?.requestFinished?()
    }
}

req 中以 weak 的方式持有了 delegate,因为网络请求是一个异步过程,很可能会遇到用户不愿意等待而选择放弃的情况。这种情况下一般都会将 RequestManager 进行清理,所以我们其实是无法保证在拿到返回时作为 delegate 的 RequestManager 对象是一定存在的。因此我们使用了 weak 而非 unowned,并在调用前进行了判断。

闭包和循环引用

另一种闭包的情况稍微复杂一些:我们首先要知道,闭包中对任何其他元素的引用都是会被闭包自动持有的。如果我们在闭包中写了 self 这样的东西的话,那我们其实也就在闭包内持有了当前的对象。这里就出现了一个在实际开发中比较隐蔽的陷阱:如果当前的实例直接或者间接地对这个闭包又有引用的话,就形成了一个 self -> 闭包 -> self 的循环引用。最简单的例子是,我们声明了一个闭包用来以特定的形式打印 self 中的一个字符串:

class Person {
    let name: String
    lazy var printName: ()->() = {
        print("The name is \(self.name)")
    }

    init(personName: String) {
        name = personName
    }

    deinit {
        print("Person deinit \(self.name)")
    }
}

var xiaoMing: Person? = Person(personName: "XiaoMing")
xiaoMing!.printName()
xiaoMing = nil
// 输出:
// The name is XiaoMing,没有被释放

printName 是 self 的属性,会被 self 持有,而它本身又在闭包内持有 self,这导致了 xiaoMing 的 deinit 在自身超过作用域后还是没有被调用,也就是没有被释放。为了解决这种闭包内的循环引用,我们需要在闭包开始的时候添加一个标注,来表示这个闭包内的某些要素应该以何种特定的方式来使用。可以将 printName 修改为这样:

lazy var printName: ()->() = {
    [weak self] in
    if let strongSelf = self {
        print("The name is \(strongSelf.name)")
    }
}

现在内存释放就正确了:

// 输出:
// The name is XiaoMing
// Person deinit XiaoMing

如果我们可以确定在整个过程中 self 不会被释放的话,我们可以将上面的 weak 改为 unowned,这样就不再需要 strongSelf 的判断。但是如果在过程中 self 被释放了而 printName 这个闭包没有被释放的话 (比如 生成 Person 后,某个外部变量持有了 printName,随后这个 Persone 对象被释放了,但是 printName 已然存在并可能被调用),使用 unowned 将造成崩溃。在这里我们需要根据实际的需求来决定是使用 weak 还是 unowned

这种在闭包参数的位置进行标注的语法结构是将要标注的内容放在原来参数的前面,并使用中括号括起来。如果有多个需要标注的元素的话,在同一个中括号内用逗号隔开,举个例子:

// 标注前
{ (number: Int) -> Bool in
    //...
    return true
}

// 标注后
{ [unowned self, weak someObject] (number: Int) -> Bool in
    //...
    return true
}

GPUImage2(GPUImage swift版本) 使用pod安装

 swift  GPUImage2(GPUImage swift版本) 使用pod安装已关闭评论
12月 192019
 

GPUImage2常见的安装方法是project导入和静态库方法,实在太麻烦了,在GPUImage2 github的issue中有人提供了一个pod安装方法(详见:https://github.com/BradLarson/GPUImage2/issues/1):

 

在Podfile文件所在的同级目录下新建一个podspec文件,如:GPUImage2.podspec, 并且输入下面内容:

Pod::Spec.new do |s|
s.name = ‘GPUImage2’
s.version = ‘0.1.0’
s.license = ‘BSD’
s.summary = ‘An open source iOS framework for GPU-based image and video processing.’
s.homepage = ‘https://github.com/BradLarson/GPUImage2’
s.author = { ‘Brad Larson’ => ‘[email protected]’ }

# This commit on that fork of GPUImage should contain just upgrades needed for Swift 4 compatibility. See https://github.com/BradLarson/GPUImage2/pull/212
# Replace with https://github.com/BradLarson/GPUImage2.git when merged
# into BradLarson’s repository.
s.source = { :git => ‘https://github.com/andrewcampoli/GPUImage2’, :commit => ‘148c84e6b4194daeba122e77449f5ee9c8188161’ }

s.source_files = ‘framework/Source/**/*.{swift}’
s.resources = ‘framework/Source/Operations/Shaders/*.{fsh}’
s.requires_arc = true
s.xcconfig = { ‘CLANG_MODULES_AUTOLINK’ => ‘YES’, ‘OTHER_SWIFT_FLAGS’ => “$(inherited) -DGLES”}

s.ios.deployment_target = ‘8.0’
s.ios.exclude_files = ‘framework/Source/Mac’, ‘framework/Source/Linux’, ‘framework/Source/Operations/Shaders/ConvertedShaders_GL.swift’
s.frameworks = [‘OpenGLES’, ‘CoreMedia’, ‘QuartzCore’, ‘AVFoundation’]

end

 

并且在Podfile中加入:
pod ‘GPUImage2’, :podspec => ‘./GPUImage2.podspec’

 

然后pod install 即可

mac下使用第三方工具Xnip完美滚动截图

 mac  mac下使用第三方工具Xnip完美滚动截图已关闭评论
9月 182019
 

Mac可以使用腾讯的Snip进行滚动截图,但这种截图有个缺陷: 它只能截取完整的窗口,有没有可以选取部分的滚动截图?? 有,Xnip, 而且免费,功能更是逆天!!!!!

很久以来 macOS 都被吐槽没有足够好用的截屏工具,以至于当非常强大的 Snipaste 问世时,很多用户的第一反应就是「什么时候有 Mac 版?」。在愿望成真之余,另一款截屏工具 Xnip 也悄然发布——小巧而强大,还有很多非常实用的独有功能哦。

常见的截屏功能 Xnip 都支持,比如窗口识别、标注等;特别是 Xnip 支持「步骤标注」,需要经常制作教程的用户一定喜欢。

Xnip 还优化了截屏的流程。一般来讲截屏的步骤分为「区域选择 – 确认选择 – 标注 / 编辑 – 保存」,而一旦进入了「标注 / 编辑」状态,用户便无法再次改变选择的区域。虽然可能很多用户都觉得无所谓,重新来一遍或是事后再裁剪就好了,但是如果已经标注了一大堆才发现区域不对,也挺烦恼的。而 Xnip 允许用户在标注过程中随意改变已选择的区域,不得不佩服开发者的细心。

Xnip 也支持滚动截屏,使用体验和别家也是非常不一样,滚动的时候旁边有实时预览,感觉非常爽快自然。

特别值得一提的是,Xnip 的滚动截图还能保留动图的动态,虽然我暂时没想到使用场景是什么,可能…用作呈堂证供?再次佩服开发者对各种细节的观察与思考。

Xnip 是国内开发者 ZekeDa 的作品,可在 Mac App Store 免费下载。免费版可使用所有功能,只是在保存时会被加上「水印」——说是水印,其实只会加在截屏以外的区域,并不影响阅读或破坏截屏完整性。

Python模板-Mako语法介绍

 python  Python模板-Mako语法介绍已关闭评论
9月 052019
 

资源

官网 http://www.makotemplates.org/

文档 http://docs.makotemplates.org/en/latest/

文档翻译 Mako模板入门 http://help.42qu.com/code/mako.html

安装

pip install mako

HelloWorld

from mako.template import Template

mytemplate = Template("hello world!")
print mytemplate.render()

-------------------------

from mako.template import Template
print Template("hello ${data}!").render(data="world")

语法

输出变量 ${x}

数学计算 ${1+1}
the contents within the ${} tag are evaluated by Python directly, so full expressions are OK

filter
${"test"|u}
${"test"|u,trim}
内置filter列表
    u : URL escaping, provided by urllib.quote_plus(string.encode('utf-8'))
    h : HTML escaping, provided by markupsafe.escape(string)
    x : XML escaping
    trim : whitespace trimming, provided by string.strip()
    entity : produces HTML entity references for applicable strings, derived from htmlentitydefs
    unicode (str on Python 3): produces a Python unicode string (this function is applied by default)
    decode.<some encoding> : decode input into a Python unicode with the specified encoding
    n : disable all default filtering; only filters specified in the local expression tag will be applied.

分支
% if x == 5:
    abcd
% endif

循环
% for a in ['1', '2', '3']:
    % if a == '1':
      abc
    % elif a == '2':
      def
    % else:
      gh
    % endif
$ endfor

Python语法
this is a template
<%
    x = db.get_resource('foo')
    y = [z.element for z in x if x.frobnizzle==5]
%>
% for elem in y:
    element: ${elem}
% endfor

换行

加 / 强制不换行


设置变量
% for item in ('apple', 'banana'):
    <%
        isBanana = False
    %>
    % if item == 'banana':
    <%
        isBanana = True
    %>
    %endif
    % if isBanana:
        <span> Bought a banana</span>
    %endif
%endfor

注释

## 这是一个注释.
...text ...

多行
<%doc>
这里是注释
更多注释
</%doc>

模块级别语句

<% %> 的一个变体是 <%! %>,代表模块级别的代码块。其中的代码会在模板的模块级别执行,而不是在模板的 rendering 函数中。

<%!
import mylib
import re

def filter(text):
    return re.sub(r'^@', '', text)
%>

标签

定义了当前模板的总体特性,包括缓存参数,以及模板被调用时期待的参数列表(非必须)
<%page args="x, y, z='default'"/>
<%page cached="True" cache_type="memory"/>


<%include file="header.html"/>
hello world
<%include file="footer.html"/>

%def 标签用于定义包含一系列内容的一个 Python 函数,此函数在当前模板的其他某个地方被调用到
<%def name="myfunc(x)">
this is myfunc, x is ${x}
</%def>
${myfunc(7)}

<%block filter="h">
some <html> stuff.
</%block>
<%block name="header">
    <h2><%block name="title"/></h2>
</%block>

Mako 中的 %namespace 等价于 Python 里的 import 语句。它允许访问其他模板文件的所有 rendering 函数和元数据
<%namespace file="functions.html" import="*"/>

<%inherit file="base.html"/>

处理多行注释:
<%doc>
    these are comments
    more comments
</%doc>

该标签使得 Mako 的词法器对模板指令的常规解析动作停止,并以纯文本的形式返回其整个内容部分
<%text filter="h">
heres some fake mako ${syntax}
<%def name="x()">${x}</%def>
</%text>

有时你想中途停止执行一个模板或者 <%def> 方法,只返回已经收集到的文本信息,可以通过在 Python 代码块中使用 return 语句来完成

% if not len(records):
    No records found.
    <% return %>
% endif

文件template

为提高性能,从文件中加载的 Template, 可以将它产生的模块的源代码以普通 python 模块文件的形式(.py),

缓存到文件系统中。只要加一个参数 module_directory 即可做到这一点:

from mako.template import Template

mytemplate = Template(filename='/docs/mytmpl.txt', module_directory='/tmp/mako_modules')
print mytemplate.render()

当上述代码被 render 的时候,会创建文件 /tmp/mako_modules/docs/mytmpl.txt.py.

下一次 Template 对象被用同样参数调用的时候,就会直接重用该模块文件。

文件TemplateLookup

#有一个对 header.txt 文件的包含引用。而从何处去查找 header.txt, 则由 TemplateLookup 指明,是 "/docs" 目录
from mako.template import Template
from mako.lookup import TemplateLookup

mylookup = TemplateLookup(directories=['/docs'])
mytemplate = Template("""<%include file="header.txt"/> hello world!""", lookup=mylookup)


--------------

#可以直接通过 TemplateLookup 来获取模板对象,利用 TemplateLookup 的 get_template 方法,
#并传递模板的 URI 作为参数
mylookup = TemplateLookup(directories=['/docs'], output_encoding='utf-8', encoding_errors='replace')
mytemplate = mylookup.get_template("foo.txt")
print mytemplate.render()

-------------
参数
mylookup = TemplateLookup(directories=['/docs'], output_encoding='utf-8', encoding_errors='replace', , collection_size=500)
TemplateLookup 同时也会在内存中缓存一组模板,所以并不是每一次请求都会导致模板的重新编译和模块重新加载。默认 TemplateLookup 的大小没有限制,但你可以通过 collection_size 参数来限制它
以上的 lookup 会持续加载模板到内存中,直到达到 500 的时候,它就会清除掉一定比例的模板缓存项,根据“最近最少访问”原则

另一个 TemplateLookup 相关的标志是  filesystem_checks. 默认为 True,
每一次 get_template() 方法返回模板后,原始的模板文件的 revision time 会和上次加载模板的时间做对比,
如果文件更新,则会加载其内容,并重新编译该模板。
在生产环境下,设置 filesystem_checks 为 False 可以带来一定的性能提升(和具体的文件系统有关)

自己创建context

from mako.template import Template
from mako.runtime import Context
from StringIO import StringIO

mytemplate = Template("hello, ${name}!")
buf = StringIO()
ctx = Context(buf, name="jack")
mytemplate.render_context(ctx)
print buf.getvalue()

其他

1.解决mako中文乱码问题

TemplateLookup(... , output_encoding='utf-8', ...)
Template(..., input_encoding='utf-8')
又在mako的模板文件的首行添加
## -*- encoding:utf8 -*-

Linux下文件分割与合并(split, cat)(转)

 linux  Linux下文件分割与合并(split, cat)(转)已关闭评论
9月 032019
 

1、前记

Linux学习系列主要侧重数据处理的命令实战学习,包括但不限于awk,grep,sed等命令的实战学习。

2、文件分割(split)

2.1 命令语法

split [--help][--version][-<行数>][-b <字节>][-C <字节>][-l <行数>][要切割的文件][输出文件名]

参数解释

  • -a:指定输出文件名的后缀长度,默认为2个(aa,ab…);
  • -d:指定输出文件名的后缀用数字代替;
  • -l<行数>:行数分割模式,指定每多少行切成一个小文件;
  • -b<字节>:二进制分割模式,指定每多少字切成一个小文件,支持单位:m,k;
  • -C<字节>:文件大小分割模式,与-b参数类似,但切割时尽量维持每行的完整性;
  • –help:显示帮助;
  • –version:显示版本信息;
  • [输出文件名]:设置切割后文件的前置文件名,split会自动在前置文件名后再加上编号。

2.2 使用实例

(1)查看文件总行数

wc -l seven.sql

输出:3307194 seven.sql,即约330万行,分割时以30万行为单位。

(2)分割文件

split -l 300000 seven.sql /home/kinson/Desktop/test1/seven_
--解释:
--参数"l"表示按行分割;
--"300000"表示每个文件30w行
--"seven.sql"为将分割文件;
--"/home/kinson/Desktop/test1/seven_"为分割后的文件路径与命名。

(3)分割结果

分割结果

3、文件合并(cat)

cat命令的用途是连接文件或标准输入并打印。这个命令常用来显示文件内容,或者将几个文件连接起来显示,或者从标准输入读取内容并显示,它常与重定向符号配合使用。

cat主要有如下三大功能:

  • 一次显示整个文件:cat filename;
  • 从键盘创建一个文件:cat > filename 只能创建新文件,不能编辑已有文件;
  • 将几个文件合并为一个文件:cat file1 file2 > file。

3.1 命令语法

cat [-AbeEnstTuv] [--help] [--version] fileName 

参数解释

  • -A:–show-all,等价于 -vET;
  • -b: –number-nonblank,对非空输出行编号;
  • -e:等价于 -vE;
  • -E: –show-ends,在每行结束处显示 $;
  • -n:–number,对输出的所有行编号,由1开始对所有输出的行数编号;
  • -s:–squeeze-blank,有连续两行以上的空白行,就代换为一行的空白行;
  • -t:与 -vT 等价;
  • -T:–show-tabs,将跳格字符显示为 ^I;
  • -v:–show-nonprinting,使用 ^ 和 M- 引用,除了 LFD 和 TAB 之外。

3.2 使用实例

(1)合并全部已分割文件并检验行数

cat test1/seven_* >newseven
wc -l newseven

输出:3307194 newseven,与原文件seven.sql行数一致。

(2)合并部分已分割文件并检验行数

cat test1/seven_aa > seven_part
cat test1/seven_ab >> seven_part
cat test1/seven_ac >> seven_part
wc -l seven_part 

>>表示追加,输出900000 seven_part,即合并了3个文件,每个30万行,所以合并后文件总行数为90万行。

转自链接:https://www.jianshu.com/p/014ec71b0215

linux下分别使用find和grep进行查找

 linux  linux下分别使用find和grep进行查找已关闭评论
8月 302019
 

网上的资料,这个介绍的很详细,收藏下:

在使用linux时,经常需要进行文件查找。其中查找的命令主要有find和grep。两个命令是有区的。

区别:(1)find命令是根据文件的属性进行查找,如文件名,文件大小,所有者,所属组,是否为空,访问时间,修改时间等。

(2)grep是根据文件的内容进行查找,会对文件的每一行按照给定的模式(patter)进行匹配查找。

一.find命令

    基本格式:find  path expression

1.按照文件名查找

(1)find / -name httpd.conf  #在根目录下查找文件httpd.conf,表示在整个硬盘查找
(2)find /etc -name httpd.conf  #在/etc目录下文件httpd.conf
(3)find /etc -name ‘*srm*’  #使用通配符*(0或者任意多个)。表示在/etc目录下查找文件名中含有字符串‘srm’的文件
(4)find . -name ‘srm*’   #表示当前目录下查找文件名开头是字符串‘srm’的文件

2.按照文件特征查找

(1)find / -amin -10   # 查找在系统中最后10分钟访问的文件(access time)
(2)find / -atime -2   # 查找在系统中最后48小时访问的文件
(3)find / -empty   # 查找在系统中为空的文件或者文件夹
(4)find / -group cat   # 查找在系统中属于 group为cat的文件
(5)find / -mmin -5   # 查找在系统中最后5分钟里修改过的文件(modify time)
(6)find / -mtime -1   #查找在系统中最后24小时里修改过的文件
(7)find / -user fred   #查找在系统中属于fred这个用户的文件
(8)find / -size +10000c  #查找出大于10000000字节的文件(c:字节,w:双字,k:KB,M:MB,G:GB)
(9)find / -size -1000k   #查找出小于1000KB的文件

3.使用混合查找方式查找文件

 参数有: !,-and(-a),-or(-o)。

(1)find /tmp -size +10000c -and -mtime +2   #在/tmp目录下查找大于10000字节并在最后2分钟内修改的文件
(2)find / -user fred -or -user george   #在/目录下查找用户是fred或者george的文件文件
(3)find /tmp ! -user panda  #在/tmp目录中查找所有不属于panda用户的文件

二、grep命令

   基本格式:find  expression

 1.主要参数

[options]主要参数:
-c:只输出匹配行的计数。
-i:不区分大小写
-h:查询多文件时不显示文件名。
-l:查询多文件时只输出包含匹配字符的文件名。
-n:显示匹配行及行号。
-s:不显示不存在或无匹配文本的错误信息。
-v:显示不包含匹配文本的所有行。

pattern正则表达式主要参数:
\: 忽略正则表达式中特殊字符的原有含义。
^:匹配正则表达式的开始行。
$: 匹配正则表达式的结束行。
\<:从匹配正则表达 式的行开始。
\>:到匹配正则表达式的行结束。
[ ]:单个字符,如[A]即A符合要求 。
[ – ]:范围,如[A-Z],即A、B、C一直到Z都符合要求 。
.:所有的单个字符。
* :有字符,长度可以为0。

2.实例

(1)grep ‘test’ d*  #显示所有以d开头的文件中包含 test的行
(2)grep ‘test’ aa bb cc    #显示在aa,bb,cc文件中包含test的行
(3)grep ‘[a-z]\{5\}’ aa   #显示所有包含每行字符串至少有5个连续小写字符的字符串的行
(4)grep magic /usr/src  #显示/usr/src目录下的文件(不含子目录)包含magic的行
(5)grep -r magic /usr/src  #显示/usr/src目录下的文件(包含子目录)包含magic的行

(6)grep -w pattern files :只匹配整个单词,而不是字符串的一部分(如匹配’magic’,而不是’magical’),

 

转自:https://www.cnblogs.com/xudong-bupt/archive/2013/03/23/2976793.html

Swift中的!和?, 问号 和叹号 的使用和区别介绍

 swift  Swift中的!和?, 问号 和叹号 的使用和区别介绍已关闭评论
8月 132019
 

一般我们在一下两种情况下会遇到!和?的使用
1.声明变量时

var number :Int?
var str : String

2.在对变量操作时

number?.hashValue
str!.hashValue

由于这两种情况的意义不同,所以分开进行解释:

1.声明变量时
在声明一个变量时如果不手动初始化,Swift不会自动初始化该变量为一个默认值的。

var a : String
var b = a           //error:因为没有初始化a,a没有值

但是对于Optional的变量则不同,Optional的变量在声明时如果不初始化,Swift会自动将该变量初始化为nil。声明变量时在类型后添加?或者!就是告诉编译器这个一个Optional的变量,如果没有初始化,你就将其初始化为nil

var a : String?           //a 为nil
var b : String!           //b 为nil
var a_test = a            //a_test为nil
var b_test = b            //b_test为nil

但是这两者之间还是有一些区别的,介绍后面之后再讲。
Optional事实上是一个枚举类型,从下图可以看出,Optional包含None和Some两种类型,而nil就是Optional.None,非nil就是Optional.some。如果Optional变量在声明时不初始化,Swift会调用init()来初始化变量为nil,而用非nil的值初始化变量时,会通过Some(T)把该原始值包装,所以在之后使用的时候我们需要通过解包取出原始值才能使用。

请输入图片描述

2.对变量进行操作时

var arrayCount = dataList?.count

这时问号的意思类似于isResponseToSelector,即如果变量是nil,则不能响应后面的方法,所以会直接返回nil。如果变量非nil,就会拆Some(T)的包,取出原始值执行后面的操作。

var arrayCount = dataList!.count

这里的叹号和之前的问号则不同,这里表示我确定dataList一定是非nil的,所以直接拆包取出原始值进行处理。因此此处如果不小心让dataList为nil,程序就会crash掉。

回到上面声明时?和!区别的问题上去
声明变量时的?只是单纯的告诉Swift这是Optional的,如果没有初始化就默认为nil,而通过!声明,则之后对该变量操作的时候都会隐式的在操作前添加一个!。

总结

  1. 问号?
    a.声明时添加?,告诉编译器这个是Optional的,如果声明时没有手动初始化,就自动初始化为nil
    b.在对变量值操作前添加?,判断如果变量时nil,则不响应后面的方法。
  2. 叹号!
    a.声明时添加!,告诉编译器这个是Optional的,并且之后对该变量操作的时候,都隐式的在操作前添加!
    b.在对变量操作前添加!,表示默认为非nil,直接解包进行处理

 

来自:https://segmentfault.com/a/1190000000533936。

Swift闭包的用法,简介, IN用法

 swift  Swift闭包的用法,简介, IN用法已关闭评论
8月 072019
 

非常好的资料https://www.jianshu.com/p/7dcecea05dbb

 

本篇将详细总结介绍Swift闭包的用法;
闭包是自包含的函数代码块,可以在代码中被传递和使用。Swift中的闭包与C和 Objective-C中的代码块(blocks)以及其他一些编程语言中的匿名函数比较相似。

主要内容:
1.闭包表达式
2.闭包的使用与优化
3.值捕获
4.逃逸闭包
5.自动闭包

一、闭包表达式

Swift闭包的三种存在形式:
1.全局函数是一个有名字但不会捕获任何值的闭包
2.嵌套函数是一个有名字并可以捕获其封闭函数域内值的闭包
3.闭包表达式是一个利用轻量级语法所写的可以捕获其上下文中变量或常量值的匿名闭包
闭包表达式的语法一般有如下的一般形式:

{ (parameters) -> returnType in
    statements
}

说明:
1.闭包的外层是一个大括号,先写的参数和返回值,然后操作部分之前使用in;
2.闭包就相当于OC中的block, 也可以看做是匿名函数;
3.闭包表达式参数可以是in-out参数,但不能设定默认值;
4.闭包的函数体部分由关键字in引入,该关键字表示闭包参数和返回值类型已经完成,闭包函数体开始;

二、闭包的使用与优化

下面,我们使用Swift标准库中的sorted(by:)方法来测试闭包的使用。sorted(by:)方法允许外部传入一个用于排序的闭包函数将已知类型数组中的值进行排序,完成排序之后,该方法会返回一个与原数组大小相同,包含同类型元素已正确排序的新数组:

//定义一个整型数组
var someInts: [Int] = [5,9,7,0,1,3]
//定义一个排序函数
func biggerNumFirst(num1:Int, num2:Int) -> Bool{
    return num1 > num2
}
//普通用法:将biggerNumFirst函数传入sorted函数,实现排序
var sortInts = someInts.sorted(by: biggerNumFirst)
print(sortInts)     //[9, 7, 5, 3, 1, 0]

//闭包用法:为sorted函数参数传入一个闭包,实现排序
sortInts = someInts.sorted(by:{ (a:Int, b:Int) -> Bool in
    return a > b
})
print(sortInts)     //[9, 7, 5, 3, 1, 0]

注意:因为闭包不会在其他地方调用,所以不使用外部参数名

闭包使用起来十分灵活,我们可以在某些特定情况下对齐进行优化,下面是对上述闭包的优化:

2.1.根据上下文推断类型,省略参数类型与括号

由于排序闭包函数是作为sorted(by:)方法的参数传入的,Swift可以推断其类型和返回值类型。所以sorted(by:)方法被一个Int类型的数组调用,其参数必定是(Int,Int)->Bool类型的函数。最后,根据上下文推断类型,我们可以省略参数类型和参数周围的括号。

sortInts = someInts.sorted(by: {a,b in
    return a > b
})
print(sortInts)

2.2.对于不会发生歧义的闭包,可将其写成一行

sortInts = someInts.sorted(by:{a,b in return a > b})
print(sortInts)

2.3.单行闭包表达式,省略return关键字

省略return关键字的条件:
sorted(by:)方法的参数类型明确了闭包必须返回一个Bool类型值
单行闭包表达式中,其返回值类型没有歧义

sortInts = someInts.sorted(by: {a,b in a > b})
print(sortInts)

2.4.使用参数名缩写(不推荐使用)

Swift 自动为内联闭包提供了参数名称缩写功能,你可以直接通过$0,$1,$2 来顺序调用闭包的参数,以此类推。
如果我们在闭包表达式中使用参数名称缩写, 我们就可以在闭包定义中省略参数列表,并且对应参数名称缩写的类型会通过函数类型进行推断。in关键字也同样可以被省略,因为此时闭包表达式完全由闭包函数体构成:

sortInts = someInts.sorted(by: {$0>$1})
print(sortInts)

2.5.使用运算符简化闭包(不推荐使用)

Swift的Int类型定义了关于大于号(>)的字符串实现,其作为一个函数接受两个Int类型的参数并返回Bool类型的值。而这正好与sorted(by:)方法的参数需要的函数类型相符合。可以使用大于号来代替闭包

sortInts = someInts.sorted(by: >)
print(sortInts)

2.6.尾随闭包,解决长闭包的书写问题

如果你需要将一个很长的闭包表达式作为最后一个参数传递给函数,可以使用尾随闭包来增强函数的可读性。
尾随闭包的写法:将闭包书写在函数括号之后,函数会支持将其作为最后一个参数调用,使用尾随闭包,不需要写出它的参数标签。

func someFunctionThatTakesAClosure(closure: () -> Void) {
    //函数体部分
    closure(); //调用闭包
}

//不使用尾随闭包进行函数调用
someFunctionThatTakesAClosure(closure: {
    //闭包主体部分
})

//使用尾随闭包进行函数调用
someFunctionThatTakesAClosure() {
    //闭包主体部分
}

//注意:如果闭包表达式是函数或方法的唯一参数,则当你使用尾随闭包时,你甚至可以把 () 省略掉:
someFunctionThatTakesAClosure {
    print("Hello World!")    //打印:Hello World!
}

总结Swift闭包主要的四种优化方法:
1.利用上下文推断参数和返回值类型,省略参数类型与括号
2.隐式返回单表达式闭包,即单表达式闭包可以省略return关键字
3.参数名称缩写
4.尾随闭包语法

三、值捕获

闭包可以在其被定义的上下文中捕获常量或变量。即使定义这些常量和变量的原作用域已经不存在,闭包仍然可以在闭包函数体内引用和修改这些值。Swift会为你管理在捕获过程中涉及到的所有内存操作。

func makeIncrementer(forIncrement amount: Int) -> () -> Int {
    var runningTotal = 0
    func incrementer() -> Int {
        runningTotal += amount
        return runningTotal
    }
    return incrementer
}

代码分析:
1.makeIncrementer函数以amount为参数,以()->Int作为返回值类型,其函数体中还嵌套了另一个函数incrementer。
2.如果我们把incrementer单独拿出来,会发现其中runingTotal和amount变量都无法使用,因为这两个变量的引用是incrementer从外部捕获的。
3.Swift会负责被捕获变量的所有内存管理工作,包括对捕获的一份值拷贝,也包括释放不再需要的变量。

现在再来测试makeIncrementer函数的使用:

let incrementByTen = makeIncrementer(forIncrement: 10)
incrementByTen();    //10
incrementByTen();    //20

let incrementBySeven = makeIncrementer(forIncrement: 7)
incrementBySeven()   //7
incrementBySeven();  //14

let alsoIncrementByTen = incrementByTen
alsoIncrementByTen() //30

代码分析:
1.incrementByTen与incrementBySeven,是通过makeIncrementer函数传入不同的增量参数amount而创建的;
2.两个函数都有属于各自的引用,其中的runningTotal变量都是从makeIncrementer中捕获的,但是已经各自没有关系;
3.函数和闭包都是引用类型,将其赋值给变量或者常量,都只是操作的它们的引用,而不会改变闭包或者函数本身;

四、逃逸闭包

当一个闭包作为参数传到一个函数中,但是这个闭包在函数返回之后才被执行,我们称该闭包从函数中逃逸。
逃逸闭包:在定义接受闭包作为参数的函数时,我们需要在参数名之前标注@escaping,以此表明这个闭包是允许”逃逸”出这个函数的。

var completionHandlers: [() -> Void] = []
func someFunctionWithEscapingClosure(completionHandler: @escaping () -> Void) {
    //代码1:执行闭包,不需要添加@escaping
    //completionHandler();
    //代码2:函数外部对闭包进行了操作
    completionHandlers.append(completionHandler)
 }

代码分析:
someFunctionWithEscapingClosure(_:) 函数接受一个闭包作为参数,该闭包被添加到一个函数外定义的数组中。如果不将这个参数标记为@escaping,就会得到一个编译错误。

4.1.逃逸闭包的使用

逃逸闭包和非逃逸闭包在使用上有所不同。将一个闭包标记为@escaping(即逃逸闭包)后,在调用这个闭包时就必须在闭包中显式地引用 self。一个示例如下:

//定义一个带有非逃逸闭包参数的函数
func someFunctionWithNonescapingClosure(closure: () -> Void) {
    closure()
}

//定义一个可以使用闭包的类
class SomeClass {
    var x = 10
    func doSomething() {
        //调用逃逸闭包:必须在闭包中显式引用self
        someFunctionWithEscapingClosure { self.x = 100 }
        //调用非逃逸闭包:可以隐式引用self
        someFunctionWithNonescapingClosure { x = 200 }
    }
}

let instance = SomeClass()
instance.doSomething()
print(instance.x)         //打印出 "200”

五、自动闭包

自动闭包:一种自动创建的闭包,用与包装传递给函数作为参数的表达式;自动闭包的特点:
1.自动闭包不接受任何参数;
2.自动闭包被调用的时候,会返回被包装在其中的表达式的值;
3.自动闭包是用一个普通的表达式来代替显式的闭包,能够省略闭包的花括号;

其实,我们经常调用采用自动闭包的函数,但是却少去实现这样的函数,assert函数就是其中之一:

 assert(condition:, message:)

assert函数中的condition参数可以接受自动闭包作为值,condition参数仅会在debug模式下被求值,在condidtion被调用返回值为false时,message参数将被使用。

5.1.自动闭包的基本使用

自动闭包能够实现延迟求值,直到调用这个闭包时,代码才会被执行。这对于有副作用或者高计算成本的代码来说是有益处的;下面的代码展示了自动闭包实现延时求值的具体做法:

var customersInLine = ["Chris", "Alex", "Ewa", "Barry", "Daniella"]
print(customersInLine.count)    //打印出 “5"

//自动闭包不接受参数,只是一个表达式
let customerProvider = { customersInLine.remove(at: 0) }
print(customersInLine.count)    //打印出 “5"

//调用自动闭包
print("Now serving \(customerProvider())!")   // Prints "Now serving Chris!"
print(customersInLine.count)               //打印出 "4”

代码分析:闭包实现了移除第一元素的功能,但是在闭包被调用之前,这个元素是不会被移除的。这就实现了延迟的作用

5.2.自动闭包在函数中的使用

现在将闭包作为参数传递给一个函数,同样可以实现延时求值行为。下面的serve函数接受了一个闭包参数(具有删除第一个元素且返回这个元素的功能)。

//customersInLine is ["Alex", "Ewa", "Barry", "Daniella"]
func serve(customer customerProvider: () -> String) {
    print("Now serving \(customerProvider())!")
}

//以闭包的形式传入参数
serve(customer: { customersInLine.remove(at: 0) } )  //打印出"Now serving Alex!”

现在使用自动闭包来实现上述函数功能,使用@autoclosure关键字,标明参数使用的是自动闭包,具体示例如下:

// customersInLine is ["Ewa", "Barry", "Daniella"]
func serve(customer customerProvider: @autoclosure () -> String) {
    print("Now serving \(customerProvider())!")
}
//由于标明了自动闭包,这里直接省略了闭包的花括号
serve(customer: customersInLine.remove(at: 0))  //打印出"Now serving Ewa!\n"

注意:
过度使用 autoclosures 会让你的代码变得难以理解。上下文和函数名应该能够清晰地表明求值是被延迟执行的。

5.3.可”逃逸”的自动闭包

一个自动闭包可以“逃逸”,这时候应该同时使用 @autoclosure 和 @escaping 属性,下面举例说明:

// customersInLine is ["Barry", "Daniella"]
var customerProviders: [() -> String] = []
func collectCustomerProviders(_ customerProvider: @autoclosure @escaping () -> String) {
    customerProviders.append(customerProvider)
}
//调用collectCustomerProviders,向数组中追加闭包
collectCustomerProviders(customersInLine.remove(at: 0))
collectCustomerProviders(customersInLine.remove(at: 0))
print("Collected \(customerProviders.count) closures.")   //打印 "Collected 2 closures."
//循环数组中闭包,并且执行
for customerProvider in customerProviders {
    print("Now serving \(customerProvider())!")
}
// 打印 "Now serving Barry!"
// 打印 "Now serving Daniella!”

代码分析:
作为逃逸闭包:
collectCustomerProviders函数中,闭包customerProvider被追加到customerProviders中,而这个数据是定义在函数作用域范围之外的,这意味数组内的闭包能够在函数返回之后被调用,所以customerProvider必须允许
“逃逸”出函数作用域。

作为自动闭包:
调用collectCustomerProviders函数时,传入的闭包是表达式的形式,自动闭包省略了闭包花括号

 

virtualbox 里使用itunes步骤及注意事项

 virtualBox  virtualbox 里使用itunes步骤及注意事项已关闭评论
8月 022019
 

在virtualbox 里想使用itunes,会发现插入usb端口,设备连接了但发现不了iphone设备等问题, 网上找到一篇英文等资料,有详细步骤及注意事项,大家可以参考下(我遇到的问题就是原来打开的是USB 1.0,实际需要打开:USB 2.0 EHCI controller ):

Ok, for the record, I’m not the biggest fan of iTunes for Windows, and that’s why when I needed to use iTunes I decided to install it within an Oracle Virtualbox virtual machine.

This configuration provides the benefit of portability and it also prevents Apple iTunes from wreaking havoc on your host PC.

Hooray for no iTunes on my host machine!!!

Anyway, this is how you do it,

How to set it up

  1. Download and install Oracle VirtualBox from https://www.virtualbox.org/
  2. Open VirtualBox and click MACHINE > NEW to create a new virtual machine.
  3. Select your OS and memory allocation. In my case I chose Microsoft Windows 7 32 bit
    Oracle VirtualBox Screenshot
  4. Choose option Create a virtual hard drive now. You can use the defaults for the next prompts. It’s advisable that you  allocate more hard disk space if you are going to store your media files within the Virtual Machine. In my situation I created a shared folder on my host which I mapped to the Virtual Machine.
  5. Plug in your iDevice to your PC.
  6. You should now see your Virtual Machine in the VirtualBox Manager window. Click SETTINGS to configuring your VM.
    Oracle VirtualBox screenshot
  7. You’ll need to mount the OS storage media within the Storagepanel to install your OS.
  8. Enable the Network Adapter in Bridged Mode so that you have internet access.
  9. In the USB pane, ensure the USB 2.0 EHCI controller is enabled otherwise your iDevice will not operate correctly. If you plugged in your iDevice then click the green ‘+’ button and select your iDevice, this will ensure that control of your device is passed straight to your VM automatically each time you start your VM. Otherwise you can manually ‘pass through’ the device once the VM is started.
    Oracle VirtualBox Screenshot
  10. It’s also advisable to enable 2D hardware acceleration and add additional Video Memory for improved performance.
  11. Click OK to save your changes
  12. Start your Virtual Machine and install the OS and Apple iTunes
  13. Setup your iTunes library to find your media files. It’s recommended that you use the ‘Consolidate Files‘ library option in iTunes to store all your media files in one single location, that way it’ll be easier for you to relocate your Virtual Machine in future to another PC.
  14. If you need access to the CD Drive on your host machine, simply start your Virtual Machine and ‘pass through’ control of your CD drive to your Virtual Machine through the DEVICES > CD/DVD DEVICES > HOST DRIVE option
  15. iTunes should now see your device and should sync successfully.
    iTunes Screenshot

Issues that I encountered

I had iTunes setup to consolidate all media files to an external USB drive. I experienced device ‘timeout‘ issues and ‘Could not connect to lockdown port‘ errors while syncing with my iPhone. After much hair pulling I realised that the timeouts were occurring only when I configured VirtualBox to ‘pass through’ my USB drive to the Virtual Machine. The issue was solved by instead creating a Shared Machine Folder within the Virtual Machine settings mapping to my USB drive.

I would certainly suggest that you create a snapshot of your Virtual Machine once you’ve created it incase anything goes wrong and you need to rollback to an earlier time.

SpringBoot整合Redis和StringRedisTemplate的使用

 spring, spring-boot  SpringBoot整合Redis和StringRedisTemplate的使用已关闭评论
1月 212019
 

springboot下使用redis非常方便:


Maven依赖

<dependency>

    <groupId>org.springframework.boot</groupId>

    <artifactId>spring-boot-starter-data-redis</artifactId>

    <!– <artifactId>spring-boot-starter-redis</artifactId> –>    

</dependency>

 

配置文件 application.properties添加

#redis的ip地址 

spring.redis.hostName=127.0.0.1

#数据库,默认为0

spring.redis.database=1

#端口号 

spring.redis.port=6379

#如果有密码 

spring.redis.password=xxxxx

#客户端超时时间单位是毫秒 默认是2000

spring.redis.timeout=20000 

 

StringRedisTemplate使用

stringRedisTemplate.opsForValue();//操作字符串

stringRedisTemplate.opsForHash();//操作hash

stringRedisTemplate.opsForList();//操作list

stringRedisTemplate.opsForSet();//操作set

stringRedisTemplate.opsForZSet();//操作有序set

示例

import
org.springframework.beans.factory.annotation.Autowired;

import
org.springframework.data.redis.core.StringRedisTemplate;

import org.springframework.stereotype.Service;

 

import java.util.concurrent.TimeUnit;

 

@Service

public class RedisService {

    @Autowired

   
StringRedisTemplate stringRedisTemplate;

 

    /**

     *
stringRedisTemplate基本操作

     */

    public void redis(){

       
stringRedisTemplate.opsForValue().set(“key”, “value”,60*10,
TimeUnit.SECONDS);//向redis里存入数据和设置缓存时间(10分钟)

       
stringRedisTemplate.boundValueOps(“key”).increment(1);//val做1操作

       
stringRedisTemplate.opsForValue().get(“key”);//根据key获取缓存中的val

       
stringRedisTemplate.boundValueOps(“key”).increment(1);//val +1

       
stringRedisTemplate.getExpire(“key”);//根据key获取过期时间

       
stringRedisTemplate.getExpire(“key”,TimeUnit.SECONDS);//根据key获取过期时间并换算成指定单位

       
stringRedisTemplate.delete(“key”);//根据key删除缓存

       
stringRedisTemplate.hasKey(“key”);//检查key是否存在,返回boolean

       
stringRedisTemplate.opsForSet().add(“key”, “5”,”6″,”7″);//向指定key中存放set集合

       
stringRedisTemplate.expire(“key”,1000 ,
TimeUnit.MILLISECONDS);//设置过期时间

       
stringRedisTemplate.opsForSet().isMember(“key”, “1”);//根据key查看集合中是否存在指定数据

       
stringRedisTemplate.opsForSet().members(“key”);//根据key获取set集合

 

    }

}

 

 

ffmpeg常用使用场景及命令

 ffmpeg  ffmpeg常用使用场景及命令已关闭评论
1月 102019
 

网上收集的ffmpeg常用使用场景及命令使用:

应用场景1:格式转换

我想把用iPhone拍的.MOV文件转成.avi文件。最简单了,可以执行下面的命令:

ffmpeg -i D:\Media\IMG_0873.MOV D:\Media\output.avi

意思是,把D:\Media目录下的源文件IMG_0873.MOV(视频:h.264,音频:aac)转换成output.avi(编码格式自动选择为:视频mpeg4,音频mp3),目标文件仍然保存到D:\Media目录下。问题来了:我想自己指定编码格式,怎么办呢?一种方法是,通过目标文件的扩展名(.flv、.mpg、.mp4、.wmv等)来控制,比如:

ffmpeg -i D:\Media\IMG_0873.MOV D:\Media\output2.flv

另一种方法是通过-c:v参数来控制,比如我想输出的视频格式是H.265(警告:编码时间会比较长哦)。命令行如下:

ffmpeg -i D:\Media\IMG_0873.MOV -c:v libx265 D:\Media\output265.avi

注:可以先用ffmpeg -encoders命令查看一下所有可选的编码格式。

不再深究了,我们继续。我发现源文件的图像帧尺寸是1920x 1080,我不需要这么大——能有720 x 480就够了。于是,就要用上-s参数了。为了保证图像缩放后的质量,最好加上码率参数-b:v。如下:

ffmpeg -i D:\Media\IMG_0873.MOV -s 720×480 -b:v 1500k D:\Media\output2.avi

还可以更简单一点,使用-target参数匹配行业标准,参数值可以是vcd、svcd、dvd、dv、dv50等,可能还需要加上电视制式作为前缀(pal-、ntsc-或film-)。如下:

ffmpeg -i D:\Media\IMG_0873.MOV -target pal-dvd D:\Media\output2dvd.avi

又来一个问题:我发现用手机拍的视频中,有些是颠倒的,我想让它顺时针旋转90度。这时候,可以使用-vf参数加入一个过滤器,如下:

ffmpeg -i D:\Media\IMG_0873.MOV -vf “rotate=90*PI/180” D:\Media\output3.avi

注:如果想逆时针旋转90度,90前面加个负号就可以了。

如果我只需要从源视频里截取一小段,怎么办呢?比如从第2秒的地方开始,往后截取10秒钟。命令行可以这样:

ffmpeg -ss 2 -t 10 -i D:\Media\IMG_0873.MOV D:\Media\output4.avi

注:这种情况下,-ss和-t参数必须放在-i前面,表示是限定后面跟着的输入文件的。

应用场景2:视频合成

我发现,用手机拍的视频有时候背景噪音比较大。怎么把噪音去掉,换成一段美妙的音乐呢?使用FFmpeg也能轻易做到。

第一步:把源文件里的音频去掉,生成一个临时文件tmp.mov

ffmpeg -i D:\Media\IMG_0873.MOV -vcodec copy -an D:\Media\tmp.mov

注:-vcodeccopy的意思是对源视频不解码,直接拷贝到目标文件;-an的意思是将源文件里的音频丢弃。

第二步:把这个无声的视频文件(tmp.mov)与一个音乐文件(music.mp3)合成,最终生成output.mov

ffmpeg -i D:\Media\tmp.mov -ss 30 -t 52 -i D:\Media\music.mp3 -vcodec copy D:\Media\output5.avi

为了保证良好的合成效果,音乐时长必须匹配视频时长。这里我们事先知道视频时长为52秒,于是截取music.mp3文件的第30秒往后的52秒与视频合成。另外,为了保证音频时长截取的准确性,我们这里没有使用-acodec copy,而是让音频重新转码。

还有一种情况:我们希望在一段视频上叠加一张图片。可以简单实现如下:

ffmpeg -i D:\Media\IMG_0873.MOV -i D:\Media\logo.png -filter_complex ‘overlay’ D:\Media\output6.avi

应用场景3:视频播放

格式转换或合成之后,我们需要试着播放一下。播放器的选择很多。这里顺手用ffplay工具也行:

ffplay -i D:\Media\output6.avi

应用场景4:获取视频信息

有时候,我只是想看看这个视频文件的格式信息。可以用ffprobe工具:

ffprobe -i D:\Media\IMG_0873.MOV

应用场景5: 截取视频片段转为GIF动画

可以简单地执行下面的命令行:

ffmpeg -ss 25 -t 10 -i D:\Media\bear.wmv -f gif D:\a.gif

意思是:将D:\Media目录下的源文件bear.wmv,从第25秒的位置开始,截取10秒长度的视频转成GIF文件,保存为D:\a.gif。

想要知道FFmpeg到底支持哪些格式吗?执行ffmpeg –formats即可。

问题来了,你的源文件可能是1080P的高清视频,帧率可能还比较高。为了便于网络分享,GIF文件最好小一点。于是,我们需要使用-s参数来进行图像的缩放,使用-r参数来限制目标文件的帧率。命令行如下:

ffmpeg -ss 25 -t 10 -i D:\Media\bear.wmv -s 320×240 -f gif -r 1 D:\b.gif

把b.gif拖进浏览器预览,结果发现:虽然帧率降到了1 fps(从源视频里每隔一秒抽取一帧图像输出到目标文件),整个动画播放还是持续了10秒钟,看着很揪心!能不能在源视频跳帧的情况下同时提高GIF的播放速率呢(比如说在2秒内播完)?查了一遍FFmpeg的说明文档,似乎没有哪个参数可以快速达到这样的目的。也罢,那就分两步走吧:

首先,执行ffmpeg -ss 25 -t 10 -i D:\Media\bear.wmv -r 1 -s 320×240 -f image2 D:\foo-%03d.jpeg,从源视频中每秒钟抽取一帧图像,保存为一系列JPEG文件。然后,再执行ffmpeg -f image2 -framerate 5 -i D:\foo-%03d.jpeg D:\c.gif,将这一系列JPEG图像合成为帧率5 fps的GIF文件。Bingo!

上面提到,把GIF文件拖进浏览器可以进行预览。当然,使用ffplay.exe工具也是可以的——命令行:ffplay D:\a.gif。

p.s. 附送一条指令:截取视频内任意时间点(比如第16.1秒处)的一帧图像保存为JPEG文件:ffmpeg -ss 16.1 -i D:\Media\bear.wmv -s 320×240 -vframes 1 -f image2 D:\d.jpeg

其他应用

FFmpeg的功能非常强大。关键是要理解各种参数的意义,并且巧妙搭配。必要的话,就把在线文档完整读一遍吧:http://www.ffmpeg.org/ffmpeg.html

================ffmpeg 常用基本命令=========================

1.分离视频音频流

ffmpeg -i input_file -vcodec copy -an output_file_video //分离视频流 ffmpeg -i input_file -acodec copy -vn output_file_audio //分离音频流

2.视频解复用

ffmpeg –i test.mp4 –vcodec copy –an –f m4v test.264ffmpeg –i test.avi –vcodec copy –an –f m4v test.264

3.视频转码

ffmpeg –i test.mp4 –vcodec h264 –s 352*278 –an –f m4v test.264              //转码为码流原始文件 ffmpeg –i test.mp4 –vcodec h264 –bf 0 –g 25 –s 352*278 –an –f m4v test.264  //转码为码流原始文件 ffmpeg –i test.avi -vcodec mpeg4 –vtag xvid –qsame test_xvid.avi            //转码为封装文件//-bf B帧数目控制,-g 关键帧间隔控制,-s 分辨率控制

4.视频封装

ffmpeg –i video_file –i audio_file –vcodec copy –acodec copy output_file

5.视频剪切

ffmpeg –i test.avi –r 1 –f image2 image-%3d.jpeg        //提取图片 ffmpeg -ss 0:1:30 -t 0:0:20 -i input.avi -vcodec copy -acodec copy output.avi    //剪切视频//-r 提取图像的频率,-ss 开始时间,-t 持续时间

6.视频录制

ffmpeg –i rtsp://192.168.3.205:5555/test –vcodec copy out.avi

7.YUV序列播放

ffplay -f rawvideo -video_size 1920x1080 input.yuv

8.YUV序列转AVI

ffmpeg –s w*h –pix_fmt yuv420p –i input.yuv –vcodec mpeg4 output.avi

常用参数说明:

主要参数: -i 设定输入流 -f 设定输出格式 -ss 开始时间 视频参数: -b 设定视频流量,默认为200Kbit/s -r 设定帧速率,默认为25 -s 设定画面的宽与高 -aspect 设定画面的比例 -vn 不处理视频 -vcodec 设定视频编解码器,未设定时则使用与输入流相同的编解码器 音频参数: -ar 设定采样率 -ac 设定声音的Channel数 -acodec 设定声音编解码器,未设定时则使用与输入流相同的编解码器 -an 不处理音频

——————————————————————————————–

——————————————————————————————–

1、将文件当做直播送至live

ffmpeg -re -i localFile.mp4 -c copy -f flv rtmp://server/live/streamName

2、将直播媒体保存至本地文件

 

ffmpeg -i rtmp://server/live/streamName -c copy dump.flv

3、将其中一个直播流,视频改用h264压缩,音频不变,送至另外一个直播服务流

 

ffmpeg -i rtmp://server/live/originalStream -c:a copy -c:v libx264 -vpre slow -f flv rtmp://server/live/h264Stream

 

4、将其中一个直播流,视频改用h264压缩,音频改用faac压缩,送至另外一个直播服务流

ffmpeg -i rtmp://server/live/originalStream -c:a libfaac -ar 44100 -ab 48k -c:v libx264 -vpre slow -vpre baseline -f flv rtmp://server/live/h264Stream

5、将其中一个直播流,视频不变,音频改用faac压缩,送至另外一个直播服务流

ffmpeg -i rtmp://server/live/originalStream -acodec libfaac -ar 44100 -ab 48k -vcodec copy -f flv rtmp://server/live/h264_AAC_Stream

6、将一个高清流,复制为几个不同视频清晰度的流重新发布,其中音频不变

ffmpeg -re -i rtmp://server/live/high_FMLE_stream -acodec copy -vcodec x264lib -s 640×360 -b 500k -vpre medium -vpre baseline rtmp://server/live/baseline_500k -acodec copy -vcodec x264lib -s 480×272 -b 300k -vpre medium -vpre baseline rtmp://server/live/baseline_300k -acodec copy -vcodec x264lib -s 320×200 -b 150k -vpre medium -vpre baseline rtmp://server/live/baseline_150k -acodec libfaac -vn -ab 48k rtmp://server/live/audio_only_AAC_48k

7、功能一样,只是采用-x264opts选项

ffmpeg -re -i rtmp://server/live/high_FMLE_stream -c:a copy -c:v x264lib -s 640×360 -x264opts bitrate=500:profile=baseline:preset=slow rtmp://server/live/baseline_500k -c:a copy -c:v x264lib -s 480×272 -x264opts bitrate=300:profile=baseline:preset=slow rtmp://server/live/baseline_300k -c:a copy -c:v x264lib -s 320×200 -x264opts bitrate=150:profile=baseline:preset=slow rtmp://server/live/baseline_150k -c:a libfaac -vn -b:a 48k rtmp://server/live/audio_only_AAC_48k

8、将当前摄像头及音频通过DSSHOW采集,视频h264、音频faac压缩后发布

ffmpeg -r 25 -f dshow -s 640×480 -i video=”video source name”:audio=”audio source name” -vcodec libx264 -b 600k -vpre slow -acodec libfaac -ab 128k -f flv rtmp://server/application/stream_name

9、将一个JPG图片经过h264压缩循环输出为mp4视频

ffmpeg.exe -i INPUT.jpg -an -vcodec libx264 -coder 1 -flags +loop -cmp +chroma -subq 10 -qcomp 0.6 -qmin 10 -qmax 51 -qdiff 4 -flags2 +dct8x8 -trellis 2 -partitions +parti8x8+parti4x4 -crf 24 -threads 0 -r 25 -g 25 -y OUTPUT.mp4

10、将普通流视频改用h264压缩,音频不变,送至高清流服务(新版本FMS live=1)

ffmpeg -i rtmp://server/live/originalStream -c:a copy -c:v libx264 -vpre slow -f flv “rtmp://server/live/h264Stream live=1〃

————————————————————————

————————————————————————

1.采集usb摄像头视频命令:

ffmpeg -t 20 -f vfwcap -i 0 -r 8 -f mp4 cap1111.mp4

 

./ffmpeg -t 10 -f vfwcap -i 0 -r 8 -f mp4 cap.mp4

具体说明如下:我们采集10秒,采集设备为vfwcap类型设备,第0个vfwcap采集设备(如果系统有多个vfw的视频采集设备,可以通过-i num来选择),每秒8帧,输出方式为文件,格式为mp4。

 

2.最简单的抓屏:

ffmpeg -f gdigrab -i desktop out.mpg 

 

3.从屏幕的(10,20)点处开始,抓取640×480的屏幕,设定帧率为5 :

ffmpeg -f gdigrab -framerate 5 -offset_x 10 -offset_y 20 -video_size 640×480 -i desktop out.mpg 

 

4.ffmpeg从视频中生成gif图片:

ffmpeg -i capx.mp4 -t 10 -s 320×240 -pix_fmt rgb24 jidu1.gif

 

5.ffmpeg将图片转换为视频:

http://blog.sina.com.cn/s/blog_40d73279010113c2.html