0%

NSCopying协议:

1
2
3
4
5
6
7
8
9
10
11
@protocol NSCopying

- (id)copyWithZone:(nullable NSZone *)zone; //实例方法

@end

@protocol NSMutableCopying

- (id)mutableCopyWithZone:(nullable NSZone *)zone; //实例方法

@end

分为不可变和可变两种拷贝。zone参数是被忽略的,因此可以不管。

实现:

具体怎么实现直接参考知名开源框架的写法,跟着大佬来肯定没错。

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
@property (nonatomic, strong, nullable) NSSet <NSData *> *pinnedCertificates;
- (instancetype)copyWithZone:(NSZone *)zone {
AFSecurityPolicy *securityPolicy = [[[self class] allocWithZone:zone] init];
securityPolicy.SSLPinningMode = self.SSLPinningMode; //值类型,直接赋值就可以了。
securityPolicy.allowInvalidCertificates = self.allowInvalidCertificates;
securityPolicy.validatesDomainName = self.validatesDomainName;
securityPolicy.pinnedCertificates = [self.pinnedCertificates copyWithZone:zone]; //对象类型调用copyWithZone,需要确保对象已经实现了copy协议。这里不需要可变所以调用copyWithZone

return securityPolicy;
}


@property (readwrite, nonatomic, strong) NSMutableDictionary *mutableHTTPRequestHeaders;
- (instancetype)copyWithZone:(NSZone *)zone {
AFHTTPRequestSerializer *serializer = [[[self class] allocWithZone:zone] init];
dispatch_sync(self.requestHeaderModificationQueue, ^{
//这里需要可变所以调用mutableCopyWithZone
serializer.mutableHTTPRequestHeaders = [self.mutableHTTPRequestHeaders mutableCopyWithZone:zone];
});
serializer.queryStringSerializationStyle = self.queryStringSerializationStyle;
serializer.queryStringSerialization = self.queryStringSerialization;

return serializer;
}

//---AFHTTPResponseSerializer
#pragma mark - NSCopying

- (instancetype)copyWithZone:(NSZone *)zone {
AFHTTPResponseSerializer *serializer = [[[self class] allocWithZone:zone] init]; //不能写死为[AFHTTPResponseSerializer allocWithZone:zone]
serializer.acceptableStatusCodes = [self.acceptableStatusCodes copyWithZone:zone];
serializer.acceptableContentTypes = [self.acceptableContentTypes copyWithZone:zone];

return serializer;
}

//---AFCompoundResponseSerializer:AFHTTPResponseSerializer
@property (readwrite, nonatomic, copy) NSArray *responseSerializers;
- (instancetype)copyWithZone:(NSZone *)zone {
AFCompoundResponseSerializer *serializer = [super copyWithZone:zone];
serializer.responseSerializers = self.responseSerializers; //responseSerializers属性是copy,所以可以直接赋值。setter方法里会调用copy.

return serializer;
}

如果父类实现了则必须调用super,否则会丢失信息:

1
2
3
4
5
6
7
- (instancetype)copyWithZone:(NSZone *)zone {
AFPropertyListResponseSerializer *serializer = [super copyWithZone:zone]; //调用super
serializer.format = self.format;
serializer.readOptions = self.readOptions;

return serializer;
}

注意:NSObject是没有实现copy协议的。

NSObject下有两个实例方法:

阅读全文 »

OC的内存管理大致可以分为MRC阶段和ARC阶段。

MRC:手动引用计数,“手动”很好理解就是亲自动手写代码,“引用计数”就是数数,数一下这个对象的引用次数。当我们需要用这个对象的时候先调用一下retain,引用计数就加1,不用了就调用一下release,引用计数就减1,减到0系统就会帮我们释放这个对象的内存。

为什么会有“引用计数”这么个东西?
试想一下如果没有引用计数会怎样,比如你创建了一个对象,这个对象传来传去在很多个地方使用,那么什么时候调用free销毁对象成了一个难题。如下:类A创建了一个对象obj,然后obj作为参数传给类B。

1
2
3
4
5
6
7
- (BOOL)function
{
id obj = [xxx new];
[B someMethod:obj];
...
return true
}

obj传给类B后,B有可能保存也可能不保存obj。这时类A对obj的处理就非常尴尬了,如果贸然调用free,万一类B还在用就会导致野指针访问崩溃,不调用吧,万一类B又没在用就会内存泄露。但是通过引用计数,我们就不需要考虑什么时候调用free了,系统会根据引用计数是否为0来决定是否销毁对象,你只需要retain和release。

举个生活中的例子:

场景1:张三去厕所拉屎,里面一片漆黑,于是张三打开灯,找了个坑位关上门开始造。造完后,出来关上灯。完美!

场景2:还是张三去厕所拉屎,里面一片漆黑,于是张三打开灯,找了个坑位关上门开始造。这时,李四也来拉屎,由于灯是亮的于是李四径直找了个坑位关上门开始造。过了一会张三拉完了,由于张三并不知道李四进来了,于是出门把灯关了。只听见在漆黑里的李四一声卧槽!

从上面的例子可以看出什么时候关灯确实是个问题,怎么解决这个问题呢?方法有很多比如进去之后大喊一声俺来了让里面所有人都知道有人来了。但是最简单的办法还是大家都遵守一套规则:进门前请按加号键,出门后请按减号键。这就相当于引用计数了。

场景3:张三去厕所拉屎,里面一片漆黑,外面告示写着进门前请按加号键,出门后请按减号键。这时张三按了一下墙壁上的加号键,灯亮了,于是找了个坑位关上门开始造。这时李四也来拉屎,他也按照告示按了下墙壁上的加号键,接着他找了个坑位开始安心拉屎。这时张三拉完屎,出门时按照告示按下了减号键,灯没有灭,这时张三明白了期间又来人了,虽然不知道在哪个坑位,但肯定是有人正在拉。当李四拉完后,也按了下墙壁上的减号键,这时确实没人在拉屎了,于是灯灭了,非常的完美。在这个系统里,你不需要知道中途进来了多少人,也不需要任何交流,只需要遵守外面的告示,就解决了灯何时灭的问题。

阅读全文 »

如何声明一个可变参数的函数

在无法给出所有传递给函数的参数的类型和数目时,可以使用省略号(…)指定函数参数表。有如下几种形式:

1
2
3
void fun1(int a, double b, ...); //给出确定的几个参数,其他用省略号
void fun2(int a ...); //省略号前有或者没有逗号都是可以的
void fun3(...); //也可以不确定任何参数,但和没有参数是不一样的

举个例子:计算n个整数的和。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
- (int)test_n_number_add:(int)n, ... {
int res = 0;

va_list ap; //声明
va_start(ap, n); //绑定
while (n > 0) {
res += va_arg(ap, int); //获取参数。va_arg返回的就是参数的值。
n -= 1;
}
va_end(ap); //释放
NSLog(@"res:%d", res);

return res;
}

va_list存在的两个隐患:

1.如何确定参数的类型。

没有其他办法,就是通过强制转换。va_arg就是把当前指针所指向的内容强制转换到指定类型。像printf函数还有个格式化参数来说明后面参数的类型。如果是自己写的,同样得事先约定好一套规则。

2.如何确定参数的个数。也就是参数结束标志。

也没有规定,同样得事先约定好一套规则。比如这里第一个固定参数说明了后续可变参数的个数。再比如求自然数的平方和,那么可以把负数和0作为它的结束标志。其他系统函数如scanf把接收到的回车符作为结束标志,大家熟知的printf对字符串的处理用’\0’作为结束标志。

阅读全文 »

给NextGrowingTextView添加扩展,支持设置光标大小。

NextGrowingTextView是一个第三方库,内部有一个继承自UITextView的NextGrowingInternalTextView,这个textview外部是访问不了的。而设置textview的光标大小需要继承UITextView并重写UITextInput.caretRect(for:)方法。怎么办呢?

大概有三种办法:

1、私有化NextGrowingTextView。直接改NextGrowingInternalTextView类。这种最简单,但是以后就需要自己维护这个库了。

2、方法交换UITextView的caretRect(for:)实现。这种也比较简单,但是由于交换的是UITextView的实现,影响的范围比较大。

3、动态给NextGrowingInternalTextView添加caretRect(for:)实现。这个影响的范围只有NextGrowingInternalTextView,并且也不需要私有化NextGrowingTextView。个人觉得是最佳的解决办法了。这个方案的难点是添加自己的实现后怎么调用父类的实现。解决办法就是先获取父类的IMP,再添加自己的实现。

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
extension NextGrowingTextView {
private struct AssociatedKeys {
static var caretSize = "caretSize"
}

var caretSize: CGSize? {
get {
return objc_getAssociatedObject(self, &AssociatedKeys.caretSize) as? CGSize
}
set {
objc_setAssociatedObject(self, &AssociatedKeys.caretSize, newValue, objc_AssociationPolicy.OBJC_ASSOCIATION_RETAIN_NONATOMIC)
hookCaretRectMethodOnce()
}
}

private func hookCaretRectMethodOnce() {
DispatchQueue.once {
let cls: AnyClass = type(of: self.textView)
let sel: Selector = #selector(UITextInput.caretRect(for:))
guard let originalMethod = class_getInstanceMethod(cls, sel) else {return}

let originalIMP = method_getImplementation(originalMethod)
typealias Function = @convention(c) (AnyObject, Selector, Any?) -> CGRect
let function = unsafeBitCast(originalIMP, to: Function.self)
let implementBlock: @convention(block) (UITextView, UITextPosition) -> CGRect = { (textView, position) -> CGRect in
var rect = function(textView, sel, position)
guard let parentView = textView.superview as? NextGrowingTextView else {return rect}
guard let caretSize = parentView.caretSize else {return rect}
rect.origin.y -= (caretSize.height - rect.size.height)
rect.size = caretSize
return rect
}
let types = "{CGRect={CGPoint=dd}{CGSize=dd}}@:@"
class_addMethod(cls, sel, imp_implementationWithBlock(unsafeBitCast(implementBlock, to: AnyObject.self)), types)
}
}
}

参考

每周 Swift 社区问答:@convention

git拉代码到本地后,编译没有问题,但是Xcode一运行在安装app的时候就失败,弹窗提示

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
Details

Unable to install "XXX"
Domain: com.apple.dt.MobileDeviceErrorDomain
Code: -402653081
Recovery Suggestion: Please check your project settings and ensure that a valid product has been built.
User Info: {
DVTErrorCreationDateKey = "2022-08-18 07:53:05 +0000";
IDERunOperationFailingWorker = IDEInstalliPhoneLauncher;
}
--
There was an internal API error.
Domain: com.apple.dt.MobileDeviceErrorDomain
Code: -402653081
User Info: {
DVTRadarComponentKey = 261622;
MobileDeviceErrorCode = "(0xE8000067)";
"com.apple.dtdevicekit.stacktrace" = (
0 DTDeviceKitBase 0x000000028daad614 DTDKCreateNSErrorFromAMDErrorCode + 272
1 DTDeviceKitBase 0x000000028dae6dd8 __90-[DTDKMobileDeviceToken installApplicationBundleAtPath:withOptions:andError:withCallback:]_block_invoke + 160
2 DVTFoundation 0x0000000102eb5bd0 DVTInvokeWithStrongOwnership + 76
3 DTDeviceKitBase 0x000000028dae6b30 -[DTDKMobileDeviceToken installApplicationBundleAtPath:withOptions:andError:withCallback:] + 1336
4 IDEiOSSupportCore 0x0000000133351590 __118-[DVTiOSDevice(DVTiPhoneApplicationInstallation) processAppInstallSet:appUninstallSet:installOptions:completionBlock:]_block_invoke.301 + 2916
5 DVTFoundation 0x0000000102fdcf50 __DVT_CALLING_CLIENT_BLOCK__ + 16
6 DVTFoundation 0x0000000102fde068 __DVTDispatchAsync_block_invoke + 364
7 libdispatch.dylib 0x00000001a2504e60 _dispatch_call_block_and_release + 32
8 libdispatch.dylib 0x00000001a2506bac _dispatch_client_callout + 20
9 libdispatch.dylib 0x00000001a250e330 _dispatch_lane_serial_drain + 672
10 libdispatch.dylib 0x00000001a250eea4 _dispatch_lane_invoke + 392
11 libdispatch.dylib 0x00000001a2519708 _dispatch_workloop_worker_thread + 656
12 libsystem_pthread.dylib 0x00000001a26c15b0 _pthread_wqthread + 288
13 libsystem_pthread.dylib 0x00000001a26c02c4 start_wqthread + 8
);
}
--

Analytics Event: com.apple.dt.IDERunOperationWorkerFinished : {
"device_model" = "iPhone9,2";
"device_osBuild" = "15.6 (19G71)";
"device_platform" = "com.apple.platform.iphoneos";
"launchSession_schemeCommand" = Run;
"launchSession_state" = 1;
"launchSession_targetArch" = arm64;
"operation_duration_ms" = 6186;
"operation_errorCode" = "-402653081";
"operation_errorDomain" = "com.apple.dt.MobileDeviceErrorDomain";
"operation_errorWorker" = IDEInstalliPhoneLauncher;
"operation_name" = IDEiPhoneRunOperationWorkerGroup;
"param_consoleMode" = 0;
"param_debugger_attachToExtensions" = 0;
"param_debugger_attachToXPC" = 1;
"param_debugger_type" = 5;
"param_destination_isProxy" = 0;
"param_destination_platform" = "com.apple.platform.iphoneos";
"param_diag_MainThreadChecker_stopOnIssue" = 0;
"param_diag_MallocStackLogging_enableDuringAttach" = 0;
"param_diag_MallocStackLogging_enableForXPC" = 1;
"param_diag_allowLocationSimulation" = 1;
"param_diag_gpu_frameCapture_enable" = 0;
"param_diag_gpu_shaderValidation_enable" = 0;
"param_diag_gpu_validation_enable" = 0;
"param_diag_memoryGraphOnResourceException" = 0;
"param_diag_queueDebugging_enable" = 1;
"param_diag_runtimeProfile_generate" = 0;
"param_diag_sanitizer_asan_enable" = 0;
"param_diag_sanitizer_tsan_enable" = 0;
"param_diag_sanitizer_tsan_stopOnIssue" = 0;
"param_diag_sanitizer_ubsan_stopOnIssue" = 0;
"param_diag_showNonLocalizedStrings" = 0;
"param_diag_viewDebugging_enabled" = 1;
"param_diag_viewDebugging_insertDylibOnLaunch" = 1;
"param_install_style" = 0;
"param_launcher_UID" = 2;
"param_launcher_allowDeviceSensorReplayData" = 0;
"param_launcher_kind" = 0;
"param_launcher_style" = 0;
"param_launcher_substyle" = 0;
"param_runnable_appExtensionHostRunMode" = 0;
"param_runnable_productType" = "com.apple.product-type.application";
"param_runnable_swiftVersion" = "5.6.1";
"param_runnable_type" = 2;
"param_testing_launchedForTesting" = 0;
"param_testing_suppressSimulatorApp" = 0;
"param_testing_usingCLI" = 0;
"sdk_canonicalName" = "iphoneos15.5";
"sdk_osVersion" = "15.5";
"sdk_variant" = iphoneos;
}
--


System Information

macOS Version 12.0.1 (Build 21A559)
Xcode 13.4.1 (20504) (Build 13F100)
Timestamp: 2022-08-18T15:53:05+08:00

先clean工程,再删DerivedData文件,重启手机,重启电脑,但是都没有用。clean、删除好几遍都没有用,其实到这里就可以肯定是和缓存没关系了,再怎么clean都没用,因为可能是其他地方的原因导致的。完全clean两次如果没用就应该找其他原因了,如果早意识到这一点还可以节省点时间。

查了1个半小时,就发现一篇文章提到删除target里的通知扩展,删除后确实可以运行安装,但是这不是解决办法啊。估摸着大概就是通知扩展那一块有问题,于是比对好的工程的设置,最终发现是NotificationService.swift文件没有加到NotificationService target里去导致的。添加后解决。网上好多类似的问题,但和我这个完全一样的几乎没有,好浪费时间无语。

MessageKit是一个开源的IM 消息聊天UI框架。

MessageKit采用UICollectionView而非UITableView作为主体架构(为什么要这么设计Why?),采用UICollectionViewCell实现聊天cell。一个section对应一条聊天消息,默认一个section里面只有一个cell(为什么要这么设计Why?)。

作为一个框架,难就难在如何封装各种风格迥异的聊天cell?MessageKit自定义了一种CollectionView布局方式MessagesCollectionViewFlowLayout,它继承自UICollectionViewFlowLayout。对应需要自定义layoutAttributes—MessagesCollectionViewLayoutAttributes,继承自UICollectionViewLayoutAttributes。

框架内部提供了几种默认的消息类型:

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
/// An enum representing the kind of message and its underlying kind.
public enum MessageKind {

/// A standard text message.
///
/// - Note: The font used for this message will be the value of the
/// `messageLabelFont` property in the `MessagesCollectionViewFlowLayout` object.
///
/// Using `MessageKind.attributedText(NSAttributedString)` doesn't require you
/// to set this property and results in higher performance.
case text(String)

/// A message with attributed text.
case attributedText(NSAttributedString)

/// A photo message.
case photo(MediaItem)

/// A video message.
case video(MediaItem)

/// A location message.
case location(LocationItem)

/// An emoji message.
case emoji(String)

/// An audio message.
case audio(AudioItem)

/// A contact message.
case contact(ContactItem)

/// A link preview message.
case linkPreview(LinkItem)

/// A custom message.
/// - Note: Using this case requires that you implement the following methods and handle this case:
/// - MessagesDataSource: customCell(for message: MessageType, at indexPath: IndexPath, in messagesCollectionView: MessagesCollectionView) -> UICollectionViewCell
/// - MessagesLayoutDelegate: customCellSizeCalculator(for message: MessageType, at indexPath: IndexPath, in messagesCollectionView: MessagesCollectionView) -> CellSizeCalculator
case custom(Any?)

// MARK: - Not supported yet

// case system(String)
//
// case placeholder

}

每种消息类型提供了默认的cell样式,如果不想用默认的样式可以通过代理自己返回。每种cell对应一种CellSizeCalculator,用于提供cell的宽高。内部提供的比如:TextMessageCell—TextMessageSizeCalculator、MediaMessageCell—MediaMessageSizeCalculator、AudioMessageCell—AudioMessageSizeCalculator。

MessageKit的设计思想真的吊,框架提供了默认样式,如果不想用默认的样式可以通过代理自己返回。

几种基类

MessagesViewController—>UIViewController

实际使用时可以继承MessagesViewController或拷贝其源码到自己的控制器。

阅读全文 »

有一个容器控制器P,它下面有三个子控制器A、B、C。有时候我们需要在ABC之间共享一些状态信息。

一种可行的做法:在P下面挂一个statusContext对象(信息少也可以直接平铺在P下),三个子控制器访问这个statusContext对象达到数据通信的目的。这种方案适合状态查询这种非即时处理的场景,如果是那种需要即时处理事件的场景,那么还是使用代理,通知,block比较好。

另外一种方法:直接单例搞起,单例在ABC之间穿梭。不太推荐。有几个原因:1.单例因为使用过于方便导致很容易被滥用,滥用后,会很难追踪某个属性到底是在哪个地方修改的。2.单例创建后一般不会主动销毁,导致很难维护它上面的状态信息,下一次使用时可能会残存上一次的状态信息。

malloc

函数声明:

1
void	*malloc(size_t __size) __result_use_check __alloc_size(1);

Initialization: malloc() allocates memory block of given size (in bytes) and returns a pointer to the beginning of the block. malloc() doesn’t initialize the allocated memory. If we try to acess the content of memory block then we’ll get garbage values.

calloc

函数声明:

1
void	*calloc(size_t __count, size_t __size) __result_use_check __alloc_size(1,2);

参数说明:

  1. Number of blocks to be allocated.
  2. Size of each block.

calloc() allocates the memory and also initializes the allocates memory block to zero. If we try to access the content of these blocks then we’ll get 0.

阅读全文 »

具体是什么时候开始有干眼症的症状已经记不清楚了,大概是2016年患干眼症,目前(2022年08月06日)干眼症症状:干燥、异物感、瘙痒、灼痛,闭眼睡觉(不管是白天还是晚上,只要是闭眼一段时间)特别明显,睁眼后不明显。

干眼症目前治疗水平:干眼症是一种慢性、并且通常是进行性病症。根据其原因和严重程度,干眼症有时并不能完全治愈。但在大多数情况下,干眼症的疾病管理可以很成功,通过施以适当的治疗手段,可以明显改善眼睛舒适度、缓解干眼症状、并且有时甚至可以获得更清晰的视力。

辅助治疗方案

去医院看过几次,不过效果不大。由于是慢性长期的病,所以还是要有一套能够随手就用的辅助治疗方案。

  1. 早睡早起。23:00准备入睡。23:30—7:30。

  2. 按摩热敷。早晚各一次。每次12分钟。

  3. 食疗。多喝水,忌辛辣,油腻,冰的。

  4. 多户外活动。进行户外跑步。建议每周两次户外跑,每次锻炼半小时,大概可以跑3-4公里。

  5. 改善日常生活习惯。

    • 多眨眼。

    • 在电脑使用过程中经常休息。一个比较好的方法是——至少每隔30分钟远眺(半米以外)至少直径为半米以上的物体3分钟,它可以帮助缓解干眼症和电脑眼疲劳。

    • 不躺着玩手机、不在关灯后玩手机。

    • 减少电子设备的使用。少玩游戏,少玩手机、电脑。初期建议每次玩游戏时间不超过2小时。

  6. 保持积极心态。不要啥也不干,天天躺着。空闲时间多读书,听书也行。

辅助治疗方案需要长期的坚持下去。希望能够对干眼症有所缓解。

参考

医学上早就确认减少眨眼率的活动会增加干眼症的患病率。例如,在计算机上花费很长时间是干眼症的一个确定的风险因素。由于干眼症可能有多种原因,因此采用的治疗方法可能各有不同。通常并没有能够“快速治愈”干眼症的方法。

热敷疗法

该方法可以作为睑板腺疗法的一种更加舒适的替代手段,它通过简单地对闭合的眼睑施热敷以软化睑板。不过为了达到更好的效果,一些研究人员指出必须使热敷在42摄氏度下保持超过10分钟,并且压敷必须在这段时间内至少施加两次。大多数人不能或不愿意正确地进行这种干眼治疗,只在较低温度下进行压敷,所以经常达不到预期效果。

阅读全文 »