说明

Moya是一个网络库,其灵感来自以类型安全的方式封装网络请求(通常使用枚举)的概念,该概念为使用网络层提供了信心。成为Moya的网络超级英雄!

注意:本教程使用Xcode 10和Swift 4.2。它依赖的库尚未针对Swift 4.2进行更新,但可以正常使用。您需要忽略单个警告,告诉您Swift 4.2转换可用。

制作精美而高性能的iOS应用程序涉及许多动人的事。其中最重要的部分,如果不是最适合现代应用重要,是网络。作为iOS开发人员,您可以采用许多不同的方式来构建网络层-无论是使用URLSession还是使用第三方库。

在本教程中,您将学习名为Moya的第三方网络库,该库旨在为网络服务和请求创建类型安全的结构。

您可能会问自己:“这是什么Moya?我已经知道并喜欢Alamofire!” 而且,如果您不了解和喜欢它,现在将是查看我们关于该主题的出色教程的绝佳时机。

好吧,这是最重要的部分:Moya实际上使用Alamofire,同时提供了另一种方法来构建网络层。在本教程的后面,您将学到更多有关Moya和Alamofire之间关系的信息。

在本教程中,您将构建一个名为ComicCards的简洁小应用程序,在其中您将使用Marvel API向用户显示在给定的一周内发行的漫画列表,以及其封面图像和其他有趣的信息。当用户选择漫画时,您的应用将生成包含漫画信息和图像的可共享卡的图像,让用户将其上传到Imgur服务并共享:

哇-在一个应用程序中有两种不同的API服务?不用担心 它并不像听起来那样难。让我们开始吧!

注意:本教程假定您具有HTTP API的基本知识,尽管即使您只有很少的知识,您也可以轻松地遵循本教程。但是,如果您想了解更多有关HTTP API的信息,请参考前面提到的Alamofire教程,或者参考这个有趣的站点,以获取有关REST API基础的更多信息。

代码下载

https://koenig-media.raywenderlich.com/uploads/2018/07/ComicCards.zip

入门

使用本教程顶部或底部的“ 下载材料”按钮,可下载已捆绑Moya 的ComicCards入门项目。打开ComicCards.xcworkspace而不是项目文件-这很重要。

在项目打开的情况下,签出Main.storyboard以大致了解应用程序的结构:

该ComicCards应用程序由两个不同的屏幕:

  • ComicsViewController:负责将漫画列表呈现给用户的视图控制器。
  • CardViewController:视图控制器,负责为所选漫画创建卡并让用户共享生成的卡。
    生成并运行项目。您应该看到以下屏幕:

    毫不奇怪,由于尚未实现与从服务器提取漫画并将其显示在应用中有关的逻辑,因此会看到一个错误屏幕。您很快就会添加所有必需的代码,但是首先您需要学习一些有关Moya的知识。

Moya:是什么?

什么是Moya?

Moya是一个网络库,专注于以类型安全的方式封装网络请求,通常通过使用枚举(例如enum)在与您的网络层一起使用时提供编译时保证和信心以及增加的可发现性。

它是由Ash Furrow和Orta Therox为Artsy的Eidolon应用程序构建的,并迅速流行。如今,它完全由热情的开源贡献者社区维护。

Moya与Alamofire有何关系?

如本教程的简介中所述,Moya和Alamofire之间的紧密联系只是因为Moya本身并没有真正进行任何联网。它使用Alamofire经过实战检验的网络功能,并且仅提供其他功能,类型和概念来进一步抽象Alamofire。

实际上,您正在使用Alamofire!而不是直接使用它,而是使用Moya,后者在引擎盖下使用Alamofire。

查看启动项目的Podfile.lock可以发现,Alamofire是Moya的依赖项:

Moya的积木

Moya引入了一些独特的概念和构建块,在开始编写代码之前,您应该了解这些概念和构建块。它使用以下构建块来描述整个网络链:

  • Provider:Moya MoyaProvider将是您与任何网络服务进行交互时要创建和使用的主要对象。这是一个通用对象,在初始化时会使用Moya目标。
  • Target:Moya目标通常描述整个API服务;在这种情况下,将指定Marvel目标和Imgur目标。这些目标中的每一个都描述服务,其可能的端点以及每个端点执行请求所需的信息。您可以通过遵循TargetType协议来定义目标。
  • Endpoint:Moya使用半内部Endpoint对象描述执行网络请求所需的基本信息,例如HTTP方法,请求正文,标头等。莫亚(Moya)MoyaProvider将每个目标Endpoint都转换为,最终将其转换为原始图像URLRequest。端点是高度可定制的,但是由于不需要任何自定义映射,因此不在本教程的讨论范围之内。
    现在您已经掌握了所有基本理论,是时候编写一些代码了!

漫威API –英雄的API

在奇迹API是世界上最大的漫画API,由Marvel本身创建和维护。

首先创建一个免费帐户。设置完毕后,请返回“ 我的开发者帐户”页面,在该页面中,您将找到新的公共和私有密钥:

保持两个键都在手;几分钟后您将需要它们。

创建您的第一个Moya Target

返回到ComicCards Xcode项目。在您的项目导航器中,右键单击ComicCards / Network文件夹,然后选择New File…创建一个新的Swift文件并将其命名为Marvel.swift

之后import Foundation,添加以下代码:

import Moyapublic enum Marvel {// 1static private let publicKey = "YOUR PUBLIC KEY"static private let privateKey = "YOUR PRIVATE KEY"// 2case comics
}

您刚刚创建了一个非常简单的枚举,描述了将要使用的API服务:

这些是您的Marvel公钥和私钥。您将它们与服务定义一起存储,以确保可以轻松地将密钥作为服务配置的一部分进行访问。确保将占位符替换为上一步中生成的实际密钥。
一个名为的枚举案例comics,它代表您将在Marvel API中使用的唯一端点-GET / v1 / public / comics。
现在,您已经配置了基本的枚举,是时候通过遵循使其实际成为目标TargetType。

将以下代码添加到文件末尾(大括号后):

extension Marvel: TargetType {// 1public var baseURL: URL {return URL(string: "https://gateway.marvel.com/v1/public")!}// 2public var path: String {switch self {case .comics: return "/comics"}}// 3public var method: Moya.Method {switch self {case .comics: return .get}}// 4public var sampleData: Data {return Data()}// 5public var task: Task {return .requestPlain // TODO}// 6public var headers: [String: String]? {return ["Content-Type": "application/json"]}// 7public var validationType: ValidationType {return .successCodes}
}

这看起来可能像是大量的代码,但这仅仅是为了符合TargetType。让我们分解一下:

  1. 每个目标(例如服务)都需要一个基本URL。Moya将使用它最终构建正确的Endpoint对象。
  2. 对于目标的每种情况,您都需要定义path相对于基本URL 的确切匹配。由于漫画的API位于https://gateway.marvel.com/v1/public/comics,因此此处的值就是/comics。
  3. 您需要为目标的每种情况提供正确的HTTP方法。这.get就是你想要的。
  4. sampleData用于提供API的模拟/存根版本以进行测试。对于您的情况,您可能只想返回一个或两个漫画的假回复。创建单元测试时,Moya可以将这种“伪”响应返回给您,而无需联系网络。由于您不会在本教程中进行单元测试,因此您将返回一个空Data对象。
  5. task这可能是最重要的属性。您应该Task为每个要使用的端点返回一个枚举用例。您可以使用许多任务选项,例如,普通请求,数据请求,参数请求,上传请求等等。当前将其标记为“要做”,因为您将在下一部分中对其进行处理。
  6. headers是您为目标的每个端点返回适当的HTTP标头的位置。由于所有Marvel API端点都返回JSON响应,因此您可以安全地将Content-Type: application/json标头用于所有端点。
  7. validationType用于提供您对成功的API请求的定义。有许多选项可用,对于您而言,您只需使用.successCodes它即可,这意味着如果请求的HTTP代码在200到299之间,则该请求将被视为成功。

注意:请注意,switch即使只有一个大小写(.comics),您也在所有属性中使用语句。这是一般的最佳做法,因为您的目标可能会轻松演变并添加更多端点。对于不同的目标属性,任何新端点都将需要其自己的值。

哇,这是很多知识!考虑到这是以Moya的最基本形式工作所需要了解的大部分知识,您应该感到非常自豪!

新Marvel目标中仅缺少一件事-代码中剩下的“要做”,即返回Task。

在Marvel的API中授权请求

Marvel API使用自定义授权方案,在该方案中,您将使用唯一的标识符(例如时间戳),私钥和公钥创建一个“哈希”,这些标识符全部串联在一起并使用MD5进行哈希处理。您可以在API参考中的“服务器端应用程序的身份验证”下阅读完整规范。

在Marvel.swift中,替换task为以下内容:

public var task: Task {let ts = "\(Date().timeIntervalSince1970)"// 1let hash = (ts + Marvel.privateKey + Marvel.publicKey).md5// 2let authParams = ["apikey": Marvel.publicKey, "ts": ts, "hash": hash]switch self {case .comics:// 3return .requestParameters(parameters: ["format": "comic","formatType": "comic","orderBy": "-onsaleDate","dateDescriptor": "lastWeek","limit": 50] + authParams,encoding: URLEncoding.default)}
}

您的任务已准备就绪!这是做什么的:

  1. 如前所述,您可以通过将随机时间戳记,私钥和公钥进行串联来创建所需的哈希,然后将整个字符串哈希为MD5。您正在使用Helpers / String + MD5.swift中的md5helper属性。
  2. 该authParams字典包含所需的授权参数:apikey,ts和hash,包含公开密钥,时间戳和哈希,分别。
  3. 代替.requestPlain使用先前的任务,而是切换为使用.requestParameters任务类型,该类型处理带有参数的HTTP请求。您为任务提供了几个参数,这些参数指示您要在给定的星期内最多获取50幅漫画,并按最新顺序排序onsaleDate。您可以将authParams之前创建的参数添加到参数字典中,以便它们与其余请求参数一起发送。
    至此,您的新Marvel目标已准备就绪!接下来,您将进行更新ComicsViewController以使用它。

使用目标

转到ComicsViewController.swift并在视图控制器类的开头添加以下内容:

let provider = MoyaProvider<Marvel>()

如前所述,您将用于与Moya目标进行交互的主要类是MoyaProvider,因此您首先创建一个MoyaProvider使用新Marvel目标的实例。

接下来,在您的内viewDidLoad(),替换为:

state = .error

带有:

// 1
state = .loading// 2
provider.request(.comics) { [weak self] result inguard let self = self else { return }// 3switch result {case .success(let response):do {// 4print(try response.mapJSON())} catch {self.state = .error}case .failure:// 5self.state = .error}
}

新代码执行以下操作:

  1. 首先,将视图的状态设置为.loading。
  2. 使用提供程序在.comics端点上执行请求。注意,这是完全类型安全的,因为.comics是这种enum情况。因此,不必担心错误输入错误的选项。以及为目标的每个端点获取自动完成案例的附加价值。
  3. 闭包提供的a result可以是.success(Moya.Response)或.failure(Error)。
  4. 如果请求成功,则使用Moya的mapJSON方法将成功的响应映射到JSON对象,然后将其打印到控制台。如果转换引发异常,则将视图的状态更改为.error。
  5. 如果返回的result是.failure,则还可以将视图的状态设置.error为。
    生成并运行该应用程序。Xcode调试控制台应显示类似于以下内容:
{attributionHTML = "<a href=\"http://marvel.com\">Data provided by Marvel. \U00a9 2018 MARVEL</a>";attributionText = "Data provided by Marvel. \U00a9 2018 MARVEL";code = 200;copyright = "\U00a9 2018 MARVEL";data =     {count = 19;limit = 50;offset = 0;results =         ({comic object},{comic object},{comic object},...)
}

很棒的工作,您使用Moya和新Marvel目标从后端获得了有效的JSON响应!

注意:结果可能需要几秒钟才能出现在调试控制台中。


完成此视图控制器的最后一步实际上是将JSON响应映射到适当的数据模型—在您的情况下,是预配置的Comic结构。

这是使用其他Moya响应映射器的最佳时机,该映射器将响应映射到Decodable而不是原始JSON。

您可能已经注意到JSON响应的结构类似于:

data ->results -> [ Array of Comics ]

在到达对象本身之前data,意味着两个层次的嵌套(,results)。入门项目已经包括Decodable负责解码的适当对象。

替换以下内容:

print(try response.mapJSON())

带有:

self.state = .ready(try response.map(MarvelResponse<Comic>.self).data.results)

而不是将对象映射到原始JSON响应,而是使用将MarvelResponse通用对象Decodable与Comic结构一起使用的映射器。这还将解决两个嵌套层次的解析,使您可以通过访问来访问漫画数组data.results。

您可以将视图的状态设置为,.ready并将其关联的值为Comic从Decodable映射返回的对象数组。

生成并运行项目。您应该会看到第一个屏幕功能齐全!

然后进入细节视图!

当您点击漫画时,入门项目已经具有用于显示a CardViewController并将所选内容传递Comic给它的代码。但是,您可能会注意到,点击漫画只会显示一张空牌,而没有任何漫画细节。让我们来照顾它!

切换到CardViewController.swift并找到layoutCard(comic:)方法。在方法内部,添加:

// 1
lblTitle.text = comic.title
lblDesc.text = comic.description ?? "Not available"// 2
if comic.characters.items.isEmpty {lblChars.text = "No characters"
} else {lblChars.text = comic.characters.items.map { $0.name }.joined(separator: ", ")
}// 3
lblDate.text = dateFormatter.string(from: comic.onsaleDate)// 4
image.kf.setImage(with: comic.thumbnail.url)

此代码Comic通过以下方式使用提供的结构中的信息更新屏幕:

  1. 设置漫画的标题和漫画的描述。
  2. 设置漫画的字符列表,如果没有字符,则设置为“无字符”。
  3. 使用预先配置的设置漫画的“发售”日期DateFormatter。
  4. 使用翠鸟(Kingfisher)加载漫画的图像-伟大的第三方库,用于加载Web图像。

生成并运行您的应用程序,然后点击列表中的漫画之一-您应该会看到一张漂亮的信息卡:

您还需要添加两个功能:将卡上传到Imgur,并允许用户删除卡。

Imgur –与朋友分享!

为此,您将创建另一个名为Moya的目标Imgur,该目标可让您与两个不同的终结点进行交互以进行图像处理:一个终结点用于上传,另一个终结点用于删除。

与Marvel API相似,您需要在Imgur 上注册一个免费帐户。

之后,您需要创建一个Imgur Application。您可以使用任何伪造的URL进行回调,因为这里不会使用OAuth。您也可以简单地选择没有回调URL的OAuth 2授权

提交表单后,Imgur将为您提供新的Imgur 客户ID和客户密码。保存这些以进行下一步。

创建 Target

右键单击ComicCards / Network文件夹,然后选择New File…,然后创建一个新的Swift文件,并将其命名为Imgur.swift

添加以下代码以定义将要实现和使用的Imgur端点:

import UIKit
import Moyapublic enum Imgur {// 1static private let clientId = "YOUR CLIENT ID"// 2case upload(UIImage)case delete(String)
}

与Marvel API相似,您可以:

  1. 将您的Imgur客户ID存储在中clientId。确保将其替换为上一步中生成的“客户端ID”(不需要密码)。
  2. 定义将要使用的两个端点:upload,用于上传图像的端点,和delete,对先前上传的图像进行哈希处理,然后将其从Imgur中删除。这些在Imgur API中表示为POST / image和DELETE / image / {imageDeleteHash}。
    接下来,您将符合TargetType。在新代码的正下方添加以下代码enum:
extension Imgur: TargetType {// 1public var baseURL: URL {return URL(string: "https://api.imgur.com/3")!}// 2public var path: String {switch self {case .upload: return "/image"case .delete(let deletehash): return "/image/\(deletehash)"}}// 3public var method: Moya.Method {switch self {case .upload: return .postcase .delete: return .delete}}// 4public var sampleData: Data {return Data()}// 5public var task: Task {switch self {case .upload(let image):let imageData = image.jpegData(compressionQuality: 1.0)!return .uploadMultipart([MultipartFormData(provider: .data(imageData),name: "image",fileName: "card.jpg",mimeType: "image/jpg")])case .delete:return .requestPlain}}// 6public var headers: [String: String]? {return ["Authorization": "Client-ID \(Imgur.clientId)","Content-Type": "application/json"]}// 7public var validationType: ValidationType {return .successCodes}
}

现在您应该已经熟悉了。让我们浏览一下新Imgur目标的七个协议属性。

  1. Imgur API的基本URL设置为https://api.imgur.com/3。

  2. 您path根据情况返回适当的端点。/image为.upload和/image/{deletehash}为.delete。

  3. method不同的情况也因情况而不同:.postfor .upload和.deletefor .delete。

  4. 与之前一样,您为返回一个空Data结构sampleData。

  5. 这task是事情变得有趣的地方。您为每个端点返回一个不同的 Task值。该.delete案例不需要任何参数或内容,因为它是一个简单的DELETE请求,但是该.upload案例需要更多的工作。
    要上传文件,您将使用.uploadMultipart任务类型,该类型需要一个MultipartFormData结构数组。然后,您MultipartFormData使用适当的图像数据,字段名称,文件名和图像mime类型创建的实例。

  6. 与Marvel API一样,该headers属性返回一个Content-Type: application/json标头和一个附加标头。Imgur API使用标头授权,因此您需要在每个请求的标头中以形式提供您的客户ID Authorization: Client-ID (YOUR CLIENT ID)。

  7. 该.validationType是和以前一样-适用于200和299之间的任何状态代码。

您的Imgur目标完成了!到此,ComicCards应用程序的Moya相关代码结束。恭喜您!

最后一步是CardViewController使其使用您新创建的Moya目标。

包装CardViewController

返回CardViewController.swift并在CardViewController类的开头在comic属性下方添加以下行:

private let provider = MoyaProvider<Imgur>()
private var uploadResult: UploadResult?

像以前一样,您MoyaProvider这次使用Imgur目标创建实例。您还定义uploadResult了一个可选UploadResult属性,用于存储上传结果,删除图像时将需要该属性。

您有两种实现方法:uploadCard()和deleteCard()。

在的末尾uploadCard(),添加以下代码:

// 1
let card = snapCard()// 2
provider.request(.upload(card),// 3callbackQueue: DispatchQueue.main,progress: { [weak self] progress in// 4self?.progressBar.setProgress(Float(progress.progress), animated: true)},completion: { [weak self] response inguard let self = self else { return }// 5UIView.animate(withDuration: 0.15) {self.viewUpload.alpha = 0.0self.btnShare.alpha = 0.0}// 6switch response {case .success(let result):do {let upload = try result.map(ImgurResponse<UploadResult>.self)self.uploadResult = upload.dataself.btnDelete.alpha = 1.0self.presentShare(image: card, url: upload.data.link)} catch {self.presentError()}case .failure:self.presentError()}
})

这段大代码肯定需要一些解释,但不必担心-大多数应该相对熟悉。

  1. 您使用称为的帮助程序方法从屏幕上显示的卡片snapCard()生成UIImage。
  2. 与Marvel API一样,您可以使用提供程序来调用upload具有卡图像相关值的端点。
  3. callbackQueue允许提供一个队列,在下一个回调中您将在该队列上接收上传进度更新。您提供DispatchQueue主线程以确保在主线程上发生进度更新。
  4. 您定义一个进度关闭,当您的图像上传到Imgur时将调用该关闭。这将设置进度条的进度,并将在中DispatchQueue提供的主菜单上调用callbackQueue。
  5. 请求完成后,您将淡出上传视图和共享按钮。
  6. 和以前一样,您可以处理结果的success和failure选项。如果成功,则尝试将响应映射到,然后将映射的响应存储在之前定义的实例属性中。 ImgurResponse
    您稍后将在完成该deleteCard()方法时使用此属性。存储上载结果后,您将触发该presentShare方法,该方法将显示正确的共享警报以及上载图像的URL和图像本身。失败将触发该presentError()方法。

对于当天的最后一段代码:在下面添加以下代码deleteCard():

// 1
guard let uploadResult = uploadResult else { return }
btnDelete.isEnabled = false// 2
provider.request(.delete(uploadResult.deletehash)) { [weak self] response inguard let self = self else { return }let message: String// 3switch response {case .success:message = "Deleted successfully!"self.btnDelete.alpha = 0.0case .failure:message = "Failed deleting card! Try again later."self.btnDelete.isEnabled = true}let alert = UIAlertController(title: message, message: nil, preferredStyle: .alert)alert.addAction(UIAlertAction(title: "Done", style: .cancel))self.present(alert, animated: true, completion: nil)
}

此方法非常简单,其工作方式如下:

  1. 您确保uploadResult可用,并禁用删除按钮,以便用户不再点击它。
  2. 您可以使用Imgur提供程序来调用delete具有上载结果的关联值的终结点deletehash。此哈希唯一地标识上传的图像。
  3. 如果删除成功或失败,则会显示相应的消息。

这就对了!最后一次构建并运行您的应用。选择漫画并将您的图像分享给Imgur。完成后,您可以点击从Imgur删除按钮将其删除。

注意:您可能会注意到,只有在卡片视图控制器中,您才能删除上传的图像。离开后,视图控制器uploadResult将被清除,并且deletehash将丢失。在不同的会话中持久保存所有生成的图像的哈希是一个不错的挑战,您可能想解决:]。

将Moya推向新的高度

Moya是一个极其通用的网络库,具有太多的附加功能,无法在本教程中全面介绍,但是绝对值得一提:

  1. 响应式扩展: Moya为RxSwift和ReactiveSwift提供并维护了Moya两种出色的响应式添加,分别命名为RxMoya和ReactiveMoya。
  2. 插件:Moya可让您创建名为Plugins的片段,可用于修改请求和响应或执行副作用。例如,它对于记录请求和响应或在运行网络请求时自动显示网络活动指示符很有用。
  3. 测试:如前所述,每个属性TargetType都有一个sampleData属性,您可以在其中为端点提供桩头响应。创建时MoyaProvider,您可以提供stubClosure,定义要Moya返回存根响应还是真实响应(默认值)。您可以在Moya的测试文档中了解更多信息。
  4. Harvey:说到响应响应-Moya背后的一些团队正在开发一个名为Harvey的独立框架,以轻松模拟网络响应。它仍处于早期开发阶段,但我强烈建议您跟随这个项目。

然后去哪儿?

您可以使用本教程顶部或底部的“ 下载材料”按钮下载项目的完整版本。不要忘记在项目中设置您的Imgur客户ID和Marvel公共和私有密钥!

在本教程中,您已经学习了使用Moya的基础知识,然后再学习一些!您拥有将网络层提升到新层次所需的一切。

继续探索Moya的最佳地方是它的官方文档页面,该页面内容丰富,深入探讨Moya的各个方面,甚至保留中文翻译。

同时,如果您对本教程或一般网络有任何疑问或意见,请加入下面的论坛讨论。

翻译自

https://www.raywenderlich.com/5121-moya-tutorial-for-ios-getting-started

【翻译】iOS Swift Moya从入门到精通,优雅、安全的Alamofire相关推荐

  1. 《iOS移动开发从入门到精通》图书连载一:如果你也想开发一款自己的APP,可以看一下这篇文

    前言:互联网+时代给自己多一个选择的机会,尝试开发一款属于自己的APP,绝对是件激动人心的事情!<iOS移动开发从入门到精通>已经上市并和大家见面.从今天起,我会将把图书的部分内容以连载的 ...

  2. 视频教程-iOS移动开发从入门到精通(Xcode11 Swift5)-iOS

    iOS移动开发从入门到精通(Xcode11 & Swift5) 15年以上IT行业工作经验.8年以上IT行业教学经验.丰富的项目经验和授课经验,授课形式不拘一格.熟悉iOS开发,网页开发.Ja ...

  3. 《iOS移动开发从入门到精通》图书连载5:Xcode 8的使用(上)

    微信公众号:酷课堂(ID:coolketang)独家文章,其他媒体转载请注明出处 本期导读 从今天开始,我们将要讲述的是<ios移动开发从入门到精通> 这本书的第二章内容"Xco ...

  4. 《iOS移动开发从入门到精通》图书连载4:iOS应用的生命周期

    iOS应用的生命周期 iOS应用的生命周期是指从应用程序的启动,到应用程序结束整个阶段的全过程. 整个iOS应用的生命周期包含了各种状态,有时系统会从应用的一种状态切换至另一种状态来响应系统发生的事件 ...

  5. iOS swift UITest 基础入门(一)

    在项目组内做UITest几个月了,输出才是真正的提高嘛,总结了一下,写出来做一个UITest的讲解. 首先说一下目的:UITest,可以模拟人的操作,当然还可以使用第三方用以模拟网络请求,再加上数据库 ...

  6. 《iOS移动开发从入门到精通》图书连载7:iOS模拟器的使用

    酷课堂(ID:coolketang)独家文章,其他媒体转载请注明出处 本期导读 当您在使用Xcode软件开发iOS平台的应用程序时,可以使用Apple提供的iOS模拟器进行应用程序的测试.Apple提 ...

  7. 翻译:swift 5 iOS Accessibility从入门到精通

    在此 iOS 辅助功能教程中,了解如何使用 VoiceOver 和辅助功能检查器让应用更易于访问. 各行各业.各个年龄段和不同背景的人都使用智能手机应用程序,包括残疾人士.在设计您的应用时考虑到可访问 ...

  8. 翻译:Swift 5创建和使用Framework, XCFramework 从入门到精通

    说明 了解如何构建iOS框架,该框架可让您在应用程序之间共享代码,模块化代码或将其分发为第三方库. 下载代码 更新说明:本教程已由Emad Ghorbaninia更新到iOS 14,Xcode 12和 ...

  9. 【翻译】WF从入门到精通(第十一章):并行活动

    上一篇:[翻译]WF从入门到精通(第十章):事件活动 学习完本章,你将掌握:     1.理解在工作流环境中Parallel活动是怎样执行的,并且懂得如何使用它们     2.并行执行路径中的同步数据 ...

  10. IOS之Swift的CoreData入门使用案例

    IOS之Swift的CoreData入门使用案例 CoreData和SQLite3类似,用来把数据存在磁盘上的.可以随时读写. 创建项目钩上 当APP退出的时候,数据消失. 使用CoreData,退出 ...

最新文章

  1. php的反射作用是什么意思,php反射的作用是什么
  2. 两个port贴合七夕主题,百度输入法的“情感营销”策略
  3. Android: how to resolve Application’s parameter NullPointerException
  4. 算法---------二叉树的后序遍历
  5. 如何使用Mockito模拟void方法
  6. python知识点总结全_【转】Python高级知识点总结
  7. ubuntu下面supertux2的玩法
  8. Linux服务器重启后crs_stat -t 命令无法正常使用以及解决思路
  9. Java—BIO模型
  10. 领域应用 | 智能导购?你只看到了阿里知识图谱冰山一角
  11. shell判端mysql数据库是否存在_Shell脚本检测和检查mysql数据库是否存在坏表
  12. ios- uitextview的详细使用方法
  13. allgro pcb铜皮编辑_干货技巧-Allegro如何设置整体铜皮连接或设置单个管脚连接方式...
  14. wdcp虚拟主机管理系统注入漏洞
  15. C# 参数1:值参数----值类型和引用类型及特例string
  16. 微信开工具报getLocation:fail, the permission value is offline verifying
  17. 光纤中的多种光学模式芯径_光纤激光器的重要参数BPP(M2)
  18. mysql登陆策略_教你mysql mssql服务器安全设置策略
  19. 谷歌首度证实重返中国的Dragonfly计划存在丨Q新闻
  20. 网络对抗技术——网络嗅探与欺骗(第三部分)

热门文章

  1. 【随手写】BAT脚本之获取N天前日期
  2. 【嵌入式 C】C语言中格式输出二进制的两种方法
  3. python获取交互式ssh shell
  4. VTK四面体文件格式
  5. 如何查看linux的系统配置,多少个核心,多少个线程?CPU的主频 查看内存
  6. 7-14 设计一个风扇Fan类 (20 分)
  7. 易语言解析ip138.com的查询接口
  8. 计算机主机usb端口使用不了,解答电脑usb接口不能用怎么解决
  9. 如何更换阿里云邮箱绑定手机号
  10. 怎么样可以在阿里云搭建个人网站及域名绑定介绍