IOS 5 까지는 UDID라고 디바이스 고유번호를 사용할 수 있었는데 이후 막혔다.

현재는 UUID를 활용해서 디바이스 고유번호를 사용하고 있지만 약간의 제약사항이 있다.

UUID 생성 하고 UserDefault에 저장하거나 했을경우 앱을 삭제하고 새로 설치하면 UUID가 변경된다.

그래서 키체인에 저장해서 사용하는 방법을 사용하긴 한다.

하지만 이것도 디바이스 공장초기화를 하게되면 UUID가 변경되는 문제가 있다.

 

KeyChainWrapper.swift

//  Created by DongOh Lim on 2023/04/18.
//  참고사이트 : https://developerbee.tistory.com/56

import Foundation
import Security

class KeyChainWrapper: NSObject {
    /*
     * 외부로 제공되는 메소드
     * serviceIdentifier: 키체인에서 해당 앱을 식별하는 값으로 앱만의 고유한 값을 써야합니다. (데이터를 해당 앱에서만 사용하기 위해)
     * userAccount: 앱 내에서 데이터를 식별하기 위한 키에 해당하는 값입니다.
     */
    
    public class func saveData(serviceIdentifier:NSString, userAccount:NSString, data: String) {
        self.save(service: serviceIdentifier, userAccount: userAccount, data: data)
    }
    
    public class func loadData(serviceIdentifier:NSString, userAccount:NSString) -> String? {
        let data = self.load(service: serviceIdentifier, userAccount: userAccount)
        
        return data
    }
    
    public class func updateData(serviceIdentifier:NSString, userAccount:NSString, data: String) {
        self.update(service: serviceIdentifier, userAccount: userAccount, data: data)
    }
    
    public class func dataDelete(serviceIdentifier:NSString, userAccount:NSString) {
        self.delete(service: serviceIdentifier, userAccount: userAccount)
    }
    
    /*
     * Keychain 에 실제 접근하는 내부 메소드
     */
    
    private class func save(service: NSString, userAccount:NSString, data: String) {
        let dataFromString: Data = data.data(using: String.Encoding.utf8)!
        
        // Instantiate a new default keychain query
        let query: [String: Any] = [kSecClass as String: kSecClassGenericPassword as String,
                                    kSecAttrService as String: service,
                                    kSecAttrAccount as String: userAccount,
                                    kSecValueData as String: dataFromString]
        
        // Delete any existing items
        SecItemDelete(query as CFDictionary)
        
        // Add the new keychain item
        SecItemAdd(query as CFDictionary, nil)
    }
    
    private class func load(service: NSString, userAccount:NSString) -> String? {
        // Instantiate a new default keychain query
        // Tell the query to return a result
        // Limit our results to one item
        let query: [String: Any] = [kSecClass as String: kSecClassGenericPassword as String,
                                    kSecAttrService as String: service,
                                    kSecAttrAccount as String: userAccount,
                                    kSecReturnData as String: kCFBooleanTrue!,
                                    kSecMatchLimit as String: kSecMatchLimitOne as String]
        
        var retrievedData: NSData?
        var dataTypeRef:AnyObject?
        var contentsOfKeychain: String?
        
        let status: OSStatus = SecItemCopyMatching(query as CFDictionary, &dataTypeRef)
        
        if (status == errSecSuccess) {
            retrievedData = dataTypeRef as? NSData
            contentsOfKeychain = String(data: retrievedData! as Data, encoding: String.Encoding.utf8)
        }
        else
        {
            print("Nothing was retrieved from the keychain. Status code \(status)")
            contentsOfKeychain = nil
        }
        
        return contentsOfKeychain
    }
    
    private class func update(service: NSString, userAccount: NSString, data: String) {
        let dataFromString: Data = data.data(using: String.Encoding.utf8)!
        
        let previousQuery: [String: Any] = [kSecClass as String: kSecClassGenericPassword as String,
                                              kSecAttrService as String: service,
                                              kSecAttrAccount as String: userAccount]
        let updateQuery: [CFString: Any] = [kSecValueData: dataFromString]
        let status = SecItemUpdate(previousQuery as CFDictionary, updateQuery as CFDictionary)
        if status == errSecSuccess {
            print("update complete")
        } else {
            print("not finished update")
        }
    }
    
    private class func delete(service: NSString, userAccount: NSString) {
        let deleteQuery: [String: Any] = [kSecClass as String: kSecClassGenericPassword as String,
                                            kSecAttrService as String: service,
                                            kSecAttrAccount as String: userAccount]
        let status = SecItemDelete(deleteQuery as CFDictionary)
        if status == errSecSuccess {
            print("remove key-data complete")
        } else {
            print("remove key-data failed")
        }
    }
}
}

 

사용방법

class AppDelegate: UIResponder, UIApplicationDelegate {
	var uuid: String = ""
    
				...... 생략 ......
	// 디바이스 UUID 키체인 체크 및 저장
    // serviceIdentifier : 고유값 사용하면 되지만 그냥 번들 아이디 사용한다.
    // uuid 저장되어 있는지 확인
    let deviceUUID = KeyChainWrapper.loadData(serviceIdentifier: "번들아이디", userAccount: "uuid")
        
    if deviceUUID != nil {
    	// uuid 가 있으면 사용하면 된다.
        log(direction: .ETC, ofType: self, datas: "UUID : \(String(describing: deviceUUID))")    
        uuid = deviceUUID!
     } else {  
     	// uuid 가 없을 경우 생성한다. 
        // UIDevice.deviceUUID 게시물 확인하세요. https://limdongo.tistory.com/13
        
 	    if let device_uuid = UIDevice.deviceUUID {
            log(direction: .ETC, ofType: self, datas: "UUID : \(String(describing: device_uuid))")
            KeyChainWrapper.saveData(serviceIdentifier: "번들아이디", userAccount: "uuid", data: device_uuid)
        } else {
            log(direction: .ERROR, ofType: self, datas: "UUID NULL")
        }       
     }
				...... 생략 ......
}
 

+ Recent posts