| ページ一覧 | ブログ | twitter |  書式 | 書式(表) |

MyMemoWiki

「Swift Sample」の版間の差分

提供: MyMemoWiki
ナビゲーションに移動 検索に移動
(ページの作成:「| Swift | Xcode | Mac | ==Swift Sample== ===Network=== ---- ====データ取得==== <pre> let url = URL(string: "https://www.typea.info/blog/")!…」)
 
 
(同じ利用者による、間の24版が非表示)
1行目: 1行目:
| [[Swift]] | [[Xcode]] | [[Mac]] |
+
| [[Swift]] | [[SwiftUI]] |[[Xcode]] | [[Mac]] |
 
==[[Swift]] Sample==
 
==[[Swift]] Sample==
  
===Network===
+
==Network==
 +
===データ取得===
 
----
 
----
====データ取得====
 
 
<pre>
 
<pre>
 
         let url = URL(string: "https://www.typea.info/blog/")!
 
         let url = URL(string: "https://www.typea.info/blog/")!
24行目: 24行目:
 
         task.resume()
 
         task.resume()
 
</pre>
 
</pre>
=====[[MacOS]]でエラーの場合=====
+
====[[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/
*https://developer.apple.com/documentation/xcode/adding_capabilities_to_your_app
+
*https://developer.apple.com/documentation/xcode/adding_capabilities_to_your_app
 +
[[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>
 +
 
 +
===ディレクトリ判定===
 +
----
 +
<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 の派生クラス
 +
*contentフィールドに、@Published アノテーション
 +
*Viewで、@ObservedObjectを付与しインスタンスを生成
 +
*上記で、バックグラウンドから、observableobj.contentを操作すると、UIはメインスレッドから触るように怒られる。
 +
<blockquote>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.</blockquote>
 +
*DispatchQueue.main.syncで囲む
 +
<pre>
 +
DispatchQueue.main.sync {
 +
    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>

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

Swift macos net error.png

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)
        }
    }

Swift json encode.png

書式を指定
        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")
	}
}