如何构造 Swift 框架(四)

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

上期提到了使用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 平台,用于搭建基于苹果平台的应用程序。

    36 引用 • 37 回帖 • 535 关注

相关帖子

欢迎来到这里!

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

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

推荐标签 标签

  • Ubuntu

    Ubuntu(友帮拓、优般图、乌班图)是一个以桌面应用为主的 Linux 操作系统,其名称来自非洲南部祖鲁语或豪萨语的“ubuntu”一词,意思是“人性”、“我的存在是因为大家的存在”,是非洲传统的一种价值观,类似华人社会的“仁爱”思想。Ubuntu 的目标在于为一般用户提供一个最新的、同时又相当稳定的主要由自由软件构建而成的操作系统。

    126 引用 • 169 回帖
  • Thymeleaf

    Thymeleaf 是一款用于渲染 XML/XHTML/HTML5 内容的模板引擎。类似 Velocity、 FreeMarker 等,它也可以轻易的与 Spring 等 Web 框架进行集成作为 Web 应用的模板引擎。与其它模板引擎相比,Thymeleaf 最大的特点是能够直接在浏览器中打开并正确显示模板页面,而不需要启动整个 Web 应用。

    11 引用 • 19 回帖 • 364 关注
  • SendCloud

    SendCloud 由搜狐武汉研发中心孵化的项目,是致力于为开发者提供高质量的触发邮件服务的云端邮件发送平台,为开发者提供便利的 API 接口来调用服务,让邮件准确迅速到达用户收件箱并获得强大的追踪数据。

    2 引用 • 8 回帖 • 486 关注
  • 导航

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

    42 引用 • 175 回帖
  • CentOS

    CentOS(Community Enterprise Operating System)是 Linux 发行版之一,它是来自于 Red Hat Enterprise Linux 依照开放源代码规定释出的源代码所编译而成。由于出自同样的源代码,因此有些要求高度稳定的服务器以 CentOS 替代商业版的 Red Hat Enterprise Linux 使用。两者的不同在于 CentOS 并不包含封闭源代码软件。

    238 引用 • 224 回帖
  • Hprose

    Hprose 是一款先进的轻量级、跨语言、跨平台、无侵入式、高性能动态远程对象调用引擎库。它不仅简单易用,而且功能强大。你无需专门学习,只需看上几眼,就能用它轻松构建分布式应用系统。

    9 引用 • 17 回帖 • 612 关注
  • 微软

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

    8 引用 • 44 回帖 • 1 关注
  • 星云链

    星云链是一个开源公链,业内简单的将其称为区块链上的谷歌。其实它不仅仅是区块链搜索引擎,一个公链的所有功能,它基本都有,比如你可以用它来开发部署你的去中心化的 APP,你可以在上面编写智能合约,发送交易等等。3 分钟快速接入星云链 (NAS) 测试网

    3 引用 • 16 回帖 • 6 关注
  • LeetCode

    LeetCode(力扣)是一个全球极客挚爱的高质量技术成长平台,想要学习和提升专业能力从这里开始,充足技术干货等你来啃,轻松拿下 Dream Offer!

    209 引用 • 72 回帖
  • OpenShift

    红帽提供的 PaaS 云,支持多种编程语言,为开发人员提供了更为灵活的框架、存储选择。

    14 引用 • 20 回帖 • 632 关注
  • 开源

    Open Source, Open Mind, Open Sight, Open Future!

    407 引用 • 3578 回帖 • 1 关注
  • Ruby

    Ruby 是一种开源的面向对象程序设计的服务器端脚本语言,在 20 世纪 90 年代中期由日本的松本行弘(まつもとゆきひろ/Yukihiro Matsumoto)设计并开发。在 Ruby 社区,松本也被称为马茨(Matz)。

    7 引用 • 31 回帖 • 216 关注
  • OAuth

    OAuth 协议为用户资源的授权提供了一个安全的、开放而又简易的标准。与以往的授权方式不同之处是 oAuth 的授权不会使第三方触及到用户的帐号信息(如用户名与密码),即第三方无需使用用户的用户名与密码就可以申请获得该用户资源的授权,因此 oAuth 是安全的。oAuth 是 Open Authorization 的简写。

    36 引用 • 103 回帖 • 17 关注
  • JSON

    JSON (JavaScript Object Notation)是一种轻量级的数据交换格式。易于人类阅读和编写。同时也易于机器解析和生成。

    52 引用 • 190 回帖
  • 国际化

    i18n(其来源是英文单词 internationalization 的首末字符 i 和 n,18 为中间的字符数)是“国际化”的简称。对程序来说,国际化是指在不修改代码的情况下,能根据不同语言及地区显示相应的界面。

    8 引用 • 26 回帖 • 1 关注
  • PostgreSQL

    PostgreSQL 是一款功能强大的企业级数据库系统,在 BSD 开源许可证下发布。

    22 引用 • 22 回帖 • 1 关注
  • TGIF

    Thank God It's Friday! 感谢老天,总算到星期五啦!

    288 引用 • 4485 回帖 • 663 关注
  • 宕机

    宕机,多指一些网站、游戏、网络应用等服务器一种区别于正常运行的状态,也叫“Down 机”、“当机”或“死机”。宕机状态不仅仅是指服务器“挂掉了”、“死机了”状态,也包括服务器假死、停用、关闭等一些原因而导致出现的不能够正常运行的状态。

    13 引用 • 82 回帖 • 59 关注
  • 友情链接

    确认过眼神后的灵魂连接,站在链在!

    24 引用 • 373 回帖
  • WebClipper

    Web Clipper 是一款浏览器剪藏扩展,它可以帮助你把网页内容剪藏到本地。

    3 引用 • 9 回帖 • 4 关注
  • etcd

    etcd 是一个分布式、高可用的 key-value 数据存储,专门用于在分布式系统中保存关键数据。

    5 引用 • 26 回帖 • 528 关注
  • 服务器

    服务器,也称伺服器,是提供计算服务的设备。由于服务器需要响应服务请求,并进行处理,因此一般来说服务器应具备承担服务并且保障服务的能力。

    125 引用 • 588 回帖
  • C++

    C++ 是在 C 语言的基础上开发的一种通用编程语言,应用广泛。C++ 支持多种编程范式,面向对象编程、泛型编程和过程化编程。

    107 引用 • 153 回帖
  • 创业

    你比 99% 的人都优秀么?

    85 引用 • 1399 回帖 • 1 关注
  • JWT

    JWT(JSON Web Token)是一种用于双方之间传递信息的简洁的、安全的表述性声明规范。JWT 作为一个开放的标准(RFC 7519),定义了一种简洁的,自包含的方法用于通信双方之间以 JSON 的形式安全的传递信息。

    20 引用 • 15 回帖 • 6 关注
  • 架构

    我们平时所说的“架构”主要是指软件架构,这是有关软件整体结构与组件的抽象描述,用于指导软件系统各个方面的设计。另外还有“业务架构”、“网络架构”、“硬件架构”等细分领域。

    142 引用 • 442 回帖 • 1 关注
  • 强迫症

    强迫症(OCD)属于焦虑障碍的一种类型,是一组以强迫思维和强迫行为为主要临床表现的神经精神疾病,其特点为有意识的强迫和反强迫并存,一些毫无意义、甚至违背自己意愿的想法或冲动反反复复侵入患者的日常生活。

    15 引用 • 161 回帖 • 2 关注