我想请iOS开发者社区回顾我的POC,也就是我创建的GithubUserFinder,这是T手机孟加拉语工作筛选测试的一部分。
我有大约两年的编程经验,我有24小时来完成这个POC。
下面是招聘人员通过邮件得到的反馈:至于Pooja,我可能会说不。他们的代码质量有点混乱和稀疏。他们错过了一部分任务,他们的简历看起来不太好,他们错过了一年的工作。
我要求他们提供一些关于“杂乱无章、稀疏的代码”或赋值中“丢失”什么的指针,但没有得到答复。
恳请各位通过我的POC,即GithubUserFinder,并提供您的宝贵意见。
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)
}
}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);
}
}
}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)
}
}
}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发布于 2020-06-05 19:57:31
我不能回顾您代码的所有方面,但是这里有一些可能会有所帮助的说明。
在User.CodingKeys的定义中,可以省略与大小写名称相等的键的原始值:
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中)的转换:
let decoder = JSONDecoder()
decoder.keyDecodingStrategy = .convertFromSnakeCase
AF.request(url, parameters: nil).validate()
.responseDecodable(of: SearchResponse.self, decoder: decoder) { response in
// ...
}这使得User.CodingKeys过时了:
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()中),它变得更简单:
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()中的条件大小写:
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吗?好的,删除搜索字段,然后再试一次:现在,表视图根本没有更新。似乎什么都没发生了。
为了隔离这个问题,我添加了一个
print("ERROR", response.error)在getUsers()的失败部分。这表明,经过一段时间后,HTTP响应时出现了一个GitHub错误403。事实上,GitHub搜索API文档声明搜索请求仅限于每分钟30个请求,对于未经身份验证的用户甚至限制为每分钟10个请求。
对于这一限制,人们无能为力,但一个小的补救办法可能是“延迟搜索”:如果用户键入"ABCD“,那么就没有必要搜索"A",然后搜索"AB","ABC",最后搜索"ABCD”。如果在每次修改搜索文本之后启动一个短计时器,并且只有在搜索文本在一段时间内没有修改时才启动实际搜索,则搜索请求的数量已经减少。
还可以缓存搜索结果,以便提交更少的请求。
此外,默认情况下,GitHub响应是对30个项的分页响应。这最多可以增加到100,但是即使这样,通常情况下也不会有一个请求就可以得到所有的条目。
搜索响应中的链接标头包含必要的信息。你可以用它
https://codereview.stackexchange.com/questions/243304
复制相似问题