首页
学习
活动
专区
圈层
工具
发布
社区首页 >问答首页 >用于GithubUserFinder的POC

用于GithubUserFinder的POC
EN

Code Review用户
提问于 2020-06-03 08:32:20
回答 1查看 113关注 0票数 4

我想请iOS开发者社区回顾我的POC,也就是我创建的GithubUserFinder,这是T手机孟加拉语工作筛选测试的一部分。

我有大约两年的编程经验,我有24小时来完成这个POC。

下面是招聘人员通过邮件得到的反馈:至于Pooja,我可能会说不。他们的代码质量有点混乱和稀疏。他们错过了一部分任务,他们的简历看起来不太好,他们错过了一年的工作。

我要求他们提供一些关于“杂乱无章、稀疏的代码”或赋值中“丢失”什么的指针,但没有得到答复。

恳请各位通过我的POC,即GithubUserFinder,并提供您的宝贵意见。

这里是用户搜索功能的代码:

1. ViewController (搜索用户)

代码语言:javascript
复制
import UIKit
import Alamofire

class ViewController: UIViewController {

    @IBOutlet private weak var tableView: UITableView!
    @IBOutlet private weak var searchBar: UISearchBar!

    //datasource
    private var users: [User] = [] {
        didSet {
            tableView.reloadData()
        }
    }

    private enum constants {
        static let userCellNibName = "UserCell"
        static let cellReuseIdentifier = "Cell"
        static let searchUsersPlaceholderText = "Search Users"
        static let segueIdentifier = "detailSegue"
        static let placeholderImageName = "placeholder"
    }

    //MARK:- View life cycle
    override func viewDidLoad() {
        super.viewDidLoad()

        setupSearchBar()
        setUpTableView()
    }

    override func viewWillAppear(_ animated: Bool) {
        if let indexPath = tableView.indexPathForSelectedRow {
            tableView.deselectRow(at: indexPath, animated: true)
        }
    }

    //MARK:- Setup
    private func setUpTableView() {
        tableView.register(UINib(nibName: constants.userCellNibName, bundle: nil), forCellReuseIdentifier: constants.cellReuseIdentifier)
    }

    private func setupSearchBar() {
        searchBar.placeholder = constants.searchUsersPlaceholderText
    }

    private func searchUsers(for name: String) {
        ServiceManager.getUsers(for: name) {[weak self] (response) in
            guard let self = self else { return }
            guard let response = response as? SearchResponse else { return }
            self.users = response.users
        }
    }

    override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
        if segue.identifier == constants.segueIdentifier {
            guard let destinationVC = segue.destination as? UserDetailViewController else {
                return
            }
            destinationVC.user = sender as? User
        }
    }

}

//MARK:- TableView datasource
extension ViewController: UITableViewDataSource {
    func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
        return users.count
    }

    func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
        guard let cell: UserCell = tableView.dequeueReusableCell(withIdentifier: constants.cellReuseIdentifier, for: indexPath) as? UserCell else {
            return UITableViewCell()
        }
        let user = users[indexPath.row]
        cell.populate(user: user)
        return cell
    }
}

extension ViewController: UITableViewDelegate {
    func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
        tableView.deselectRow(at: indexPath, animated: true)
        performSegue(withIdentifier: constants.segueIdentifier, sender: users[indexPath.row]);
    }
}

//MARK:- SearchResultBar delegate
extension ViewController: UISearchBarDelegate {
    func searchBar(_ searchBar: UISearchBar, textDidChange searchText: String) {
        if let text = searchBar.text, !text.isEmpty {
            searchUsers(for: text)
        } else {
            users = []
        }
    }

    func searchBarSearchButtonClicked(_ searchBar: UISearchBar) {
        searchBar.endEditing(true)
    }
}

2. TableView Cell (UserCell.swift)

代码语言:javascript
复制
import UIKit
import AlamofireImage

class UserCell: UITableViewCell {

    @IBOutlet private weak var userImageView: UIImageView!

    @IBOutlet private weak var nameLabel: UILabel!
    @IBOutlet private weak var repoLable: UILabel!

    private enum constants {
        static let placeholderImageName = "placeholder"
    }

    func populate(user: User) {
        nameLabel.text = user.login
        let placeholderImage = UIImage(named: constants.placeholderImageName)
        if let imageUrl = URL(string: user.avatarUrl) {
            userImageView.af.setImage(withURL: imageUrl, cacheKey: "\(AppConstants.kCacheImagekey)\(user.id)", placeholderImage: placeholderImage);
        }
    }
}

3.服务管理器(ServiceManager.swift)

代码语言:javascript
复制
import Foundation
import Alamofire

enum ServiceType {
    case users
    case details
    case repos
}

class ServiceManager {

    typealias CompletionHandler = (_ response:Any?) -> Void

    private static let baseUrl = "https://api.github.com/"

    // Add your client Id and client secret here..
    private static let clientId = ""
    private static let clientSecret = ""

    private static func getMethod(type: ServiceType) -> String {
        switch type {
        case .users:
            return "search/users?q="
        case .details, .repos:
            return "users/"
        }
    }

    private static func clientIdSecret() -> String {
        //Add your client Id and client secret if you hit max hour limit which is only
        // 50-60 request per hour. And enable this.
        //return "&client_id=\(ServiceManager.clientId)&client_secret=\(ServiceManager.clinetSecret)"
        return ""
    }

    public static func getUsers(for query: String, completion: @escaping CompletionHandler) {
        let url = baseUrl + getMethod(type: .users) + query + clientIdSecret()
        AF.request(url, parameters: nil).validate()
            .responseDecodable(of: SearchResponse.self) { response in
                guard let result = response.value else {
                    //TODO:- Currently the errors are not handled.
                    return
                }
                completion(result)
        }
    }

    public static func getDetails(for query: String, completion: @escaping CompletionHandler) {
        let url = baseUrl + getMethod(type: .details) + query
        AF.request(url, parameters: nil).validate()
            .responseDecodable(of: UserDetails.self) { response in
                guard let result = response.value else { return }
                completion(result)
        }
    }

    public static func getRepos(for query: String, completion: @escaping CompletionHandler) {
        let url = baseUrl + getMethod(type: .repos) + query + "/repos"
        AF.request(url, parameters: nil).validate()
            .responseDecodable(of: [Repo].self) { response in
                guard let result = response.value else { return }
                completion(result)
        }
    }

}

4.模型(Users.swift)

代码语言:javascript
复制
import Foundation

struct SearchResponse: Decodable {

    let users: [User]

  enum CodingKeys: String, CodingKey {
    case users = "items"
  }
}

struct User: Decodable {
    let login: String
    let id: Int
    let avatarUrl: String
    let url: String
    let reposUrl: String

    enum CodingKeys: String, CodingKey {
        case login = "login"
        case id = "id"
        case avatarUrl = "avatar_url"
        case url = "url"
        case reposUrl = "repos_url"
    }
}


  [1]: https://github.com/pooja-iosTech13/GithubUserFinder
EN

回答 1

Code Review用户

发布于 2020-06-05 19:57:31

我不能回顾您代码的所有方面,但是这里有一些可能会有所帮助的说明。

JSON解码

User.CodingKeys的定义中,可以省略与大小写名称相等的键的原始值:

代码语言:javascript
复制
struct User: Decodable {
    let login: String 
    let id: Int
    let avatarUrl: String
    let url: String
    let reposUrl: String

    enum CodingKeys: String, CodingKey {
        case login // Instead of: case login = "login" ...
        case id
        case avatarUrl = "avatar_url"
        case url
        case reposUrl = "repos_url"
    }
}

通过在keyDecodingStrategy中的JSONDecoder上设置一个getUsers(),可以完成从“snake-case”(在JSON中)到“camel-case”(在Swift API中)的转换:

代码语言:javascript
复制
let decoder = JSONDecoder()
decoder.keyDecodingStrategy = .convertFromSnakeCase
AF.request(url, parameters: nil).validate()
    .responseDecodable(of: SearchResponse.self, decoder: decoder) { response in
        // ...
}

这使得User.CodingKeys过时了:

代码语言:javascript
复制
struct User: Decodable {
    let login: String
    let id: Int
    let avatarUrl: String
    let url: String
    let reposUrl: String
}

Any

的不必要转换

ServiceManager中的函数都采用(_ response:Any?) -> Void类型的回调参数。因此,来自AlamoFile的(正确类型的)响应被转换为Any?,然后在该函数的调用方中转换回正确的类型。

通过采用适当类型的回调(例如,在getUsers()中),它变得更简单:

代码语言:javascript
复制
public static func getUsers(for query: String, completion: @escaping (SearchResponse) -> Void) {
    // ...
    AF.request(url, parameters: nil).validate()
        .responseDecodable(of: SearchResponse.self, decoder: decoder) { response in
            guard let result = response.value else { ... }
            completion(result)
    }
}

它允许去掉调用函数searchUsers()中的条件大小写:

代码语言:javascript
复制
ServiceManager.getUsers(for: name) {[weak self] (response) in
    guard let self = self else { return }
    // guard let response = response as? SearchResponse else { return }
    // ^-- No longer needed.
    self.users = response.users
    print("RESPONSE", name, response.users.count)
}

句柄速率限制和分页响应

当然,我做的第一件事就是下载代码并编译应用程序。这是完美无缺的,它的编译没有警告。太棒了!

然后我在iOS模拟器中运行了这个应用程序。起初,它似乎是正确的,但后来我在搜索字段中输入了我的GitHub用户名:没有结果- what吗?好的,删除搜索字段,然后再试一次:现在,表视图根本没有更新。似乎什么都没发生了。

为了隔离这个问题,我添加了一个

代码语言:javascript
复制
print("ERROR", response.error)

getUsers()的失败部分。这表明,经过一段时间后,HTTP响应时出现了一个GitHub错误403。事实上,GitHub搜索API文档声明搜索请求仅限于每分钟30个请求,对于未经身份验证的用户甚至限制为每分钟10个请求。

对于这一限制,人们无能为力,但一个小的补救办法可能是“延迟搜索”:如果用户键入"ABCD“,那么就没有必要搜索"A",然后搜索"AB","ABC",最后搜索"ABCD”。如果在每次修改搜索文本之后启动一个短计时器,并且只有在搜索文本在一段时间内没有修改时才启动实际搜索,则搜索请求的数量已经减少。

还可以缓存搜索结果,以便提交更少的请求。

此外,默认情况下,GitHub响应是对30个项的分页响应。这最多可以增加到100,但是即使这样,通常情况下也不会有一个请求就可以得到所有的条目。

搜索响应中的链接标头包含必要的信息。你可以用它

  • 对其余项目发出更多请求。也许只有当用户实际滚动到当前列表的末尾时,再一次是为了限制请求的数量。
  • 或者,只需在列表的末尾显示“更多.”,这样用户就知道显示的项目比当前显示的多。
票数 3
EN
页面原文内容由Code Review提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://codereview.stackexchange.com/questions/243304

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档