You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何在Swift中使用Sequence和IteratorProtocol实现类属性可迭代并完成实例属性匹配对比

Hey there! Let's work through your problem step by step. You want a Contact class that's iterable (no reflection/mirroring allowed), plus a method to compare two instances and spot matching properties. Let's fix your code, address your questions, and find a clean solution.

First, Fixing Your Iterable Contact Class

Your original code has a key issue: the private var properties = [givenName, familyName, middleName] line stores copies of the initial empty strings, not references to the instance's actual properties. When you update givenName later, the array doesn't reflect those changes. Also, it only holds values, not property names—so you can't tell which property matched during comparison.

Let's build a proper implementation using Sequence and IteratorProtocol, with a helper type to track both property names and values:

// Helper type to hold a property's name and its current value
struct ContactProperty {
    let name: String
    let value: String
}

class Contact: Sequence {
    // Define our iterator and element types for the Sequence protocol
    typealias Iterator = ContactIterator
    typealias Element = ContactProperty
    
    // Our contact properties
    var givenName: String = ""
    var familyName: String = ""
    var middleName: String = ""
    
    // Required method for Sequence: create our custom iterator
    func makeIterator() -> ContactIterator {
        return ContactIterator(contact: self)
    }
    
    // Class method to find matching properties between two contacts
    static func findMatchingProperties(between left: Contact, and right: Contact) -> [String] {
        var matchingProperties = [String]()
        
        // Iterate through left's properties and compare to right's
        for property in left {
            // Get the corresponding value from right based on property name
            let rightValue: String?
            switch property.name {
            case "givenName": rightValue = right.givenName
            case "familyName": rightValue = right.familyName
            case "middleName": rightValue = right.middleName
            default: rightValue = nil
            }
            
            // If values match, add the property name to our results
            if let rightVal = rightValue, rightVal == property.value {
                matchingProperties.append(property.name)
            }
        }
        
        return matchingProperties
    }
}

// Custom iterator to loop through Contact's properties in order
struct ContactIterator: IteratorProtocol {
    typealias Element = ContactProperty
    
    private let contact: Contact
    private var currentIndex = 0
    
    init(contact: Contact) {
        self.contact = contact
    }
    
    mutating func next() -> ContactProperty? {
        defer { currentIndex += 1 }
        
        // Return the next property in sequence, or nil when done
        switch currentIndex {
        case 0:
            return ContactProperty(name: "givenName", value: contact.givenName)
        case 1:
            return ContactProperty(name: "familyName", value: contact.familyName)
        case 2:
            return ContactProperty(name: "middleName", value: contact.middleName)
        default:
            return nil
        }
    }
}

Test this implementation with sample data:

let leftContact = Contact()
leftContact.givenName = "John"
leftContact.familyName = "Doe"

let rightContact = Contact()
rightContact.givenName = "John"
rightContact.middleName = "Michael"

let matches = Contact.findMatchingProperties(between: leftContact, and: rightContact)
print("Matching properties: \(matches)") // Output: Matching properties: ["givenName"]

Now you can iterate over a Contact instance directly too:

for prop in leftContact {
    print("\(prop.name): \(prop.value)")
}
// Output:
// givenName: John
// familyName: Doe
// middleName:

Answering Your Questions

1. Can we implement this with an enum?

Absolutely! Using an enum with CaseIterable makes the code even cleaner and more type-safe. It eliminates manual index tracking and centralizes our property definitions:

// Enum listing all contact fields, conforming to CaseIterable for easy iteration
enum ContactField: CaseIterable {
    case givenName, familyName, middleName
    
    // Human-readable property name
    var name: String {
        switch self {
        case .givenName: return "givenName"
        case .familyName: return "familyName"
        case .middleName: return "middleName"
        }
    }
    
    // Fetch the corresponding value from a Contact instance
    func value(from contact: Contact) -> String {
        switch self {
        case .givenName: return contact.givenName
        case .familyName: return contact.familyName
        case .middleName: return contact.middleName
        }
    }
}

class Contact: Sequence {
    // Reuse the enum's iterator and element type
    typealias Iterator = ContactField.Iterator
    typealias Element = ContactField
    
    var givenName: String = ""
    var familyName: String = ""
    var middleName: String = ""
    
    func makeIterator() -> Iterator {
        // Let the enum handle iteration logic
        return ContactField.allCases.makeIterator()
    }
    
    static func findMatchingProperties(between left: Contact, and right: Contact) -> [String] {
        // Filter matching fields and map to their names
        return ContactField.allCases.filter { field in
            field.value(from: left) == field.value(from: right)
        }.map { $0.name }
    }
}

This approach is more maintainable—if you add a new property later, you just add a new case to the enum, and everything else updates automatically.

2. Is there a protocol that lets us iterate properties via hashes/identifiers?

Swift's standard library doesn't have a built-in protocol for this, since property order is a class-specific detail. However, the CaseIterable enum approach we just used is a great alternative: it lets you define the exact order of properties you want to iterate, and uses the enum's cases as type-safe "identifiers" instead of raw hashes or strings.

If you don't want to use an enum, you could manually maintain an array of property names (e.g., ["givenName", "familyName", "middleName"]) and use that to drive iteration—but this is error-prone (typos in property names will break things) and less type-safe than the enum method.

Why Your Original Code Failed

  • The properties array stored copies of initial empty strings, not references to the instance's live properties. Updates to givenName didn't affect the array.
  • Your Name and NameIterator structs weren't connected to the Contact class, so they didn't enable iteration of Contact properties.
  • There was no way to link property values back to their names, making meaningful comparison impossible.

内容的提问来源于stack exchange,提问作者daniel

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.04.27 19:57:33