打log
在 Xcode 的 Build Setting 中,在 Other Swift flags 的 Debug 栏中加入 -D DEBUG 即可加入一个编译标识。
添加方法:
/// 打log方法,release不会输出
///
/// - Parameters:
/// - message: log 文本
/// - fileName: 文件名,有默认值
/// - methodName: 方法名,有默认值
/// - lineNumber: 行数,有默认值
public func DebugLog<T>(item: T, fileName: String = #file, methodName: String = #function, lineNumber: Int = #line)
{
#if DEBUG
let str : String = ((fileName as NSString).pathComponents.last! as NSString).replacingOccurrences(of: "swift", with: "")
print("\(str)\(methodName)[\(lineNumber)]:\(item)")
#endif
}
测试,在didFinishLaunchingWithOptions打印各种情况:
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool {
// Override point for customization after application launch.
DebugLog(item: "输出字符串")
DebugLog(item: ["arr1", "arr2"])
DebugLog(item: ["key" : "value"])
DebugLog(item: testLog())
return true
}
func testLog() -> String {
DebugLog(message: "test")
return "testResult"
}
输出结果:
AppDelegate.application(_:didFinishLaunchingWithOptions:)[20]:输出字符串
AppDelegate.application(_:didFinishLaunchingWithOptions:)[21]:["arr1", "arr2"]
AppDelegate.application(_:didFinishLaunchingWithOptions:)[22]:["key": "value"]
AppDelegate.testLog()[28]:test
AppDelegate.application(_:didFinishLaunchingWithOptions:)[23]:testResult
注意这里如果你传入的 items 是一个表达式而不是直接的变量的话,这个表达式还是会被先执行求值的。如果这对性能也产生了可测的影响的话,我们最好用 @autoclosure 修饰参数来重新包装 print。这可以将求值运行推迟到方法内部,这样在 Release 时这个求值也会被一并去掉:
func dPrint(@autoclosure item: () -> Any) {
#if DEBUG
print(item())
#endif
}
dPrint(resultFromHeavyWork())
// Release 版本中 resultFromHeavyWork() 不会被执行
xcode8添加注释快捷键
闭包循环引用的解决办法和区别
方法1: OC 的方法
注意weak 的变量在运行时有可能被设置为 nil,weak不能使用 let
//weakSelf -> ViewController?
//self - ViewController
//'weak' must be a mutable variable, because it may change at runtime
//weak 的变量在运行时有可能被设置为 nil,weak不能使用 let
weak var weakSelf = self
loadData { (result) -> () in
print(result)
print(weakSelf)
}
方法2: Swift 的方法[weak self] - 首选
// [weak self] 表示 闭包中的 self 都是弱引用的,不需要再使用其他的变量
// weak 的变量在运行时有可能被设置为 nil,所以闭包中的 self,都是可选的
loadData { [weak self] (result) -> () in
// unexpectedly found nil while unwrapping an Optional value
// ! 强行解包,值不存在抛出异常
// ? 可选解包,值不存在,给nil发送消息
print(self?.view)
}
方法3: [unowned self]
// [unowned self] 和 OC 中的 assign 是一样的,对象释放之后,指针保持不变
// 1> 闭包中不需要考虑解包的问题
// 2> 但是如果 self 释放,会出现野指针访
loadData { [unowned self] (result) -> () in
// EXC_BREAKPOINT 野指针访问
print(self.view)
}
weak 和 unowned 关于两者使用的选择,Apple 给我们的建议是如果能够确定在访问时不会已被释放的话,尽量使用 unowned,如果存在被释放的可能,那就选择用 weak
Swift 关于计算属性的一种用法
今天看了喵神的Kingfisher项目代码,发现一段有趣的代码,给我造成了很大困扰:
public final class Kingfisher<Base> {
public let base: Base
public init(_ base: Base) {
self.base = base
}
}
/**
A type that has Kingfisher extensions.
*/
public protocol KingfisherCompatible {
associatedtype CompatibleType
var kf: CompatibleType { get }
}
public extension KingfisherCompatible {
public var kf: Kingfisher<Self> {
get { return Kingfisher(self) }
}
}
extension ImageView: KingfisherCompatible { }
没看过其他源码可能不明白是什么意思,我们简化一下:
class A {
let base: B
init(_ base: B) {
self.base = base
}
deinit {
print("A dinit")
}
}
class B {
var kf: A {
get { return A(self) }
}
deinit {
print("B dinit")
}
}
看到这段代码,我的第一反应是调用b.kf这不会造成循环引用吗? 通过测试发现并不会造成循环引用,这是怎么回事? 通过测试终于发现了其中的道理:
原来每次在调用b.kf时都会生成一个新的A对象,而且是临时变量,在出了大括号后就会被立即释放。B并没有直接持有A的对象。
所以在遇到计算属性时一定要理解好它其中的代码的真正意义。同时这种写法也值得学习。
关于didset不能再初始化方法调用的问题
Apple’s docs specify that:
willSet and didSet observers are not called when a property is first initialized. They are only called when the property’s value is set outside of an initialization context.
意思是:
class SomeClass {
var someProperty: AnyObject {
didSet {
doStuff()
}
}
init(someProperty: AnyObject) {
self.someProperty = someProperty
}
func doStuff() {
// do stuff now that someProperty is set
}
}
doStuff是不会调用的,解决方法也有很多,如:
class SomeClass {
var someProperty: AnyObject! {
didSet {
//do some Stuff
}
}
init(someProperty: AnyObject) {
setSomeProperty(someProperty)
}
func setSomeProperty(newValue:AnyObject) {
self.someProperty = newValue
}
}
或者:
public class MyNewType : NSObject {
public var myRequiredField:Int
public var myOptionalField:Float? {
willSet {
if let newValue = newValue {
print("I'm going to change to \(newValue)")
}
}
didSet {
if let myOptionalField = self.myOptionalField {
print("Now I'm \(myOptionalField)")
}
}
}
override public init() {
self.myRequiredField = 1
super.init()
// Non-defered
self.myOptionalField = 6.28
// Defered
defer {
self.myOptionalField = 3.14
}
}
}
可以参考:Is it possible to allow didSet to be called during initialization in Swift?
xcode 8 Visual Debugging with Xcode
具体可以查看Visual Debugging with Xcode xcode 提供了新的方式可以更快的发现内存问题等。
这里记录下导出Memory Graph的方法:可以在启动Memory Graph后,选择 file->Export Memory Graph导出文件。
Swift 选项集(option)写法
在oc中写法如下:
typedef enum {
NSCaseInsensitiveSearch = 1,
NSLiteralSearch = 2,
NSBackwardsSearch = 4,
NSAnchoredSearch = 8,
NSNumericSearch = 64,
NSDiacriticInsensitiveSearch = 128,
NSWidthInsensitiveSearch = 256,
NSForcedOrderingSearch = 512,
NSRegularExpressionSearch = 1024
} NSStringCompareOptions;
使用:
NSStringCompareOptions options = NSCaseInsensitiveSearch | NSBackwardsSearch;
// → 5 (= 1 + 4)
在Swift中写法如下:
struct Sports: OptionSet {
let rawValue: Int
static let running = Sports(rawValue: 1)
static let cycling = Sports(rawValue: 2)
static let swimming = Sports(rawValue: 4)
static let fencing = Sports(rawValue: 8)
static let shooting = Sports(rawValue: 32)
static let horseJumping = Sports(rawValue: 512)
}
使用:
let triathlon: Sports = [.swimming, .cycling, .running]
triathlon.contains(.swimming) // → true
triathlon.contains(.fencing) // → false 注意赋值需要 2 的整数次幂按照升序赋给你的选项,也可以使用左移 1 << 0 这种写法 #### 讨论:当您使用中括号将选项集 (option set) 括起来之后,您将得到的是一个新的选项集。这意味着在 Swift 当中,[.foo] 是等同于 .foo 的。
当代码当中需要传递一个选项集的时候,那么就应该让该参数看起来像一个选项集,所以建议在写选项集参数时,即使只有一个值也加上[]。
Swift 关联对象
Swift 扩展只能添加计算属性。然而如果你愿意用 Objective-C 的关联对象,你可以在 Swift 中做一些同样有趣的事。
推荐一种写法如下:
import Foundation
func associatedObject<ValueType: AnyObject>(
base: AnyObject,
key: UnsafePointer<UInt8>,
initialiser: () -> ValueType)
-> ValueType {
if let associated = objc_getAssociatedObject(base, key)
as? ValueType { return associated }
let associated = initialiser()
objc_setAssociatedObject(base, key, associated,
.OBJC_ASSOCIATION_RETAIN)
return associated
}
func associateObject<ValueType: AnyObject>(
base: AnyObject,
key: UnsafePointer<UInt8>,
value: ValueType) {
objc_setAssociatedObject(base, key, value,
.OBJC_ASSOCIATION_RETAIN)
}
函数就那么长。你可以这样使用它们:
class Miller {} // 这是我们要扩展的类
class Cat { // 每个磨坊主都有一只猫
var name = “Puss”
}
private var catKey: UInt8 = 0 // 我们还是需要这样的模板
extension Miller {
var cat: Cat { // cat「实际上」是一个存储属性
get {
return associatedObject(self, key: &catKey)
{ return Cat() } // 设置变量的初始值
}
set { associateObject(self, key: &catKey, value: newValue) }
}
}
NSStringFromClass 替代方案
在自定义 TableViewCell 时,我们一般用类名作为 cell 的 identifier,在oc时我们用NSStringFromClass来获取类名字符串,但是swift没有相应的方法,但swift提供了更优雅的方式:
swift2 时:
// This now works!!!
String(MyTableViewCell)
swift3以后:
// This now works!!!
String(describing: MyTableViewCell.self)
使用闭包初始化
声明常量后,在一个紧接着的闭包中进行初始化,而不是之后在 viewDidLoad 或其他类似的方法中进行设置,这在 Swift 中是很常见的写法(也确实是一种不错的写法!)。
let purpleView: UIView = {
// 在此初始化 view
// 直接叫 "view" 真的好吗?
let view = UIView()
view.backgroundColor = .purpleColor()
return view
}()
但上面的代码并不好看,闭包中的view该如何命名?
下面推荐另一种写法
let yellowView: UIView = {
$0.backgroundColor = .yellowColor()
return $0
}(UIView()) // 确保这一行的括号内要传入 UIView()
这相当于一个方法传入一个 UIView() 参数,之后在对这个UIview对象进行设置,我们不需要显示地再命名一个变量
闭包捕获
先写个延迟方法
func delay(seconds: Int, closure: ()->()) {
let time = DispatchTime.now() + .seconds(seconds)
DispatchQueue.main.after(when: time) {
print("🕑")
closure()
}
}
写一个正常的闭包:
func demo4() {
var value = 42
print("before closure: \(value)")
delay(1) {
print("inside closure 1, before change: \(value)")
value = 1337
print("inside closure 1, after change: \(value)")
}
delay(2) {
print("inside closure 2: \(value)")
}
}
输出结果如下:
before closure: 42
🕑
inside closure 1, before change: 42
inside closure 1, after change: 1337
🕑
inside closure 2: 1337
上面的代码说明:
- 在 Swift 闭包中使用的所有外部变量,闭包会自动捕获这些变量的引用
- 在闭包执行时,会根据这些变量引用得到所对应的具体值
- 因为我们捕获的是变量的引用(而不是变量自身的值),所以你可以在闭包内部修改变量的值(当然变量要声明为 var,而不能是 let)
如果想要在闭包创建时捕获变量的值,而不是在闭包执行时才去获取变量的值,你可以使用 捕获列表
func demo5() {
var value = 42
print("before closure: \(value)")
delay(1) { [constValue = value] in
print("inside closure: \(constValue)")
}
value = 1337
print("after closure: \(value)")
}
输出结果:
before closure: 42
after closure: 1337
🕑
inside closure: 42
与上面的 demo4() 比较,这次闭包打印的是变量创建时的值,而不是后来赋的新值 1337,即使整个闭包的执行是在对变量重新赋值之后。
这就是 [constValue = value] 在闭包中所做的事情:在闭包创建时捕获变量 value 的内容 — 而不是变量的引用。
上面说明:
- 你可以在闭包创建时获取变量中的值,然后把它存储到本地常量中,而不是捕获变量的引用。我们可以使用带中括号的捕获列表来实现。
阅读swift源码
总结:根据官方readme从源码编译swift,当 build 构建完成后,你可以在 ./build/Xcode-ReleaseAssert/swift-macosx-x86_64/ 的子文件夹中找到结果。其中会有一个 Swift.xcodeproj Xcode 项目。
设置status bar style
在info.plist文件中添加一个属性,选中表空白地方,右键选中Add Row, 然后在列表中选中最后一个选项,也就是View controller-based status bar appearance,设置为No(不支持在文件中设置状态栏样式),然后在AppDelegate中写上[UIApplication sharedApplication].statusBarStyle = UIStatusBarStyleLightContent,这样的话,全局的状态栏样式就OK了。其他的界面如果想要改变样式的话,直接用 [UIApplication sharedApplication].statusBarStyle = UIStatusBarStyleDefault;修改就可以了。