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

如何在Firebase Swift中实现用户间好友关系?附DataService.swift文件

Implementing Friend Relationships in Firebase (Swift)

Hey there! Let's walk through how to build a solid friend relationship system in Firebase for your Swift app, integrating it right into your existing DataService.swift. We'll cover data structure design, core functionality, real-time updates, and security rules to keep things safe.

1. Firebase Data Structure Design

First, we'll use a scalable Firestore structure (adjust if you're using Realtime Database) that supports bidirectional relationships and easy querying:

// Firestore Collections
- friendRequests
  - [senderUserId]
    - sentRequests
      - [receiverUserId]: { timestamp: Timestamp, status: "pending" }
  - [receiverUserId]
    - receivedRequests
      - [senderUserId]: { timestamp: Timestamp, status: "pending" }
- friends
  - [userId]
    - userFriends
      - [friendUserId]: { addedAt: Timestamp }
- users (your existing user collection, storing profiles)

Storing friendships bidirectionally means each user has their own list of friends, making it fast to fetch a user's friends without complex queries.

2. Core Functions for DataService.swift

Assuming your DataService already has a Firestore reference (let db = Firestore.firestore()), add these methods to handle friend interactions:

Send a Friend Request

This method checks for existing friendships/requests first, then sends the request atomically using a batch write:

func sendFriendRequest(to receiverId: String, from senderId: String, completion: @escaping (Error?) -> Void) {
    // Check if already friends
    let friendCheckRef = db.collection("friends").document(senderId).collection("userFriends").document(receiverId)
    friendCheckRef.getDocument { snapshot, error in
        if let error = error {
            completion(error)
            return
        }
        if snapshot?.exists == true {
            completion(NSError(domain: "FriendError", code: 0, userInfo: [NSLocalizedDescriptionKey: "You're already friends!"]))
            return
        }
        
        // Check if request already sent
        let sentRequestRef = db.collection("friendRequests").document(senderId).collection("sentRequests").document(receiverId)
        sentRequestRef.getDocument { snapshot, error in
            if let error = error {
                completion(error)
                return
            }
            if snapshot?.exists == true {
                completion(NSError(domain: "FriendError", code: 1, userInfo: [NSLocalizedDescriptionKey: "Request already sent!"]))
                return
            }
            
            // Batch write to update both sender/receiver request lists
            let batch = self.db.batch()
            let requestData = ["timestamp": Timestamp(), "status": "pending"]
            
            batch.setData(requestData, forDocument: sentRequestRef)
            let receivedRequestRef = self.db.collection("friendRequests").document(receiverId).collection("receivedRequests").document(senderId)
            batch.setData(requestData, forDocument: receivedRequestRef)
            
            batch.commit { error in
                completion(error)
            }
        }
    }
}

Accept a Friend Request

This removes the pending request and adds the bidirectional friendship:

func acceptFriendRequest(from senderId: String, for receiverId: String, completion: @escaping (Error?) -> Void) {
    let batch = db.batch()
    
    // Delete the request from both sides
    let receivedRequestRef = db.collection("friendRequests").document(receiverId).collection("receivedRequests").document(senderId)
    batch.deleteDocument(receivedRequestRef)
    
    let sentRequestRef = db.collection("friendRequests").document(senderId).collection("sentRequests").document(receiverId)
    batch.deleteDocument(sentRequestRef)
    
    // Add bidirectional friendship
    let receiverFriendRef = db.collection("friends").document(receiverId).collection("userFriends").document(senderId)
    batch.setData(["addedAt": Timestamp()], forDocument: receiverFriendRef)
    
    let senderFriendRef = db.collection("friends").document(senderId).collection("userFriends").document(receiverId)
    batch.setData(["addedAt": Timestamp()], forDocument: senderFriendRef)
    
    batch.commit { error in
        completion(error)
    }
}

Decline a Friend Request

Simply remove the pending request from both users' lists:

func declineFriendRequest(from senderId: String, for receiverId: String, completion: @escaping (Error?) -> Void) {
    let batch = db.batch()
    
    let receivedRequestRef = db.collection("friendRequests").document(receiverId).collection("receivedRequests").document(senderId)
    batch.deleteDocument(receivedRequestRef)
    
    let sentRequestRef = db.collection("friendRequests").document(senderId).collection("sentRequests").document(receiverId)
    batch.deleteDocument(sentRequestRef)
    
    batch.commit { error in
        completion(error)
    }
}

Fetch a User's Friends

Get a list of friend IDs, or extend it to fetch full user profiles:

// Fetch friend IDs only
func fetchFriendIds(for userId: String, completion: @escaping ([String]?, Error?) -> Void) {
    db.collection("friends").document(userId).collection("userFriends").getDocuments { snapshot, error in
        if let error = error {
            completion(nil, error)
            return
        }
        let friendIds = snapshot?.documents.map { $0.documentID } ?? []
        completion(friendIds, nil)
    }
}

// Fetch full friend profiles (assuming you have a User Codable model)
func fetchFriendProfiles(for userId: String, completion: @escaping ([User]?, Error?) -> Void) {
    fetchFriendIds(for: userId) { friendIds, error in
        if let error = error {
            completion(nil, error)
            return
        }
        guard let friendIds = friendIds, !friendIds.isEmpty else {
            completion([], nil)
            return
        }
        
        let dispatchGroup = DispatchGroup()
        var friends = [User]()
        
        for id in friendIds {
            dispatchGroup.enter()
            self.db.collection("users").document(id).getDocument { snapshot, error in
                defer { dispatchGroup.leave() }
                if let error = error {
                    print("Failed to fetch user \(id): \(error.localizedDescription)")
                    return
                }
                if let user = try? snapshot?.data(as: User.self) {
                    friends.append(user)
                }
            }
        }
        
        dispatchGroup.notify(queue: .main) {
            completion(friends, nil)
        }
    }
}

Real-Time Friend Request Notifications

Add a listener to get real-time updates when new requests come in:

// Define a Codable model for friend requests
struct FriendRequest: Codable {
    var timestamp: Timestamp
    var status: String
    var senderId: String?
}

// Listen for incoming requests
func listenForIncomingRequests(for userId: String, completion: @escaping ([FriendRequest]?, Error?) -> Void) -> ListenerRegistration {
    let requestRef = db.collection("friendRequests").document(userId).collection("receivedRequests")
    return requestRef.addSnapshotListener { snapshot, error in
        if let error = error {
            completion(nil, error)
            return
        }
        let requests = snapshot?.documents.compactMap { doc in
            var request = try? doc.data(as: FriendRequest.self)
            request?.senderId = doc.documentID
            return request
        } ?? []
        completion(requests, nil)
    }
}

Don't forget to remove the listener when it's no longer needed (e.g., in viewWillDisappear).

3. Firebase Security Rules

Lock down your data with these Firestore rules to ensure users can only access their own requests and friends:

rules_version = '2';
service cloud.firestore {
  match /databases/{database}/documents {
    // Restrict friend requests to the sender/receiver
    match /friendRequests/{userId}/{requestType}/{otherUserId} {
      allow read, write: if request.auth.uid == userId;
    }
    
    // Only let users access their own friend list
    match /friends/{userId}/userFriends/{friendId} {
      allow read, write: if request.auth.uid == userId;
    }
    
    // Allow public read access to user profiles (adjust if needed)
    match /users/{userId} {
      allow read: if true;
      allow write: if request.auth.uid == userId;
    }
  }
}

Final Notes

  • Adjust the code to match your existing DataService structure (e.g., if you use Realtime Database instead of Firestore, the reference syntax will change, but the core logic of bidirectional relationships remains the same).
  • Add error handling in your UI to give users clear feedback (e.g., "Request sent!", "Already friends").
  • Consider adding a "remove friend" function by deleting the bidirectional entries in the friends collection.

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.19 03:38:59