0%

题外话

C语言由Dennis M. Ritchie在1973年设计和实现。从那以后使用者逐渐增加。到1978年Ritchie和Bell实验室的另一位程序专家Kernighan合写了著名的《The C Programming Language》,将C语言推向全世界,许多国家都出了译本,国内有一些C语言书就是这本书的翻译或者编译。由这本书定义的C语言后来被人们称作K&R C。

随着C语言使用得越来越广泛,出现了许多新问题,人们日益强烈地要求对C语言进行标准化。这个标准化的工作在美国国家标准局(ANSI)的框架中进行(1983-1988),最终结果是1988年10月颁布的ANSI标准X3.159-1989,也就是后来人们所说的ANSIC标准。由这个标准定义的C语言被称作ANSI C。

ANSI C标准很快被采纳为国际标准和各国的标准。国际标准为ISO/IEC 9899-1990,中国国家标准GB/T15272-94是国际ISO标准的中文翻译。

ANSI C标准化工作的一个主要目标是清除原来C语言中的不安全、不合理、不精确、不完善的东西。由此也产生了ANSIC与K&R C之间的差异。从总体上看,这些差异反应的是C语言走向完善、走向成熟。

我们一般采用ANSI C就可以了。

整型

整型包括:字符,短整型,整型,长整型。每种类型又分为有符号和无符号。

K&R C并没有规定长整型必须比短整型长,只是规定它不得比短整型短。

1
长整型 >= 整型 >= 短整型
阅读全文 »

DDOSLogger控制台打印,没有使用logMessage的时间戳而是os_log_xxx执行时的时间戳,因此会和logMessage的创建时间有个时间差。如果打印时选择的是DDLogFlagError,打印将是同步执行,这个时间差会非常小。而如果是DDLogFlagError以下级别,那么这条打印将是异步执行的,这时的时间差会更大,和NSLog混用时可能会误导你。因此和NSLog混用或者场景同步打印要求比较高,建议使用DDLogFlagError同步打印日志。

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
- (DDLoggerName)loggerName {
return DDLoggerNameOS;
}

- (void)logMessage:(DDLogMessage *)logMessage {
// Skip captured log messages
if ([logMessage->_fileName isEqualToString:@"DDASLLogCapture"]) {
return;
}

if (@available(iOS 10.0, macOS 10.12, tvOS 10.0, watchOS 3.0, *)) {
NSString * message = _logFormatter ? [_logFormatter formatLogMessage:logMessage] : logMessage->_message;
if (message != nil) {
const char *msg = [message UTF8String];
__auto_type logger = [self logger];
switch (logMessage->_flag) {
case DDLogFlagError :
os_log_error(logger, "%{public}s", msg);
break;
case DDLogFlagWarning:
case DDLogFlagInfo :
os_log_info(logger, "%{public}s", msg);
break;
case DDLogFlagDebug :
case DDLogFlagVerbose:
default :
os_log_debug(logger, "%{public}s", msg);
break;
}
}
}
}

例子:打印时选择的是DDLogFlagDebug。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
- (void)URLSession:(NSURLSession *)session dataTask:(NSURLSessionDataTask *)dataTask
didReceiveResponse:(NSURLResponse *)response
completionHandler:(void (^)(NSURLSessionResponseDisposition disposition))completionHandler
{
NSLog(@"接收到响应1:%@, 线程:%@", response.URL.lastPathComponent, [NSThread currentThread]);
MATLogDebug(@"MAT,接收到响应:%@, 线程:%@", response, [NSThread currentThread]);
NSLog(@"接收到响应2:%@, 线程:%@", response.URL.lastPathComponent, [NSThread currentThread]);

completionHandler(NSURLSessionResponseAllow);
}

- (void)URLSession:(NSURLSession *)session dataTask:(NSURLSessionDataTask *)dataTask didReceiveData:(NSData *)data
{
NSURL *url = dataTask.originalRequest.URL;
NSMutableData *dataContainer = [self dataWithURL:url];
[dataContainer appendData:data];

NSLog(@"本次接收1:%ld,线程:%@, url:%@", data.length, [NSThread currentThread], url.lastPathComponent);

if (url.absoluteString == landscapeUrl) {
sleep(10);
}
NSLog(@"本次接收2:%ld,总共接收:%ld,线程:%@, url:%@", data.length, dataContainer.length, [NSThread currentThread], url.lastPathComponent);
}

日志:

1
2
3
4
5
6
7
8
9
2023-04-04 11:05:59.463803+0800 AFNetworking_fx[3039:152895] <__NSURLSessionLocal: 0x103b48e60>
2023-04-04 11:06:00.468738+0800 AFNetworking_fx[3039:152895] 开始请求:https://tinypng.com/images/example-orig.png
2023-04-04 11:06:00.598328+0800 AFNetworking_fx[3039:154867] 接收到响应1:example-orig.png, 线程:<NSThread: 0x2824d0f40>{number = 16, name = (null)}
2023-04-04 11:06:00.599692+0800 AFNetworking_fx[3039:154867] -----1:2023-04-04 11:06:00:600---------
2023-04-04 11:06:00.600230+0800 AFNetworking_fx[3039:154867] -----2:2023-04-04 11:06:00:600---2023-04-04 11:06:00:600------
2023-04-04 11:06:00.600749+0800 AFNetworking_fx[3039:154867] 接收到响应2:example-orig.png, 线程:<NSThread: 0x2824d0f40>{number = 16, name = (null)}
2023-04-04 11:06:00.601729+0800 AFNetworking_fx[3039:154867] 本次接收1:5455,线程:<NSThread: 0x2824d0f40>{number = 16, name = (null)}, url:example-orig.png
2023-04-04 11:06:00.601922+0800 AFNetworking_fx[3039:154874] 2023-04-04 11:06:00:600 -[SystemSessionViewController URLSession:dataTask:didReceiveResponse:completionHandler:] +171 [level:Debug,module:0]
MAT,接收到响应:<NSHTTPURLResponse: 0x2831fa500> { URL: https://tinypng.com/images/example-orig.png } { Status Code: 200, Headers {

logMessage的创建时间是2023-04-04 11:06:00:600,但控制台显示的时间戳是2023-04-04 11:06:00.601922+0800。相差2ms。还以为是didReceiveData的调用跑到didReceiveResponse前面去了,原来是打印的问题。

OC中的错误是一个类NSError:

1
2
3
4
5
6
7
8
9
// Immutable, and NSError must be Sendable because it conforms to Error in Swift
open class NSError : NSObject, NSCopying, NSSecureCoding, @unchecked Sendable {


/* Domain cannot be nil; dict may be nil if no userInfo desired.
*/
public init(domain: String, code: Int, userInfo dict: [String : Any]? = nil)
....
}

而Swift中的错误则是一个协议Error:

1
2
3
4
5
6
7
8
9
10
11
12
13
public protocol Error : Sendable {
}
public protocol Sendable {
}

extension Error {

/// Retrieve the localized description for this error.
public var localizedDescription: String { get }
}

extension NSError : Error {
}

实际上这个协议没有任何内容,因此任意类、结构体、枚举都可以轻松实现Error协议。

定义Swift Error:

使用枚举

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
enum NetworkError: Error {
case domainError
case decodingError
case noDataError

var localizedDescription: String {
switch self {
case .domainError:
return "url错误"
case .decodingError:
return "解析出错"
case .noDataError:
return "无数据"
}
}
}

由于错误一般是后台接口给出,所以个人觉得使用结构体表示错误,使用是最方便的:

1
2
3
4
5
6
7
8
9
10
11
12
13
struct BusinessError: Error {
var code = ""
var message = ""

init(code: String, message: String) {
self.code = code
self.message = message
}

var localizedDescription: String {
return message
}
}
阅读全文 »

1.swift的枚举只能拥有原始值和关联值的其中一种,不能同时拥有,会报错:Enum with raw type cannot have cases with arguments。

2.读取关联值时不能写参数的类型,否则编译报错。

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
enum BinaryTree<Element> {
case leaf
indirect case node(v: Element, l: BinaryTree<Element>, r: BinaryTree<Element>)
}

extension BinaryTree {
init(_ val: Element) {
self = .node(v: val, l: .leaf, r: .leaf)
}
}

//读取关联值时不能写参数的类型,否则编译报错。
//case let .node(v: Element, l: BinaryTree<Element>, r: BinaryTree<Element>):
extension BinaryTree {
var values: [Element] {
switch self {
case .leaf:
return []
case let .node(v, l, r): //直接填写变量名称。
return l.values + [v] + r.values
// case .node(let v, var l, var r):
// return l.values + [v] + r.values
}
}
}

参考

Enum with raw type cannot have cases with arguments

OC中提供了4个访问控制符:@private @package @protected @public。

@private(当前类访问权限):成员只能在当前类内部可以访问,在类实现部分定义的成员变量相当于默认使用了这种访问权限。

@package(同映像访问权限):成员可以在当前类或和当前类实现的同一映像中使用,同一映像就是编译后生成的同一框架或同一个执行文件,跨框架不可用。简单点讲就是使用package后,该成员变量在同一个框架里的所有类里都可以访问到,别的框架访问不到。

@protected(子类访问权限):成员可以在当前类和当前类的子类中访问。在类接口部分定义的成员变量默认是这种访问权限。

@public(公共访问权限):成员可以在任意地方访问。

private和public比较简单,所以这里具体说一下package和protected的使用场景。

使用package修饰的成员只能在当前框架内被访问。

比如A框架里有一个类XQSheet,它有一个_buttons成员。

1
2
3
4
5
6
@interface XQSheet : JHGrandPopupView
{
@package NSMutableArray *_buttons;
}

@end

使用package修饰后,那么_buttons只能在A框架内部被访问到(A框架的任意类里都可以),但不能在框架B里被访问。

阅读全文 »

@inline

说明:函数内联,一种编译器优化技术。声明内联的函数,编译器会把该函数调用的地方用它的实现替换有点类似C语言的宏,这样就减少了函数的调用开销,属于一种微优化,如果函数实现简单,调用非常频繁可以使用内联进行优化。由于内联是把函数调用的地方用它的实现替换,因此内联可能会让程序的二进制包变大,取决于编译优化等级,编译器可能会自动对一些频繁调用的函数进行内联,如果这个函数实现比较复杂,那么此时的内联可能弊大于利,这个时候可以使用 @inline(never) 明确告知编译器关闭这一特性。@inline的另一个应用场景就是代码混淆增加逆向难度。

用法:使用@inline(__always) 和 @inline(never) 修饰函数。函数的访问权限可以是public也可以是private。

参考:The Forbidden @inline Attribute in Swift

@inlinable

说明:@inlinable 属性允许您为特定方法启用跨模块内联。这样做了之后,方法的实现将作为模块公共接口的一部分来公开,允许编译器进一步优化来自不同模块的调用。使用 @inlinable 最大的收益就是有些方法可能会有性能开销,尽管大多数的方法这种开销可以忽略不计,但像那种包含泛型和闭包的,开销很大。使用@inlinable 可以带来性能提升,不过包的大小会增加。

用法:’@inlinable’ 只能用于 public 函数,不能修饰private函数。

参考:Understanding @inlinable in Swift

@autoclosure

The @autoclosure attribute can be applied to a closure parameter for a function, and automatically creates a closure from an expression you pass in. When you call a function that uses this attribute, the code you write isn’t a closure, but it becomes a closure, which can be a bit confusing – even the official Swift reference guide warns that overusing autoclosures makes your code harder to understand.

阅读全文 »

在开发pod库时,遇到了一个编译报错的问题,记录一下。

主pod是OC实现的,主pod里依赖了WCDB(一个OC++库),主pod里有一个子pod,子pod采用swift实现。当宿主工程集成子pod时,编译通不过,报错在:WCDB.h里。Since WCDB…

1
2
3
4
5
6
#ifndef WCDB_h
#define WCDB_h

#ifndef __cplusplus
#error Since WCDB is an Objective-C++ framework, for those files in your project that includes WCDB, you should rename their extension .m to .mm.
#endif

Google了半天也没搜到类似的,感觉应该是混编导致的错误。

我的podspec文件:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
spec.swift_versions = ['5.5', '5.6', '5.7']
spec.requires_arc = true
spec.dependency "CocoaLumberjack", "~> 3.7.4"
spec.dependency "WCDB", "~> 1.1.0"

spec.default_subspecs = 'Core'

spec.subspec 'Core' do |ss|
ss.source_files = 'Sources/MATLog/**/*.{h,m,mm}'
end

spec.subspec 'Swift' do |ss|
ss.dependency 'MATLog/Core'
ss.source_files = 'Sources/MATLogSwift/**/*.{swift,h,mm}'
end

子pod里压根就没使用到WCDB,很奇怪为啥会报那个错。搜也无从搜起,冥思苦想半天,也就 MATLogModel+WCTTableCoding.h 头文件里包含了WCDB.h,后来发现确实是这里引起的。

因为OC pod库头文件默认是public,所以子库能访问到主库所有暴露的头文件,修改podspec文件如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
spec.swift_versions = ['5.5', '5.6', '5.7']
spec.requires_arc = true
spec.dependency "CocoaLumberjack", "~> 3.7.4"
spec.dependency "WCDB", "~> 1.1.0"

spec.default_subspecs = 'Core'

spec.subspec 'Core' do |ss|
ss.source_files = 'Sources/MATLog/**/*.{h,m,mm}'
ss.private_header_files = 'Sources/MATLog/*WCTTableCoding.{h}' #控制OC头文件访问权限的有privateprojectpublic三种。默认public
end

spec.subspec 'Swift' do |ss|
ss.dependency 'MATLog/Core'
ss.source_files = 'Sources/MATLogSwift/**/*.{swift,h,mm}'
end

这时 MATLogModel+WCTTableCoding.h 头文件就变成private了,编译也能通过了。

阅读全文 »

1.创建私钥

创建不带密码的私钥:

1
openssl genrsa -out test_rsa_private.pem 1024

1024表示私钥的长度,长度越长安全性越高。如果对安全要求比较高可以指定私钥长度为2048。

查看私钥:

1
cat my_test_private.key

打印:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
-----BEGIN RSA PRIVATE KEY-----
MIICXAIBAAKBgQCj5vQ2rQsavYxtJyNh/nugHqOvE2kmY492h8l4lPSr/zMT1I1Z
D/yTkh3sDSZHNThWk5UJukXZPGsKf5xlAaj6WZiaPSBw5WW/f7Pm8M1+kNBKZGeK
sSATuVfyPs69bTBl1Mn9YcBwHXkuu7ijqSxeE8aVlKsUSAR3JDIlFiCn0QIDAQAB
AoGAfRuopCeYR1QSYaszVfSzlvhsRxJQ/A2ZD4f8oH9K+BL3gRaIwkfyqw4oqusq
ocYc9/D1HZTDBlwY9M2NqogG28FJSMp2yVIHg8pG29FnGi5TGJBNV4kx4rmkpulr
/E7TL1oRVioP2I6ZWxXk5xuiNvwzm60zhyRxuDHn5DHob1UCQQDRMzaTpawx5qHj
p/yWLmgFyCVB07upFXF9jGOJHmZevgxgwoe6SSh9hzCEmJLY5kYo5eDW/76Nifd+
so9ACSD7AkEAyJGNa/B7GRLPoYyyA8YUeTTTdWtDmU7lS4D2eceLPrzJbTrKnKR6
nijlAWry/dKPFIFOq1z28X3gwxeT8aF4owJAJIvW1/pUV69bzsKVDMN0prXtVE+h
9Arr9avl45ls9tYqoWi6f1+ydCN+5VsmJEAuN4zZN5Yb+uwEUZzuC5jMqwJAA6Xi
FJyDIKme7SlJ85eet7WmQvR4fklZEk5+LSjb94Anib0QAllbgZTs1WHEmalCwPS5
IZTHSQ0pEWNUZYiyUQJBAIr0BOB3myFPMVGBB4EvQegnTd40JSU7hKXaK3ZBbrE/
aHoxdlz8JJsuuufaaK4Rvkdy78QTuNu0vty9jonQQeM=
-----END RSA PRIVATE KEY-----

上述打印是base64过的。可以使用-text,以明文形式输出各个参数值

1
openssl rsa -in test_rsa_private.pem -text -noout
阅读全文 »

KVO使用

KVO,观察者与被观察者.被观察者添加某个观察者,观察自身某一属性的变化.当被观察者的属性值发生变化时,被观察者会直接将变化发送给观察者 (通过给观察者发送observeValueForKeyPath:消息),这也是和通知差别比较大的地方.

1.观察者需要实现observeValueForKeyPath:方法,并在该方法中做出相应行为.如果观察者没有实现observeValueForKeyPath:方法,且其父类也没有实现,那么最终会走到NSObject的实现,而NSObject的默认实现就是崩溃:An -observeValueForKeyPath:ofObject:change:context: message was received but not handled.如果观察者本身没有实现observeValueForKeyPath:…方法,但是其父类实现了,那么执行的就是父类的实现,这也是消息的传递过程.

2.[super observeValueForKeyPath:]

对于网上一种写法:

1
2
3
4
5
6
7
8
9
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object
change:(NSDictionary *)change context:(void *)context
{
if (object == _tableView && [keyPath isEqualToString:@"contentOffset"]) {
[self doSomethingWhenContentOffsetChanges];
} else {
[super observeValueForKeyPath:keyPath ofObject:object change:change context:context];
}
}

这里首先要清楚[super method]的作用,[super method]表示的是去执行父类里的实现,消息的接收者还是子类对象本身.并不是说消息的接收者变成了一个父类对象在执行该方法.完整的写法确实应该在else处添加super调用。但由于这种场景很少出现,所以很少有人特意去调super,一般也不会出问题。

3.KVO的context参数取值

context参数虽然可以传入任意值,但如果传入的是一个对象,则必须保证在KVO期间该对象不会被销毁.否则在observeValueForKeyPath方法中将导致野指针访问崩溃.可以传入一些常量比如字符串常量,或类对象.

1
2
3
[self.people addObserver:self forKeyPath:@"age" options:NSKeyValueObservingOptionNew | NSKeyValueObservingOptionOld context:(__bridge void * _Nullable)(self.class)];

[self.people addObserver:self forKeyPath:@"age" options:NSKeyValueObservingOptionNew | NSKeyValueObservingOptionOld context:@"mycontext"];
阅读全文 »

左移与右移:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
">>"右移
操作规则:最低位丢弃。正数前面补零,负数前面补1
"<<"左移
操作规则:最高位丢弃。无论正负数,后面补零。
建议:左右移时参数最好限定为无符号数或正整数。有符号数需要注意的地方太多了。

有符号数:
根据内存分布,按上述规则移动。
左移:由于有符号数最高位为符号位,左移时可能会发生符号位变化所以最好自己根据内存分布计算。
右移:如果数为正,不溢出的情况下,每移一位相当于除以2并向下取整,等价于除以2。如果数为负,每移一位相当于除以2并向上取整。如-1,-1/2 = -0.5向上取整 = -1。-3,-3/2 = -1.5向上取整 = -2

无符号数:
左移:不溢出的情况下,左移一位相当于乘2
右移:不溢出的情况下,右移一位相当于除2并向下取整,等价于除以2

n = 1010 1000
n - 1 = 1010 0111
n & (n - 1) = 1010 0000 //相当于将n的最右边的1消为0n为正数或负数都可以。
而右移需要参数为正数。
在数二进制1的个数方面,n & (n - 1)是最佳解决办法。

代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
char a = -3;
printf("a = %d\n", a);

int cnt = hammingWeight(a); //会被强转为int。
printf("cnt = %d\n", cnt);

char b = a >> 1;
printf("b = %d,a / 2 = %d\n", b, a / 2);

unsigned char c = 3;
printf("c = %d\n", c);

unsigned char d = c >> 1;
printf("d = %d\n", d);


int hammingWeight(int n) {
int cnt = 0;
while (n != 0) {
cnt += 1;
n &= n - 1;
}
return cnt;
}

打印:

1
2
3
4
5
a = -3
cnt = 31
b = -2,a / 2 = -1
c = 3
d = 1