0%

最近给我的MacBook Pro外接了显示器和键盘,显示器是三星的 LU28R55 ,键盘是惠普的。
但是发现外接显示器、键盘后有几个影响体验的问题:
1.外接的显示器是没有内置扬声器的需要额外接个音响才行,否则设置声音的输出为外接显示器也没用。
2.外接的显示器调节亮度和声音只能用显示器自带的按钮按,无法通过键盘控制。

于是Google了一下发现使用 MonitorControl + Karabiner 这两个软件可以达到外接键盘控制外接显示器的亮度和声音。
MonitorControl下载地址
https://github.com/MonitorControl/MonitorControl/releases/tag/v2.1.0

Karabiner下载地址
https://github.com/pqrs-org/Karabiner-Elements/releases/tag/v13.4.0

MonitorControl的作用是通过软件控制外接显示器的亮度和声音。

Karabiner的作用是按键映射。为什么需要这个APP呢?因为外接显示器后笔记本基本上是合盖使用的,自然就没法使用笔记本的键盘了,而我这里外接的键盘是惠普的键盘跟苹果的键盘是不一样的,所以需要映射一下。当然如果你的外接键盘是苹果的配件那使用MonitorControl就够了。

MonitorControl的安装很简单,这里主要说一下Karabiner,Karabiner安装后会多出两个APP:Karabiner-Elements 和 Karabiner-EventViewer。这里主要使用Karabiner-Elements,而Karabiner-EventViewer这里虽然使用不到但需要打开一下以允许一些权限。要不然仅使用Karabiner-Elements在Mac的一些系统上可能会出现即使设置了但却没效果的现象。
image.png

Karabiner-Elements的设置也很简单,当然对于小白的我来说也是整了一会的主要是没注意到Karabiner-EventViewer还要打开一下这个坑。
Karabiner-Elements最好多设置几个profile,方便切换,用回笔记本自身的键盘时就切换为默认的。
profile:
image.png

按键的映射:
image.png
这样就可以通过非苹果外接键盘控制苹果电脑的扬声器了。

现在,你可以通过外接键盘控制声音和外接显示器的亮度了,是不是感觉体验度瞬间上升了好几楼了呢?

另外吐槽下三星的 LU28R55,这款显示器亮度设为0了后感觉还是挺亮的,真不知道当初设计的人是怎么想的就不能让它继续调低吗?在我看来完全是一个缺陷。

阅读全文 »

MacBook Pro笔记本的显示器着实是小了点,想买一个外接显示器。没想到买个显示器也是有很多知识是需要学习的。在京东上随便挑了一个卖的靠前的显示器三星C24F390FHC,牌子是三星的感觉问题不大,价格999块也还能接受。参数啥的看了一下,但因为什么都不懂看了也仅仅是看了。

买回来装好,心情还有点小激动,连上电脑后一看效果傻了,显示器显示的文字毛边太明显了,看着太闹心了。用久了苹果的产品以为显示器都是retina屏的。然后看了一下产品参数是1920x1080p的,但一般宣传上写的是“1920x1080p(全高清)”,看到全高清三个字对于小白的我来说以为妥了,肯定够用了,殊不知上面还有2K屏,4K屏。产品营销文案确实是一个值得玩味的东西,随便一个术语就可以把一个小白唬的一愣一愣的。装好体验了几分钟实在受不了这种模糊的感觉,只能退了,等京东退钱后再打算加点钱换个4K屏的。

总结:
1.买电子产品前最好做一下功课。了解一下电子产品的参数,这些参数的含义。明确好自己的需求,挑出能够满足自己需求的几个关键性参数,如果产品达不到自己的硬性要求就不要买了。
不过这一点好像挺难,可能大多数人都不知道有哪些关键性参数需要重点考虑。比如我自己以为所有的显示器都是retina屏的,压根就没想到原来还有很多低分辨率的显示器。难道是我脱离生活太久变成了何不食肉糜的晋惠帝了?所幸在发达的互联网我们可以搜几篇科普文扫扫盲,这样就能够确定好自己需要关注的参数了。
2.了解下电子产品的附件。显示器是附赠HDMI线的有的还送DP线的,这是连接电脑和显示器线的。当时买的时候也没看,额外买了一根HDMI线。
3.网购的时候是否有必要随大流选所谓的热销的、爆款的?
这个我也不太清楚,网上的东西假的太多,好评基本没有参考价值。但是买东西前做下功课确定好自己想买什么而不是头脑发热掉进商家的宣传,刷爆了信用卡,空悲切。

最近想学习一下mmap的使用,没想到一来就掉坑里了。

测试代码如下:

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
- (void)test_mmap {
__autoreleasing NSError *err = nil;
// self.videoData = [NSData dataWithContentsOfFile:[[NSBundle mainBundle] pathForResource:@"ddcada74ee08d06dda5cd13b4117ad30" ofType:@"mp4"] options:0 error:&er];
// self.videoData = [NSData dataWithContentsOfFile:[[NSBundle mainBundle] pathForResource:@"ddcada74ee08d06dda5cd13b4117ad30" ofType:@"mp4"] options:NSDataReadingUncached error:&er];
self.videoData = [NSData dataWithContentsOfFile:[[NSBundle mainBundle] pathForResource:@"ddcada74ee08d06dda5cd13b4117ad30" ofType:@"mp4"] options:NSDataReadingMappedIfSafe error:&err]; //1
if (err != nil) {
NSLog(@"er:%@", err);
} else {
NSLog(@"videoData:%lu", (unsigned long)self.videoData.length);
}
// long bufferSize = 4096;
long bufferSize = 1000000;
// long bufferSize = 1024 * 1024 - 30 * 1024;
unsigned char buffer[bufferSize];
[self.videoData getBytes:buffer range:NSMakeRange(0, bufferSize)]; //2
NSData *someData = [[NSData alloc] initWithBytes:buffer length:bufferSize];
NSLog(@"someData:%lu", (unsigned long)someData.length);
[self saveDataToCaches:someData];
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(5 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
self.videoData = nil;
});
}

- (void)saveDataToCaches:(NSData *)data {
NSString *cachespath = NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES).lastObject;
NSString *fileName = [NSString stringWithFormat:@"%@", NSDate.date];
NSString *filePath = [cachespath stringByAppendingPathComponent:fileName];
NSError *err;
BOOL ret = [data writeToFile:filePath options:NSDataWritingAtomic error:&err];
if (err) {
NSLog(@"写入错误:%@", err);
}
NSLog(@"ret:%d", ret);
}

1处通过mmap的方式读取一个500多兆的视频数据,2处读取视频中某一段的数据。最开始的时候bufferSize设置的是4096,运行的时候一切正常。正常之后自然想改个其他的值试试,于是就设置bufferSize为视频大小,结果一运行就崩溃了很快啊,然后就是提示EXC_BAD_ACCESS。当时觉得很奇怪怎么会EXC_BAD_ACCESS呢?也没看到哪个地方有过度释放的对象啊。最后一点点修改bufferSize的大小,发现小于1000000代码就可以正常运行,而超过则会EXC_BAD_ACCESS。不过依然搞不懂为啥会这样。百思不得其解之后只能求助Google,不过也不知道怎么搜索关键字,一通搜索之后也没找到有用的东西,期间一度想放弃,不过还是坚持了下来。

直接上结论:上述崩溃的原因就是stack overflow了。

我们可以验证一下,打好断点后分别p &errp &cachespath得到:

1
2
3
4
(lldb) p &err
(NSError **) $0 = 0x000000016f5b97d0
(lldb) p &cachespath
(NSString **) $1 = 0x000000016f4c5470

$0 - $1 = 0xF4360 = 1000288 = 0.954M,基本上快占满整个栈空间了(这里有点马后炮了因为很多人可能都不知道主线程的栈空间有多大,甚至都没有栈溢出这个概念自然也不会往这个方面想了)。因此代码可能会崩溃在后面的任意一行代码,具体是哪一行视栈的剩余空间大小。比如有可能崩溃在

1
NSError *err;  Thread 1: EXC_BAD_ACCESS (code=2, address=0x16f4bfad0)

也有可能会崩溃在:

阅读全文 »

源码版本:objc4-781

我们都知道OC的内存管理是通过引用计数来管理的,对象刚创建时引用计数为1,retain后+1,release后-1,当引用计数减为0时就会被销毁。那么问题就来了,操作的这个引用计数值是保存在哪的?今天就来研究一下这个问题。

先说结论:早期OC对象的引用计数都是存储在引用计数表中的,但是后来Apple的工程师可能觉得每次都从表中查找修改效率有点低,于是做了一些优化,如果引用计数值较小时就保存在 struct objc_object 的isa成员变量里,只有当isa里装不下时才存放到引用计数表里,这一点可以从isa的类型变迁看出。除此之外,苹果还引入了Tagged pointer对象,这种对象实际上已经没有在堆上分配内存了,它的值就保存在指针里,这样的对象由于并没有真正在堆上分配内存因此讨论它的引用计数也就没有什么意义了。

之前的文章 runtime源码分析(一)--类型 里专门分析了 struct objc_objectstruct objc_classisa 类型的定义,这里不再赘述,仅做简单说明。

isa的定义

旧版本的定义:

isa是一个指针类型 struct objc_class *

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
/// An opaque type that represents an Objective-C class.
typedef struct objc_class *Class;

/// Represents an instance of a class.
struct objc_object {
Class _Nonnull isa OBJC_ISA_AVAILABILITY;
};

struct objc_class {
Class _Nonnull isa OBJC_ISA_AVAILABILITY; //旧版本isa为一个指针指向objc_class结构体

#if !__OBJC2__
Class _Nullable super_class OBJC2_UNAVAILABLE;
const char * _Nonnull name OBJC2_UNAVAILABLE;
long version OBJC2_UNAVAILABLE;
long info OBJC2_UNAVAILABLE;
long instance_size OBJC2_UNAVAILABLE;
struct objc_ivar_list * _Nullable ivars OBJC2_UNAVAILABLE;
struct objc_method_list * _Nullable * _Nullable methodLists OBJC2_UNAVAILABLE;
struct objc_cache * _Nonnull cache OBJC2_UNAVAILABLE;
struct objc_protocol_list * _Nullable protocols OBJC2_UNAVAILABLE;
#endif

} OBJC2_UNAVAILABLE;
/* Use `Class` instead of `struct objc_class *` */

新版本oc 2.0定义:

isa是一个联合类型 union isa_t

阅读全文 »

前置知识

质数

质数又称素数,指在大于1的自然数中,除了1和该数自身外,无法被其他自然数整除的数(也可定义为只有1与该数本身两个正因数的数)。大于1的自然数若不是素数,则称之为合数(也称为合成数)。最小的质数为2。

互质关系

如果两个正整数,除了1以外,没有其他公因子,我们就称这两个数是互质关系(coprime)。比如,15和32没有公因子,所以它们是互质关系。这说明,不是质数也可以构成互质关系。

关于互质关系,不难得到以下结论:

1
2
3
4
5
6
7
8
9
10
11
1. 任意两个质数构成互质关系,比如1361。important

2. 一个数是质数,另一个数只要不是前者的倍数,两者就构成互质关系,比如310

3. 如果两个数之中,较大的那个数是质数,则两者构成互质关系,比如9757

4. 1和任意一个自然数是都是互质关系,比如199

5. p是大于1的整数,则p和p-1构成互质关系,比如5756。 important

6. p是大于1的奇数,则p和p-2构成互质关系,比如1715

生成RSA密钥对,即公钥和私钥

1:随机找两个质数 p 和 q ,p 与 q 越大,越安全。

比如 p = 3 ,q = 11。这里为了方便后面计算,选了2个很小的质数,实际应用需要选择两个非常大的质数。

阅读全文 »

为什么要进行内存对齐

主要有两个原因:1.内存对齐后可以减少内存的读取次数,提高内存的访问效率。2.提高程序的可移植性,因为有的平台对寻址的起始地址有要求。

现代计算机中内存空间都是按照 byte 划分的,从理论上讲似乎对任何类型的变量的访问可以从任何地址开始,但是实际的计算机系统对数据在内存中存放的位置是有限制的,它们会要求这些数据的内存首地址的值是某个数k(通常它为4或8)的倍数,这就是所谓的内存对齐。
注:某个数是4的倍数,不一定就是8的倍数,比如4、12、20、28、36。但反过来是8的倍数则必定是4的倍数。

内存对齐主要是为了提高内存的访问效率,CPU访问内存时,并不是逐个字节访问,而是以字长(word size)为单位访问。比如32位的CPU,字长为4字节,那么CPU访问内存的单位也是4字节,即一次读取4个字节的内存。又比如intel 32位cpu,每个总线周期都是从偶数地址开始读取32位的内存数据,如果数据存放地址不是从偶数开始,则可能出现需要两个总线周期才能读取到想要的数据,因此需要在内存中存放数据时进行对齐。

总之一句话,只有当数据的首地址为某个数k(通常它为4或8)的倍数时(偶数地址),CPU需要进行数据剔除的情况才会越少发生。当剔除操作无法避免时,剔除次数越少越好,如果数据的首地址是奇数,那么CPU需要剔除前后的数据。而如果是偶数则最好情况下只需要剔除前面或后面的数据。char类型的变量因为只占1个字节就随便了。

假如没有内存对齐机制,数据可以在任意内存地址处开始存放:

现在一个int变量存放在从地址1开始的连续四个字节地址中,该处理器去取数据时,要先从0地址开始读取第一个4字节块,剔除不想要的字节(0地址),然后从地址4开始读取下一个4字节块,同样剔除不要的数据(5,6,7地址),最后留下的两块数据合并放入寄存器.这需要做很多工作.

如果int变量存放在从地址4开始的连续四个字节地址中,就可以一次读取完数据。

所以int变量的内存首地址通常在4的倍数的地址处开始,比如上面的编号为4或8的地址处,而不可能是2或5这样的地址处。

阅读全文 »

前言

什么是边下边播?

个人理解边下边播指的是从服务器获取到的数据一边供给播放器播放一边缓存到本地,即使用一遍的流量完成播放和缓存。下次播放时有缓存的地方则播放缓存数据,没有缓存的地方则从服务器获取再播放。

0.边下边播方案

目前的边下边播实现方案大致有两种:一种是使用本地代理服务器,一种是使用系统的AVAssetResourceLoaderDelegate。

使用本地代理服务器

该方案是在播放器与视频源服务器之间加一层代理服务器,拦截视频播放器发送的请求,根据拦截的请求,向网络服务器请求数据,然后写到本地。本地代理服务器从文件中读取数据并发送给播放器进行播放。

对于 iOS 端代理服务器的实现,可以参考和使用 CocoaHTTPServer。对于 iOS 端的视频缓存管理,可以参考和使用 KTVHTTPCache。这种方案难度较高,但自主可控性更高。

使用系统原生API—AVAssetResourceLoaderDelegate

阅读全文 »

这里不谈宏的使用。只是突然想知道定义一个宏之后,这个宏的作用域有多大。

宏定义作用域

C语言标准中宏定义的作用域是:如果是在块作用域内,则从定义位置开始,到块作用域结束,否则到其当前文件结尾,其他文件如果没有通过#include包含这个文件,则使用该宏是无效的。

比如我在a.m里面定义了一个宏#define kCondTrue 1,在b.m里面有如下代码:

1
2
3
4
5
6
7
- (void)btnDidClicked:(UIButton *)sender {
NSLog(@"btnDidClicked");

#if kCondTrue //不会有编译错误
NSLog(@"111");
#endif
}

由于kCondTrue宏对b.m是不可见的,所以这里不会打印111。

通常编译器会有一些预定义宏,预定义的宏大多会遵守命名规则:__xxx_yyy__,以双下划线开始和结束。比如__OBJC2__就是一个预定义的宏。

宏定义很强大,组合起来可以实现一个很复杂的功能。but 这样的宏可读性是很差的,你可以想象一下一个由十几个宏共同组合完成的宏有多么可怕,OC开源源码里面原子操作相关的宏是真滴恐怖。感觉如果一个宏过于复杂最好还是用方法代替。

完了。

参考

阅读全文 »

环境:Xcode11

ARC

在MRC时代 id obj1 = obj; 这一行代码仅仅是赋值,不会引起obj对象引用计数的任何变化。而在ARC时代这一行代码会导致对象的引用计数+1。这背后究竟发生了什么?接下来让我们研究一下。

ARC:自动引用计数。不再需要手动调用 retain 、release、autorelease 等方法,而是由编译器在合适的地方自动添加。那么编译器又是怎么知道是添加retain还是添加autorelease或者weak的呢?这就需要所有权修饰符了。通过不同类型的所有权修饰符,编译器就知道该添加什么语句了。

ARC 有效时,id 类型和对象类型必须附加所有权修饰符,有如下几种:

  • __strong 修饰符
  • __weak 修饰符
  • __unsafe_unretained 修饰符
  • __autoreleasing 修饰符

其中 __strong 修饰符是OC对象类型的默认修饰符。

接下来从汇编角度看,当一个指针变量使用这些修饰符后,编译器会作何处理。

__strong 修饰符

对于下面的代码:

阅读全文 »

源码版本:objc4-781

0.weak 修饰符的作用

之前的文章提到过 __weak 修饰符作用:

1.当我们将一个对象赋值给一个 __weak 修饰符的指针变量时,编译器会插入 objc_initWeakobjc_storeWeak 函数将该弱指针变量从旧对象的weak表剔除(如果之前有指向对象的话)并注册到新对象的weak表里,然后将弱指针指向新对象。

2.当使用附有 __weak 修饰符的变量时,编译器会在使用前插入 objc_loadWeakRetained 函数将对象引用计数+1,保证对象在后面的使用过程中不被释放。并在使用后插入 objc_release(tmp1); 释放对象。

3.当弱引用指针变量超出作用域被废弃时,编译器会插入 objc_destroyWeak 将该指针变量从weak表里移除。

4.当对象销毁时,weak表中对象的所有弱引用指针变量会被赋值为nil,避免了野指针崩溃。比如对nil对象发送消息不会有任何反应,需要注意的是如果是对nil进行解引用还是会崩溃的。

这些功能是assign所不具备的,assign唯一与weak相同的地方就是不会导致对象的引用计数+1。

note:weak 只能用于修饰对象类型。并且只能用于iOS5及以上,在iOS4可以使用 __unsafe_unretained 代替.另外weak在MRC下也是有效果的,不过需要设置 Weak References in Manual Retain Release 为 YES。

今天就来看一下weak的上述功能是如何实现的,在看源码之前,可以带着一些问题,比如:

阅读全文 »