转自:
https://github.com/KQAppleDev/SwiftLearn
http://blog.csdn.net/qq_16437739/article/details/52819452
http://www.jianshu.com/p/460b5424942a
http://www.qingpingshan.com/rjbc/swift/155193.html
Swift 3 出来也有一阵子了,Swift 3做了很大的改动,逐渐脱离OC的影子。语法上很多对象去掉了NS开头,去掉了繁琐的命名。
本文主要讨论Swift 3中的一些坑和使用过程中的一些小技巧,排名无理由~~
转变为结构体的类
在之前的Swift版本中,苹果引入了String、Array和Dictionary这三个结构体来代替OC中的NSString、NSArray和NSDictionary这三个类,当然这三个OC类依然可以使用。但是在平时的开发使用中,Swift的这三个结构体使用起来更方便,大部分情况下效率更高。 在Swift3.0中,苹果又推出了以下新的结构体,原有OC类依然可以使用。并且可以相互转化。
常用系统提供单例类的获取方法Swift风格化
Foundation框架部分类名去掉NS前缀
包括:UserDefaults、URL、NotificationCenter、Bundle、Timer、Thread、RunLoop
BOOL属性的命名规则
在OC中,官方建议我们将BOOL属性的getter方法命名为isXXX,Swift中由于只是将原有OCUIKit框架进行Swift转换,所以这个规则在之前是Swift中并没有体现。
在Swift3.0中,这个规则被再次应用,所有的BOOL类型都重新命名为isXXX,所以以后我们的自定义类中BOOL属性的命名也应体现这个规则。
private和fileprivate
- private: 私有属性和方法,仅在当前类中可以访问,不包括分类;
- fileprivate: 文件内私有属性和方法,仅在当前文件中可以访问,包括同一个文件中不同的类。
/// 以下所有的类都在同一个文件中
class Test: NSObject {
// 只能在当前大括号内访问
private var value: Int = 0
// 只能在当前文件内访问
fileprivate var value1: Int = 0
// 只能在当前大括号内访问
private func privatePractise() {
value = 1
value1 = 1
fileprivatePractise()
fileprivatePractise1()
print("privatePractise方法被调用了")
}
// 只能在当前文件内访问
fileprivate func fileprivatePractise() {
privatePractise()
fileprivatePractise()
fileprivatePractise1()
print("fileprivatePractise方法被调用了")
}
}
public和open
在Swift2.3中,pubic有两层含义:
- 这个元素可以在其他作用域被访问
- 这个元素可以在其他作用域被继承或者override
继承是一件危险的事情。尤其对于一个framework或者module的设计者而言。在自身的module内,类或者属性对于作者而言是清晰的,能否被继承或者override都是可控的。但是对于使用它的人,作者有时会希望传达出这个类或者属性不应该被继承或者修改。这个对应的就是 final。
final的问题在于在标记之后,在任何地方都不能override。而对于lib的设计者而言,希望得到的是在module内可以被override,在被import到其他地方后其他用户使用的时候不能被override。
这就是 open产生的初衷。通过open和public标记区别一个元素在其他module中是只能被访问还是可以被override。
在Swift3.0中
- public表示当前类、属性或者方法只能在当前module内被继承或者override,在当前module意外只能被访问;
- open表示当前类、属性或者方法可以在任何地方被继承或者override;
- final是一个辅助修饰词,表示当前类、属性或者方法在任何地方都只能被访问,不能被继承或者override;
internal表示默认级别。
/// ModuleA:
import UIKit
/// 这个类在ModuleA的范围外是不能被继承的,只能被访问
public class NonSubclassableParentClass: NSObject {
// 这个方法在ModuleA的范围外只能被访问,不能被override
public func test() {
print(“test”)
}
//这是**错误**的写法,因为class已经不能被继承,
//所以�它的方法的访问权限不能大于类的访问权限
open func bar() {
print(“bar”)
}
// 这个方法在任何地方都只能被访问,不能被override
public final func baz() {
print(“baz”)
}
}
/// 在ModuleA的范围外可以被继承
open class SubclassableParentClass: NSObject {
// 这个属性在ModuleA的范围外只能被访问,不能被override
public var size: Int = 0
// 这个方法在ModuleA的范围外只能被访问,不能被override
public func foo() {
print(“foo”)
}
// 这个方法在任何地方都可以被override
open func baz() {
print(“baz”)
}
// 这个方法在任何地方都只能被访问,不能被override
public final func bar() {
print(“bar”)
}
}
/// 这个类在任何地方都不能被继承
public final class FinalClass {
}
Swfit3.0中,访问控制权限由高到低依次为:open、public、internal(默认)、fileprivate,private。
用stride 代替 C-style 循环
Swift 3 移除了 C 语言风格的 for 循环:
for var a = -6.28; a <= 6.28; a += 0.1 {
print(a, separator: " ", terminator: " ")
}
采用stride
for a in stride(from: -6.28, to: 6.28, by: 0.1) { // from ..< to
print(a, separator: " ", terminator: " ")
}
for x in stride(from: -6.28, through: 6.28, by: 0.1) { // from ... to
print(x, separator: " ", terminator: " ")
}
@discardableResult 消除返回值警告
在Swift 3中,如果方法的返回值没有处理xCode会报一个警告,如果在方法前加上 @discardableResult
不处理的时候就不会有警告了。也可以用
_ = xxx()
来消除警告。
浮点数取余数和除法
在Swift 3中 ,如果你声明一个 let m = 12.0
默认m是 Double, Double是不能和Float做运算的。 CGFloat在32位设备上是 Float32 在64位设备上是 Float64, 所以如果一个 Double 和一个 Float 做运算时先要转换类型的
let m = 12.0
let n:CGFloat = 19.0
let x = CGFloat(m)/n
let k = m.multiplied(by: Double(n)) // 乘法
let y = m.divided(by: Double(n)) // 除法
但是取余算法是不能作用于浮点型的,如果这样就会报错 CGFloat(m)%n
正确的做法是:
let z = CGFloat(m).truncatingRemainder(dividingBy: n) //取余 12.0
AnyObject、 Any
这两个类型都是Swift中很早就出现的类型,但是我们经常使用AnyObject,很少使用Any。
AnyObject类似于OC中的id类型,表示任意的class的实例对象,但是在Swift中,例如我们常见的String和Array都变为结构体了,
而且在Swift3.0中,更多的类型或者枚举被写为结构体了,AnyObject的适用范围变相被削弱了,
所以在Swift3.0的API中曾经许多AnyOjbect的类型被替换为Any了。
之前整个项目基本只用 AnyObject 代表大多数实例,基本也不和 Any 有什么交集。因为Swift 2 针对 Int、 String 等结构体进行了转换,编译器会自动桥接为 NSNumber 和 NSString 这种对象类型,在swift3中 AnyObject 不能表示结构体了 。而 Any 可以代表 struct、 class、 func 等几乎所有类型。
这个改动导致项目很多地方都要随着改,而且大多数库也做了改变,如Alamofire的参数从 [String:AnyObject]? 变成 [String:Any]?
值得一提的是 Any 可以代表任何可空类型,不用指定 Any?
栗子:
let str:String? = "xwwa"
var param:[String:Any] = ["x":1,"code":str]
// ["code": Optional("xwwa"), "x": 1]
str 是一个 Optional 类型的,输出出来也是 Optional。因为我们以前的请求是需要在header中带参数的json机密,换成 Any 怎么都过不去,后来发现有 Optional 值。
这里写了个方法转化了下
func absArray(param:[String:Any])->[String:Any]{
let res = param.map { (key,value) -> (String,Any?) in
let newValue = Mirror(reflecting: value)
if newValue.displayStyle == Mirror.DisplayStyle.optional{
if let v = newValue.children.first?.value{
return (key,v)
}else{
return (key,nil)
}
} return (key,value)
}
var newParam:[String:Any] = [:]
res.forEach { (key,v) in
newParam[key] = v
}
return newParam
}
print(absArray(param:param)) // ["code": "xwwa", "x": 1]
用了反射判断如果值是optional就取出他实际的值.
Swift 3中 Notification 使用方法
Swift 3.0 中NSNotification和Notification创建时,通知的name参数类型都变为“Notification.Name”类型
extension Notification.Name {
static let kNoticeDemo = Notification.Name("xx.xx.ww.ss")
}
class DE{
func test(){
NotificationCenter.default.post(name:Notification.Name.kNoticeDemo , object: nil)
NotificationCenter.default.addObserver(self, selector: #selector(demo), name: Notification.Name.kNoticeDemo, object: nil)
NotificationCenter.default.removeObserver(self, name: Notification.Name.kNoticeDemo, object: nil)
}
@objc func demo(){
}
}
自定义操作符
在swift 2中自定义操作符
infix operator =~ {
associativity none
precedence 130
}
现在在Swift 3中这样的话会报警告 Operator should no longer be declared with body;use a precedence group instead
自定义操作符 别名类型
// >>>操作符, 优先级别名
infix operator >>> : ATPrecedence
precedencegroup ATPrecedence { //定义运算符优先级ATPrecedence
associativity: left
higherThan: AdditionPrecedence
lowerThan: MultiplicationPrecedence
}
直接指定操作符的类型,对这个类型进行定义,
- associativity: left 表示左结合
- higherThan 优先级高于 AdditionPrecedence 这个是加法的类型
- lowerThan 优先级低于 MultiplicationPrecedence 乘除
这里给出常用类型对应的group
infix operator || : LogicalDisjunctionPrecedence
infix operator && : LogicalConjunctionPrecedence
infix operator < : ComparisonPrecedence
infix operator <= : ComparisonPrecedence
infix operator > : ComparisonPrecedence
infix operator >= : ComparisonPrecedence
infix operator == : ComparisonPrecedence
infix operator != : ComparisonPrecedence
infix operator === : ComparisonPrecedence
infix operator !== : ComparisonPrecedence
infix operator ~= : ComparisonPrecedence
infix operator ?? : NilCoalescingPrecedence
infix operator + : AdditionPrecedence
infix operator - : AdditionPrecedence
infix operator &+ : AdditionPrecedence
infix operator &- : AdditionPrecedence
infix operator | : AdditionPrecedence
infix operator ^ : AdditionPrecedence
infix operator * : MultiplicationPrecedence
infix operator / : MultiplicationPrecedence
infix operator % : MultiplicationPrecedence
infix operator &* : MultiplicationPrecedence
infix operator & : MultiplicationPrecedence
infix operator << : BitwiseShiftPrecedence
infix operator >> : BitwiseShiftPrecedence
infix operator ..< : RangeFormationPrecedence
infix operator ... : RangeFormationPrecedence
infix operator *= : AssignmentPrecedence
infix operator /= : AssignmentPrecedence
infix operator %= : AssignmentPrecedence
infix operator += : AssignmentPrecedence
infix operator -= : AssignmentPrecedence
infix operator <<= : AssignmentPrecedence
infix operator >>= : AssignmentPrecedence
infix operator &= : AssignmentPrecedence
infix operator ^= : AssignmentPrecedence
infix operator |= : AssignmentPrecedence
合理的使用异常处理,提高代码质量
在日常开发中,可能遇到很多特殊情况,使得程序不能继续执行下去。有的来自系统语法方面,有的是来自业务方面的。这时候可以使用自定义异常,在底层代码中不断throw 在最后一层中去处理。
struct ZError : Error {
let domain: String
let code: Int
}
func canThrow() throws{
let age = 10
if a < 18{
let error = ZError(domain: "xxx", code: 990)
throw error
}
}
do {
try canThrow()
} catch let error as ZError {
print("Error: \(error.code) - \(error.domain)") // Error: 990 - xxx
}
是时候放弃前缀的扩展了
以前我们要给 UIView 扩展是这样的
extension UIView {
var zz_height:CGFloat{
set(v){
self.frame.size.height = v
}
get{
return self.frame.size.height
}
}
}
这样在自己写的属性前面加一个前缀。但是Swift 3出来后更多的选择应该是这样的view.zz.height 。 以前 kingfisher是 imageView.kf_setImage 现在变成 imageView.kf.setImage 。 SnapKit 也改变成了 make.snp.left 之类的语法那么怎么写这样的扩展呢?
这里看了 KingFisher 的代码,给出他的解决方案。比如我们想写一个UIView的扩展。
// 写一个协议 定义一个只读的类型
public protocol UIViewCompatible {
associatedtype CompatableType
var zz: CompatableType { get }
}
public extension UIViewCompatible {
// 指定泛型类型为自身 , 自身是协议 谁实现了此协议就是谁了
public var zz: Auto<Self> {
get { return Auto(self) } // 初始化 传入自己
set { }
}
}
// Auto是一个接受一个泛型类型的结构体
public struct Auto<Base> {
// 定义该泛型类型属性
public let base: Base
public init(_ base: Base) {
self.base = base
}
}
// 写一个Auto的扩展 指定泛型类型是UIView 或者其子类
public extension Auto where Base:UIView {
var height:CGFloat{
set(v){
self.base.frame.size.height = v
} get{
return self.base.frame.size.height
}
}
}
// 扩展 UIView 实现 UIViewCompatible 协议,就拥有了zz属性 zz又是Auto类型 Auto是用泛型实例化的 这个泛型就是UIView了
extension UIView : UIViewCompatible{
}
// 使用
view.zz.height
上面的注释已经尽量详细的解释了这段代码,hope you can understand !
GCD 的改变
swift 3彻底改变了GCD的使用方式,这里给出日常最基本的几个
你不需要在去用 dispatch_get_main_queue ( ) 来获取主线程,而是 DispatchQueue . main ,那么要放到主线程的代码怎么执行呢?只需要在线程后边使用 . async { } 即可,也就是说,大概是这样:
DispatchQueue.main.async {
print("这里在主线程执行")
}
优先级
- DISPATCH_QUEUE_PRIORITY_HIGH: .userInitiated
- DISPATCH_QUEUE_PRIORITY_DEFAULT: .default
- DISPATCH_QUEUE_PRIORITY_LOW: .utility
- DISPATCH_QUEUE_PRIORITY_BACKGROUND: .background
//global 中设置优先级 不设置默认是 default
DispatchQueue.global(qos: .userInitiated).async {
print("设置优先级")
}
创建一个队列
let queue = DispatchQueue(label: "im.demo.test")
也可以指定优先级和队列
let highQueue = DispatchQueue(
label: "high.demo.test.queue",
qos: DispatchQoS.background,
attributes: DispatchQueue.Attributes.concurrent,
autoreleaseFrequency: DispatchQueue.AutoreleaseFrequency.inherit,
target: nil
)
highQueue.async {
print("ceshi")
}
3秒后执行
DispatchQueue.main.asyncAfter(deadline: DispatchTime.now() + 3.0) {
print("after")
}
根据View查找VC
如果你在一个 UITableViewCell 或者 cell上自定义的一个view上想使用这个view所在的vc怎么办? 代理 ? 层层引用 ? NO ! 一个扩展解决。
一个UIView的扩展
// 查找vc
func responderViewController() -> UIViewController {
var responder: UIResponder! = nil
var next = self.superview
while next != nil {
responder = next?.next
if (responder!.isKind(of: UIViewController.self)){
return (responder as! UIViewController)
}
next = next?.superview
}
return (responder as! UIViewController)
}
记得写在扩展中哦,加上前面的技巧 。不论你在哪个view中。只需要这样let vc = view.zz.responderViewController()
就能拿到所处的vc了。
View中的第一响应者
又是一个View的扩展也很好用,上代码
func findFirstResponder()->UIView?{
if self.isFirstResponder{
return self
}
for subView in self.subviews{
let view = subView.findFirstResponder()
if view != nil {
return view
}
}
return nil
}
用法同上,这个东西能干啥呢?
利用这个可以在 NSNotification.Name.UIKeyboardWillShow 通知通知中拿到第一响应者,如果第一响应者是UITextfield,可以算出局底下的距离,给挡墙view做变换。避免被覆盖。而且这个东西可以写在协议的默认实现中或者
写在一个基类中公用。本文为杂谈不细说了,点到为止咯!
UIViewController 返回是否显示状态栏的方法变化
控制器方法改为属性
获取string的字符串长度方法的改变
获取沙盒指定文件夹路径的方法变化
获取文件路径统一交给FileManager来管理
Swift3.0中selecter的Swift风格化
在Swift2.2中,当我们为一个按钮添加点击事件时常常这样写:
Swift 2.3 中 Selector写法
在Swift2.2更新到Swift2.3后可以看到警告告诉我们这是一个OC风格的写法,建议改为Swift风格的写法。在Swift3.0中两种写法依然都可以使用,但是建议统一写为Swift风格的,因为你不知道什么时候OC风格的就不被允许了。
Swift3中字符串处理的变化
//字符串的索引
str = "Hello, Swift"
//str.characters[0] //这是错误的
let startIndex = str.startIndex //str字符串的起始Index, 注意它是Index类型的, 并不是Int类型.
str[startIndex] //"H"
//str[startIndex.advancedBy(5)] //"," //swift2
str[str.index(startIndex, offsetBy: 5)] //"," //swift3
//let spaceIndex = startIndex.advancedBy(6) //6 //swift2
let spaceIndex = str.index(startIndex, offsetBy: 6) //6 //swift3
spaceIndex //6
//str[spaceIndex.predecessor()] //"," //spaceIndex前一个字符. //swift2
str[str.index(before: spaceIndex)] //"," //spaceIndex前一个字符. //swift3
//str[spaceIndex.successor()] //"S" spaceIndex后一个字符.
str[str.index(after: spaceIndex)]//"S" spaceIndex后一个字符.
let endIndex = str.endIndex //12 最后一个字符的下标.
//str[endIndex] //下标越界了
//str[endIndex.predecessor()] //用endIndex.predecessor()来表示最后一个字符. //swift2
str[str.index(before: endIndex)] //swift3
str[startIndex..<spaceIndex] //"Hello,"
//let range = startIndex..<spaceIndex.predecessor() //圈定str字符串中的某个区间. //swift2
let range = startIndex..<str.index(before: spaceIndex) // swift3
//str.replaceRange(range, with: "Hi") //用 "Hi" 替换range中的字符串. //swift2
str.replaceSubrange(range, with: "Hi") //swift3
//str.appendContentsOf("123") // "Hi, Swift123" /swift2
str.append("123") //swift3
//str.insert("?", atIndex: str.endIndex) //"Hi, Swift123" //swift2
str.insert("?", at: str.endIndex) //swfit3
//str.removeAtIndex(str.endIndex.predecessor()) //"?" //swift2
str.remove(at: str.index(before: str.endIndex)) //swift3
str //"Hi, Swift123"
//str.removeRange(str.endIndex.advancedBy(-2)..<str.endIndex) //"Hi, Swift1" 删除后两个字符. //swift2
str.removeSubrange(str.index(str.endIndex, offsetBy: -2)..<str.endIndex) //swift3
//str.uppercaseString //"HI, SWIFT1" //swift2
str.uppercased() //swift3
//str.lowercaseString //"hi, swift1" //swift2
str.lowercased() //swift3
//str.capitalizedString //"Hi, Swift1" 单词首字母大写 方法 //swift2
str.capitalized //swift3
//str.containsString("Hello") //false //swift2
str.contains("Hello") //swift3
str.hasPrefix("Hi") //true 是否以"Hi"开头
str.hasSuffix("ft1") //true 是否以"ft1"结束
let p1 = "one third is \(1.0/3.0)" //"one third is 0.333333333333333"
// 使用OC中的NSString 格式化保留两位小数, 并使用as强制转换为Swift的String类型.
let p2:String = NSString(format: "one third is %.2f", 1.0/3.0) as String
var p3:NSString = "one third is 0.33" //声明并赋值一个NSString字符串变量.
//p3.substringFromIndex(4) //NSString截取字符从第4个字符开始. //swift2
p3.substring(from: 4) //swift3
//p3.substringToIndex(3) //NSString截取字符到第3个字符. //swift2
p3.substring(to: 3) //swift3
//p3.substringWithRange(NSMakeRange(4, 5)) //"third" 4~9 //swift2
p3.substring(with: NSRange(location: 4, length: 5)) //swift3