## SOLVED: Using a for-in loop to project yearly 401k balances based on monthly deposits accounting for several future promotions/pay raises

 Feb '23 Purpose of project: allow an employee to calculate/project future 401k yearly balances based on several future promotions. With promotions come pay raises. One assumption is that 401k monthly deposits are an unchanging percentage of income. I'm currenty working within a macOS Command Line Tool for debugging purposes. Eventually I will move this into an iOS app project. The entire code is found on GitHub here. However I was able to fit it all here below. Here is the first section: ``````enum Calculations { private static let pennyRoundingBehavior = NSDecimalNumberHandler( roundingMode: .bankers, scale: 2, raiseOnExactness: false, raiseOnOverflow: true, raiseOnUnderflow: true, raiseOnDivideByZero: true ) static func computeMonthlyEarnings(hours hoursWorked: Int, atPayRate payRate: Int) -> Decimal { let base = Decimal(hoursWorked * payRate) as NSDecimalNumber return base.rounding(accordingToBehavior: pennyRoundingBehavior) as Decimal } static func computeMonthlySavings(hours hoursWorked: Int, atPayRate payRate: Int, percentSaved percent: Double) -> Decimal { let base = Decimal(hoursWorked * payRate) * Decimal(percent / 100.0) as NSDecimalNumber return base.rounding(accordingToBehavior: pennyRoundingBehavior) as Decimal } static func computeMonthlyRoth(hours hoursWorked: Int, atPayRate payRate: Int, percentToRoth percent: Double) -> Decimal { let base = Decimal(hoursWorked * payRate) * Decimal(percent / 100.0) as NSDecimalNumber return base.rounding(accordingToBehavior: pennyRoundingBehavior) as Decimal } } extension Array { func chunked(into size: Int) -> [[Element]] { return stride(from: 0, to: count, by: size).map { Array(self[\$0 ..< Swift.min(\$0 + size, count)]) } } } enum Department: RawRepresentable { case sales case management case seniorManagement case other(String) init?(rawValue: String) { switch rawValue { case "Sales": self = .sales case "Management": self = .management case "Senior Management": self = .seniorManagement case let dept: self = .other(dept) } } var rawValue: String { switch self { case .sales: return "Sales" case .management: return "Management" case .seniorManagement: return "Senior Management" case .other(let dept): return dept } } func payRate(forYearsWorked numberOfYears: Int) -> Int { let rates: [Int] switch self { case .sales: rates = [20,22,24,26,28,30,32,34,36,38,40] case .management: rates = [30,32,34,36,38,40,42,44,46,48,50] case .seniorManagement: rates = [40,42,44,46,48,50,52,54,56,58,60] default: rates = [10,12,14,16,18,20,22,24,26,28,30] } return rates[min(numberOfYears, rates.count) - 1] } } struct Employee { let name: String var birthdate: Date var hiredate: Date var retirementAge: Int var department: Department var monthlyHoursWorked: Int var percentToSave: Double var existing401kbalance: Decimal var percentToRoth: Double var percentToTrad: Double private(set) var employmentHistory: [Department] init(_ name: String, birthdate: Date, hiredate: Date, retirementAge: Int, department: Department = .other("Employee"), monthlyHoursWorked: Int, percentToSave: Double, existing401kbalance: Decimal, percentToRoth: Double, percentToTrad: Double) { self.name = name self.birthdate = birthdate self.hiredate = hiredate self.retirementAge = retirementAge self.department = department self.monthlyHoursWorked = monthlyHoursWorked self.percentToSave = percentToSave self.existing401kbalance = existing401kbalance self.percentToRoth = percentToRoth self.percentToTrad = percentToTrad self.employmentHistory = [] } } extension Employee { mutating func promote(to newDepartment: Department) { department = newDepartment } mutating func work(months: Int) { employmentHistory .append(contentsOf: Array.init(repeating: department, count: months)) } mutating func work(years: Int) { work(months: years * 12) } mutating func work(years: Int, months: Int) { work(months: years * 12 + months) } func yearlyEarnings() -> [Decimal] { let years = employmentHistory.chunked(into: 12) var earnings: [Decimal] = [] for (index, months) in years.enumerated() { let yearEarnings: Decimal = months.reduce(0) { runningTotal, dept in let payRate = dept.payRate(forYearsWorked: index + 1) let monthlyEarnings = Calculations.computeMonthlyEarnings( hours: monthlyHoursWorked, atPayRate: payRate ) return runningTotal + monthlyEarnings } earnings.append(yearEarnings) } return earnings } func yearlySavings() -> [Decimal] { let years = employmentHistory.chunked(into: 12) var savings: [Decimal] = [] for (index, months) in years.enumerated() { let yearSavings: Decimal = months.reduce(0) { runningTotal, dept in let payRate = dept.payRate(forYearsWorked: index + 1) let monthlySavings = Calculations.computeMonthlySavings( hours: monthlyHoursWorked, atPayRate: payRate, percentSaved: percentToSave ) return runningTotal + monthlySavings } savings.append(yearSavings) } return savings } func calc401k(principal: Decimal, deposits: Decimal, rateOfReturn: Decimal) -> Decimal { //Compound interest for principal // P(1 + r/n)^(nt) //Future value of a series // PMT x {[(1 + r/n)^(nt) - 1] / (r/n)} //Key: // PMT = monthly payment amount // r = annual interest rate // n = numebr of times interest is compounded per year // t = time in years // ^ = ... to the power of... //Start with Conpound interest for principal // first segment: (1 + r/n) // n = compounded 12 times a year let principalBase = 1 + ((rateOfReturn/100)/12) // second segment: ^(nt) // n = compounded 12 times a year // t = time is 1 year let principalPower = pow(principalBase,12) // third segment: times P let principalInterest = principal * principalPower //Now Future value of the series //first segment: (1 + r/n) let rateFrequencyCompounded = 1+((rateOfReturn/100)/12) //second segment: // ^(nt) - 1 // n = compounded 12 times a year // t = time is 1 year let toThePower = pow(rateFrequencyCompounded,12) - 1 //third segment: // divided: / (r/n) // n = compounded 12 times a year let divided = toThePower / ((rateOfReturn/100)/12) //fourth segment: // mlutiplied by monthly deposit PMT let futureInterest = divided * deposits return futureInterest + principalInterest }`````` Here is the section of code that I need to perfect. I currently have three errors: ``````func depositsToRoth() -> [Decimal] { let years = employmentHistory.chunked(into: 12) var toRoth: [Decimal] = [] var rothBalances: [Decimal] = [] for (index, months) in years.enumerated() { let yearToRoth: Decimal = months.reduce(0) { runningTotal, dept in let payRate = dept.payRate(forYearsWorked: index + 1) let monthlyToRoth = Calculations.computeMonthlyRoth( hours: monthlyHoursWorked, atPayRate: payRate, percentToRoth: percentToRoth ) return runningTotal + monthlyToRoth } toRoth.append(yearToRoth) //chunk the toRoth array into 12 month groups and then reduce each to an average let yearsAverageOfMonthlyRothDepositsRaw = toRoth.chunked(into: 12) let yearsAverageOfMonthlyRothDeposits = yearsAverageOfMonthlyRothDepositsRaw.reduce(0, +) / yearsAverageOfMonthlyRothDepositsRaw.count //<- Error #1 for idx in yearsAverageOfMonthlyRothDeposits.indices { let principal = idx == 0 ? existing401kbalance : yearsAverageOfMonthlyRothDeposits[idx - 1] let balance401k = calc401k(principal: principal, deposits: yearsAverageOfMonthlyRothDeposits[idx], rateOfReturn: 10 ) return balance401k //<- Error #2 } rothBalances.append(balance401k) } return toRoth; balance401k //<- Error #3 }`````` The rest of the code... ``````extension Employee { func cumulativeSavingsSinceFirstHired() -> [Decimal] { //get our savings amount for each year of employment let savingsByYear = yearlySavings() //create an array to hold the cumulative savings for each year //we initialize each slot as 0 //this saves us having to do a bunch of appends var cumulativeSavings: [Decimal] = .init(repeating: .zero, count: savingsByYear.count) //now loop through the yearly savings for idx in savingsByYear.indices { //and use reduce on a slice of the array from the startIndex (i.e., 0) // to the current idx //so 0...0, then 0...1, then 0...2, etc cumulativeSavings[idx] = savingsByYear[0...idx].reduce(0) { accum, yrAmount in //simply add the cumulative total and the amount for the year accum + yrAmount } } //send it back out return cumulativeSavings } } extension Employee { func yearlyDepartments() -> [String] { let years = employmentHistory.chunked(into: 12) let departments: [String] = years.map { months in var seenDepts: Set = [] return months.reduce(into: [String]()) { acc, dept in if seenDepts.insert(dept.rawValue).inserted { acc.append(dept.rawValue) } }.joined(separator: ",") //concat the final result with , separator } return departments } func yearlyDepartmentsEarningsAndSavings() -> [(String, (Decimal, Decimal))] { let yearlyDepartments = yearlyDepartments() let yearlyEarnings = yearlyEarnings() let yearlySavings = yearlySavings() let yearlyDepartmentsEarningsAndSavings: [(departments: String, (earnings: Decimal, savings: Decimal))] = Array(zip(yearlyDepartments, zip(yearlyEarnings, yearlySavings))) return yearlyDepartmentsEarningsAndSavings } } var birthdate = DateComponents(calendar: .current, year: 1965, month: 7, day: 20).date! var hiredate = DateComponents(calendar: .current, year: 2021, month: 12, day: 28).date! var promotion1date = DateComponents(calendar: .current, year: 2023, month: 12, day: 28).date! var firstDeptMonths: Int { Calendar.current.dateComponents([.month], from: hiredate, to: promotion1date).month! } var promotion2date = DateComponents(calendar: .current, year: 2025, month: 6, day: 28).date! var secondDeptMonths: Int { Calendar.current.dateComponents([.month], from: promotion1date, to: promotion2date).month! } var promotion3date = DateComponents(calendar: .current, year: 2026, month: 12, day: 28).date! var thirdDeptMonths: Int { Calendar.current.dateComponents([.month], from: promotion2date, to: promotion3date).month! } var retireAge = 65 let retireDate = Calendar.current.date(byAdding: .year, value: retireAge, to: birthdate)! var fourthDeptMonths: Int { Calendar.current.dateComponents([.month], from: promotion3date, to: retireDate).month! } extension Date { var age: Int { Calendar.current.dateComponents([.year], from: self, to: Date()).year! } var yearGroup: Int { Calendar.current.dateComponents([.year], from: self, to: Date()).year! + 1} var totalYearsRemaining: Int { Calendar.current.dateComponents([.year], from: self, to: retireDate).year! } var totalMonthsRemaining: Int { Calendar.current.dateComponents([.month], from: self, to: retireDate).month! } } //age let age = birthdate.age //number of years employee has worked for the company let yearGroup = hiredate.yearGroup func printYearlyDepartmentsAndEarnings(_ yearlyDeptsAndEarnings: [(departments: String, (earnings: Decimal, savings: Decimal))]) { for (year, deptsAndEarnings) in yearlyDeptsAndEarnings.enumerated() { print("Department(s) for Year \(year + yearGroup + 1): \(deptsAndEarnings.departments)") print("Earnings for Year \(year + yearGroup + 1): \(deptsAndEarnings.1.earnings)") print("Savings for Year \(year + yearGroup + 1): \(deptsAndEarnings.1.savings)") print("Age: \(year + age)") print("") } } func printSavingsBalance(_ savingsBalance: [(Decimal)]) { for (year, save) in savingsBalance.enumerated() { print("Savings Balance Year \(year + yearGroup + 1): \(save)") } } //create a new employee var charlotte = Employee("Charlotte Grote", birthdate: birthdate, hiredate: hiredate, retirementAge: 65, department: .other("Hospitality"), monthlyHoursWorked: 160, percentToSave: 10.0, existing401kbalance: 1000, percentToRoth: 10.0, percentToTrad: 16.0) //give our employee some work history //months in Hospitality charlotte.work(months: firstDeptMonths) //ohh, a promotion! charlotte.promote(to: .sales) //months in Sales charlotte.work(months: secondDeptMonths) //another promotion. way to go, Charlotte! charlotte.promote(to: .management) //months in Management charlotte.work(months: thirdDeptMonths) //another promotion. way to go, Charlotte! charlotte.promote(to: .seniorManagement) //months in Senior Management charlotte.work(months: fourthDeptMonths) //how much money has Charlotte made after all this time? printYearlyDepartmentsAndEarnings(charlotte.yearlyDepartmentsEarningsAndSavings()) printSavingsBalance(charlotte.cumulativeSavingsSinceFirstHired())`````` Error 1: Cannot convert value of type '(Int) -> Int' to expected argument type '(Int, [Decimal]) throws -> Int' Error 2 & 3: Cannot find 'balance401k' in scope Error 1 leads me to believe that using `.reduce` in this way is limited to Int parameters. Is that correct? I want to be able to later print the yearly sum totals of each year's roth deposits. I think that will be possible by referencing the `yearsAverageOfMonthlyRothDeposits` array, but I need to clear all these errors first to find out. I also want to be able to later print each year's 401k balances. 2 Feb '23 Taking your `depositsToRoth` function and adding some comments to help me figure out what is intended and what is actually going on... ``````func depositsToRoth() -> [Decimal] { //chunk the employmentHistory into years (i.e., 12-month groups let years = employmentHistory.chunked(into: 12) //how much does the employee put into their Roth account each year? var toRoth: [Decimal] = [] //balance in the employee's Roth account on a yearly basis var rothBalances: [Decimal] = [] for (index, months) in years.enumerated() { //for one year, how much was contributed by the employee // to their Roth account? let yearToRoth: Decimal = months.reduce(0) { runningTotal, dept in //what is the payRate for this month given a department // and a number of years worked? let payRate = dept.payRate(forYearsWorked: index + 1) //calculate how much was put into the Roth account // this month let monthlyToRoth = Calculations.computeMonthlyRoth( hours: monthlyHoursWorked, atPayRate: payRate, percentToRoth: percentToRoth ) //add the monthly Roth amount to the amounts for the // rest of the amounts in this year return runningTotal + monthlyToRoth } //add this year's amount to the array of other year amounts toRoth.append(yearToRoth) //----- Here's where we run into issues ----- //chunk the toRoth array into 12 month groups and then reduce each to an average let yearsAverageOfMonthlyRothDepositsRaw = toRoth.chunked(into: 12) let yearsAverageOfMonthlyRothDeposits = yearsAverageOfMonthlyRothDepositsRaw.reduce(0, +) / yearsAverageOfMonthlyRothDepositsRaw.count //<- Error #1 for idx in yearsAverageOfMonthlyRothDeposits.indices { let principal = idx == 0 ? existing401kbalance : yearsAverageOfMonthlyRothDeposits[idx - 1] let balance401k = calc401k(principal: principal, deposits: yearsAverageOfMonthlyRothDeposits[idx], rateOfReturn: 10 ) return balance401k //<- Error #2 } rothBalances.append(balance401k) } return toRoth; balance401k //<- Error #3 }`````` Breaking things down... ``let yearsAverageOfMonthlyRothDepositsRaw = toRoth.chunked(into: 12)`` There is no need to chunk `toRoth` because it is already an array of yearly amounts. We chunk `employmentHistory` because it is an array of month data that we want to group by 12 to get a year's worth of data. ``let yearsAverageOfMonthlyRothDeposits = yearsAverageOfMonthlyRothDepositsRaw.reduce(0, +) / yearsAverageOfMonthlyRothDepositsRaw.count`` You get the error here because since you've chunked `toRoth`, then `yearsAverageOfMonthlyRothDepositsRaw` has a type of `[[Decimal]]` instead of `[Decimal]`. So when you try to `reduce` it, your function looks like this: ``````let yearsAverageOfMonthlyRothDeposits = yearsAverageOfMonthlyRothDepositsRaw.reduce(0) { accum, current in //accum is an Int because you kicked off the reduce with a 0 //current is [Decimal] because yearsAverageOfMonthlyRothDepositsRaw is [[Decimal]] }`````` I think your `for` loop looks mostly okay from a code standpoint, but we'll need to fix it up once we address all the other issues. Put a pin in it. Except for this line: ``return balance401k`` If you return from inside a `for` loop, your loop ends right there and then. So if you need to keep processing items, you can't. ``rothBalances.append(balance401k)`` This is where you actually get the first error about `balance401k` not being found. And that's because this line is outside the loop but `balance401k` is declared inside the loop. Thus the "out of scope" message. ``return toRoth; balance401k`` You get an error here because `balance401k` is defined inside the loop but this statement is outside the loop, and also `;` creates a second statement on the line that causes the compiler to throw a warning. I'm not entirely sure what `balance401k` is even doing here, to be honest. Also, you are returning `toRoth`, but you just spent a loop setting up `rothBalances` and then do nothing with it. Let me know if any of this makes sense. 2 Feb '23 And here is `depositsToRoth` rewritten... ``````func depositsToRoth() -> [Decimal] { //chunk the employmentHistory into years (i.e., 12-month groups let years = employmentHistory.chunked(into: 12) //start with an empty array of how much the employee // puts into their Roth account each year var averageRothContribution: [Decimal] = [] for (index, months) in years.enumerated() { //for one year, how much was contributed by the employee // to their Roth account? let yearToRoth: Decimal = months.reduce(0) { runningTotal, dept in //what is the payRate for this month given a department // and a number of years worked? let payRate = dept.payRate(forYearsWorked: index + 1) //calculate how much was put into the Roth account // this month let monthlyToRoth = Calculations.computeMonthlyRoth( hours: monthlyHoursWorked, atPayRate: payRate, percentToRoth: percentToRoth ) //add the monthly Roth amount to the amounts for the // rest of the amounts in this year return runningTotal + monthlyToRoth } //add the average of this year's amounts to the array of other year average amounts let averageAmount = yearToRoth / Decimal(months.count) averageRothContribution.append(averageAmount) } //averageRothContribution now contains the average amount // in each year that the employee contributed //start with an empty array of the balance in the // employee's Roth account on a yearly basis var rothBalances: [Decimal] = [] for idx in averageRothContribution.indices { //our principal will either be existingBalance (for the first item) // or the previous item (for items 2...n) let principal = idx == 0 ? existing401kbalance : averageRothContribution[idx - 1] //calculate the yearly balance let balance401k = calc401k(principal: principal, deposits: averageRothContribution[idx], rateOfReturn: 10 ) //and add it to our list of balances rothBalances.append(balance401k) } //give it back to the caller return rothBalances }`````` Using this rewritten function, when I run the project, I get this output: ``````Department(s) for Year 3: Hospitality Earnings for Year 3: 19200 Savings for Year 3: 1920 Age: 57 Department(s) for Year 4: Hospitality Earnings for Year 4: 23040 Savings for Year 4: 2304 Age: 58 Department(s) for Year 5: Sales Earnings for Year 5: 46080 Savings for Year 5: 4608 Age: 59 Department(s) for Year 6: Sales,Management Earnings for Year 6: 59520 Savings for Year 6: 5952 Age: 60 Department(s) for Year 7: Management Earnings for Year 7: 72960 Savings for Year 7: 7296 Age: 61 Department(s) for Year 8: Senior Management Earnings for Year 8: 96000 Savings for Year 8: 9600 Age: 62 Department(s) for Year 9: Senior Management Earnings for Year 9: 99840 Savings for Year 9: 9984 Age: 63 Department(s) for Year 10: Senior Management Earnings for Year 10: 103680 Savings for Year 10: 10368 Age: 64 Department(s) for Year 11: Senior Management Earnings for Year 11: 53760 Savings for Year 11: 5376 Age: 65 Savings Balance Year 3: 1920 Savings Balance Year 4: 4224 Savings Balance Year 5: 8832 Savings Balance Year 6: 14784 Savings Balance Year 7: 22080 Savings Balance Year 8: 31680 Savings Balance Year 9: 41664 Savings Balance Year 10: 52032 Savings Balance Year 11: 57408`````` Is this what you are looking for? 2 Feb '23 @roosterboy Thank you for explaining my flaws in the chunk actions and for-in loop return! I've added the following print function: ``````func printRothBalance(_ rothBalance: [(Decimal)]) { for (year, roth) in rothBalance.enumerated() { print("Roth 401k Balance Year \(year + yearGroup + 1): \(roth)") } } printRothBalance(charlotte.depositsToRoth())`````` Which returns the following: ``````Roth 401k Balance Year 3: 3115.20396231420428012956723300993944886 Roth 401k Balance Year 4: 2589.34316463809600490128513890250471354 Roth 401k Balance Year 5: 5037.2830566437059628789769804869266424 Roth 401k Balance Year 6: 6656.7315920034699602416631448627432051 Roth 401k Balance Year 7: 8187.8030819679301782771034984147580275 Roth 401k Balance Year 8: 10724.1200193688439155820411508229816967 Roth 401k Balance Year 9: 11338.3231072921543936752300163425894125 Roth 401k Balance Year 10: 11775.7721044248573131139272602145936474 Roth 401k Balance Year 11: 12213.2211015575602325526245040865978824`````` I'm confused on how the balance decreased in year 4. And generally why the balances are growing so slowly. Any ideas? I ran the following in playground to see what results we should expect: ``````var yearOneRothBalance = calc401k(principal: 1000, deposits: 1920/12,rateOfReturn: 10.0) var yearTwoRothBalance = calc401k(principal: yearOneRothBalance, deposits: 4224/12, rateOfReturn: 10.0) var yearThreeRothBalance = calc401k(principal: yearTwoRothBalance, deposits: 8832/12, rateOfReturn: 10.0) yearOneRothBalance // 3115.20396231420428012956723300993944886 yearTwoRothBalance // 7864.4864936338034283135300277731831885 yearThreeRothBalance // 17936.2591146482235336136415751132136957`````` 2 Feb '23 Ack, I think I messed up the logic in the `for` loop. See if this is more like what you were expecting: ``````func depositsToRoth() -> [Decimal] { //chunk the employmentHistory into years (i.e., 12-month groups let years = employmentHistory.chunked(into: 12) //start with an empty array of how much the employee // puts into their Roth account each year var averageRothContribution: [Decimal] = [] for (index, months) in years.enumerated() { //for one year, how much was contributed by the employee // to their Roth account? let yearToRoth: Decimal = months.reduce(0) { runningTotal, dept in //what is the payRate for this month given a department // and a number of years worked? let payRate = dept.payRate(forYearsWorked: index + 1) //calculate how much was put into the Roth account // this month let monthlyToRoth = Calculations.computeMonthlyRoth( hours: monthlyHoursWorked, atPayRate: payRate, percentToRoth: percentToRoth ) //add the monthly Roth amount to the amounts for the // rest of the amounts in this year return runningTotal + monthlyToRoth } //add this year's amount to the array of other year amounts averageRothContribution.append(yearToRoth / Decimal(months.count)) } //averageRothContribution now contains the average amount // in each year that the employee contributed //start with an empty array of the balance in the // employee's Roth account on a yearly basis var rothBalances: [Decimal] = [] for idx in averageRothContribution.indices { //our principal will either be existingBalance (for the first item) // or the previously added item in rothBalances (for items 2...n) let principal = idx == 0 ? existing401kbalance : rothBalances[idx - 1] //calculate the yearly balance let balance401k = calc401k(principal: principal, deposits: averageRothContribution[idx], rateOfReturn: 10 ) //and add it to our list of balances rothBalances.append(balance401k) } //give it back to the caller return rothBalances }`````` And the results from `printRothBalance()`: ``````Roth 401k Balance Year 3: 3115.20396231420428012956723300993944886 Roth 401k Balance Year 4: 5853.9955987608963897745354300607654945 Roth 401k Balance Year 5: 11292.1635823899802529271777112270741682 Roth 401k Balance Year 6: 18707.1224432569547352497187466561965481 Roth 401k Balance Year 7: 28305.8680178073722134124570576884100876 Roth 401k Balance Year 8: 41322.31675890502944286963025766616666 Roth 401k Balance Year 9: 56103.85595385001563979726361027458934 Roth 401k Balance Year 10: 72835.31363837603611857781045942813189 Roth 401k Balance Year 11: 91720.871758787622174674797995751069435`````` Although... I notice from your Playground example code: ``````var yearOneRothBalance = calc401k(principal: 1000, deposits: 1920/12,rateOfReturn: 10.0) var yearTwoRothBalance = calc401k(principal: yearOneRothBalance, deposits: 4224/12, rateOfReturn: 10.0) var yearThreeRothBalance = calc401k(principal: yearTwoRothBalance, deposits: 8832/12, rateOfReturn: 10.0)`````` that you are passing into the `deposits` parameter the cumulative savings balance for each year and dividing that by 12. The code I posted is using the annual balance for each year and dividing by 12 to get the average monthly contribution. That accounts for some of the discrepancy. To be honest, I'm not sure which is the correct one to use; all this financial math stuff makes my eyes glaze over. I probably misunderstood what the requirements were. 3

SAVE 50% To celebrate WWDC24, all our books and bundles are half price, so you can take your Swift knowledge further without spending big! Get the Swift Power Pack to build your iOS career faster, get the Swift Platform Pack to builds apps for macOS, watchOS, and beyond, or get the Swift Plus Pack to learn advanced design patterns, testing skills, and more.

### Archived topic

This topic has been closed due to inactivity, so you can't reply. Please create a new topic if you need to.

All interactions here are governed by our code of conduct.

You are not logged in