# GET Requests

In this section, we will be using Alamofire and callbacks to perform network requests, specifically GET requests.

### Creating a NetworkManager Class

It is common in iOS development to create a class that represents our backend calls. Let’s create this class and name it `NetworkManager`. Make sure to import Alamofire.

```swift
import Alamofire

class NetworkManager {
    /// Shared singleton instance
    static let shared = NetworkManager()
    
    // Prevent other instances from being created
    private init() { }
}
```

We create this static variable called `shared` that holds a singleton and make the initializer `private`. This guarantees that only one instance of this class is created. To access this variable, we can simply use `NetworkManager.shared`

### Creating a GET Request

Inside of this `NetworkManager` class, we write functions to be called by our application. These functions will use Alamofire to send HTTP requests.

Assume there is a struct called `Member` that represents an AppDev member.

```swift
struct Member: Codable {
    let name: String
    let subteam: String
    let position: String
}
```

We can write the following code to fetch the AppDev roster.

```swift
// 1. Create the function
func fetchRoster(completion: @escaping ([Member]) -> Void) {
    // 2. Specify the endpoint
    let endpoint = "<Enter URL String Here>"
    
    // 3. Create a decoder
    let decoder = JSONDecoder()
    // decoder.dateDecodingStrategy = .iso8601 // Only if needed
    // decoder.keyDecodingStrategy = .convertFromSnakeCase // Only if needed
    
    // 4. Create the request
    AF.request(endpoint, method: .get)
        .validate()
        .responseDecodable(of: [Member].self, decoder: decoder) { response in
	    // 5. Handle the response
            switch response.result {
            case .success(let members):
                print("Successfully fetched \(members.count) members")
                completion(members)
            case .failure(let error):
                print("Error in NetworkManager.fetchRoster: \(error)")
            }
        }
}
```

Let’s break down this code.

1. Create a function called `fetchRoster` that takes in a callback (completion handler) as an argument. This callback takes in an array of `Member` objects.
2. Specify the endpoint which is the URL that we will call to fetch the data. We can test this with Postman.
3. Create a decoder to decode the data. If our object contains a `Date` property, then we need to specify the date decoding strategy. If the JSON contains fields with snake\_case, then we also need to specify the key decoding strategy.
4. Create the request using Alamofire.
   * Pass in the endpoint and specify the method (`.get` for GET, `.post` for POST, etc).
   * Validate the request to ensure that the status code is 2xx and if the content type matches.
   * Decode the response to `[Member]` using the decoder we created in Step 3.
5. Perform a switch statement on the response’s result.
   * If successful, pass to the callback the decoded response. A print statement is optional but recommended.
   * If failed, print an error statement about the error.

{% hint style="info" %}
**Note that we do not have to create the decoder inside of the function call. We can create a&#x20;**<mark style="color:red;">**`JSONDecoder`**</mark>**&#x20;object and store it as a property in our&#x20;**<mark style="color:red;">**`NetworkManager`**</mark>**&#x20;class to be used for all functions. The same can be applied to the endpoint variable containing a String value of our endpoint.**
{% endhint %}

### Calling the Network Request

{% hint style="danger" %}
**There is a problem with this code that will be discussed in the next section below.**
{% endhint %}

```swift
NetworkManager.shared.fetchRoster { fetchedMembers in
    // Do something with the data such as...
    self.members = fetchedMembers
    
    DispatchQueue.main.async {
        // Perform UI updates such as...
        self.collectionView.reloadData()
    }
}
```

We call the function that we just created inside of the `NetworkManager` class. Remember to use `NetworkManager.shared`. To access the value passed into the callback, we simply use the `in` keyword. For example, earlier we passed in an array of `Member` objects to the callback. In this case, `fetchedMembers` is holding the value that was passed in. We can call the variable whatever we want but it is very helpful to make it representative of the data.

Then, we do whatever we want with the data that we just fetched. If we are referencing a property outside of this function call, we will need to use `self`.

Next, if we need to perform any UI updates such as updating a collection view, we put that in a `DispatchQueue.main.async` block. This runs any code inside of the block on the main queue asynchronously. This is an advanced topic but the reason for this is so that the UI does not freeze while we are sending a network request.

### Using `weak self`

Although the above code works, there is one major issue: we are retaining a **strong reference** to `self` inside of a closure which can cause retain cycles (causing memory leaks). To go around this, we will retain a **weak reference** to self inside of the closure and then unwrap a strong reference. We do this by adding `[weak self]` in our closure and using a `guard let` to unwrap a strong reference.

```swift
NetworkManager.shared.fetchRoster { [weak self] fetchedMembers in
    // Unwrap a strong reference
    guard let self = self else { return }

    // Do something with the data such as...
    self.members = fetchedMembers
    
    DispatchQueue.main.async {
	// Perform UI updates such as...
        self.collectionView.reloadData()
    }
}
```

You may have noticed that we also use `weak` when creating our delegate properties. **This is an advanced topic that we will discuss later, but for now, make sure to use `weak self` every time we need to use `self` in our networking calls.**
