Tag: Clean Code

如何在可编码协议中使用Swift 5结果

我已经写过有关Alamofire API管理器的文章,但是有了新的Swift 5 Result功能,我想对其进行一些升级。 我们将它与Codable协议一起使用,使我们的生活更加轻松。 如果您不是Alamofire粉丝,则可以将其删除。 但是,结合Codable和Result的原理,您应该尝试在网络处理程序中实现。 这个想法是建立一个可重用和直接的API管理器。 我们必须使用单一职责原则创建一些帮助程序类-ErrorObject,EndpointType,EndpointItem,NetworkEnvironment。 我现在不会解决这些问题,因为您在这里。 API管理器 我们的APIManager使用EndpointItem对象创建URL请求。 同样,它解析数据。 使用Codable时,解析是一项简单的任务。 因此,代表数据模型的所有结构和类都应实现Decodable和Encodable协议。 我们使用通用参数T,其中T是可编码的。 T通知APIManager我们正在获取哪种数据以及希望管理器返回哪种类型的对象。 T可以是实现“可解码”和“可编码”协议的任何对象或对象数组。 处理程序需要Result类型。 因此,Result的第一个参数是通用参数T,第二个是错误对象。 我们使用Swift.Result表示法,因为Alamofire也有其Result对象,并且我们需要指定要使用的对象。 APIManager —带有Swift 5结果的通用方法 收到响应后,APIManager尝试解码数据。 如果成功,他将退还。 在其他情况下,它将返回AlertMessage对象。 值得一提的是,Result对象的第二个参数必须符合Error协议。 我们需要扩展AlertMessage对象。 AlertMessage —错误对象 在APIDemoViewModel中,我们使用API​​Manager的方法。 在这里,我们指定要获取的结构,以便将数据解码为适当的对象。 APIDemoViewModel-实现的APIManager通用方法 好处 如果将APIManager与Results和没有结果进行比较,则主要优点是我们不再具有可选参数,并且可以使用强类型引用。 这意味着我们的代码更直接,更容易读写。 结果错误也很重要,因为它们是强类型的,我们可以创建自定义错误,使它们适合我们的项目。 只是不要忘记它需要符合Swift的Error类型。 现在我们确定我们将获得成功或失败,不可能两者皆有或两者皆有。 没有Swift 5结果的APIManager旧版本 结论 我们将Swift 5结果和Codable协议结合在一起,使我们的代码更清洁,更高效。 这是一个简单的代码示例。 如果您有任何问题或建议,请随时发表评论。 资源资源 示范项目 Alamofire Api Manager-原始帖子

如何保持您的iOS本地化文件整洁-Swift脚本版本

与bash脚本类似,我们首先通过create()从根目录中递归搜索本地化的字符串文件。 Apple的FileManager通过枚举对象使文件搜索变得容易。 注意,由于我们通常将打包在本地化文件中的Pod本地化文件打包,因此我们将忽略它们。在parse(_ path: String)方法中的本地化文件并存储时,我们将在此处搜索键重复来优化脚本。 Set的键。 该方法将为所有重复的密钥以及找到的文件路径打印error 。 ( 注意:任何 print(“error:”) 或 print(“warning:”) 格式都会通知Xcode在工具栏上显示相应的消息以及其他构建失败和警告 ) 接下来,我们将验证validateMatchKeys方法中所有可本地化的字符串文件中的键是否匹配。 我们将使用symmetricDifference编织出任何与基本文件都不匹配的额外键。 如果任何可本地化的文件不包含相同的键,则此方法将引发错误并中止脚本。 这使您有机会在下一次执行cleanWrite()之前清理可本地化的文件,它会使用已排序的键并删除多余的空格和换行符来重写可本地化的文件。 下一步是搜索代码库,存储这些文件中使用的密钥,并根据我们的基本密钥进行验证。 有了一点正则表达式,我们就可以成功找到密钥并将它们和文件的路径存储在LocalizationCodeFile结构中,同时还可以通过紧凑地映射出没有密钥的任何文件来优化脚本。 一旦有了我们的结构,我们将点击validateMissingKeys() -将遍历我们的LocalizationCodeFile集合,并减去基本键的每组键。 此结果中的任何其他键都不是我们基本键的子集,因此会给我们丢失的键。 如果找到任何丢失的键,则会引发错误。 我们的最后一个策略是搜索任何死键,这些死键是在可本地化文件中定义但未在我们的代码库中使用的键。 我们可以通过从flatization从LocalizationCodeFile集合中收集的所有键减去基本键来检索死键。 此处找到的所有键均显示为警告,因为此步骤更多是建议清除技术债务,而不是影响问题的用户。 对具有600多个可执行文件和估计800多个密钥的企业代码库执行bash脚本平均要花费81秒 。 在相同的代码库上运行此swift脚本平均要花3秒 ! 🚀🙌 保持干净 ,可读的代码库应该是每个工程师的目标。 通过将此Swift脚本注入到您的后期构建中,清理和维护可本地化的文件应有助于实现该目标并感到自动化。 您可以阅读我以前的博客,以了解如何将脚本注入到Xcode构建后编译中( https://buildingvts.com/clean-ios-localizable-files-8b910413b985 )。 我还在其中包括了一个包含整个脚本的GitHub项目以及此处的示例项目:https://github.com/ginowu7/CleanSwiftLocalizableExample。 随时发表任何意见或建议,您可以在Twitter @ ginowu07上关注我! 编码愉快! 🙏

我如何使用UIAppearance管理我的应用主题(第1/2部分)

在第一部分中,我将解释动机,方法和定义我使用的对象。 在第二部分中,我将更深入地研究与UIKit API交互的ThemeManager对象及其在应用程序中的使用方式。 最近,我添加了更改应用程序“电视流”主题的可能性,这是最终结果: 为了以一种容易,可维护和可自定义的方式进行操作,我使用了UIKit中的UIAppearance。 该协议具有一些方法,这些方法使您可以更改几个UIKit组件的外观,例如UINavigationBar,UIBarButtonItem等。 此外,您可以对其进行自定义,以便仅当该组件包含在其他组件类型中时更改外观。 由于视图遵循树结构,因此当UIBarButtonItem位于UINavigationBar或UIToolbar内部时,可以选择具有不同的外观。 阅读完Apple文档后,我发现Ray Wenderlich的教程和Abhimuralidharan的教程很有用。 我还以Siesta示例应用程序中的代码为起点。 我使用以下对象来管理主题: ThemeName (枚举):以独特的方式列出所有可能的主题; 主题 (协议):定义定义主题所需的所有属性; LightTheme,DarkTheme,BlackTheme (结构):符合主题协议并实现主题变体; ThemeRepository (协议):摘要如何加载和保存主题; DefaultsThemeRepository (类):使用标准的UserDefaults保存和加载主题; ThemeManager (类):返回当前主题并应用不同的主题,使用存储库检索当前主题并保存所应用的主题;

在Swift中避免原始痴迷

确保您的代码代表要解决的问题称为域建模 ,这是软件工艺的重要组成部分。 这意味着您应该创建代表问题的类( 或结构! ),而不是使用字典或元组之类的结构来存储信息。 创建这些域概念的好处是,您可以创建更丰富的API,并减少开发人员理解一段代码所花费的精力。 领域建模的反面-使用基元表示复杂的想法-被称为基元痴迷,是一种代码味道。 例如,通过将网站存储为String来表示网站的URL。 与String相比,URL具有更多信息和特定属性(例如,方案,查询参数,协议),并且通过将其存储为字符串,您将无法在没有其他代码的情况下访问这些特定于URL项(域概念)。 作为域建模的一部分,您要针对两件事: 使代码尽可能说明性,而无需文档 充当其自己的文档的代码是软件开发的重要内容之一。 如果代码以这种方式进行自我记录,则说明文档将永远不会过时,因为它是代码本身的一部分。 您不必每次更改代码都记得要更新文档,这很容易忽略。 防止以对业务无济于事的方式滥用您的代码 好的代码经常被忽略的一件事是,它很难做错事情。 需要URL的函数应将URL作为参数,而不是字符串。 如果该函数的用户可以传递String可能会更容易,但该函数现在必须确保String实际上也是URL。 通过强迫用户执行此操作,您将给他们带来一些负担,同时还可以防止他们不仅滥用您的API,而且可以滥用它。 让我们专注于这段代码,看看如何更好地对此建模: func showDetailsForEmail(withId:String){ //推送新的视图控制器 } 使用类型别名 typealias是一个关键字(在Objective-C和Swift中都可用),用于显示代码中的其他内容可以轻松表示的位置: typealias EmailId =字符串 func showDetailsForEmail(withId:EmailId){ //推送新的视图控制器 } Typealiases很好地解释了在这种情况下可以使用什么,它们满足了第一个目标(使您的代码更具解释性)。 在这里,通过查看功能签名,您可以看到发送到该功能才能使其正常工作所需的内容-电子邮件的ID。 以前,当函数只需要一个String ,该字符串应代表什么以及如何创建一个字符串就不太明显了。 将一个完全不相关的String传递给方法,这太容易了,这不是您想要的。 不幸的是,类型别名未能满足第二个要求-防止滥用API。 类型别名是另一个对象的“昵称”。 它们不是单独的类型,因此不会阻止在其位置使用“昵称”类型。 即使上面的函数需要一个EmailId ,您仍然可以在其位置传递一个String (或什至其他类型的别名): typealias EmailId =字符串 typealias SMSId =字符串 func showDetailsForEmail(withId:EmailId){ //推送新的视图控制器 } 让smsOne:SMSId […]

在Swift中避免回调地狱

能够从事最多样化的项目,这使我有机会与几种类型的开发人员和代码库联系。 除了它们的核心差异外,在此过程中对我而言突出的是,成熟度较低的项目将始终面临类似的问题。 也许他们选择了错误的体系结构,或者缺少单元测试导致一个讨厌的bug潜入了生产环境,但是有一个特定的问题总是引起我的注意-回调地狱。 如果不从一开始就进行处理,当在其他回调或条件中链接回调时,这些可怕的花括号金字塔就会使困扰着代码库的问题陷入千篇一律的不可能的代码审阅和遥远的“此方法到底在干什么?”的尖叫。 ”。 私有函数requestNetwork (请求:T,完成时间:(结果->无效)?){ 如果isUserLogged { 做{ 让urlRequest =试试request.toRequest() session.dataTask(with:urlRequest){(数据,响应,错误)在 如果让httpResponse = response为? HTTPURLResponse { 如果acceptedStatuses?.contains(httpResponse.statusCode)!= true { 如果让apiError = errorParser?.possibleError(from:data){ 完成(.failure(错误)) 返回 } } } preprocess(data){(processedData,error)在 如果让错误=错误{ 完成(.failure(错误)) } 如果让加工数据=处理数据{ 做{ 让结果=尝试request.serialize(processedData) 完成(。成功(结果)) } { 完成(.failure(错误)) } }其他{ 完成(.failure(HTTPError.unknown)) } } } } { 完成(.failure(错误)) } }其他{ 完成(.failure(HTTPError.loggedOut)) } } 它们难以阅读,几乎无法审查,但不幸的是,它们非常容易编写,巩固了其作为初级开发人员的祸根的地位。 […]