「Swift Sample」の版間の差分
ナビゲーションに移動
検索に移動
(→UI) |
|||
| (同じ利用者による、間の17版が非表示) | |||
| 2行目: | 2行目: | ||
==[[Swift]] Sample== | ==[[Swift]] Sample== | ||
| − | + | ==Network== | |
| − | + | ===データ取得=== | |
| − | |||
---- | ---- | ||
<pre> | <pre> | ||
| 25行目: | 24行目: | ||
task.resume() | task.resume() | ||
</pre> | </pre> | ||
| − | + | ====[[MacOS]]でエラーの場合==== | |
<code>Error Domain=NSURLErrorDomain Code=-1003</code> | <code>Error Domain=NSURLErrorDomain Code=-1003</code> | ||
*https://www.poly-rhythm.com/hostname-could-not-be-found/ | *https://www.poly-rhythm.com/hostname-could-not-be-found/ | ||
| 31行目: | 30行目: | ||
[[File:swift_macos_net_error.png|600px]] | [[File:swift_macos_net_error.png|600px]] | ||
| − | === | + | ==File== |
| + | ===JSONにシリアライズしてドキュメントディレクトリへ書き出し=== | ||
| + | *データ(Codableを適用する) | ||
| + | <pre> | ||
| + | struct Host : Codable { | ||
| + | var host: String = "" | ||
| + | var ip: String = "" | ||
| + | var macaddr: String = "" | ||
| + | } | ||
| + | </pre> | ||
| + | *読み込み、コレクションに追加、書き出し処理 | ||
| + | <pre> | ||
| + | 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) | ||
| + | } | ||
| + | } | ||
| + | </pre> | ||
| + | [[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> | ||
| + | |||
| + | ===ディレクトリ判定=== | ||
---- | ---- | ||
| − | ====バックグラウンドからUIを操作する | + | <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を操作する=== | ||
---- | ---- | ||
*observableobj が、ObservableObject の派生クラス | *observableobj が、ObservableObject の派生クラス | ||
| 44行目: | 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> | ||
| + | |||
| + | ==処理== | ||
| + | ===サブプロセスを起動=== | ||
| + | ---- | ||
| + | *プロセスを起動し、arp -a を呼び出し、出力 | ||
| + | <pre> | ||
| + | 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!) | ||
| + | } | ||
| + | </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アドレスを抜きだす=== | ||
| + | ---- | ||
| + | <pre> | ||
| + | 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") | ||
| + | } | ||
| + | </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> | ||
| + | |||
| + | ===名前を付与してキャプチャ=== | ||
| + | ---- | ||
| + | <pre> | ||
| + | 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") | ||
| + | } | ||
} | } | ||
</pre> | </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 矢木浩人