肇鑫的技术博客

业精于勤,荒于嬉

iOS开发存储策略

程序沙盒

  • 需要相同代码签名和包名
  • Home目录:NSHomeDirectory()
  • 临时文件目录:NSTemporaryDirectory(),在home目录外,但在沙盒里
  • iOS的沙盒内,包含程序自身

程序组容器目录

  • 需要相同开发团队
  • 位置:~/Library/Group Containers/<application-group-id>
  • 调用:FileManagercontainerURL(forSecurityApplicationGroupIdentifier:)方法

使用策略

  • 需要长期保存的数据存在Home目录
  • 需要共享的数据存在程序组容器目录

思考

单独将数据保存在程序组容器目录是否安全?
**安全。**因为只有相同开发团队的人才能访问。而且可以简化开发。
**不安全。**因为一个app的数据有被其它app删除的风险。另外,是否应该限制这部分数据,使只有需要分享的数据才方到这里。
**结论。**我个人更倾向于安全。因为团队内部的人应该被认为是可信的。但是这么做的确会存在过多分享的问题。因此,我认为还是不要把主数据库放在这边,而是将其作为辅助数据库更好。至于说需要额外处理的代码问题。我相信,从长期看,这部分代码必然是必要的。

WKAudioFilePlayer的播放问题

假设手表扩展中存在xishuai.mp3文件。

let url = Bundle.main.url(forResource: "xishuai", withExtension: "mp3")!
let playAsset = WKAudioFileAsset(url: url)
let playItem = WKAudioFilePlayerItem(asset: playAsset)
player = WKAudioFilePlayer(playerItem: playItem)

WKAudioFilePlayer目前在真机下,如果真机没有连接蓝牙耳机,则播放无声音。因此,不能保证作为通知声音的替代。

`==`与`hashValue`

Hashable是最常见的协议之一。也许是由于它太常见了。以至于好多人都没法正确的实现它。下面总结一下实现它需要的原则。

基本原则

基本原则只有两条:

  1. Hashable继承了Equatable。因此,要实现Hashable必须同时实现Equatable
  2. 当相同类型的ab相等时,它们的hashValue相等。反之不成立,即hashValue相等的两个相同类型的ab,不一定相等。
class Fruit:Hashable {
    let name:String
    
    var hashValue: Int {
        return name.hashValue
    }
    
    init(name:String) {
        self.name = name
    }
    
    static func ==(lhs: Fruit, rhs: Fruit) -> Bool {
        return lhs.name == rhs.name
    }
}

注意

var hashValue: Int只要求返回值,因此如何实现都可以,设置只返回固定的值,比如var hashValue: Int { return 10 }也是可以的。
不能使用hashValue来判断是否相等。

不同的解读

上面的代码在涉及到子类时,不同的人会有不同的理解。比如:

class Apple:Fruit {
    override var hashValue: Int {
        return super.hashValue - "apple".characters.count
    }
}

class Banana:Fruit {
    override var hashValue: Int {
        return super.hashValue - "banana".characters.count
    }
}

let a = Apple(name: "")
let b = Banana(name: "")
print(a == b) // true
print(a.hashValue) // prints 4799450059485596700
print(b.hashValue) // prints 4799450059485596699

上面的代码,print(a == b) // true,但是hashValue却不相等。这违背了上面提到的基本原则2。这段代码需要修改。但是如何修改,不同的人有不同的看法。

判断类型

我认为最好的办法就是优先判断类型。因为,类型不同的类,就不应该相等。比如一个苹果就不应该和一个香蕉相等。

class Fruit:Hashable {
    let name:String
    
    var hashValue: Int {
        return name.hashValue
    }
    
    init(name:String) {
        self.name = name
    }
    
    static func ==(lhs: Fruit, rhs: Fruit) -> Bool {
        return type(of:lhs) == type(of:rhs) && lhs.name == rhs.name
    }
}

class Apple:Fruit {
    override var hashValue: Int {
        return super.hashValue - "apple".characters.count
    }
}

class Banana:Fruit {
    override var hashValue: Int {
        return super.hashValue - "banana".characters.count
    }
}

let a = Apple(name: "")
let b = Banana(name: "")
print(a == b) // false
print(a.hashValue) // prints 4799450059485596700
print(b.hashValue) // prints 4799450059485596699

即在非final的类的==里,总是先比较类型,类型相同的,再毕竟其它。这么做之后,就永远不会发生一个苹果等于一个香蕉的笑话了。但是这里还有一个问题,我们没有编写子类的==。这里暂时没有问题,但是如果遇到有其它属性的子类,就可能出现问题。

class Orange:Fruit {
    let weight:Double
    
    override var hashValue: Int {
        return super.hashValue - "orange".characters.count + Int(weight)
    }
    
    init(name: String, weight: Double) {
        self.weight = weight
        super.init(name: name)
    }
}

let o1 = Orange(name: "", weight: 0.4)
let o2 = Orange(name: "", weight: 1.2)
print(o1 == o2) // true
print(o1.hashValue) // prints 4799450059485596699
print(o2.hashValue) // prints 4799450059485596700

由于Orange没有重新定义==,比较时,直接调用了Fruit==,导致了不符合基本原则2。补救办法就是在Orange重新定义==

class Orange:Fruit {
    let weight:Double
    
    override var hashValue: Int {
        return super.hashValue - "orange".characters.count + Int(weight)
    }
    
    static func ==(lhs: Orange, rhs: Orange) -> Bool {
        return type(of:lhs) == type(of:rhs) && lhs.name == rhs.name && lhs.weight == rhs.weight
    }
    
    init(name: String, weight: Double) {
        self.weight = weight
        super.init(name: name)
    }
}

let o1 = Orange(name: "", weight: 0.4)
let o2 = Orange(name: "", weight: 1.2)
print(o1 == o2) // false
print(o1.hashValue) // prints 4799450059485596699
print(o2.hashValue) // prints 4799450059485596700

不重写hashValue,仅重新定义==

一些特殊的情况下,也许你可以不重写hashValue,而仅重新定义==

import Foundation
import Cocoa

class Fruit:Hashable {
    let name:String
    
    var hashValue: Int {
        return name.hashValue
    }
    
    init(name:String) {
        self.name = name
    }
    
    static func ==(lhs: Fruit, rhs: Fruit) -> Bool {
        return lhs.name == rhs.name
    }
}

class Apple:Fruit {

}

class ColoredApple:Apple {
    let color:NSColor
    
    init(name:String, color:NSColor) {
        self.color = color
        super.init(name: name)
    }
    
    static func ==(lhs: ColoredApple, rhs: ColoredApple) -> Bool {
        return lhs.name == rhs.name && lhs.color == rhs.color
    }
}

let greenApple = ColoredApple(name: "green apple", color: .green)
let apple = Apple(name: "green apple")
print(greenApple == apple) // true
print(greenApple.hashValue) // -2580839601755588497
print(apple.hashValue) //-2580839601755588497

var set:Set<Apple> = [greenApple, apple]
print(set.count) // 1

var d:Dictionary<Apple, Int> = [greenApple:10]
// eat one
d.updateValue(d[greenApple]! - 1, forKey: apple)
print(d[greenApple]!) // 9

这种做法不常见,一旦选择了不重写hashValue,就必须在该基类的所有子类都保持一致。

注意

由于Set和Dictionary使用hashValue进行判断,放入其中的元素的hashValue在没有从其中移出时,不能改变,否则会出现问题。
可以看到applegreenApple因为hashValue相同,它们在Set和Dictionary里被当做是同一元素/键值。