I'm having an issue developing my app. The thing is this, when I enter a certain code, a JSON file starts downloading and got saved in an array. That is fine, the problem is ones it finishes downloading I tried to automatically save the information in CoreData, by running this code below.
var i:Int = 0
while(i < itemsDescargados.count){
let invitacionItems = Invitem(context: self.mangedObjectContext)
invitacionItems.titulo = itemsDescargados[i].titulo
invitacionItems.modo = itemsDescargados[i].modo
invitacionItems.id = itemsDescargados[i].id
invitacionItems.descripcion = itemsDescargados[i].descripcion
invitacionItems.category = itemsDescargados[i].category
invitacionItems.rojo = itemsDescargados[i].rojo
invitacionItems.verde = itemsDescargados[i].verde
invitacionItems.azul = itemsDescargados[i].azul
invitacionItems.imagen = itemsDescargados[i].imagen
do {
try self.mangedObjectContext.save()
print("Downloaded and Saved!")
}
catch{
print("errorCoreData: \(error)")
}
i = i + 1
}
When I put this code into a button and tap it myself it works perfectly, it saves the data and everything is fine, but the problem is that if I run this code into the network manager class class NetworkManagerInv: ObservableObject { after the download it's finished then when the app runs try self.mangedObjectContext.save() automatically prints in the Console log "errorCoreData:nilError" and doesn't save anything.
I did a lot of research but I didn't find anything helpful so I hope you guys can help me. Thanks in advance, sorry if I misspelled something English is not my main language.
Here the NetworkManagerCode
class NetworkManagerInv: ObservableObject {
#Environment(\.managedObjectContext) var mangedObjectContext
#FetchRequest(fetchRequest: Invitem.getAllItems()) var invitem:FetchedResults<Invitem>
var didChange = PassthroughSubject<NetworkManagerInv, Never>()
var jsoninvItem = [JsoninvItem]() {
didSet {
didChange.send(self)
}
}
init(){
let configuration = URLSessionConfiguration.ephemeral
let session = URLSession(configuration: configuration)
print("Network")
guard let url = URL(string: "http://192.168.1.42:" + codigo + "/items.json") else { return }
session.dataTask(with: url) { (data, _, _) in
guard let data = data else { return }
let items2 = try! JSONDecoder().decode([JsoninvItem].self, from: data)
DispatchQueue.main.async {
self.jsoninvItem = items2
itemsDescargados = items2
if(imgDescargadas.count>0){
//Here i need to put the code, this runs ones it finished
}
}
print("Compleated fetching json items")
}.resume()
}
Related
I have two viewmodels, one contains the necessary data for the user, a classic api fetch, and the second viewmodel is also an api fetch, but I want to insert the data from the first into the second, whenever I try, it always outputs nil, although before that I ensure that it cannot output it because I know it exists because the fetch function was called first.
class UserPostViewModel: ObservableObject {
#Published var user: UserResponse?
#Published var phoneNumber:String = ""
#Published var firstName:String = ""
#Published var lastName:String = ""
#Published var selectedImage: UIImage?
#Published var normalizedMobileNumberField:String = ""
func createUser() {
guard let url = URL(string: "privateapi") else {
return
}
request.httpBody = try? JSONSerialization.data(withJSONObject: body, options: .fragmentsAllowed)
let task = URLSession.shared.dataTask(with: request) {data, response, error in
guard let data = data, error == nil else{
return
}
do {
let response = try? JSONDecoder().decode(UserResponse.self, from: data)
if self.normalizedMobileNumberField != "The number you enter is invalid." {
DispatchQueue.main.async {
self.user = response
if let userData = self.user {
print(userData.data.id)
}
}
} else {
print("You can't register application")
}
}
catch {
print(error)
}
}
task.resume()
}
}
This is the second one:
class GetTokenViewModel: ObservableObject {
#Published var tokenData: DataToken?
var userVM = UserPostViewModel()
func fetchData() {
let urlString = "privateapi/\(userVM.user.token)"
guard let url = URL(string: urlString) else { return }
var request = URLRequest(url:url)
URLSession.shared.dataTask(with: request) { data, response, error in
guard let data = data else {
return
}
do {
let decodedData = try JSONDecoder().decode(ResponseToken.self, from: data)
DispatchQueue.main.async{
self.tokenData = decodedData.data
print("TOKEN: \(self.tokenData?.token)")
}
} catch {
print("Error decoding JSON in gettoken: \(error)")
}
}.resume()
}
}
How to pass data from First ViewModel to the second?
I know that these codes have some error but I deleted every private information, but I believe that there is minimal code for understanding my question.
In SwiftUI, the View struct is equivalent to a view model but it is an immutable value type instead of an object that helps prevent consistency bugs. The property wrappers help it behave like an object. You pass data down the View struct hierarchy either with let if you want read access or #Binding var if you want write access. SwiftUI will track that these lets/vars are read in body and then body will be called whenever changes are detected. Transform the data as you pass it along from complex model types to simple value types.
#State declares a source of truth and #StateObject is when you need a reference type in an #State because you are doing something asynchronous or need to work around the limitations of closures in structs. Because these states are sources of truth you can't pass anything in because they won't be able to detect changes the same way the View struct can with let or #Binding var.
The best way to call web API in SwiftUI now is .task and .task(id:). The task will start when the view appears, cancelled if it disappears, or restarted if the id changes. This can completely remove the need for an object.
I have a scenario where I need to load data from a JSON object, I've created an helper class that does that and it looks like this
protocol JSONDumpHelperDelegate {
func helper(_: JSONDumpHelper, didFinishFetching: [Link])
func helper(_: JSONDumpHelper, completionProcess: Double)
}
struct JSONDumpHelper {
static let pointsOfInterest = OSLog(subsystem: "com.mattrighetti.Ulry", category: .pointsOfInterest)
var delegate: JSONDumpHelperDelegate?
func loadFromFile(
with filemanager: FileManager = .default,
from url: URL,
decoder: JSONDecoder = JSONDecoder(),
context: NSManagedObjectContext = CoreDataStack.shared.managedContext,
dataFetcher: DataFetcher = DataFetcher()
) {
os_signpost(.begin, log: JSONDumpHelper.pointsOfInterest, name: "loadFromFile")
let data = try! Data(contentsOf: url)
let dump = try! decoder.decode(Dump.self, from: data)
var tagsHash: [UUID:Tag] = [:]
if let tagsCodable = dump.tags {
for tagCodable in tagsCodable {
let tag = Tag(context: context)
tag.id = tagCodable.id
tag.name = tagCodable.name
tag.colorHex = tagCodable.colorHex
tagsHash[tag.id] = tag
}
}
var groupHash: [UUID:Group] = [:]
if let groupsCodable = dump.groups {
for groupCodable in groupsCodable {
let group = Group(context: context)
group.id = groupCodable.id
group.name = groupCodable.name
group.colorHex = groupCodable.colorHex
group.iconName = groupCodable.iconName
groupHash[group.id] = group
}
}
var links: [Link] = []
if let linksCodable = dump.links {
let total = linksCodable.count
var completed = 0.0
delegate?.helper(self, completionProcess: 0.0)
for linkCodable in linksCodable {
let link = Link(context: context)
link.id = linkCodable.id
link.url = linkCodable.url
link.createdAt = linkCodable.createdAt
link.updatedAt = linkCodable.updatedAt
link.colorHex = linkCodable.colorHex
link.note = linkCodable.note
link.starred = linkCodable.starred
link.unread = linkCodable.unread
if let uuidGroup = linkCodable.group?.id {
link.group = groupHash[uuidGroup]
}
if let tags = linkCodable.tags {
link.tags = Set<Tag>()
for tagUUID in tags.map({ $0.id }) {
link.tags?.insert(tagsHash[tagUUID]!)
}
}
links.append(link)
completed += 1
delegate?.helper(self, completionProcess: completed / Double(total))
}
}
os_signpost(.end, log: JSONDumpHelper.pointsOfInterest, name: "loadFromFile")
}
}
This could potentially be a very long running task, just imagine an array with 1k records that also need to fetch data from the internet (not shown in implementation, error still exist with posted code) and you can easily end up with 10s in execution time.
What I'm trying to achieve is to show the user an alert that will show him the progress of the import process, by updating the values with the delegate protocols below.
extension BackupViewController: JSONDumpHelperDelegate {
func helper(_: JSONDumpHelper, didFinishFetching: [Link]) {
DispatchQueue.main.async {
self.completionViewController.remove()
}
}
func helper(_: JSONDumpHelper, completionProcess: Double) {
DispatchQueue.main.async {
self.completionViewController.descriptionLabel.text = "Completed \(Int(completionProcess * 100))%"
}
}
}
The import method is fired from a UITableView, immediately after the user choses a file from a DocumentPickerViewController
extension BackupViewController: UIDocumentPickerDelegate {
func documentPickerWasCancelled(_ controller: UIDocumentPickerViewController) {
dismiss(animated: true, completion: nil)
}
func documentPicker(_ controller: UIDocumentPickerViewController, didPickDocumentsAt urls: [URL]) {
self.initImport(for: urls)
}
}
private func initImport(for urls: [URL]) {
if let url = urls.first {
completionViewController.add(self, frame: self.view.bounds)
completionViewController.descriptionLabel.text = "Fetching"
DispatchQueue.global(qos: .userInteractive).async {
self.dumpHelper.loadFromFile(from: url)
}
}
}
The problem I am facing is that when the user initiates the import process, the UI is not updated until the process itself is finished.
If I place breakpoints both at the protocol implementations and at the delegate calls in the helper class I can see that the delegate is not called immediately but they all get fired when the process ended (but the alert controller does not update its values).
Just to place some more info I'm going to replicate an import of N elements from JSON:
User clicks import process
initImport is executed (nothing is shown on UI even if I add the custom vc to view)
JSONDumpHelper.loadFromFile is executed entirely, calling delegate N times, nothing called in the delegate implementation
loadFromFile finishes execution
delegate implementation of helper(_:, completionProcess) is executed N times, UI always shows "Completed 0%"
delegate implementation of helper(_:, didFinishFetching) is executed, controller is removed from view
Can anybody point out what is wrong with this implementation? It seems like the loadFromFile function is not working in a separate Queue and UI is stuck and can't update as expected.
I'm really new into swift & currently learning API by doing a project that shows list of games from rawg.io referring to the website's doc. I created GameFeed.swift & GameDetail.swift to pull name, release date, and rating from it and working fine in my console.
GameFeed.swift :
struct GameFeed: Codable {
let results:[GameDetail]
}
GameDetail.swift :
struct GameDetail: Codable {
let name:String
let released:String
let rating:Double
}
Now i'm trying to put the results to a simple UIlabel like gameName.text, gameReleased.text & gameRating.text from ViewController.swift so it will be show in Main.Storyboard
i did research on google about how to show it to these UIlabel by using DispatchQueue.main.async but when i'm declaring it, it receiving error :
Value of type 'GameFeed' has no member 'name'
same error messages also happened to released & rating. This is my ViewController.Swift :
class ViewController: UIViewController {
#IBOutlet weak var gameName: UILabel!
#IBOutlet weak var gameReleased: UILabel!
#IBOutlet weak var gameRating: UILabel!
override func viewDidLoad() {
super.viewDidLoad()
// Defining API Site
let urlString = "https://api.rawg.io/api/games"
let url = URL(string: urlString)
guard url != nil else {
return
}
// Calling API
let session = URLSession.shared
let dataTask = session.dataTask(with: url!){
(data, response, error) in
if error == nil && data != nil {
let decoder = JSONDecoder()
do {
let gameFeed = try decoder.decode(GameFeed.self, from: data!)
print(gameFeed)
DispatchQueue.main.async {
self.gameName.text = gameFeed.name
self.gameReleased.text = gameFeed.released
self.gameRating.text = gameFeed.rating
}
}
catch {
print("Error Parsing JSON")
}
}
}
dataTask.resume()
}
}
What should i do to make it possible to parse the data to labels?
The GameFeed contains an Array of GameDetails. But you are trying to set a single GameDetail on those labels. You should first pull out a single GameDetail from that array, then assign it in a way you like.
DispatchQueue.main.async {
let gameDetail = gameFeed.results.first // <- This will return the first one
self.gameName.text = gameDetail?.name
self.gameReleased.text = gameDetail?.released
self.gameRating.text = gameDetail?.rating
}
This is my working code to fetch one item from NASA API I used the completion handler as presented on Apple Programming book.
class PhotoInfoController {
func fetchPhotoInfo(completion: #escaping (PhotoInfo?) -> Void) {
let baseURL = URL(string: "https://api.nasa.gov/planetary/apod")!
let query: [String:String] = [
"api_key" : "DEMO_KEY"
]
let url = baseURL.withQueries(query)!
let task = URLSession.shared.dataTask(with: url) {
(data, response, error) in
let jsonDecoder = JSONDecoder()
if let data = data,
let photoInfo = try? jsonDecoder.decode(PhotoInfo.self, from: data) {
completion(photoInfo)
} else {
print("Not found or data is not sanitazed.")
completion(nil)
}
}
task.resume()
}
}
The problem I having a hard time to figuring it out is how you can return an array go items (PhotoInfo) via a completion handler. This is my code so far:
class PhotoInfoController {
func fetchPhotoInfo(completion: #escaping ([PhotoInfo]?) -> Void) {
let baseURL = URL(string: "https://api.nasa.gov/planetary/apod")!
let currentDate = Date()
let formatter = DateFormatter()
formatter.dateFormat = "YYYY-MM-d"
var photoInfoCollection: [PhotoInfo] = []
for i in 0 ... 1 {
let modifiedDate = Calendar.current.date(byAdding: .day,value: -i ,to: currentDate)!
let stringDate = formatter.string(from: modifiedDate)
let query: [String:String] = [
"api_key" : "DEMO_KEY",
"date" : stringDate
]
let url = baseURL.withQueries(query)!
let task = URLSession.shared.dataTask(with: url) { (data,
response,
error) in
let jsonDecoder = JSONDecoder()
if let data = data,
let photoInfo = try? jsonDecoder.decode(PhotoInfo.self, from: data) {
photoInfoCollection.append(photoInfo)
} else {
print("Data was not returned")
}
}
task.resume()
}
completion(photoInfoCollection)
}
}
Any ideas or guide will greatly appreciated Thanks!
Code Implemented after suggestions:
class PhotoInfoController {
private let baseURL = URL(string: "https://api.nasa.gov/planetary/apod")!
private let currentDate = Date()
private let dateFormatter: DateFormatter = {
let formatter = DateFormatter()
formatter.dateFormat = "YYYY-MM-d"
return formatter
}()
private let jsonDecoder = JSONDecoder()
func fethPhotoInfo(itemsToFetch: Int, completion: #escaping ([PhotoInfo]?) -> Void) {
var count = 0
var photoInfoCollection: [PhotoInfo] = []
for i in 0 ... itemsToFetch {
let modifiedDate = Calendar.current.date(byAdding: .day, value: -i, to: currentDate)!
let query: [String : String] = [
"api_key" : "DEMO_KEY",
"date" : dateFormatter.string(from: modifiedDate)
]
let url = baseURL.withQueries(query)!
let task = URLSession.shared.dataTask(with: url) {
(data, response, error) in
if let data = data,
let photoInfo = try? self.jsonDecoder.decode(PhotoInfo.self, from: data) {
photoInfoCollection.append(photoInfo)
count += 1
if count == itemsToFetch {
completion(photoInfoCollection)
}
} else {
print("Data for \(self.dateFormatter.string(from: modifiedDate)) not made.")
}
}
task.resume()
}
}
}
Your code won't work as written.
You use a for loop to start 2 URLSession dataTask objects. Those tasks are async; the code that invokes the network request returns right away, before the request has even been sent.
Then outside your for loop you invoke your completion handler, before your network requests have even had a chance to be sent out. You will need a mechanism to keep track of the number of pending requests and invoke the completion handler when both requests have finished.
Consider this function that simulates what you are doing:
func createAsyncArray(itemCount: Int, completion: #escaping ([Int]) -> Void) {
var count = 0; //Keep track of the number of items we have created
var array = [Int]() //Create an empty results array
//Loop itemCount times.
for _ in 1...itemCount {
let delay = Double.random(in: 0.5...1.0)
//Delay a random time before creating a random number (to simulate an async network response)
DispatchQueue.main.asyncAfter(deadline: .now() + delay) {
//Add a random number 1...10 to the array
array.append(Int.random(in: 1...10))
//Increment the number of results we have added to the array
count += 1
print("In loop, count = \(count)")
//If we have have added enough items to the array, invoke the completion handler.
if count == itemCount {
completion(array)
}
}
}
print("at this point in the code, count = \(count)")
}
You might call that code like this:
let itemCount = Int.random(in: 2...10)
print("\(itemCount) items")
createAsyncArray(itemCount: itemCount) { array in
for i in 0..<itemCount {
print("array[\(i)] = \(array[i])")
}
}
Sample output from that function might look like this:
9 items
at this point in the code, count = 0
In loop, count = 1
In loop, count = 2
In loop, count = 3
In loop, count = 4
In loop, count = 5
In loop, count = 6
In loop, count = 7
In loop, count = 8
In loop, count = 9
array[0] = 8
array[1] = 6
array[2] = 5
array[3] = 4
array[4] = 7
array[5] = 10
array[6] = 2
array[7] = 4
array[8] = 7
Note that the output displays "at this point in the code, count = 0" before any of the entries have been added to the array. That's because each call to DispatchQueue.main.asyncAfter() returns immediately, before the code inside the closure has been executed.
The function above uses a local variable count to keep track of how many items have been added to the array. Once the count reaches the desired number, the function invokes the completion handler.
You should use an approach like that in your code.
Edit:
You should be aware that your network requests may complete out of order. Your code submits itemsToFetch+1 different requests. You have no idea what order those requests will finish in, and it is very unlikely that the requests will complete in the order they are submitted. If your second request completes faster than the first, its closure will execute first.
You're complicating it for yourself with trying to do everything in one method. Imagine you have the fetchPhotoInfo function working (it actually works, so, good job so far):
struct PhotoInfo: Codable {
let copyright: String
}
class PhotoInfoController {
private let base = "https://api.nasa.gov/planetary/apod"
private let dateFormatter: DateFormatter = {
let formatter = DateFormatter()
formatter.dateFormat = "YYYY-MM-d"
return formatter
}()
func fetchPhotoInfo(forDate date: Date, completion: #escaping (PhotoInfo?) -> Void) {
guard var components = URLComponents(string: base) else {
completion(nil)
return
}
components.queryItems = [
URLQueryItem(name: "api_key", value: "DEMO_KEY"),
URLQueryItem(name: "date", value: dateFormatter.string(from: date))
]
guard let url = components.url else {
completion(nil)
return
}
let task = URLSession.shared.dataTask(with: url) { (data, response, error) in
guard let data = data else {
completion(nil)
return
}
let photoInfo = try? JSONDecoder().decode(PhotoInfo.self, from:data)
completion(photoInfo)
}
task.resume()
}
}
Your next goal is to fetch multiple photo info. Keep your class, keep your method and add another one which leverages what you already have. One way to achieve this is to use DispatchGroup:
func fetchPhotoInfo(forDates dates: [Date], completion: #escaping ([PhotoInfo]) -> Void) {
// Group of tasks
let taskGroup = DispatchGroup()
// Result of photos
var result: [PhotoInfo] = []
// For each date ...
dates.forEach {
// ... enter the group
taskGroup.enter()
// Fetch photo info
fetchPhotoInfo(forDate: $0) { photoInfo in
defer {
// Whenever the fetchPhotoInfo completion closure ends, leave
// the task group, but no sooner.
taskGroup.leave()
}
// If we've got the photo ...
if let photoInfo = photoInfo {
// ... add it to the result. We can safely add it here, because
// the fetchPhotoInfo completion block is called on the
// URLSession.shared.delegateQueue which has maxConcurrentOperationCount
// set to 1 by default. But you should be aware of this,
// do not rely on it and introduce some kind of synchronization.
result.append(photoInfo)
}
}
}
// At this point, we told the URLSession (via fetchPhotoInfo) that we'd like to
// execute data tasks. These tasks already started (or not, we don't know), but
// they didn't finish yet (most likely not, but we don't know either). That's
// the reason for our DispatchGroup. We have to wait for completion of all
// our asynchronous tasks.
taskGroup.notify(queue: .main) {
completion(result)
}
}
You can use it in this way:
let pic = PhotoInfoController()
pic.fetchPhotoInfo(forDate: Date()) { info in
print(String(describing: info))
}
pic.fetchPhotoInfo(forDates: [Date(), Date().addingTimeInterval(-24*60*60)]) { infos in
print(infos)
}
And the output is:
Optional(NASA.PhotoInfo(copyright: "Stephane Guisard"))
[NASA.PhotoInfo(copyright: "Stephane Guisard"), NASA.PhotoInfo(copyright: "Zixuan LinBeijing Normal U.")]
There's no error handling, you have to add it yourself.
Even if I did provide an answer to your question, I'm going to mark it as a duplicate of Wait until swift for loop with asynchronous network requests finishes executing. Your code to fetch single photo info works and you are just struggling to understand how to wait for multiple asynchronous tasks.
I am currently writing an analytics system.
Currently, it caches Events in RAM.
It writes to the Filesystem via NSUserDefaults (iOS) and SharedPreferences (Android) when the App closes, as JSON.
This Data is read when the app opens.
It also sends every N seconds or when the amount of Events reaches 20.
When the sending was successful, it deletes all events that were send from the RAM.
This has some obvious flaws: When the app crashes, all data from N seconds is lost. When the server cannot be reached (because Server is down for example) and the app crashes, even more data are lost.
My question here is: How can I improve the "safety" of my data and prevent massive data loss when the server is down or not reachable?
Here is my current code (unimportant parts removed)
import Foundation
class BackendTrackingHandler : TrackingHandler {
static let KEY_CACHE_EVENT = "TrackingCache"
private static let SEND_INTERVAL:TimeInterval = 10
var cachedEvents: [TrackingEvent] = []
var temporaryCachedEvents: [TrackingEvent] = []
var prefix: String
var endpoint: String
var timer : Timer?
//whether we currently wait for a response
var isSending: Bool = false
override init() {
//init
readCachedEventsFromDisk()
timer = Timer.scheduledTimer(timeInterval: BackendTrackingHandler.SEND_INTERVAL, target: self, selector: #selector(send), userInfo: nil, repeats: true)
}
override func trackEvent(_ event: TrackingEvent) {
cachedEvents.append(event)
if((cachedEvents.count) >= 20) {
send()
}
}
#objc func send() {
if((cachedEvents.count) < 1) {
return
}
if(isSending) {
return
}
isSending = true
let enc = JSONEncoder()
enc.outputFormatting = .prettyPrinted
let data = try! enc.encode(cachedEvents)
// Constructring Request here
let session = URLSession.shared
//while the request is on the way, we can trigger new events. Make a temporary copy
temporaryCachedEvents = cachedEvents
let taksID = UIApplication.shared.beginBackgroundTask()
let task = session.dataTask(with: request) { (data: Data?, response: URLResponse?, error: Error?) -> Void in
if(error != nil)
{
self.isSending = false
UIApplication.shared.endBackgroundTask(taksID)
}else {
let httpResponse = response as! HTTPURLResponse
if(httpResponse.statusCode >= 200 && httpResponse.statusCode <= 299) {
//success, Data was sent so we can create a new cached event
//remove all events we already sent
self.cachedEvents = self.cachedEvents.filter{!self.temporaryCachedEvents.contains($0)}
self.isSending = false
UIApplication.shared.endBackgroundTask(taksID)
}else {
self.isSending = false
UIApplication.shared.endBackgroundTask(taksID)
}
}
}
task.resume()
}
func readCachedEventsFromDisk() {
let dec = JSONDecoder()
guard let data = UserDefaults.standard.data(forKey: BackendTrackingHandler.KEY_CACHE_EVENT) else {
cachedEvents = []
return
}
do {
cachedEvents = try dec.decode([TrackingEvent].self, from: data)
} catch {
cachedEvents = []
}
}
func writeCachedEventsToDisk() {
let enc = JSONEncoder()
let data = try! enc.encode(cachedEvents)
UserDefaults.standard.set(data, forKey: BackendTrackingHandler.KEY_CACHE_EVENT)
}
override func onApplicationBecomeActive() {
}
override func onApplicationBecomeInactive() {
let taskID = UIApplication.shared.beginBackgroundTask()
writeCachedEventsToDisk()
UIApplication.shared.endBackgroundTask(taskID)
}
}
€dit:
TrackingEvent is a struct that is shared among multiple TrackingHandlers. There is an additional FirebaseTrackingHandler, which is meant to be operated side-by-side our own analytics system.
I think the easiest way is writing "Property Wrapper" for cachedEvents so it would directly access to UserDefaults, it seems the operation is not so huge to bother.
Second way - you could simply save cache to UserDefaults every N seconds/minutes or so if you care about performance a lot. Though, it wouldn't made your system bulletproof