「Swift Sample」の版間の差分
ナビゲーションに移動
検索に移動
(→UI) |
|||
(同じ利用者による、間の10版が非表示) | |||
40行目: | 40行目: | ||
} | } | ||
</pre> | </pre> | ||
− | * | + | *読み込み、コレクションに追加、書き出し処理 |
<pre> | <pre> | ||
+ | func addHostListDocument(host: WoL.Host) { | ||
let docPath = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask)[0] | let docPath = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask)[0] | ||
let filePath = URL(fileURLWithPath: "hosts.json", relativeTo:docPath) | let filePath = URL(fileURLWithPath: "hosts.json", relativeTo:docPath) | ||
+ | |||
+ | var hostList: [WoL.Host] = [] | ||
+ | |||
+ | let decoder = JSONDecoder() | ||
+ | do { | ||
+ | let rawData = try Data(contentsOf: filePath) | ||
+ | print(rawData) | ||
+ | let tmpHostList = try decoder.decode([WoL.Host].self, from: rawData) | ||
+ | for tmpHost in tmpHostList { | ||
+ | hostList.append(tmpHost) | ||
+ | } | ||
+ | } catch { | ||
+ | print(error) | ||
+ | } | ||
+ | |||
+ | hostList.append(host) | ||
let encoder = JSONEncoder() | let encoder = JSONEncoder() | ||
do { | do { | ||
− | let line = try encoder.encode( | + | let line = try encoder.encode(hostList) |
try line.write(to: filePath) | try line.write(to: filePath) | ||
} catch { | } catch { | ||
print(error) | print(error) | ||
} | } | ||
+ | } | ||
</pre> | </pre> | ||
[[File:swift_json_encode.png|300px]] | [[File:swift_json_encode.png|300px]] | ||
+ | =====書式を指定===== | ||
+ | <pre> | ||
+ | let encoder = JSONEncoder() | ||
+ | encoder.outputFormatting = [.prettyPrinted, .sortedKeys] | ||
+ | </pre> | ||
+ | =====Stringに変換===== | ||
+ | <pre> | ||
+ | let json = try encoder.encode(hosts.hosts) | ||
+ | let jsonString = String(data: json, encoding: .utf8)! | ||
+ | print(jsonString) | ||
+ | </pre> | ||
+ | =====画面とオブジェクトを共有する===== | ||
+ | *struct -> class | ||
+ | *ObservableObject を適用 | ||
+ | *@Published | ||
+ | *上記を適用すると、Codable に適合させるには、明示的に処理を記述する必要がある | ||
+ | <pre> | ||
+ | class Host : Codable, ObservableObject { | ||
+ | @Published var host: String = "" | ||
+ | @Published var ip: String = "" | ||
+ | @Published var macaddr: String = "" | ||
+ | @Published var comment: String = "" | ||
+ | |||
+ | init(host: String, ip: String, macaddr: String, comment: String) { | ||
+ | self.host = host | ||
+ | self.ip = ip | ||
+ | self.macaddr = macaddr | ||
+ | self.comment = comment | ||
+ | } | ||
+ | init() {} | ||
+ | |||
+ | /// 変換対象プロパティ | ||
+ | enum CodingKeys: CodingKey { | ||
+ | case host | ||
+ | case ip | ||
+ | case macaddr | ||
+ | case comment | ||
+ | } | ||
+ | |||
+ | required init(from decoder: Decoder) throws { | ||
+ | let container = try decoder.container(keyedBy: CodingKeys.self) | ||
+ | host = try container.decode(String.self, forKey: .host) | ||
+ | ip = try container.decode(String.self, forKey: .ip) | ||
+ | macaddr = try container.decode(String.self, forKey: .macaddr) | ||
+ | comment = try container.decode(String.self, forKey: .comment) | ||
+ | } | ||
+ | |||
+ | func encode(to encoder: Encoder) throws { | ||
+ | var container = encoder.container(keyedBy: CodingKeys.self) | ||
+ | try container.encode(host, forKey: .host) | ||
+ | try container.encode(ip, forKey: .ip) | ||
+ | try container.encode(macaddr, forKey: .macaddr) | ||
+ | try container.encode(comment, forKey: .comment) | ||
+ | } | ||
+ | </pre> | ||
+ | |||
+ | ===ディレクトリ判定=== | ||
+ | ---- | ||
+ | <pre> | ||
+ | private static let fm = FileManager.default | ||
+ | static func isDirectory(path: String) -> Bool { | ||
+ | var isDir = ObjCBool(false) | ||
+ | fm.fileExists(atPath: path, isDirectory: &isDir) | ||
+ | return isDir.boolValue | ||
+ | } | ||
+ | </pre> | ||
==UI== | ==UI== | ||
66行目: | 150行目: | ||
DispatchQueue.main.sync { | DispatchQueue.main.sync { | ||
observableobj.content = text | observableobj.content = text | ||
+ | } | ||
+ | </pre> | ||
+ | ===ディレクトリを選択=== | ||
+ | ---- | ||
+ | <pre> | ||
+ | static func chooseDir() -> String { | ||
+ | |||
+ | let dialog = NSOpenPanel(); | ||
+ | |||
+ | dialog.title = "Choose a root directory" | ||
+ | dialog.showsResizeIndicator = true | ||
+ | dialog.showsHiddenFiles = false | ||
+ | dialog.allowsMultipleSelection = false | ||
+ | dialog.canChooseDirectories = true | ||
+ | dialog.canChooseFiles = false | ||
+ | |||
+ | if (dialog.runModal() == NSApplication.ModalResponse.OK) { | ||
+ | let result = dialog.url | ||
+ | |||
+ | if (result != nil) { | ||
+ | let path: String = result!.path | ||
+ | return path | ||
+ | } | ||
+ | |||
+ | } | ||
+ | return ""; | ||
} | } | ||
</pre> | </pre> | ||
71行目: | 181行目: | ||
==処理== | ==処理== | ||
===サブプロセスを起動=== | ===サブプロセスを起動=== | ||
+ | ---- | ||
*プロセスを起動し、arp -a を呼び出し、出力 | *プロセスを起動し、arp -a を呼び出し、出力 | ||
<pre> | <pre> | ||
95行目: | 206行目: | ||
} | } | ||
</pre> | </pre> | ||
+ | |||
+ | ===非同期処理=== | ||
+ | ---- | ||
+ | <pre> | ||
+ | let c = { | ||
+ | (ip:String) -> String in | ||
+ | let hostName = getHostName(ip: ip) // 時間がかかる処理 | ||
+ | return hostName | ||
+ | } | ||
+ | |||
+ | let que = DispatchQueue.global(qos: .default) | ||
+ | for host in hosts.hosts { | ||
+ | print("CALL-\(host.ip)") | ||
+ | que.async { // 非同期処理 | ||
+ | let hostname = c(host.ip) // 時間がかかる処理 | ||
+ | DispatchQueue.main.async { // 画面に反映 | ||
+ | host.host = hostname | ||
+ | } | ||
+ | } | ||
+ | } | ||
+ | </pre> | ||
+ | |||
==[[正規表現]]== | ==[[正規表現]]== | ||
===arp -a の結果からIPアドレスを抜きだす=== | ===arp -a の結果からIPアドレスを抜きだす=== | ||
127行目: | 260行目: | ||
} | } | ||
</pre> | </pre> | ||
+ | ===digを経由してBonjourでIPアドレスをホスト名に解決=== | ||
+ | *コマンドをswiftで呼び出し dig +short -x 192.168.0.45 @224.0.0.251 -p 5353 | ||
+ | <pre> | ||
+ | let outputPipe = Pipe() | ||
+ | let _ = shell(outputPipe, | ||
+ | path:"/usr/bin/dig", | ||
+ | args: "+short", | ||
+ | "-x", ip, | ||
+ | "@224.0.0.251", "-p","5353") // Bonjour IPアドレス | ||
+ | let theTaskData = outputPipe.fileHandleForReading.readDataToEndOfFile() | ||
+ | let stringResult = String(data: theTaskData, encoding: .utf8) | ||
+ | </pre> | ||
+ | |||
===名前を付与してキャプチャ=== | ===名前を付与してキャプチャ=== | ||
---- | ---- |
2022年3月19日 (土) 01:48時点における最新版
| Swift | SwiftUI |Xcode | Mac |
Swift Sample
Network
データ取得
- let url = URL(string: "https://www.typea.info/blog/")!
- let task = URLSession.shared.dataTask(with: url) { data, response, error in
- if let error = error {
- print("\(error)\n")
- return
- }
- guard let data = data, let response = response as? HTTPURLResponse else {
- print("no data or no response.")
- return
- }
- if response.statusCode == 200 {
- if let text = String(bytes: data, encoding: .utf8) {
- print(text)
- }
- }
- }
- task.resume()
MacOSでエラーの場合
Error Domain=NSURLErrorDomain Code=-1003
- https://www.poly-rhythm.com/hostname-could-not-be-found/
- https://developer.apple.com/documentation/xcode/adding_capabilities_to_your_app
File
JSONにシリアライズしてドキュメントディレクトリへ書き出し
- データ(Codableを適用する)
- struct Host : Codable {
- var host: String = ""
- var ip: String = ""
- var macaddr: String = ""
- }
- 読み込み、コレクションに追加、書き出し処理
- func addHostListDocument(host: WoL.Host) {
- let docPath = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask)[0]
- let filePath = URL(fileURLWithPath: "hosts.json", relativeTo:docPath)
- var hostList: [WoL.Host] = []
- let decoder = JSONDecoder()
- do {
- let rawData = try Data(contentsOf: filePath)
- print(rawData)
- let tmpHostList = try decoder.decode([WoL.Host].self, from: rawData)
- for tmpHost in tmpHostList {
- hostList.append(tmpHost)
- }
- } catch {
- print(error)
- }
- hostList.append(host)
- let encoder = JSONEncoder()
- do {
- let line = try encoder.encode(hostList)
- try line.write(to: filePath)
- } catch {
- print(error)
- }
- }
書式を指定
- let encoder = JSONEncoder()
- encoder.outputFormatting = [.prettyPrinted, .sortedKeys]
Stringに変換
- let json = try encoder.encode(hosts.hosts)
- let jsonString = String(data: json, encoding: .utf8)!
- print(jsonString)
画面とオブジェクトを共有する
- struct -> class
- ObservableObject を適用
- @Published
- 上記を適用すると、Codable に適合させるには、明示的に処理を記述する必要がある
- class Host : Codable, ObservableObject {
- @Published var host: String = ""
- @Published var ip: String = ""
- @Published var macaddr: String = ""
- @Published var comment: String = ""
- init(host: String, ip: String, macaddr: String, comment: String) {
- self.host = host
- self.ip = ip
- self.macaddr = macaddr
- self.comment = comment
- }
- init() {}
- /// 変換対象プロパティ
- enum CodingKeys: CodingKey {
- case host
- case ip
- case macaddr
- case comment
- }
- required init(from decoder: Decoder) throws {
- let container = try decoder.container(keyedBy: CodingKeys.self)
- host = try container.decode(String.self, forKey: .host)
- ip = try container.decode(String.self, forKey: .ip)
- macaddr = try container.decode(String.self, forKey: .macaddr)
- comment = try container.decode(String.self, forKey: .comment)
- }
- func encode(to encoder: Encoder) throws {
- var container = encoder.container(keyedBy: CodingKeys.self)
- try container.encode(host, forKey: .host)
- try container.encode(ip, forKey: .ip)
- try container.encode(macaddr, forKey: .macaddr)
- try container.encode(comment, forKey: .comment)
- }
ディレクトリ判定
- private static let fm = FileManager.default
- static func isDirectory(path: String) -> Bool {
- var isDir = ObjCBool(false)
- fm.fileExists(atPath: path, isDirectory: &isDir)
- return isDir.boolValue
- }
UI
バックグラウンドからUIを操作する
- observableobj が、ObservableObject の派生クラス
- contentフィールドに、@Published アノテーション
- Viewで、@ObservedObjectを付与しインスタンスを生成
- 上記で、バックグラウンドから、observableobj.contentを操作すると、UIはメインスレッドから触るように怒られる。
Publishing changes from background threads is not allowed; make sure to publish values from the main thread (via operators like receive(on:)) on model updates.
- DispatchQueue.main.syncで囲む
- DispatchQueue.main.sync {
- observableobj.content = text
- }
ディレクトリを選択
- static func chooseDir() -> String {
- let dialog = NSOpenPanel();
- dialog.title = "Choose a root directory"
- dialog.showsResizeIndicator = true
- dialog.showsHiddenFiles = false
- dialog.allowsMultipleSelection = false
- dialog.canChooseDirectories = true
- dialog.canChooseFiles = false
- if (dialog.runModal() == NSApplication.ModalResponse.OK) {
- let result = dialog.url
- if (result != nil) {
- let path: String = result!.path
- return path
- }
- }
- return "";
- }
処理
サブプロセスを起動
- プロセスを起動し、arp -a を呼び出し、出力
- func arp() {
- let outputPipe = Pipe()
- func shell(path:String ,args: String...) -> Int32 {
- let task = Process()
- task.launchPath = path
- task.arguments = args
- task.standardOutput = outputPipe
- task.standardError = outputPipe
- task.launch()
- task.waitUntilExit()
- return task.terminationStatus
- }
- let _ = shell(path:"/usr/sbin/arp",args: "-a")
- let theTaskData = outputPipe.fileHandleForReading.readDataToEndOfFile()
- let stringResult = String(data: theTaskData, encoding: .utf8)
- print(stringResult!)
- }
非同期処理
- let c = {
- (ip:String) -> String in
- let hostName = getHostName(ip: ip) // 時間がかかる処理
- return hostName
- }
- let que = DispatchQueue.global(qos: .default)
- for host in hosts.hosts {
- print("CALL-\(host.ip)")
- que.async { // 非同期処理
- let hostname = c(host.ip) // 時間がかかる処理
- DispatchQueue.main.async { // 画面に反映
- host.host = hostname
- }
- }
- }
正規表現
arp -a の結果からIPアドレスを抜きだす
- func parseArp(arpResult: String?) {
- if let input = arpResult {
- do {
- let pattern = #"[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}"#
- let regex = try NSRegularExpression(pattern: pattern, options:[])
- for subinput in input.split(separator: "\r\n") {
- let line = String(subinput)
- let maches = regex.matches(in: line, options: [], range: _NSRange(0..<line.count))
- print(line)
- for mach in maches {
- for i in 0 ..< mach.numberOfRanges {
- let start = line.index(line.startIndex, offsetBy: mach.range(at: i).location)
- let end = line.index(start, offsetBy: mach.range(at: i).length)
- let text = String(line[start..<end])
- print(text)
- }
- }
- }
- } catch {
- print("RegEx fail.")
- }
- } else {
- print("arp -a result : error")
- }
digを経由してBonjourでIPアドレスをホスト名に解決
- コマンドをswiftで呼び出し dig +short -x 192.168.0.45 @224.0.0.251 -p 5353
- let outputPipe = Pipe()
- let _ = shell(outputPipe,
- path:"/usr/bin/dig",
- args: "+short",
- "-x", ip,
- "@224.0.0.251", "-p","5353") // Bonjour IPアドレス
- let theTaskData = outputPipe.fileHandleForReading.readDataToEndOfFile()
- let stringResult = String(data: theTaskData, encoding: .utf8)
名前を付与してキャプチャ
- func parseArp(arpResult: String?) {
- if let input = arpResult {
- do {
- let pattern = #".*?(?<ip>[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}).*(?<mac>[0-9a-z]{1,2}:[0-9a-z]{1,2}:[0-9a-z]{1,2}:[0-9a-z]{1,2}:[0-9a-z]{1,2}:[0-9a-z]{1,2})"#
- let regex = try NSRegularExpression(pattern: pattern, options:[])
- for subinput in input.split(separator: "\r\n") {
- let line = String(subinput)
- let matches = regex.matches(in: line, options: [], range: _NSRange(0..<line.count))
- print(line)
- for match in matches {
- for name in ["ip", "mac"] {
- let matchRange = match.range(withName: name)
- if let substrRanget = Range(matchRange, in:line) {
- let ip = String(line[substrRanget])
- print("\(name):\(ip)")
- }
- }
- }
- }
- } catch {
- print("RegEx fail.")
- }
- } else {
- print("arp -a result : error")
- }
- }
© 2006 矢木浩人