2015年7月22日 星期三

[Swift] Use NSCoding to Persist a Bonjour Service

Regarding storing a bonjour service, Apple stated in the documents:

Persisting a Bonjour Service


If your app needs to persistently store a reference to a Bonjour service, such as in a printer chooser, store only the service name, type, and domain. By persisting only the domain, type, and name information, you ensure that your app can find the relevant service even if its IP addresses or port number has changed.
When you connect to the service later, initialize an NSNetService object with this information by calling its initWithDomain:type:name: method.
After you have initialized the service object, connect to the service by calling the getInputStream:outputStream: method or by passing the result of a hostName call to a connect-by-name function or method, as described earlier in this chapter. When you do this, Bonjour automatically resolves the hostname or domain, type, and name values into the current IP addresses and port number for the service.

Since the method of storing is not specific (and I assume its not stated in the documents for added flexibility to the developer), I will attempt to use NSCoding to store a Bonjour service.



First I create a new class to store a device's service name, type and domain. I call this class DeviceInfoPersistence:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
class DeviceInfoPersistence: NSObject, NSCoding {
    
    var serviceName: String
    var serviceType: String
    var serviceDomain: String
    
    static let DocumentsDirectory: AnyObject = NSFileManager().URLsForDirectory(.DocumentDirectory, inDomains: .UserDomainMask).first!
    static let ArchiveURL = DocumentsDirectory.URLByAppendingPathComponent("deviceInfoPersistence")
    
    struct PropertyKeys {
        static let serviceNameKey = "serviceName"
        static let serviceTypeKey = "serviceType"
        static let serviceDomainKey = "serviceDomainKey"
    }
    
    init(serviceName:String, serviceType: String, serviceDomain:String){
        self.serviceName = serviceName
        self.serviceType = serviceType
        self.serviceDomain = serviceDomain
        
        super.init()
    }
    
    func encodeWithCoder(aCoder: NSCoder) {
        aCoder.encodeObject(serviceName, forKey: PropertyKeys.serviceNameKey)
        aCoder.encodeObject(serviceType, forKey: PropertyKeys.serviceTypeKey)
        aCoder.encodeObject(serviceDomain, forKey: PropertyKeys.serviceDomainKey)
        
    }
    
    required convenience init(coder aDecoder: NSCoder) {
        let serviceName = aDecoder.decodeObjectForKey(PropertyKeys.serviceNameKey) as! String
        let serviceType = aDecoder.decodeObjectForKey(PropertyKeys.serviceTypeKey) as! String
        let serviceDomain = aDecoder.decodeObjectForKey(PropertyKeys.serviceDomainKey) as! String
        
        self.init(serviceName:serviceName, serviceType:serviceType, serviceDomain:serviceDomain)
    }
}

A couple of notes:
- When extending NSCoding to the class, we must implement both init(coder aDecoder: NSCoder) and encodeWithCoder(aCoder: NSCoder). 
- Outside of this class, we can access the data file path by using DeviceInfoPersistence.ArchiveURL

After we create the data class, we will access it in the ViewController. 
Before using the data class, we need to first initialize it: var deviceInfoPersistence = [DeviceInfoPersistence]()

We will create a save function to save data into the deviceInfoPersistence array:

1
2
3
4
5
6
7
8
    func saveDeviceInfoPersistence(){
        println("save device info \(deviceInfoPersistence.count)")
        let isSuccessfulSave = NSKeyedArchiver.archiveRootObject(deviceInfoPersistence, toFile: DeviceInfoPersistence.ArchiveURL.path!)
        
        if !isSuccessfulSave {
            print("Failed to save Device Info...")
        }
    }

Then we create a load function to load the saved devices:

1
2
3
    func loadDeviceInfoPersistence() -> [DeviceInfoPersistence]? {
        return NSKeyedUnarchiver.unarchiveObjectWithFile(DeviceInfoPersistence.ArchiveURL.path!) as? [DeviceInfoPersistence]
    }

Basically at this moment we have all the necessary mechanism we need for persisting a bonjour service. Now we will need to save/load the data at the right place at the right time. Below I demo a simple save and load when loading my list view:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
func populateDevicesList()
    {
////Omitted
        for (key,value) in devices {
            var deviceKey: AnyObject? = devices.objectForKey(key)
            var deviceInfo = DeviceInfoPersistence(serviceName: deviceKey?.objectForKey("name") as! String,
                serviceType: deviceKey?.objectForKey("type") as! String,
                serviceDomain: deviceKey?.objectForKey("domain") as! String)
            deviceInfoPersistence.append(deviceInfo)
            saveDeviceInfoPersistence()
        }    

        let deviceList = NSKeyedUnarchiver.unarchiveObjectWithFile(DeviceInfoPersistence.ArchiveURL.path!) as! [DeviceInfoPersistence]
        
        for theDevice in deviceList {
            println("Name: \(theDevice.serviceName), Domain: \(theDevice.serviceDomain), Type: \(theDevice.serviceType)")
        }
////Omitted
}

The output of this will be:
Name: Device1, Domain: local., Type: _http._tcp.
Name: Device2, Domain: local., Type: _http._tcp.

沒有留言:

張貼留言