如何构造 Swift 框架(四)

本贴最后更新于 2746 天前,其中的信息可能已经水流花落

上期提到了使用Moya作为网络基础模块,但是涉及到了一个sampleData的问题,我们也是即时的提交了一个issue来质问这样的默认Response data为什么类型竟然是Optional的。Moya的开发者举例:可以将上一次获取到的数据在需要的时候(网络请求失败)传入这里,所以进而给出建议:将var sampleData改为var cachePolicy进行缓存控制即可,缓存过期的时间由Server端使用Cache-control或Expires决定,目前有的回复是,作者觉得这个建议很棒,说不定有机会为Moya加入缓存机制。接下来继续我们的开发计划:

推送服务

应当明确的是,每家公司用的推送第三方都是不同的(大部分是阿里云、极光、个推),所以继承第三方SDK这个事情不应该出现在框架中。框架仅仅负责申请推送能力即可。测试:在测试之前,Info.plist中所需要申请权限的Key需要自己手动配置。Xcode 8 后打开推送需要在程序中打开选项:

这样,如果是单单写权限的话,直接用之前我们引入的PermissionScope就可以搞定了,Push的class可以写为open的,因为每个项目对Push的需求不同,所以在Push中我们顺便截获一下信息然后提供给用户,也很简单。所以获得推送权限的需求我们放到Permission.swift中。由于屏幕限制,所以Permission也最多允许大家同时打开3个权限。改写之前的Permission.swift

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
import Foundation
import PermissionScope
 
public enum INSPermissionType {
case notification(Set<UIUserNotificationCategory>?, String)
case locationAlways(String)
case locationWhenInUse(String)
case contact(String)
case event(String)
case microphone(String)
case camera(String)
case photos(String)
case reminders(String)
case bluetooth(String)
case motion(String)
}
 
open class Permission {
open static let `default` = Permission()
static let pscope: PermissionScope = {
let permissionScope = PermissionScope()
// Default customs
permissionScope.headerLabel.text = "嗨,你好!"
permissionScope.bodyLabel.text = "在使用我们的应用之前\n我们需要你做一些事情:"
permissionScope.closeButtonTextColor = UIColor.clear
permissionScope.permissionButtonΒorderWidth = 0.5
permissionScope.permissionButtonCornerRadius = 2
/// 如果你希望更改权限开启按钮的英文,就需要自己配置本地化文件
/// 参考这里 https://github.com/nickoneill/PermissionScope/pull/12#issuecomment-96428580
return permissionScope
}()
 
open class func requestPermission(_ permissionTypes: [INSPermissionType], _ authChange: authClosureType? = nil, cancelled: cancelClosureType? = nil) {
for item in permissionTypes {
switch item {
case .notification(let categories, let message):
pscope.addPermission(NotificationsPermission(notificationCategories: categories), message: message)
continue
case .locationAlways(let message):
pscope.addPermission(LocationWhileInUsePermission(), message: message)
continue
case .locationWhenInUse(let message):
pscope.addPermission(LocationWhileInUsePermission(), message: message)
continue
case .contact(let message):
pscope.addPermission(ContactsPermission(), message: message)
continue
case .event(let message):
pscope.addPermission(EventsPermission(), message: message)
continue
case .microphone(let message):
pscope.addPermission(MicrophonePermission(), message: message)
continue
case .camera(let message):
pscope.addPermission(CameraPermission(), message: message)
continue
case .photos(let message):
pscope.addPermission(PhotosPermission(), message: message)
continue
case .reminders(let message):
pscope.addPermission(RemindersPermission(), message: message)
continue
case .bluetooth(let message):
pscope.addPermission(BluetoothPermission(), message: message)
continue
case .motion(let message):
pscope.addPermission(MotionPermission(), message: message)
continue
default:
continue
}
}
 
pscope.show(authChange, cancelled: cancelled)
}
}

然后测试效果(别忘记在Info.plist中添加相关的请求权限的Key-Desc):

1
2
3
4
5
6
7
let permissionTypes = [
INSPermissionType.notification(nil, "打开推送服务"),
INSPermissionType.camera("打开相机服务"),
INSPermissionType.photos("希望使用照片")
]
 
Permission.requestPermission(permissionTypes)

原本计划是要写Push.swift进行截获数据的,但是总感觉这样做貌似不太合理。所以索性我们止只统计一下用户收到推送好了,在Push.swift中仅提供一个方法入口,把推送的内容传进来供我们内部处理。不要干涉AppDelegate处理推送了,而且在iOS10之后要做版本兼容,使用UNUserNotificationCenterDelegate来处理推送,而且大多数第三方SDK都会有自己的处理方式。

所以在我们的Push.swift中,我们先预留一些代码:

等会儿先看个东西:

好、继续写代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
final public class Push {
public static let `default` = Push()
 
public func DeviceToken(_ deviceToken: Data) {
 
}
 
public func ReceivedPushMessage (_ userInfo: [AnyHashable : Any]) {
 
}
 
private init() {
 
}
}

简单的预留一些方法入口即可,不急着写,接着往下写日志上报(直接改造之前的Logger类):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
final public class INSLogger {
/// 默认为输出全部日志
public static let `default` = INSLogger()
 
/// 日志级别
public var level: LogLevel = .all
/// 是否上报崩溃
public var crashCollect: Bool = true
 
/// 日志输出
///
/// - parameter lev: 日志级别
/// - parameter content: 日志内容
public func printLog(_ lev: LogLevel, _ details: String, _ items: Any) {
guard level == .all || level == lev, ModeSwitcher.currentMode == .develope else {
return
}
 
print(lev.rawValue, details, "\n", items)
}
private var exception: NSException? = nil
public func setUncaughtException() {
NSSetUncaughtExceptionHandler {
let exception = $0
let name = exception.name
let reason = exception.reason ?? "Without system crash version."
let callStack = exception.callStackSymbols
let crashLog = "name:\(name)\nreason:\(reason)\ncallStack:\(callStack.joined(separator: "\n"))"
 
// TODO: 上报
}
}
}

获取到崩溃的信息后,我们在这里加一个TODO标签。 这里需要注意的是:框架外部如果也需要做日志捕获,那么需要先使用NSGetUncaughtExceptionHandler()获取当前的捕获器,在自己的捕获成功之后也让别人的捕获成功。啊好累啊,这还不是完整的奔溃捕获,于是我们接着写代码(写代码到时无妨,主要是这里有一坑爹的事情,需要自己去查看解决,说明:无法把方法传入这些捕获方法也附上气前一个链接中的代码):

慵懒的完善了signal后(上边提到的不能使用C方法的问题自己去解决把,这里仅仅是展示,所以不写那么详细了):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
/// 设置异常捕获
public func setUncaughtException() {
 
NSSetUncaughtExceptionHandler {
let exception = $0
let name = exception.name
let reason = exception.reason ?? "Without system crash version."
let callStack = exception.callStackSymbols
let crashLog = "name:\(name)\nreason:\(reason)\ncallStack:\(callStack.joined(separator: "\n"))"
 
exception.raise()
// TODO: 上报
}
 
signal(SIGILL) {
let crashLog = "SignalRaisedException(\($0)): Illegal instruction (not reset when caught)"
// TODO: 上报
}
signal(SIGABRT) {
let crashLog = "SignalRaisedException(\($0)): Abort, abort()"
// TODO: 上报
}
signal(SIGFPE) {
let crashLog = "SignalRaisedException(\($0)): Floating point exception"
// TODO: 上报
}
signal(SIGBUS) {
let crashLog = "SignalRaisedException(\($0)): Bus Error"
// TODO: 上报
}
signal(SIGSEGV) {
let crashLog = "SignalRaisedException(\($0)): segmentation violation"
// TODO: 上报
}
signal(SIGSYS) {
let crashLog = "SignalRaisedException(\($0)): Bad argument to system call"
// TODO: 上报
}
signal(SIGPIPE) {
let crashLog = "SignalRaisedException(\($0)): Write on a pipe with no one to read it"
// TODO: 上报
}
}
 
public func unSetUncaughtException() {
NSSetUncaughtExceptionHandler(nil)
signal(SIGILL, SIG_DFL);
signal(SIGABRT, SIG_DFL);
signal(SIGFPE, SIG_DFL);
signal(SIGBUS, SIG_DFL);
signal(SIGSEGV, SIG_DFL);
signal(SIGSYS, SIG_DFL);
signal(SIGPIPE, SIG_DFL);
}

这里只捕获了一部分signal,点进去自己看了解下,我之前也写过一篇关于日志捕获的文章,可以去找找。继续往下写:信息收集,新建swift文件Analytics.swift,这里我只给出一部分思路(完整的Analytics又是一个独立的框架,建议参考的是开源的ZhugeIO):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
import Foundation
import LKDBHelper
 
let AnalyticsManagerFlushedFlagKey = "AnalyticsManagerFlushedFlagKey"
 
class AnalyticsItem: NSObject {
/// 事件名称
var eventName: String
/// 事件数据
var parameters: [String: Any]?
 
func toAnalytice() ->[String: String] {
return [eventName ?? "": "\(parameters ?? ["": ""])"]
}
 
init(_ eventName: String, _ parameters: [String: Any]? = nil) {
self.eventName = eventName
self.parameters = parameters
}
 
override static func getTableName() -> String {
return "AnalyticsItem"
}
}
 
final public class Analytics {
public static let `default` = Analytics()
public typealias FlushHandler = (_ info: [String], _ analyticsData: [[String: String]])->()
 
/// 设备唯一标识,默认是UUID
public var deviceIdentifier: String
/// 设备用户标识,以设备标识为准
public var userIdentifier: String
/// 上报间隔,会调用上报的方法,外部控制网络请求
public var flushInterval: Int = 10
/// 上报的回调方法
public var flushHandler: FlushHandler?
 
/// 存储准备上报的数组
private var analyticsItems: [AnalyticsItem] = []
/// 是否已经上报,通过检查本地值来确定
private var flushed: Bool
private var timer: Timer? = nil
 
/// 追踪事件
public func track(_ eventName: String, _ parameters: [String: Any]? = nil) {
if analyticsItems.count == 0 {
startTimer()
}
analyticsItems.append(AnalyticsItem(eventName, parameters))
}
 
/// 主动上报到服务器
public func flush() {
guard analyticsItems.count > 0, let handler = flushHandler else {
return
}
handler([deviceIdentifier, userIdentifier], analyticsItems.map { return $0.toAnalytice() })
stopTimer()
flushed = true
}
 
private func startTimer() {
stopTimer()
timer = Timer.init(timeInterval: TimeInterval(flushInterval), target: self, selector: "flush", userInfo: nil, repeats: true)
RunLoop.current.add(timer!, forMode: .commonModes)
}
 
private func stopTimer() {
timer?.invalidate()
timer = nil
analyticsItems.removeAll()
}
 
private func getLocalAnalyticsItem() {
AnalyticsItem.search(withWhere: nil).forEach {
[unowned self] in
self.analyticsItems.append($0 as! AnalyticsItem)
}
let dbHelper = AnalyticsItem.getUsingLKDBHelper()!
dbHelper.dropTable(with: AnalyticsItem.self)
}
 
public func setNeedsRestoreItems() {
analyticsItems.forEach { $0.saveToDB() }
analyticsItems.removeAll()
UserDefaults.standard.set(false, forKey: AnalyticsManagerFlushedFlagKey)
UserDefaults.standard.synchronize()
}
 
public func restoreItems() {
if flushed == false {
getLocalAnalyticsItem()
}
}
 
private func UIApplicationDidEnterBackground() {
setNeedsRestoreItems()
}
 
private func UIApplicationDidBecomeActive() {
self.flushed = UserDefaults.standard.bool(forKey: AnalyticsManagerFlushedFlagKey)
restoreItems()
}
 
private func addListener() {
NotificationCenter.default.addObserver(self, selector: "UIApplicationDidEnterBackground", name: .UIApplicationDidEnterBackground, object: nil)
NotificationCenter.default.addObserver(self, selector: "UIApplicationDidBecomeActive", name: .UIApplicationDidBecomeActive, object: nil)
}
 
private func removeListener() {
stopTimer()
NotificationCenter.default.removeObserver(self)
}
 
private init() {
self.deviceIdentifier = UUID().uuidString
self.userIdentifier = "iOS Device"
self.flushed = UserDefaults.standard.bool(forKey: AnalyticsManagerFlushedFlagKey)
restoreItems()
addListener()
}
 
deinit {
removeListener()
}
}
 
public let AnalyticsManager = Analytics.default

完成之前的奔溃时日志上报

1
2
3
4
// TODO: 上报
AnalyticsManager.track("CRASH", ["info": crashLog])
// 程序奔溃需要调用标记未上传
AnalyticsManager.setNeedsRestoreItems()

写到这里,框架其实只有30%,只有结合业务才能做出与业务相匹配的框架,接下来就是Cache,我只给出代码框架:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
//存储引擎
public enum IDPStorageType {
case disk
case sql
}
 
//缓存策略
public enum IDPCacheStoragePolicy {
case memory
case disk
case memoryAndDisk
}
 
open class INSCache {
open static let `default` = INSCache()
 
open var _nameSpace: String = "INSCache"
open var _cacheStoragePolicy: IDPCacheStoragePolicy = .memoryAndDisk
open var _memoryCapacity: Float = 0
open var _memoryTotalCost: Float = 0
open var _diskExpiredTime: Int = 0
 
open func existCacheForKey(_ key: String) ->Bool {
return false
}
 
open func clearMemory() {
 
}
 
open func existCacheForKeyInMemory(_ key: String) ->Bool {
return false
}
 
open func existCacheForKeyOnDisk(_ key: String) ->Bool {
return false
}
 
open func setObject(_ data: AnyObject, for key: String) {
 
}
 
open func getObject(for key: String) ->AnyObject? {
return nil
}
 
open func objectForKeyOnlyInMemory(_ key: String) ->AnyObject? {
return nil
}
 
open func asyncObject(forKey key: String, _ handler: (AnyObject)->()) {
 
}
 
open func removeObjcet(for key: String) {
 
}
 
open func removeObjcetForKeyOnlyInMemory(_ key: String) {
 
}
 
open func removeAll () {
 
}
 
open func removeAllInMemory() {
 
}
 
open func removeAllInDisk() {
 
}
 
open class func removeNameSpace(_ spaceName: String) {
 
}
}

包括模型的基类:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
open class Model: NSObject {
open var _ModelIdentifier: String?
 
open var _ModelUpdatedAt: Date?
open var _ModelCreatedAt: Date?
open var _ModelExpiredAt: Date?
open var _ModelNeedsCache: Bool?
 
open var _CurrentPage: Int = 0
open var _PageSize: Int = 10
open var _TotalCount: Int = 0
open var _StartAt: Int = 0
 
open func load() { }
open func refresh() { }
open func cancel() { }
 
open func goNextPage() { }
open func goPrevPage() { }
 
open func hasPrev() ->Bool{ return false }
open func hasNext() ->Bool{ return false }
 
public override init() {
 
}
}

框架到这里就不说了,接下来有时间就会实际的在使用中一步步的优化框架,让框架适应业务。最近有点忙,开了算法课程,所以框架上边大部分东西都是懒得写,但是使用到的第三方库都建议大家去阅读源码(除ASDK以外)。希望会有所提升。代码地址。仅供作为Swift的语言熟悉,不作为框架教学。

  • Swift

    Swift 是苹果于 2014 年 WWDC(苹果开发者大会)发布的开发语言,可与 Objective-C 共同运行于 Mac OS 和 iOS 平台,用于搭建基于苹果平台的应用程序。

    34 引用 • 37 回帖 • 497 关注

相关帖子

欢迎来到这里!

我们正在构建一个小众社区,大家在这里相互信任,以平等 • 自由 • 奔放的价值观进行分享交流。最终,希望大家能够找到与自己志同道合的伙伴,共同成长。

注册 关于
请输入回帖内容 ...

推荐标签 标签

  • SEO

    发布对别人有帮助的原创内容是最好的 SEO 方式。

    35 引用 • 200 回帖 • 24 关注
  • PWA

    PWA(Progressive Web App)是 Google 在 2015 年提出、2016 年 6 月开始推广的项目。它结合了一系列现代 Web 技术,在网页应用中实现和原生应用相近的用户体验。

    14 引用 • 69 回帖 • 133 关注
  • 导航

    各种网址链接、内容导航。

    37 引用 • 168 回帖
  • Redis

    Redis 是一个开源的使用 ANSI C 语言编写、支持网络、可基于内存亦可持久化的日志型、Key-Value 数据库,并提供多种语言的 API。从 2010 年 3 月 15 日起,Redis 的开发工作由 VMware 主持。从 2013 年 5 月开始,Redis 的开发由 Pivotal 赞助。

    284 引用 • 247 回帖 • 175 关注
  • 新人

    让我们欢迎这对新人。哦,不好意思说错了,让我们欢迎这位新人!
    新手上路,请谨慎驾驶!

    51 引用 • 226 回帖
  • GitBook

    GitBook 使您的团队可以轻松编写和维护高质量的文档。 分享知识,提高团队的工作效率,让用户满意。

    3 引用 • 8 回帖
  • 负能量

    上帝为你关上了一扇门,然后就去睡觉了....努力不一定能成功,但不努力一定很轻松 (° ー °〃)

    85 引用 • 1201 回帖 • 450 关注
  • 资讯

    资讯是用户因为及时地获得它并利用它而能够在相对短的时间内给自己带来价值的信息,资讯有时效性和地域性。

    53 引用 • 85 回帖
  • JRebel

    JRebel 是一款 Java 虚拟机插件,它使得 Java 程序员能在不进行重部署的情况下,即时看到代码的改变对一个应用程序带来的影响。

    26 引用 • 78 回帖 • 623 关注
  • Lute

    Lute 是一款结构化的 Markdown 引擎,支持 Go 和 JavaScript。

    25 引用 • 191 回帖 • 21 关注
  • jQuery

    jQuery 是一套跨浏览器的 JavaScript 库,强化 HTML 与 JavaScript 之间的操作。由 John Resig 在 2006 年 1 月的 BarCamp NYC 上释出第一个版本。全球约有 28% 的网站使用 jQuery,是非常受欢迎的 JavaScript 库。

    63 引用 • 134 回帖 • 740 关注
  • Gzip

    gzip (GNU zip)是 GNU 自由软件的文件压缩程序。我们在 Linux 中经常会用到后缀为 .gz 的文件,它们就是 Gzip 格式的。现今已经成为互联网上使用非常普遍的一种数据压缩格式,或者说一种文件格式。

    9 引用 • 12 回帖 • 111 关注
  • Vue.js

    Vue.js(读音 /vju ː/,类似于 view)是一个构建数据驱动的 Web 界面库。Vue.js 的目标是通过尽可能简单的 API 实现响应的数据绑定和组合的视图组件。

    261 引用 • 662 回帖
  • Mobi.css

    Mobi.css is a lightweight, flexible CSS framework that focus on mobile.

    1 引用 • 6 回帖 • 697 关注
  • WordPress

    WordPress 是一个使用 PHP 语言开发的博客平台,用户可以在支持 PHP 和 MySQL 数据库的服务器上架设自己的博客。也可以把 WordPress 当作一个内容管理系统(CMS)来使用。WordPress 是一个免费的开源项目,在 GNU 通用公共许可证(GPLv2)下授权发布。

    45 引用 • 113 回帖 • 314 关注
  • wolai

    我来 wolai:不仅仅是未来的云端笔记!

    1 引用 • 11 回帖 • 2 关注
  • 黑曜石

    黑曜石是一款强大的知识库工具,支持本地 Markdown 文件编辑,支持双向链接和关系图。

    A second brain, for you, forever.

    10 引用 • 85 回帖
  • OkHttp

    OkHttp 是一款 HTTP & HTTP/2 客户端库,专为 Android 和 Java 应用打造。

    16 引用 • 6 回帖 • 53 关注
  • 爬虫

    网络爬虫(Spider、Crawler),是一种按照一定的规则,自动地抓取万维网信息的程序。

    106 引用 • 275 回帖
  • 微软

    微软是一家美国跨国科技公司,也是世界 PC 软件开发的先导,由比尔·盖茨与保罗·艾伦创办于 1975 年,公司总部设立在华盛顿州的雷德蒙德(Redmond,邻近西雅图)。以研发、制造、授权和提供广泛的电脑软件服务业务为主。

    8 引用 • 44 回帖
  • SpaceVim

    SpaceVim 是一个社区驱动的模块化 vim/neovim 配置集合,以模块的方式组织管理插件以
    及相关配置,为不同的语言开发量身定制了相关的开发模块,该模块提供代码自动补全,
    语法检查、格式化、调试、REPL 等特性。用户仅需载入相关语言的模块即可得到一个开箱
    即用的 Vim-IDE。

    3 引用 • 31 回帖 • 71 关注
  • 正则表达式

    正则表达式(Regular Expression)使用单个字符串来描述、匹配一系列遵循某个句法规则的字符串。

    31 引用 • 94 回帖
  • PWL

    组织简介

    用爱发电 (Programming With Love) 是一个以开源精神为核心的民间开源爱好者技术组织,“用爱发电”象征开源与贡献精神,加入组织,代表你将遵守组织的“个人开源爱好者”的各项条款。申请加入:用爱发电组织邀请帖
    用爱发电组织官网:https://programmingwithlove.stackoverflow.wiki/

    用爱发电组织的核心驱动力:

    • 遵守开源守则,体现开源&贡献精神:以分享为目的,拒绝非法牟利。
    • 自我保护:使用适当的 License 保护自己的原创作品。
    • 尊重他人:不以各种理由、各种漏洞进行未经允许的抄袭、散播、洩露;以礼相待,尊重所有对社区做出贡献的开发者;通过他人的分享习得知识,要留下足迹,表示感谢。
    • 热爱编程、热爱学习:加入组织,热爱编程是首当其要的。我们欢迎热爱讨论、分享、提问的朋友,也同样欢迎默默成就的朋友。
    • 倾听:正确并恳切对待、处理问题与建议,及时修复开源项目的 Bug ,及时与反馈者沟通。不抬杠、不无视、不辱骂。
    • 平视:不诋毁、轻视、嘲讽其他开发者,主动提出建议、施以帮助,以和谐为本。只要他人肯努力,你也可能会被昔日小看的人所超越,所以请保持谦虚。
    • 乐观且活跃:你的努力决定了你的高度。不要放弃,多年后回头俯瞰,才会发现自己已经成就往日所仰望的水平。积极地将项目开源,帮助他人学习、改进,自己也会获得相应的提升、成就与成就感。
    1 引用 • 487 回帖 • 7 关注
  • NetBeans

    NetBeans 是一个始于 1997 年的 Xelfi 计划,本身是捷克布拉格查理大学的数学及物理学院的学生计划。此计划延伸而成立了一家公司进而发展这个商用版本的 NetBeans IDE,直到 1999 年 Sun 买下此公司。Sun 于次年(2000 年)六月将 NetBeans IDE 开源,直到现在 NetBeans 的社群依然持续增长。

    78 引用 • 102 回帖 • 642 关注
  • iOS

    iOS 是由苹果公司开发的移动操作系统,最早于 2007 年 1 月 9 日的 Macworld 大会上公布这个系统,最初是设计给 iPhone 使用的,后来陆续套用到 iPod touch、iPad 以及 Apple TV 等产品上。iOS 与苹果的 Mac OS X 操作系统一样,属于类 Unix 的商业操作系统。

    84 引用 • 139 回帖
  • webpack

    webpack 是一个用于前端开发的模块加载器和打包工具,它能把各种资源,例如 JS、CSS(less/sass)、图片等都作为模块来使用和处理。

    41 引用 • 130 回帖 • 294 关注
  • 深度学习

    深度学习(Deep Learning)是机器学习的分支,是一种试图使用包含复杂结构或由多重非线性变换构成的多个处理层对数据进行高层抽象的算法。

    40 引用 • 40 回帖