Tag: 工程

宣布可可足类产生

抬起头,我们已经搬家了! 如果您想继续了解Square的最新技术内容,请访问我们的新家https://developer.squareup.com/blog 在Square,我们将CocoaPods用作iOS开发工作流程的核心部分,除了在测试套件中使用的库之外,仅在Square Point of Sale应用程序中就有大量库。 这些库中有许多都生活在我们的主要iOS monorepo中,并且随着应用的增长,构建整个库所需的时间也越来越长。 CocoaPods允许我们将所有库配置保留在易于阅读的podspec文件中,与Xcode项目相比,在pull请求中查看它要容易得多。 我们的项目配置很少在Xcode本身中进行管理,这确保了文件系统与Xcode中所见内容之间的一致性。 但是,将所有这些模块集成到单个项目中存在一些缺点。 尽管我们已经投入了很多精力来改善安装依赖项的整体性能,但是运行单个pod install命令可能需要一到两分钟,这在快速迭代单个库时会加起来。 使用CocoaPods还会生成一个非常大的Pods项目,这可能导致Xcode的响应速度变慢,并且需要很长时间才能建立索引。 随着这种爆炸性的增长,显然我们需要开发人员更好的工作方式。 我们希望能够隔离地处理模块,仅构建正在积极处理的模块并仅运行随附的测试套件。 输入pod gen。 我们构建了一个CocoaPods插件,该插件使iOS开发人员可以轻松地独立处理各个库,尤其是在monorepo的情况下。 运行此插件将生成一个存根应用程序项目,该项目将集成并链接指定的库及其依赖项。 我们完整的Pods项目大约需要95秒才能生成,并且是35.6万行。 如果我们在U​​I组件库上运行pod gen,则将花费5秒钟并生成9000行项目文件。 这使得在这些基础库上的迭代变得如此快,并且工具可以轻松地处理较小的规模。 关于gen的最酷的事情是它会自动从现有Podfile和Podfile.lock中引入依赖项限制,因此所有配置都将在生成的库工作区中进行镜像。 这不仅在用于独立开发的monorepo上下文中有用。 当与可从podspec中描述测试的功能结合使用时,它在独立的存储库中也可以很好地工作。 这使独立库可以避免检入和维护自己的Xcode项目,因为CocoaPods可以对其进行管理。 这意味着您不再需要处理PR中的Xcode冲突,因为.xcodeproj只是生成的用于开发的工件。 podspec本身成为描述您的库的真理源,您可以使用简单的pod gen命令从中生成工作区。 可以简单地将测试放在文件系统中的相应文件夹中,并将其作为CI的一部分运行。 例如,我们设置了这个演示仓库,我们需要签入的唯一配置是podspecs。 我们已经在内部使用这种宝石几个月了,很高兴与世界分享它!

了解Swift的CharacterSet

tldr:单击 此处 查看 CharacterSet.decimalDigits 所有 CharacterSet.decimalDigits 您是否曾经需要检查过字符串是否仅由数字组成? 标点符号或非字母数字字符的存在如何? 一个人可以使用多种方法,从Formatter类之一到NSScanner甚至是NSPredicate ,但是您发现的最可能的代码片段涉及到使用反向的CharacterSet 。 简而言之, CharacterSet是一个由Objective-C桥接的Swift类,它代表一组Unicode字符。 它的Objective-C对应物NSCharacterSet本身是免费的,与Core Foundation的CFCharacterSet桥接。 CFCharacterSet用C编写,相当古老,至少可以追溯到Mac CFCharacterSet的主要思想是提供一种支持Unicode的数据结构,以帮助有效地搜索Unicode字符串。 NSString和NSScanner内部使用NSCharacterSet进行字符串搜索操作。 可以将CharacterSet初始化为空集,也可以将其初始化为字符串,字节或文件内容中存在的一组字符。 它带有许多方便的预定义集合(例如URL查询片段中允许的字符或字母数字字符),甚至还允许集合代数(例如并集,交集和异或)。 使用CharacterSet的预定义集合之一感觉很方便: 请注意,四个8位和两个16位都加起来为32位。 这完全是设计使然:UTF32是固定宽度格式,UTF8和UTF16可以轻松装入其中,而无需进行任何额外工作。 所有UTF32字符都包含32位,即使不是必需的。 这使格式无效,但是有一个好处 :它非常适合搜索,因为您可以遍历第32位以获得下一个字符,而不用解码每个字节来解码该字符的代码点宽度。 这正是NSCharacterSet.characterIsMember(UTF8 or UTF16 or UTF32)内部调用仅接受UTF32字符的longCharacterIsMember(UTF32)的原因NSCharacterSet.characterIsMember(UTF8 or UTF16 or UTF32) 在CharacterSet搜索字符成员资格的最佳方法是获取该CharacterSet的UTF32代码点,并将其传递给NSCharacterSet的longCharacterIsMember() 。 看起来像这样: 以下是将字符的代码点值转换为UTF8二进制文件的方法:在上表中的所有x s中填充字符的二进制值。 要确定需要多少字节,请考虑二进制字符编码的长度。 1字节的UTF8仅可容纳7位(表中仅7 x s)。 一个2字节的UTF8可以容纳11位。 3字节可容纳16位,而4字节UTF8可容纳21位。 对于“€”字符(U + 20AC 10 0000 1010 1100 ),我们至少需要14位,这意味着它将需要3个字节的结构,可以容纳12至16位。 填充到UTF8结构中的二进制数字如下所示: […]

Foursquare API + ARKit

9月初,我们看到了iOS 11的推出,从而为数百万个启用了AR(增强现实)的设备提供了支持。 在发布之后,我们考虑了一些创新的方法,Foursquare的位置智能可以为您提供体验,以吸引用户。 在进行演示之前,我们将更深入地讨论AR和ARKit。 “增强现实是通过将另一幅图像覆盖在周围环境的实时视图上而产生的增强图像或合成图像。 “ ARKit如何工作? ARKit使用一种称为视觉惯性里程表(VIO)的技术,并具有一些2D平面检测功能。 VIO意味着该软件可以实时跟踪您在太空中的位置。 这是通过您的姿势完成的,该姿势可以通过摄像头系统以及加速度计和陀螺仪(CoreMotion)进行跟踪。 就像汽车中的里程表会跟踪汽车的行驶距离一样,VIO系统也会跟踪iPhone在6D空间中的行驶距离。 6D表示xyz运动的3D(平移),加上俯仰/偏航/横摇的3D(旋转)。 ARKit的核心功能之一是平面检测。 这是必需的,因此您可以在地面上放置内容的地方,否则看起来好像在太空中漂浮得很厉害。 这是根据光学系统检测到的特征计算得出的。 现在,我们对AR和ARKit有了更多的了解,让我们深入研究一下快速演示: 为了让您入门,我们构建了一个由ARKit驱动的应用程序,以演示位置智能的功能。 在下面详述的示例中,您将能够使用Foursquare Places API在全球范围内移动手机以查看位置及其与您的距离。 我们也鼓励您查看我们的Pilgrim SDK,您可以在其中实时了解,与用户交流和互动。 快速开始 要快速上手,您可以在手机上获取该应用,或在下面克隆以下存储库。 在建造过程中,请在开始后添加您的钥匙/秘密。 git clone git@github.com:garethpaul/foursquare-ar-camera-ios.git 克隆存储库并添加密钥后,即可在任何iOS设备上进行扩展。 我们建议使用现实生活中的电话-由于在本地测试AR的复杂性。 在我们开始之前.. 我们还要感谢Mapbox和Andrew Hart的团队在此领域的启发和提供进一步工作方面所做的早期工作。 建造ARKit + Foursquare 在较高级别的示例中,我们执行三个主要功能。 确定位置 找到一些地方 将地点添加到AR 第1步-确定位置 我们利用核心位置通过感官信息确定基本位置。 该框架使用所有可用的机载硬件,包括Wi-Fi,GPS,蓝牙,磁力计,气压计和蜂窝硬件来收集数据。 LocationManager类符合CLLocationManagerDelegate并处理从CoreLocation检索位置和方向。 在我们的示例中,我们的主ViewController符合SceneLocationViewDelegate。 委托是一种简单而强大的模式,其中,我们的ViewController与另一个对象一起起作用。 委托对象保留了另一个对象的引用。 委托的主要价值在于,它使我们能够轻松自定义一个中央对象中多个对象的AR行为。 第2步-查找热门地点 加载视图后,我们将使用Foursquare的Places API从SceneLocation的LocationManager确定位置。 为简单起见,我们已将其添加到主ViewController中,但建议为静态服务(模型和控制器)创建一个单独的类。 在函数getFoursquareLocations中,我们利用端点“ venues / […]

如果您的类型名称平庸,则您的代码将构成责任

我创建代码的过程如下所示: 研究目标的细节(学习) 提出实施计划(创建) 代码(单击并键入) 当我意识到我有一个错误的假设时,请返回步骤1(遗憾) 在进行编码工作之前,正确命名类型是计划中的最后一个障碍。 就像,嗯,我知道计算机不在乎我给我命名的类型。 机器解释指令时,所有这些都将被剥夺。 我的功能不会受到影响。 为什么我花了很多时间来确定角色以及事情如何融合在一起? 因为代码不仅仅适用于机器。 适用于必须充分理解它才能正确操作它的开发人员。 该类别中包括“未来”。 要做到这一点,就需要弄清问题集,并有足够的同理心,以使听众可以预期您选择的名字的所有含义。 真名 有一个古老的想法,即如果您知道某事的“真实名称”,那么您将拥有权力。 这是幻想中的常见现象,也是宗教故事的一部分。 以伊西斯和拉的故事为例。 伊希斯(Isis)是一位出色的治疗师,但只有知道她的真实姓名时才能帮助Ra。 他试图用“小写”的名字满足她的要求,但这仅使Ra未能解决核心问题。 一旦他放弃了他的真实姓名,她就可以治愈他。 这听起来像调试我。 如果您不知道所处理事物的真正含义,就无法解决该问题。 懒惰的标签将欺骗人。 您在阅读代码时让人们做的心理锻炼越多,使用它的认知就越消耗精力。 “是的,被称为 Provider 但实际上只是转换数据。” “该 modelId 不适用于该模型,适用于其他通用模型。” 但是,如果您真正知道什么是东西,则可以轻松地对其进行操作。 这很重要,因为将来必须更改代码。 我的理想是功能与代表功能的标签之间的绝对统一。 既然是理想的,我永远都不会到达那里。 想起名字时会出现回报递减的情况,但是值得花一些时间。 在清晰度和可读性之间取得平衡 某物的最精确名称可能是…… 绝对清晰。 把它收拾好。 该名称公然忽略了所有可用的上下文。 显然,这个例子很荒谬,但是添加不必要的限定词可能很诱人。 即使您觉得这个名字不是最好的,也最好在项目内部保持一致,而不要在两个约定之间来回穿梭。 一旦人们适应了您项目的标准,他们就会知道您在说什么。 一致的信息比冲突的信息要好,即使最终效果不佳。 同样,在引入新元素时也不要害怕重命名旧元素,以澄清它们之间的差异。 我们不是用石碑编码。 有些人梦想着将来验证自己的代码并期待未来的变化。 我宁愿使代码清晰地代表当前的情况,并在反映未来的将来进行更改。 这使我省去了很多我无法控制的事情。 我们控制双方 也许您没有花时间准确地命名某件事,因为它正在做很多事情…… 再塞另一种方法 有时正确的答案可能是重构基础实现以启用更好的名称。 我们都可以控制。 […]

嘿Siri:在iOS上与Anghami交谈

自2011年推出以来,Siri一直在稳步学习越来越多的技能。 每年,Apple都会对Siri进行更改,以允许开发人员使用更多功能,并且WWDC 2018看到了一些最令人兴奋的更新。 在WDC 2018之前,要与Siri集成,开发人员必须选择他们要提出的请求类型(称为意图),并且只有少数意图类型可供选择。 如果您的应用不属于这些类别之一,那么就不可能与Siri集成。 但是,在WWDC上,Apple向SiriKit引入了两个新功能,使Anghami可以集成到Siri中:媒体意图和自定义意图。 1.媒体意图 媒体意图提供了一种方便的方法,可将与媒体相关的操作添加到Siri。 他们的工作方式很简单。 首先,创建一个INMediaItem对象,该对象由一些参数(包括标识符,标题,类型和插图)进行自定义。 让容器= INMediaItem(标识符:标识符,标题:标题,类型:类型,艺术品:图片) 就我们在​​Anghami的情况而言,类型可以是从播放列表,艺术家到歌曲的任何类型,并且标识符将是项目的唯一ID。 完成之后,将创建一个INPlayMediaIntent对象,这是我们想要提供给Siri的实际意图。 let intent = INPlayMediaIntent(mediaItems:nil,mediaContainer:mediaContainer,playShuffled:shuffled,playbackRepeatMode:.all,resumePlayback:nil) 最后,将意图本身建议给Siri,然后Apple提供一个UI,允许用户向该操作分配任何自定义短语。 用户调用操作后,将在我们的应用程序委托中调用以下函数: -(void)应用程序:(UIApplication *)应用程序handleIntent:(INIntent *)意图完成处理器:(void(^)(INIntentResponse * _Nonnull))completionHandler; 在这里进行检查,以查看该意图是否为INMediaIntent,是否从对象中检索了mediaItem,然后我们可以轻松地检索与该mediaItem相关的音乐对象(播放列表,艺术家,歌曲等)并进行播放。 最棒的是,这一切都是在后台发生的,因此用户无需解锁手机即可执行操作。 他们只是告诉Siri他们想要什么并且完成了。 2.自定义意图 媒体意图非常适合播放音乐,但是如果我们想对Siri进行更多处理,该怎么办? 在Anghami,我们还希望简化操作,包括喜欢歌曲,下载歌曲,检查歌曲的歌词,调用搜索等。这就是自定义意图的体现。 要创建自定义意图,首先应定义意图。 这是在意图定义文件中完成的(自然)。 创建一个新的自定义意图,然后必须定义一些参数。 类别参数具有“执行”,“运行”,“购买”等选项。 这用于帮助Siri在用户运行时知道如何谈论此操作。 然后定义一些更明显的参数,例如标题和描述。 最后,可以将参数添加到自定义意图。 这些将在创建意图时提供给Siri。 就像媒体意图一样,将创建一个意图对象,然后将其提供给Siri。 这很容易,因为在定义了意图之后,Apple会自动生成与新的自定义意图相关的类对象。 自定义意图的缺点在于,它们只能以两种方式运行,既可以在应用程序的前台运行,也可以在单独目标的后台运行。 这就是说,如果我们希望在不打开Anghami的情况下工作,那么我们必须这样做,同时又不能访问我们在Anghami主应用程序目标中拥有的许多有用的类和文件。 一种简单的解决方案是仅使自定义意图打开Anghami应用程序。 对用户而言,这只是一步而已,但是当我们知道一点点额外的工作可以消除这一步时,这样做是错误的。 因此,我们成功了。 我们的主要问题是我们想与应用通信,希望对当前播放的歌曲执行此特定操作。 不管是下载当前歌曲,喜欢它还是播放更多类似的歌曲都没有关系。 如果我们让应用程序确定了我们希望现在发生的事情,那么困难的部分就完成了。 处理方式是在共享容器的帮助下进行的。 共享容器基本上是一个可以存储文件/数据的存储桶,可以在主应用程序及其所有扩展(包括Intents扩展)之间共享。 因此,我们有一种与应用程序进行数据通信的方法。 有助于解决问题的另一点是,如果用户想要对当前歌曲执行操作,则音乐可能正在通过Anghami播放,这意味着我们的应用程序已经在后台运行。 因此,我们要做的就是简单地将当前歌曲的ID写入共享文件,使其始终处于更新状态,并在调用Intent时,根据所调用操作的类型将同一ID写入不同的文件(下载,例如,像这样玩,等等。 […]

在18个步骤中成为iOS开发人员

苹果的移动操作系统已有10多年的历史了。 如果五年前研究目标C和几个辅助框架就足够了,那么现在很容易被众多选择所震惊。 这就是我决定创建可视手册的主要原因,该手册将: 逐步展示值得学习该主题的方式 给出一个连贯的图片,您可以通过它跟踪进度 我没有目标创建学习iOS开发的唯一真实方法,就像我无意为您选择方法和技巧一样。 我想为您提供最广阔的视野,以便您可以判断建议的方案中哪个更适合解决特定任务,而不是追求通用解决方案或最笨拙的解决方案。 事不宜迟,让我们开始做正事。 在下面,您可以找到有关每个项目的评论,并提供指向所提到的解决方案和资源的链接。 祝好运! 1.选择一种语言 有2个选项:Objective C或Swift。 我建议Swift更现代且易于掌握。 如果您已经有使用另一种类似C语言的经验,Swift对您来说似乎很熟悉。 Swift允许更简洁和更具表现力,以编写更安全的代码。 但是不要将Objective C丢在公共汽车底下。 在任何已经存在数年的项目中,总是会有一部分目标C代码。 所有大公司都有大量的代码,其基础结构和专业知识。 当您熟悉基于Swift的iOS开发的基础知识时,就值得回过头来学习ObjectiveC。 2.安装Xcode 要开始编写和运行代码,您需要一个环境。 Apple对此进行了照顾,并创建了Xcode,其中包括让您感到舒适的所有内容。 Xcode在AppStore中免费分发,要下载Xcode,您需要在developer.apple.com上注册。 这是免费的。 仅当您要将应用程序放置在AppStore中时才需要付费。 JetBrains还有一个付费替代方案-AppCode。 3.边玩边学 为什么要选择Swift的另一个重要点是它使用Xcode内置的Playgrounds工具易于学习的功能。 使用Playgrounds,您可以实时监视代码执行的结果。 苹果还提供处理Swift所需的所有信息。 了解内存如何在Swift中工作,如何防止内存泄漏和循环依赖。 掌握了该语言后,应编写尽可能多的小程序: 记住典型的数据结构:链表,堆栈,二叉树 编写经典算法:二进制搜索,堆排序或动态编程 4.构建您的第一个应用 要开始为iOS开发,您需要学习UIKit。 这是所有iOS应用程序的基础。 在Xcode中创建一个空的iOS应用程序,并开始学习该应用程序的主要组件:UIApplication,AppDelegate,UIViewController,Storyboards等。阅读这些组件中每个组件的内置Xcode文档,直到您习惯了。 当您弄清楚iOS应用程序中的内容时,请开始编写基本示例。 这里有一些想法: 显示朋友列表和有关他们的详细信息。 创建2个屏幕:第一个屏幕将显示一个名称列表,通过单击每个屏幕上的另一个屏幕,显示更多详细信息。 显示从网上下载的几张图片。 使屏幕看起来像标准的“照片”应用。 了解如何从网上下载图片。 通过单击图片,使用“缩放”选项将其完整显示。 5.在会话之间保存数据 到目前为止,您的应用程序像《 Groundhog Day》电影中的主角一样活着。 每次发射对他们来说都是新的。 了解如何保存数据:从UserDefaults开始,然后熟悉Coding协议,然后转到CoreData。 在Realm和Firebase中定居也将很有用。 […]

在Swift中进行功能性思考

如上图所示,从服务器解析JSON响应需要进行两次转换: 从原始HTTP响应转换为数据的JSON表示形式。 从JSON表示形式到模型。 在第一步和第二步之间,我们将使用数据的JSON表示形式。 这种JSON表示形式使我们能够对服务器响应进行增量转换。 通过在模型之前解析为中间JSON表示,我们的代码变得更加可组合。 JSON的表示方式对于理解本文中的功能编程概念并不是必不可少的。 但是,为了完整起见,让我们现在对其进行定义。 如果阅读JSONSerialization的文档,您会注意到有效JSON的规则之一是: 所有对象都是NSString , NSNumber , NSArray , NSDictionary或NSNull 。 由于案例数量有限,因此使用枚举似乎是一个好情况。 定义了JSONObject类型后,我们现在知道第一次转换之前,第一次与第二次转换之间以及第二次转换之后的数据类型。 它们是Data -> JSONObject > Model (我们稍后会定义模型。) 功能方法 有一个核心概念将通过此练习来推动我们的思考过程。 我们将过程中的每个步骤都视为独立于函数外部变量的转换 。 让我们看一下如何将这种思考过程应用于上面概述的两个转换。 转换1:将数据转换为JSON对象 我们将从定义一个新类型Deserialize开始,该类型将Data转换为JSONObject 。 在函数式编程中,类型由其方法签名定义。 要使用此类型,我们编写了一个函数,该函数返回新的Deserialize类型。 我想强调关于JSON()函数的几件事。 该函数返回一个闭包。 以面向对象的思维方式看待这个问题可能看起来很奇怪,但这是标准的函数式编程。 请注意,从JSON()返回的闭包完全独立于该函数外部可能存在的任何状态。 这与我们非常依赖状态的面向对象编程形成了鲜明的对比。 转换2:将JSON对象转换为模型 正如我们在第一个转换中所做的那样,让我们​​定义一个新类型,该类型接受一个JSONObject作为参数并返回类型T的模型: typealias Decode =(JSONObject?)->(T?) 注意:我们使 Decode 函数通用,因此我们可以解码各种模型。 由于我们尚未定义任何模型,因此这是我们开始从JSON创建模型所需的所有样板代码! 下一步是定义我们的模型以及可以解析JSON的函数。 全部放在一起 让我们导出一个简单的示例,以显示与该代码交互的外观。 对于此示例,我们将期望服务器向我们发送有关用户的信息。 我们将定义User模型,并编写一个函数decodeUser() ,以执行从JSONObject到User的转换。 […]

鱼子酱的iOS:从AdvancedCollectionView迁移到PJFDataSource

幕后介绍了我们Caviar iOS应用程序中PJFDataSource的演变。 由 Michael Thole 撰写 。 抬起头,我们已经搬家了! 如果您想继续了解Square的最新技术内容,请访问我们的新家https://developer.squareup.com/blog 2014年12月,我们为鱼子酱(高品质的餐厅送餐服务)发布了iOS应用程序的第一个版本。 该应用程序的视图控制器架构主要基于苹果2014年WWDC演讲:具有集合视图的高级用户界面和随附的示例代码中列出的框架。 对于我们的应用程序来说,这是一个很好的起点,但是由于下面将要详述的原因,我们最终对它感到沮丧。 在考虑了多种方法之后,我们决定构建一个小型库,以重新实现我们最重视的AdvancedCollectionView功能,同时保留我们不需要的任何复杂性。 为了推动我们建立一个与Caviar应用程序没有太紧密联系的通用库,我们从一开始就将其构建为外部库。 我们已经成功使用它超过6个月,并决定将其作为PJFDataSource开源。 在本文中,我们将研究PJFDataSource的发展过程:它是我们早期应用程序体系结构中AdvancedCollectionView示例代码的替代品。 如果您只想潜入,请转到我们的GitHub页面。 一个好的起点 尽管我们最终遇到了问题,但将Caviar的iOS应用程序结构基于Apple的AdvancedCollectionView示例代码是一个很好的起点。 它提供了一种一致的方法来在整个应用程序中加载数据和显示内容,并促使我们迈向许多最佳实践。 最重要的是,AdvancedCollectionView帮助我们避免创建Massive View Controller,这是常见的iOS应用程序体系结构的陷阱(为此有很多好的解决方案)。 它这样做的主要目的是要求每条内容都具有一个与视图控制器本身分离的数据源对象。 这些数据源对象是内容视图的模型。 在此实现中,这些对象还负责加载其内容-另一种责任可能是使用不太严格的方法最终在视图控制器中完成的。 这些数据源甚至可以像构建块一样组合在一起构成一个“聚合数据源”,从而鼓励创建较小的组件数据源。 这允许在应用程序不同部分中的相同组件重复使用代码。 例如,您添加到购物车中的食品列表以及历史收据上的食品列表可以共享这些组件数据源之一,因为它们表示的是相同的内容,只是在不同的上下文中。 AdvancedCollectionView还提供了一种一致的方法来显示加载,无内容和错误状态。 它通过将数据源对象与显示您的内容的UICollectionView紧密耦合,并在适当的时候添加辅助的占位符视图来实现。 尽管起初并不明显,但这成为AdvancedCollectionView我们最喜欢的“功能”之一,并且在PJFDataSource的构建中起了很大的作用。 问题和解决方案 当我们摆脱了Caviar的iOS初始发行版,并开始适应常规的功能构建和错误修复发行周期时,我们开始意识到使用AdvancedCollectionView的缺点与我们一样严重。 这些都是相互关联的,但我将围绕灵活性,稳定性和社区性主题进行讨论。 灵活性 正如您可能从名称中猜到的那样,AdvancedCollectionView需要使用UICollectionView。 您可能已经听过这样的格言:“如果您只有锤子,一切看起来都像钉子”。 对于我们来说,UICollectionView是我们的锤子,尽管它并非总是最适合此工作的工具。 缺乏灵活性从多个方面打击了我们。 一个简单的具体示例来自主屏幕,其中,鱼子酱列出了您可以订购的所有餐厅。 在我们较大的市场中,这可能是数百家餐厅,每个餐厅都有一个高度可变的单元,具体取决于相关的元数据。 传统上,集合视图和表视图需要确定每个单元格的大小,以便计算其自己的内容大小。 如果内容很多,这可能会很慢,并且会对用户体验产生负面影响。 对于UITableViews,Apple添加了tableView:estimatedHeightForRowAtIndexPath :,此问题的一种优雅解决方案。 当时无法访问集合视图布局中的类似内容,因此我们不得不实施自己的快速视图大小调整和缓存-我们本来希望避免的复杂性。 更一般而言,在Caviar应用程序中几乎所有地方都使用UICollectionViews实在是太过分了。 使用UITableView可以更简单地实现几乎所有屏幕。 有些甚至可以通过基于UIStackView的布局(与OAStackView for iOS 8兼容)来实现,甚至可以通过手动的基于UIView的布局来实现。 这种经验是我们决定PJFDataSource应该与视图无关的主要原因,而将其留给应用程序来提供自己的内容视图。 PJFDataSource并没有与收集视图紧密结合,而是提供了一个“内容包装视图”,它将显示您提供的内容视图,或者显示用于处理加载,无内容和错误状态的各种占位符之一。 […]

当枚举具有超能力时(迅捷)

在Mimo,我们每周举行一次Show&Tell会议,每个团队介绍他们上周所做的工作,之后,您可以分享您最近学到的东西或您热衷的东西。 我在Mimo讨论了“枚举何时具有超能力”之后,创建了以下文章,因为我想这可能会让更多开发人员感兴趣。 这是Swift版本。 您还可以在这里找到这篇文章的Kotlin版本。 假设我们有一个用Swift编写的iOS应用,现在我们想添加一个用户登录名。 我们使用4个参数创建一个简单的登录功能。 username , password , success callback , failure callback 。 到目前为止,一切都很好。 我们还假设我们已经提前计划了。 对于在登录时遇到错误的情况,我们创建了一个IncorrectPasswordError类来处理故障回调中的此特定错误。 我们的解决方案有效。 我们可以添加诸如TimeoutError或UsernameNotFoundError类的更具体的错误,并在我们的失败回调中对其做出反应。 那么,如果它起作用了,我们为什么还要改变它呢? 🤔 我们依靠类型转换来处理特定的错误。 我们将所有AuthenticationErrors与可能发生的任何其他RuntimeError混合在一起 很难发现不同的错误。 如果您知道自己在做什么,则类型转换可能不是问题,但其他两点仍然成立。 在登录过程中,我们基本上会在登录流程中遇到一些错误,例如用户输入了错误的密码/电子邮件或发生了超时。 与可能发生的任何其他意外错误不同 ,对此类错误的处理方式不同 ,因此也许应将它们分开处理。 第二大问题是可发现性 。 如果不查看确切的实现,我们不知道我们需要处理哪种错误。 编译器在这里也无济于事。 那么我们如何做得更好? Swift具有强大的功能( 带有关联值的枚举),其中枚举可以容纳更大的数据块。 我们将使用它来重构我们的登录流程。 我们首先对所有可能发生的情况以及我们要专门处理的情况进行建模。 success – success登录将包含user对象。 usernameNotFound找不到usernameNotFound名 incorrectPassword错误的密码 timeout unexpectedError —任何其他意外错误。 现在将这些错误与其余错误分开 。 现在,我们新的登录功能只有一个带有LoginResult回调。 为了处理不同的结果,我们创建了一个简单的开关盒 。 首先,我们现在将登录流程错误与任何其他意外错误分开了。 这些意外错误在单个case块中一起处理,而其他usernameNotFound如usernameNotFound或incorrectPassword具有自己的case块。 […]

斯威夫特预期为零,不是

我的单元测试失败。 它想要nil但得到了 。 我最初的反应是指责我的计算机愚蠢。 您是否期望我提供其他类型的nil ? 只有一个nil ,名字叫nil 。 但是请稍等。 电脑还不足以欺骗我们。 我做错了什么? 字典问题 这一切都很好,而且是预期的。 我遇到了问题,因为我将值类型设置为“ Any? 而不是Any 。 Swift让我的价值翻倍! 将键设置为nil 在“字典”中将键设置为nil ,它将为您删除该键/值对。 这就是上面第8行的行为符合预期的原因。 如果要在具有可选值类型的Dictionary中将值显式设置为nil ,则必须先将其包装。 当我第一次使用var dict: [String: Any?] = [“first”, nil]创建字典时,Swift会自动执行此额外步骤var dict: [String: Any?] = [“first”, nil] —它在后台为我包装了nil。 不要这样! 最后,我重构了Dictionary,使其值不使用可选参数。 必须记住的规则越少越好。 如果我对自己的代码感到困惑,那么另一个开发人员加入该怎么办? 如果您想进一步思考这个想法,请查看Tyler Johnson关于代码设计的文章。 代码应该像政治卡通一样呈现自己-充满了直率的隐喻,带有有用的标签,没有任何微妙之处。 肖恩判断 Livefront的 代码是什么样的 。