41. Memory leak with a closure
This view controller is never deallocated after it is dismissed. Why?
class ProfileVC: UIViewController {
var onUpdate: (() -> Void)?
override func viewDidLoad() {
super.viewDidLoad()
onUpdate = {
self.reload()
}
}
func reload() {}
}- A.viewDidLoad is called twice
- B.Optionals always leak memory in Swift
- C.reload is not marked @objc
- D.The VC strongly holds the closure and the closure strongly captures self, creating a retain cycle; use [weak self]
Show answer
Answer: D. The VC strongly holds the closure and the closure strongly captures self, creating a retain cycle; use [weak self]
Swift uses ARC. The controller owns onUpdate, and the closure captures self strongly, so each keeps the other alive and the reference count never reaches zero. Capturing [weak self] (or unowned when lifetime is guaranteed) breaks the cycle. Xcode Memory Graph Debugger can show such cycles.
42. Actor reentrancy in an image cache
Two callers ask this actor for the same URL at the same moment. What happens?
actor ImageCache {
private var cache: [URL: UIImage] = [:]
func image(for url: URL) async throws -> UIImage {
if let cached = cache[url] { return cached }
let image = try await download(url)
cache[url] = image
return image
}
}- A.The actor runs one call at a time from start to finish, so the second caller waits until the first returns and then finds the image already in the cache
- B.A data race on the dictionary crashes the app
- C.It does not compile, because actors cannot have async methods
- D.Both calls may download, because actors are reentrant: while the first call awaits, the second runs and also misses the cache; store in-flight Tasks in the dictionary to share one download
Show answer
Answer: D. Both calls may download, because actors are reentrant: while the first call awaits, the second runs and also misses the cache; store in-flight Tasks in the dictionary to share one download
Actors protect their state from simultaneous access, but at every await they can run other queued calls. The second call runs while the first is suspended in download, sees no cached image and starts its own download. Caching a Task<UIImage, Error> per URL lets later callers await the same in-flight work.
43. Keychain read fails in background
A background refresh task runs while the iPhone is locked, reads the auth token from the Keychain and gets errSecInteractionNotAllowed. What is the cause?
- A.The Keychain cannot be used by background code at all, so the token should be copied into UserDefaults before the app is suspended so background tasks can read it
- B.The token was saved with kSecAttrAccessibleWhenUnlocked; items needed in the background should use kSecAttrAccessibleAfterFirstUnlock (the ThisDeviceOnly variant if it should not move to other devices)
- C.Background tasks have no network access
- D.The token has expired on the server
Show answer
Answer: B. The token was saved with kSecAttrAccessibleWhenUnlocked; items needed in the background should use kSecAttrAccessibleAfterFirstUnlock (the ThisDeviceOnly variant if it should not move to other devices)
Keychain items are protected by data protection classes tied to the device lock state. WhenUnlocked items cannot be read while the device is locked, so background work fails. AfterFirstUnlock keeps the item readable after the first unlock since boot, which is the usual choice for tokens used in background tasks.
44. When a BGAppRefreshTask runs
You submit a BGAppRefreshTaskRequest with earliestBeginDate set to 15 minutes from now. When will the task run?
- A.Exactly 15 minutes from now
- B.Every 15 minutes for as long as the app is installed, because a submitted request repeats on that interval until you cancel it with the scheduler
- C.Immediately, and again after 15 minutes
- D.No earlier than 15 minutes from now; the system chooses the time based on usage patterns, battery and network, and it may run much later or not at all
Show answer
Answer: D. No earlier than 15 minutes from now; the system chooses the time based on usage patterns, battery and network, and it may run much later or not at all
earliestBeginDate is a lower bound, not a schedule. iOS runs refresh tasks when it predicts the user will open the app soon and conditions are good, and rarely-used apps get fewer chances. Each run must schedule the next request, and critical sync should also happen when the app comes to the foreground.
45. App killed during a slow launch
An iOS app downloads config synchronously in didFinishLaunching. On slow networks, the system kills it before the first screen appears. Why?
- A.iOS forbids any network calls during the app launch phase
- B.A watchdog kills apps that block the main thread at launch
- C.The app ran out of free disk space for the download
- D.Synchronous code is not allowed in Swift apps
Show answer
Answer: B. A watchdog kills apps that block the main thread at launch
iOS uses a watchdog that terminates apps that do not finish launching or stay unresponsive for too long, with crash logs showing the 0x8badf00d code. Launch should show UI fast and load remote config asynchronously, using cached or default values meanwhile.
46. Core Data objects across threads
An iOS app fetches NSManagedObjects on a background context and passes them to the main thread for display. It crashes randomly. What is the rule?
- A.Managed objects are tied to their context; pass objectIDs instead
- B.Core Data can only be used from background threads
- C.Each fetch must create a new persistent store file
- D.Managed objects must be converted to JSON first
Show answer
Answer: A. Managed objects are tied to their context; pass objectIDs instead
An NSManagedObject belongs to the context that loaded it and must be used only on that context's queue (via perform). To hand data across, pass the NSManagedObjectID and fetch it in the other context. SwiftData has similar rules with ModelContext and model actors.
47. Large downloads in Documents on iOS
An iOS app stores 2 GB of re-downloadable videos in the Documents directory. Why might Apple reject it?
- A.Documents is limited to 100 MB per app
- B.Videos can only be stored in the Photos library
- C.Documents is shared with every other app on the phone
- D.Documents is backed up to iCloud; re-downloadable data must not be
Show answer
Answer: D. Documents is backed up to iCloud; re-downloadable data must not be
Apple's data storage rules say user-created content goes in Documents, which is backed up. Content that can be downloaded again belongs in Caches, or must be marked with isExcludedFromBackup. Large backed-up data fills users' iCloud storage and is a known rejection reason.
48. Downloads that continue after the app closes
An iOS app downloads 500 MB course videos. Users switch apps and the download stops. Which API lets the system continue the transfer?
- A.A Timer that restarts the download every 30 seconds
- B.A background URLSessionConfiguration with a download task
- C.A default URLSession on a background DispatchQueue
- D.A silent push notification sent every minute
Show answer
Answer: B. A background URLSessionConfiguration with a download task
A background URLSession hands the transfer to a system daemon, which keeps downloading while the app is suspended or even terminated. When it finishes, iOS wakes the app to handle the result through the session delegate. Normal sessions stop when the app is suspended.
49. Extension method that is not a requirement
What does this print?
protocol Greeter {}
extension Greeter {
func hello() -> String { "base" }
}
struct Hindi: Greeter {
func hello() -> String { "namaste" }
}
let g: any Greeter = Hindi()
print(g.hello())- A.namaste
- B.base
- C.It does not compile
- D.base namaste
Show answer
Answer: B. base
hello() is defined only in the protocol extension, not as a requirement. Calls through an any Greeter value are dispatched statically to the extension. If hello() were declared in the protocol, the call would use dynamic dispatch and print "namaste".
50. some Shape or any Shape
Which statement about some Shape and any Shape is correct?
- A.some can hold different types at runtime; any fixes one
- B.any can hold different conforming types; some fixes one type
- C.They are identical; any is only newer syntax
- D.any is only allowed for class protocols
Show answer
Answer: B. any can hold different conforming types; some fixes one type
some is an opaque type: one specific concrete type the compiler knows. any is an existential box that can hold any conforming value, with some dynamic cost. Prefer some or generics, and use any for heterogeneous collections.
51. Typed throws
With Swift 6 typed throws, what is the type of error in the catch block?
func pay() throws(PaymentError) { /* ... */ }
do {
try pay()
} catch {
// error’s type here?
}- A.any Error (existential)
- B.Optional<Error>
- C.NSError from Foundation
- D.PaymentError
Show answer
Answer: D. PaymentError
throws(PaymentError) declares the exact error type, so a do/catch around only that call gets error as PaymentError without casting. Plain throws is the same as throws(any Error). Typed throws suits closed error domains and embedded code.
52. Counting an emoji
What does this print?
let s = "👍🏽"
print(s.count, s.utf16.count)
- A.2 4
- B.1 4
- C.1 2
- D.4 4
Show answer
Answer: B. 1 4
Swift’s count counts grapheme clusters, so 👍🏽 (thumbs up plus skin tone modifier) is one Character. In UTF-16 it is two scalars of two code units each, so utf16.count is 4. This matters for text limits and NSRange.
53. Turning a delegate into an AsyncSequence
You want to expose location updates from a delegate-based API as for await. What is the usual tool?
- A.AsyncStream with a continuation that yields
- B.A single withCheckedContinuation call
- C.DispatchGroup with paired enter and leave
- D.A Timer that fires once every second
Show answer
Answer: A. AsyncStream with a continuation that yields
AsyncStream gives a continuation you can yield to from callbacks and finish when done. Set onTermination to stop the underlying updates when the consumer cancels. A checked continuation suits only one result.
54. What the $ prefix gives
For a property declared with a property wrapper, what does the $ prefix (as in $isOn for @State var isOn) access?
- A.The wrapper’s stored value as an optional
- B.The memory address of the property
- C.A copy of the property made at init
- D.The wrapper’s projectedValue, for @State a Binding
Show answer
Answer: D. The wrapper’s projectedValue, for @State a Binding
A property wrapper exposes wrappedValue (used by the plain name) and can expose projectedValue through $. @State projects a Binding, and @Published projects a Combine publisher.
55. Rows showing the wrong data
A List uses ForEach(names, id: \.self). When two people share the name "Priya", editing one row updates the other. Why?
- A.id: \.self only works for Int arrays
- B.Duplicate values give two rows the same identity
- C.List always mixes up rows with strings
- D.Text views cannot show duplicate strings
Show answer
Answer: B. Duplicate values give two rows the same identity
SwiftUI uses the id to track rows. With \.self, equal values share an identity, which confuses diffing, state and animations. Use a model with a unique stable ID, such as a UUID or database key.
56. sync on the main queue
What happens when this code runs on the main thread?
DispatchQueue.main.sync {
label.text = "Done"
}- A.The block runs immediately, then the rest
- B.The block runs later on a background thread
- C.It deadlocks; GCD usually traps with a crash
- D.The block is ignored without any error
Show answer
Answer: C. It deadlocks; GCD usually traps with a crash
sync waits for the block to finish on the target queue. The main queue is serial and is busy running this very code, so the block can never start. libdispatch detects this and crashes. Use async, or call the code directly.
57. App rejected for UserDefaults use
App Store Connect flags your app for using UserDefaults and file timestamp APIs without declared reasons. What is required?
- A.A PrivacyInfo.xcprivacy declaring reasons for those APIs
- B.Removing every use of UserDefaults from the app
- C.Asking users for App Tracking Transparency consent
- D.Adding a privacy policy URL in App Store Connect
Show answer
Answer: A. A PrivacyInfo.xcprivacy declaring reasons for those APIs
Apple’s privacy manifest rules require apps and SDKs to declare "required reason" APIs, such as UserDefaults, file timestamps, system boot time and disk space, with an approved reason code. Third-party SDKs need their own manifests too.
58. @State ignores the new value
A view initialises @State from a parameter. The parent later passes a different start value, but the view keeps showing the old one. Why?
struct Stepper: View {
@State private var count: Int
init(start: Int) { _count = State(initialValue: start) }
var body: some View { Text("\(count)") }
}- A.Parameters cannot be passed to views with state
- B.@State is set only when the view’s identity is first created
- C.The parent must call objectWillChange.send()
- D.State values reset only on device rotation
Show answer
Answer: B. @State is set only when the view’s identity is first created
SwiftUI keeps @State storage for the life of the view’s identity and ignores later initial values. Use a @Binding or plain property if the parent owns the value, or change the view’s .id() to reset it on purpose.
59. Which views update with @Observable
A model marked @Observable has name and cartCount. A header view reads only name. When cartCount changes, does the header’s body run again?
- A.Yes; any change to the model updates every view
- B.Yes, but only if the view uses @StateObject
- C.No; views track only the properties they read
- D.No; @Observable models never update views
Show answer
Answer: C. No; views track only the properties they read
The Observation framework records which properties a view’s body accessed and invalidates only on changes to those. ObservableObject with @Published, by contrast, notifies every observer on any change, which can cause extra updates.
60. Search that restarts on new text
A view uses .task(id: query) { await search(query) }. What happens when query changes while a search is running?
- A.Both searches run and both update the view
- B.The new query is ignored until the old ends
- C.The view crashes with a concurrency error
- D.The running task is cancelled and a new one starts
Show answer
Answer: D. The running task is cancelled and a new one starts
.task(id:) ties the task to a value. When the value changes, SwiftUI cancels the old task and starts a new one, and it also cancels when the view disappears. The search code must respect cancellation for this to stop work.