본문 바로가기

학습 노트/iOS (2021)

192. Expression

Expression


Format 문자열 대신 Class로 조건을 지정할 때 사용한다.
사용 빈도는 낮지만 평균, 합계 등의 연산에서 월등히 높은 성능을 보여준다.
대신 상대적으로 구현에 필요한 코드의 양이 많고, 동작 방식이 난해하다는 단점이 있다.

Table View Cell의 디자인은 위와 같고,
평균 연봉은 해당 부서의 직원 목록에 접근한 뒤 연봉 데이터에 접근해 계산을 진행한다.

 

ExpressionViewContoller.swift > tableView(_:cellForRowAt:)

   func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
      let cell = tableView.dequeueReusableCell(withIdentifier: "cell", for: indexPath)
      
      
      if let target = list[indexPath.row] as? NSManagedObject {
         let count = target.value(forKeyPath: "employees.@count.intValue") as? Int
         let avg = target.value(forKeyPath: "employees.@avg.salary.doubleValue") as? Double
         
         if let name = target.value(forKey: "name") as? String, let count = count {
            cell.textLabel?.text = "\(name)\n\(count) employees"
         }
         
         let avgStr = formatter.string(for: avg ?? 0) ?? "0"
         cell.detailTextLabel?.text = avgStr
      } else if let target = list[indexPath.row] as? [String: Any] {
         let count = target["count"] as? Int
         let avg = target["avg"] as? Double
         
         if let name = target["name"] as? String, let count = count {
            cell.textLabel?.text = "\(name)\n\(count) employees"
         }
         
         let avgStr = formatter.string(for: avg ?? 0) ?? "0"
         cell.detailTextLabel?.text = avgStr
      }
      
      
      return cell
   }

TableView에 표시하기 위한 데이터를 가져오는 부분을 보면
'@count'와 '@avg'를 사용하고 있는 것을 확인할 수 있다.
이 둘을 사용해도 문제는 없지만 특히 '@avg'의 경우 평균을 구하기 위해 모든 데이터를 Context로 가져와야 할 필요가 있다.

 

Product > Profile > CoreData

Cache에 데이터가 존재하지 않아 저장소에 접근하는 경우가 발생한다.
이는 퍼포먼스에 악영향을 줄 수 있으므로 다음을 고려해야한다.

  • Core Data Faults에는 가능한 적은 수의 항목이 나타날 것
  • 상태바에 가능한 얇게 그려질 것

Count와 Avg는 CoreData 내에 직계 함수로서 제공되기도 한다.
이를 Expression이라고 부르며 이들은 CoreData에서 직접 연산이 이루어 지기 때문에 Context에 전달하는 과정을 생략한다.

 

ExpressionViewContoller.swift > fetch()

   func fetch() {
      let request = NSFetchRequest<NSFetchRequestResult>(entityName: "Department")
      
      let sortByName = NSSortDescriptor(key: "name", ascending: true)
      request.sortDescriptors = [sortByName]
	   
	   request.resultType = .dictionaryResultType
      
      
      
      do {
         list = try DataManager.shared.mainContext.fetch(request)
         listTableView.reloadData()
      } catch {
         fatalError(error.localizedDescription)
      }
   }

Expression을 사용하기 위해선 request의 resultType을 '.dictionaryResultType'으로 지정해야 한다.

Expression은 NSExpression으로 구현돼 있으며,
literal, keypath, 함수의 표현식을 Class로 구현한다.

   func fetch() {
      let request = NSFetchRequest<NSFetchRequestResult>(entityName: "Department")
      
      let sortByName = NSSortDescriptor(key: "name", ascending: true)
      request.sortDescriptors = [sortByName]
	   
	   request.resultType = .dictionaryResultType
      
	   let employeeCountExprDescription = NSExpressionDescription()
	   employeeCountExprDescription.name = "count"
      
      
      do {
         list = try DataManager.shared.mainContext.fetch(request)
         listTableView.reloadData()
      } catch {
         fatalError(error.localizedDescription)
      }
   }

계산 결과를 Request에 포함시키려면 NSExpressionDescription 인스턴스를 생성한 뒤,
Expression을 저장하면 된다.

employeeCountExprDescription은 직원수를 저장할 인스턴스로,
name 속성에 할당한 문자열은 이후 key로써 작동한다.

   func fetch() {
      let request = NSFetchRequest<NSFetchRequestResult>(entityName: "Department")
      
      let sortByName = NSSortDescriptor(key: "name", ascending: true)
      request.sortDescriptors = [sortByName]
	   
	   request.resultType = .dictionaryResultType
      
	   let employeeCountExprDescription = NSExpressionDescription()
	   employeeCountExprDescription.name = "count"
      
	   let countArg = NSExpression(forKeyPath: "employees")
	   let countExpr = NSExpression(forFunction: "count:", arguments: [countArg])
	   
      do {
         list = try DataManager.shared.mainContext.fetch(request)
         listTableView.reloadData()
      } catch {
         fatalError(error.localizedDescription)
      }
   }

countArg는 employees Relation을 가리키고,
countExpr은 실제 Expression을 저장한다.

NSExpression(forFunction:arguments:)

 

Apple Developer Documentation

 

developer.apple.com

param1 : 사용할 함수
param2 : KeyPath Expression

param2를 기준으로 param1의 함수를 실행한다.

NSExpression(forKeyPath:)

 

Apple Developer Documentation

 

developer.apple.com

 

   func fetch() {
      let request = NSFetchRequest<NSFetchRequestResult>(entityName: "Department")
      
      let sortByName = NSSortDescriptor(key: "name", ascending: true)
      request.sortDescriptors = [sortByName]
	   
	   request.resultType = .dictionaryResultType
      
	   let employeeCountExprDescription = NSExpressionDescription()
	   employeeCountExprDescription.name = "count"
      
	   let countArg = NSExpression(forKeyPath: "employees")
	   let countExpr = NSExpression(forFunction: "count:", arguments: [countArg])
	   
	   employeeCountExprDescription.expression = countExpr
	   employeeCountExprDescription.expressionResultType = .integer64AttributeType
	   
      do {
         list = try DataManager.shared.mainContext.fetch(request)
         listTableView.reloadData()
      } catch {
         fatalError(error.localizedDescription)
      }
   }

완성된 Expression을 expression Description의 expression 속성에 저장하고,
계산 결과 값을 반환하기 위해 expressionResultType 속성을 지정한다.

   func fetch() {
      let request = NSFetchRequest<NSFetchRequestResult>(entityName: "Department")
      
      let sortByName = NSSortDescriptor(key: "name", ascending: true)
      request.sortDescriptors = [sortByName]
	   
	   request.resultType = .dictionaryResultType
      //count expression, expression description
	   let employeeCountExprDescription = NSExpressionDescription()
	   employeeCountExprDescription.name = "count"
      
	   let countArg = NSExpression(forKeyPath: "employees")
	   let countExpr = NSExpression(forFunction: "count:", arguments: [countArg])
	   
	   employeeCountExprDescription.expression = countExpr
	   employeeCountExprDescription.expressionResultType = .integer64AttributeType
	   
	   //average expression, expression description
	   let averageSalaryExprDescription = NSExpressionDescription()
	   averageSalaryExprDescription.name = "avg"
	   
	   let salaryArg = NSExpression(forKeyPath: "employees.salary")
	   averageSalaryExprDescription.expression = NSExpression(forFunction: "average:", arguments: [salaryArg])
	   averageSalaryExprDescription.expressionResultType = .decimalAttributeType
	   
      do {
         list = try DataManager.shared.mainContext.fetch(request)
         listTableView.reloadData()
      } catch {
         fatalError(error.localizedDescription)
      }
   }

동일한 방식으로 avg에 대한 Expression과 Expression Description을 작성한다.

   func fetch() {
      let request = NSFetchRequest<NSFetchRequestResult>(entityName: "Department")
      
      let sortByName = NSSortDescriptor(key: "name", ascending: true)
      request.sortDescriptors = [sortByName]
	   
	   request.resultType = .dictionaryResultType
      //count expression, expression description
	   let employeeCountExprDescription = NSExpressionDescription()
	   employeeCountExprDescription.name = "count"
      
	   let countArg = NSExpression(forKeyPath: "employees")
	   let countExpr = NSExpression(forFunction: "count:", arguments: [countArg])
	   
	   employeeCountExprDescription.expression = countExpr
	   employeeCountExprDescription.expressionResultType = .integer64AttributeType
	   
	   //average expression, expression description
	   let averageSalaryExprDescription = NSExpressionDescription()
	   averageSalaryExprDescription.name = "avg"
	   
	   let salaryArg = NSExpression(forKeyPath: "employees.salary")
	   averageSalaryExprDescription.expression = NSExpression(forFunction: "average:", arguments: [salaryArg])
	   averageSalaryExprDescription.expressionResultType = .decimalAttributeType
	   
	   request.propertiesToFetch = ["name", employeeCountExprDescription, averageSalaryExprDescription]
      do {
         list = try DataManager.shared.mainContext.fetch(request)
         listTableView.reloadData()
      } catch {
         fatalError(error.localizedDescription)
      }
   }

마지막으로 request의 propertiesToFetch에 Expression을 전달해 해당 조건으로 값을 받아오도록 한다.

이전과는 다르게 동일한 조건에서 Core Data Faults에 표시되던 반응들이 사라진 것을 확인할 수 있다.
이렇게 Expression을 사용하게 되면 CoreData에서 직접 계산해 전달하기 때문에
불필요한 접근이나 과정을 생략할 수 있어 속도 향상을 기대할 수 있다.

 

'학습 노트 > iOS (2021)' 카테고리의 다른 글

194. Transformable  (0) 2022.05.20
193. Fetched Result Controller  (0) 2022.04.09
191. Predicate Syntax  (0) 2022.03.24
190. Predicate  (0) 2022.03.24
187 ~ 189. Fetch Request  (0) 2022.02.25