肇鑫的技术博客

业精于勤,荒于嬉

App Architecture of SwiftUI

When developing with Cocoa/UIKit, the most common used architecture is MVC.

model_view_controller_2x

What is the architecture when using SwiftUI? Someone says it is MVVM. Someone says it is MV, MVC without C. I would like say it would be M-VC, Model with View and Controller.

M-VC

We want every element to be simple. For MV architecture, the View is too heavy for me. For M-VC, we want the View as simple as possible. The extra parts we think it to be Controller.

Split View into small Views

For a SwiftUI View, when one of the @State properties changed, all @State properties in the same view will be updated. If this is overhead, you can split those @State properties to another view, that will reduce CPU usage and speed up your app.

Get Macs, IP Addresses and Internet IP Addresses

We need to use C function to get those values. And then we check the values with command ifconfig and understand which to which.

Get Local MACs and IP Addresses

private func getLocalIPAndMACAdress() {
    var address : String?

    // Get list of all interfaces on the local machine:
    var ifaddr:UnsafeMutablePointer<ifaddrs>!
    if getifaddrs(&ifaddr) == 0 {
        // For each interface ...
        var ptr:UnsafeMutablePointer<ifaddrs>! = ifaddr
        var networkLinkDictionary = [String:[String]]()
        
        repeat {
            defer { ptr = ptr.pointee.ifa_next}
            let interface = ptr.pointee
            // Check interface name:
            let name = String(cString: interface.ifa_name)
            // Convert interface address to a human readable string:
            var hostname = [CChar](repeating: 0, count: Int(NI_MAXHOST))
            getnameinfo(interface.ifa_addr, socklen_t(interface.ifa_addr.pointee.sa_len),
                        &hostname, socklen_t(hostname.count),
                        nil, socklen_t(0), NI_NUMERICHOST)
            address = String(cString: hostname)
            var values = networkLinkDictionary[name] ?? []
            values.append(address!)
            networkLinkDictionary.updateValue(values, forKey: name)
        } while ptr != nil
   
        freeifaddrs(ifaddr)
        
        set(networkLinkDictionary: networkLinkDictionary)
        
        #if DEBUG
        networkLinkDictionary.keys.sorted().forEach { key in
            print(key, "\t", networkLinkDictionary[key]!)
        }
        #endif
    }
}

private func set(networkLinkDictionary:[String:[String]]) {
    networkLinkDictionary.keys.sorted().forEach {
        switch $0 {
            // for iMac 5K, en0 is ethernet; en1 is WiFi
        case "en0":
            enthernet = getNetworkLink(values: networkLinkDictionary["en0"] ?? [])
            #if DEBUG
            debugPrint(enthernet)
            #endif
        case "en1":
            wifi = getNetworkLink(values: networkLinkDictionary["en1"] ?? [])
            #if DEBUG
            debugPrint(wifi)
            #endif
        default:
            break
        }
    }
}

private func getNetworkLink(values:[String]) -> NetworkLink {
    let networkLink:NetworkLink
    
    switch values.count {
    case 1:
        networkLink = NetworkLink(MAC: values[0])
    case 2:
        networkLink = NetworkLink(MAC: values[0], ipv6: values[1])
    case 3:
        networkLink = NetworkLink(MAC: values[0], ipv6: values[1], ipv4: values[2])
    default:
        networkLink = NetworkLink(MAC: "")
    }
    
    return networkLink
}

public struct NetworkLink:Equatable {
    public init(MAC:String, ipv6:String? = nil, ipv4:String? = nil){
        self.MAC = MAC
        self.ipv6 = ipv6
        self.ipv4 = ipv4
    }
    
    public let MAC:String
    public var ipv6:String? = nil
    public var ipv4:String? = nil
}

Get Internet IP Addresses

We use ipify.org to get internet IP addresses.

Don't use URLSession.share

Create a new URLSession each time your own. If you use URLSession.share, you app won't detect internet IP changes, it will always show the IP when the app starts.

I had tried 3 different frameworks on github.com. They all has the above issue. So I write the code myself.

private func setInterIP(type:IPType) async {
    let url:URL
    
    if !reachable {
        switch type {
        case .ipv4:
            internetIPV4 = NSLocalizedString("Inactive", bundle: .module, comment: "")
        case .ipv6:
            internetIPV6 = NSLocalizedString("Inactive", bundle: .module, comment: "")
        }
        
        return
    }
    
    switch type {
    case .ipv4:
        url = URL(string: "https://api.ipify.org?format=json")!
        
        if internetIPV4.isEmpty {
            internetIPV4 = "..."
        }
    case .ipv6:
        url = URL(string: "https://api64.ipify.org?format=json")!
        
        if internetIPV6.isEmpty {
            internetIPV6 = "..."
        }
    }
    let urlSessionConfiguration = URLSessionConfiguration.default
    let urlSession = URLSession(configuration: urlSessionConfiguration)
    
    do {
        let (data, urlResponse) = try await urlSession.data(from: url)
        DispatchQueue.main.async { [self] in
            if let httpResponse = urlResponse as? HTTPURLResponse, httpResponse.statusCode == 200 {
                if let dic = try? JSONSerialization.jsonObject(with: data, options: []) as? [String:String] {
                    switch type {
                    case .ipv4:
                        internetIPV4 = dic["ip"]!
                        #if DEBUG
                        debugPrint(internetIPV4)
                        #endif
                    case .ipv6:
                        internetIPV6 = dic["ip"]!
                        #if DEBUG
                        debugPrint(internetIPV6)
                        #endif
                    }
                }
            } else {
                #if DEBUG
                debugPrint(urlResponse)
                #endif
            }
        }
    } catch {
        print(error)
    }
}

Fix of Xcode SPM Downloading Speed Issue

This command will fixed the issue that Xcode downloading too slow with SPM. Say you have a project name "hello", there is a Package.swift in it.

$ cd hello
$ xcodebuild -resolvePackageDependencies -scmProvider system
$ open Package.swift

hello

This issue was because when using git, Xcode ignore the proxy setting by default.

Pre-request

You should have your proxy set, for me, I have

$ cat .bashrc 
# proxy list
alias proxy='export all_proxy=socks5://127.0.0.1:7890'
alias unproxy='unset all_proxy'

# run
proxy

Xcode not follows system proxy settings to resolve SwiftPM dependencies

Xcode 13 ignores GIT proxy settings when resolving SwiftPM packages (and probably for other SCM operations as well).