0%

Bug

描述:ZATextField 自己添加自己作为target后,发现action方法不被调用。

分析

ZATextField的实现如下:

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
#import "ZATextField.h"

@implementation ZATextField

- (instancetype)initWithFrame:(CGRect)frame {
self = [super initWithFrame:frame];
if (self) {

__weak typeof(self) weakSelf = self;
[[NSNotificationCenter defaultCenter] addObserverForName:UITextFieldTextDidBeginEditingNotification object:self queue:nil usingBlock:^(NSNotification * _Nonnull note) {
if (weakSelf.rightView && weakSelf.rightViewMode == UITextFieldViewModeWhileEditing) {
weakSelf.rightView.hidden = weakSelf.text.length > 0 ? NO : YES;
}
if (weakSelf.leftView && weakSelf.leftViewMode == UITextFieldViewModeWhileEditing) {
weakSelf.leftView.hidden = weakSelf.text.length > 0 ? NO : YES;
}
}];
[[NSNotificationCenter defaultCenter] addObserverForName:UITextFieldTextDidChangeNotification object:self queue:nil usingBlock:^(NSNotification * _Nonnull note) {
if (weakSelf.rightView && weakSelf.rightViewMode == UITextFieldViewModeWhileEditing) {
weakSelf.rightView.hidden = weakSelf.text.length > 0 ? NO : YES;
}
if (weakSelf.leftView && weakSelf.leftViewMode == UITextFieldViewModeWhileEditing) {
weakSelf.leftView.hidden = weakSelf.text.length > 0 ? NO : YES;
}
}];

[self addTarget:self action:@selector(xxxxchange:) forControlEvents:UIControlEventEditingChanged];
}
return self;
}

- (void)xxxxchange:(UITextField *)sender {
NSLog(@"text:%@", self.text);
}

// 光标大小设置
- (CGRect)caretRectForPosition:(UITextPosition *)position {
CGRect originalRect = [super caretRectForPosition:position];
CGFloat caretHeight = self.font.lineHeight - 4;
originalRect.size.height = caretHeight;
originalRect.origin.y = (self.frame.size.height - caretHeight) / 2.0;
return originalRect;
}

@end

自己添加了自己作为target:

1
[self addTarget:self action:@selector(xxxxchange:) forControlEvents:UIControlEventEditingChanged];

理论上讲当有文字输入时,xxxxchange方法肯定会调用的,但是实际没有。

可能的原因

1.方法被其他地方的类别覆盖了。

阅读全文 »

objc_AssociationPolicy

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
/**
* Policies related to associative references.
* These are options to objc_setAssociatedObject()
*/
typedef OBJC_ENUM(uintptr_t, objc_AssociationPolicy) {
OBJC_ASSOCIATION_ASSIGN = 0, /**< Specifies a weak reference to the associated object. */
OBJC_ASSOCIATION_RETAIN_NONATOMIC = 1, /**< Specifies a strong reference to the associated object.
* The association is not made atomically. */
OBJC_ASSOCIATION_COPY_NONATOMIC = 3, /**< Specifies that the associated object is copied.
* The association is not made atomically. */
OBJC_ASSOCIATION_RETAIN = 01401, /**< Specifies a strong reference to the associated object.
* The association is made atomically. */0x0301
OBJC_ASSOCIATION_COPY = 01403 /**< Specifies that the associated object is copied.
* The association is made atomically. */0x0303
};

//对应的内部枚举
enum {
OBJC_ASSOCIATION_SETTER_ASSIGN = 0,
OBJC_ASSOCIATION_SETTER_RETAIN = 1,
OBJC_ASSOCIATION_SETTER_COPY = 3, // NOTE: both bits are set, so we can simply test 1 bit in releaseValue below.
OBJC_ASSOCIATION_GETTER_READ = (0 << 8),
OBJC_ASSOCIATION_GETTER_RETAIN = (1 << 8),
OBJC_ASSOCIATION_GETTER_AUTORELEASE = (2 << 8)
};

注意:上面的01401是八进制写法,对应的十六进制是0x0301。我说怎么后面的switch看不懂呢。

AssociationPolicy中NONATOMIC 和 ATOMIC 的区别

对于setter方法,NONATOMICATOMIC 没有什么区别

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
void
_object_set_associative_reference(id object, const void *key, id value, uintptr_t policy)
{
// This code used to work when nil was passed for object and key. Some code
// probably relies on that to not crash. Check and handle it explicitly.
// rdar://problem/44094390
if (!object && !value) return;

if (object->getIsa()->forbidsAssociatedObjects())
_objc_fatal("objc_setAssociatedObject called on instance (%p) of class %s which does not allow associated objects", object, object_getClassName(object));

DisguisedPtr<objc_object> disguised{(objc_object *)object};
ObjcAssociation association{policy, value};

// retain the new value (if any) outside the lock.
association.acquireValue();

bool isFirstAssociation = false;
{
AssociationsManager manager;
AssociationsHashMap &associations(manager.get());

if (value) {
//try_emplace会先查找,没找到才创建新的ObjectAssociationMap,创建时会根据需要扩容AssociationsHashMap。
auto refs_result = associations.try_emplace(disguised, ObjectAssociationMap{});
if (refs_result.second) { //指定的disguised,以前没有值,这里是新创建的。则做好标记isFirstAssociation。
/* it's the first association we make */
isFirstAssociation = true;
}

/* establish or replace the association */
auto &refs = refs_result.first->second; //refs是ObjectAssociationMap哈希表
auto result = refs.try_emplace(key, std::move(association)); //try_emplace作用同上。
if (!result.second) { //指定的key有旧值,进行swap,这样association保存的就是旧值,后面就执行释放旧值的操作。
association.swap(result.first->second); //result.first->second是ObjcAssociation对象
}
} else {
auto refs_it = associations.find(disguised);
if (refs_it != associations.end()) {
auto &refs = refs_it->second;
auto it = refs.find(key);
if (it != refs.end()) {
association.swap(it->second);
refs.erase(it);
if (refs.size() == 0) { //ObjectAssociationMap哈希表全空时,则将其从AssociationsHashMap中清除。
associations.erase(refs_it); //清除AssociationsHashMap的某个桶。erase里还会对AssociationsHashMap进行减容或扩容。

}
}
}
}
}

// Call setHasAssociatedObjects outside the lock, since this
// will call the object's _noteAssociatedObjects method if it
// has one, and this may trigger +initialize which might do
// arbitrary stuff, including setting more associated objects.
if (isFirstAssociation)
object->setHasAssociatedObjects();

// release the old value (outside of the lock).
association.releaseHeldValue();
}

inline void acquireValue() {
if (_value) {
switch (_policy & 0xFF) {
case OBJC_ASSOCIATION_SETTER_RETAIN:
_value = objc_retain(_value);
break;
case OBJC_ASSOCIATION_SETTER_COPY:
_value = ((id(*)(id, SEL))objc_msgSend)(_value, @selector(copy));
break;
}
}
}

inline void releaseHeldValue() {
if (_value && (_policy & OBJC_ASSOCIATION_SETTER_RETAIN)) {
objc_release(_value); //_policy是带retain或copy的这里都会进行release。
}
}

inline void swap(ObjcAssociation &other) {
std::swap(_policy, other._policy);
std::swap(_value, other._value);
}

大致步骤:

  1. retain新值,if needed。使用OBJC_ASSOCIATION_ASSIGN策略是不会retain的。
  2. AssociationsManagerLock加锁
  3. 将新值保存到哈希表中,将旧值保存到association里,用于后续释放。
  4. AssociationsManagerLock解锁
  5. 标记object有关联对象
  6. release旧值,if needed。

NONATOMICATOMIC 的区别只体现在 objc_getAssociatedObject 的实现上:

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
id
objc_getAssociatedObject(id object, const void *key)
{
return _object_get_associative_reference(object, key);
}

id
_object_get_associative_reference(id object, const void *key)
{
ObjcAssociation association{};

{
AssociationsManager manager; //临时变量manager,里面有全局锁,相当于对代码块进行了加锁解锁,常见写法。
AssociationsHashMap &associations(manager.get());
AssociationsHashMap::iterator i = associations.find((objc_object *)object);
if (i != associations.end()) {
ObjectAssociationMap &refs = i->second;
ObjectAssociationMap::iterator j = refs.find(key);
if (j != refs.end()) {
association = j->second;
association.retainReturnedValue();
}
}
}

return association.autoreleaseReturnedValue();
}

inline void retainReturnedValue() {
if (_value && (_policy & OBJC_ASSOCIATION_GETTER_RETAIN)) {
objc_retain(_value);
}
}

inline id autoreleaseReturnedValue() {
if (slowpath(_value && (_policy & OBJC_ASSOCIATION_GETTER_AUTORELEASE))) {
return objc_autorelease(_value);
}
return _value;
}
阅读全文 »

NSURLSession介绍

支持的协议

1
2
3
4
5
The URLSession class natively supports the data, file, ftp, http, and https URL schemes, with transparent support for proxy servers and SOCKS gateways, as configured in the user’s system preferences.

URLSession supports the HTTP/1.1, HTTP/2, and HTTP/3 protocols. HTTP/2 support, as described by RFC 7540, requires a server that supports Application-Layer Protocol Negotiation (ALPN).

You can also add support for your own custom networking protocols and URL schemes (for your app’s private use) by subclassing URLProtocol.

URLSession原生支持data, file, ftp, http, 和 https协议,当然你也可以子类化URLProtocol来支持自己的私有网络协议。URLSession支持的HTTP版本为HTTP/1.1, HTTP/2, 和 HTTP/3,当然要想使用到HTTP/2, HTTP/3服务器端也必须得支持才行。

URLSessionConfiguration

NSURLSession的基础用法

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
  - (void)viewDidLoad {
[super viewDidLoad];

NSURLSessionConfiguration *config = [NSURLSessionConfiguration defaultSessionConfiguration];
NSOperationQueue *queue = [[NSOperationQueue alloc] init];
queue.maxConcurrentOperationCount = 3; //设置代理回调队列的最大并发数。

self.session = [NSURLSession sessionWithConfiguration:config delegate:self delegateQueue:queue];
NSLog(@"%@", self.session);

NSURL *url = [NSURL URLWithString:landscapeUrl];
NSMutableURLRequest *req = [NSMutableURLRequest requestWithURL:url cachePolicy:NSURLRequestReloadIgnoringLocalCacheData timeoutInterval:60 * 5];
NSURLSessionDataTask *dataTask = [self.session dataTaskWithRequest:req];
[dataTask resume];
}

- (void)URLSession:(NSURLSession *)session dataTask:(NSURLSessionDataTask *)dataTask
didReceiveResponse:(NSURLResponse *)response
completionHandler:(void (^)(NSURLSessionResponseDisposition disposition))completionHandler
{
NSLog(@"收到响应:%@ \ndataTask:%@", response, dataTask);

self.mData = [NSMutableData data];

NSURLSessionResponseDisposition disposition = NSURLSessionResponseAllow;
completionHandler(disposition);
}

- (void)URLSession:(NSURLSession *)session dataTask:(NSURLSessionDataTask *)dataTask
didReceiveData:(NSData *)data
{
NSLog(@"%@收到data:%ld",[NSThread currentThread] ,data.length);

[self.mData appendData:data];
}

- (void)URLSession:(NSURLSession *)session task:(NSURLSessionTask *)task
didCompleteWithError:(nullable NSError *)error
{
NSLog(@"完成, error:%@", error);
if (!error) {
//在完成的时候,之前收到的data怎么取到?不借助其他的变量,在该方法里取不到?
NSDictionary *dict = [NSJSONSerialization JSONObjectWithData:self.mData options:NSJSONReadingAllowFragments error:nil];
NSLog(@"%@", dict);
}
//不把本次session Invalidate,那么session持有的delegate不会被释放.
[session finishTasksAndInvalidate];
}

对于方法

1
2
3
+ (NSURLSession *)sessionWithConfiguration:(NSURLSessionConfiguration *)configuration 
delegate:(nullable id <NSURLSessionDelegate>)delegate
delegateQueue:(nullable NSOperationQueue *)queue;

delegate和delegateQueue会被session强引用。只有当session被 invalidate后,delegate在URLSession:didBecomeInvalidWithError结束后才会被释放.对于delegateQueue,实际使用时delegateQueue最好不要设置为主队列(会增加主线程负担).当delegateQueue不是主队列时,didReceiveData:方法将随机在某个线程执行.

阅读全文 »

原子操作是指操作是不可分割的,要么发生,要么不发生。事物只会处于原始状态和成功状态两种中的一种,不会处于一种完成一半的状态。

TAS和CAS指令是原子操作,但我感觉原子操作不仅是原子操作而且还是排他的,一个线程正在执行某个原子操作时,其他线程不能同时再执行该原子操作。

C语言里的volatile

作用:

  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
    //示例一
    #include <stdio.h>
    int main (void)
    {
    int i = 10;
    int a = i; //优化
    int b = i;

    printf ("i = %d\n", b);
    return 0;
    }


    //示例二
    #include <stdio.h>
    int main (void)
    {
    volatile int i = 10;
    int a = i; //未优化
    int b = i; //这里会继续从内存里读取i的值而不是用上面读过的缓存。

    printf ("i = %d\n", b);
    return 0;
    }
  2. 禁止指令重排(貌似只在Java里有这个功能)

    保证volatile所修饰的变量之前的代码不会在该变量之后执行,该变量之后的代码不会在该变量之前执行。

volatile不能保证原子性。仅使用 volatile 修饰 i,i++也不是线程安全的。

不知道C语言里的volatile是否和Java里的意义一样?

TAS(Test And Set)

TAS指令的语义是:向某个内存地址写入值1,并且返回这块内存地址存的原始值。TAS指令是原子的,这是由实现TAS指令的硬件保证的(这里的硬件可以是CPU,也可以是实现了TAS的其他硬件)。

伪代码:

阅读全文 »

源码版本:objc4-781

@synchronized介绍

@synchronized 所做的事情跟锁(lock)一样:它防止不同的线程同时执行同一段代码。但相比于使用 NSLock 创建锁对象、加锁和解锁来说,@synchronized 用着更方便,可读性更高。

@synchronized是对mutex递归锁的封装, @synchronized(obj)内部会根据传入的同步对象obj得到一把递归锁,然后进行加锁、解锁操作。

使用示例:

1
2
3
4
5
6
- (void)increment
{
@synchronized (self) {
[_elements addObject:element];
}
}

可以看到synchronized的使用是非常简单的。

关于synchronized有几个重要问题?

1.锁是如何与你传入 @synchronized 的对象关联上的?

2.@synchronized会保持(retain,增加引用计数)被锁住的对象么?

阅读全文 »

又是一个安静的晚上,我的MacBook Pro和往常一样外接着一个4k的显示屏看着电视,突然屏幕黑屏,摸了一下电脑、扩展坞都挺热的,然后赶紧拔掉外接显示器,按了几下开机键发现开不了机了。

等电脑凉下来后,尝试了网上说的各种组合键后,还是开不了机。顿感大事不妙,大概率是主板烧坏了。预约了周末去天才吧,到了之后给工作人员说明了下情况,工作人员说要去检查下才知道,大概20分钟后工作人员告诉我确实是主板烧坏了,我问还能不能修,他回复说因为是14年的旧款了已经没有配件修了。我又问如果到外面修大概要多少钱,他说他也不清楚,说如果太贵的话不建议我修,建议我买新的电脑。

想到电脑也用了差不多6年半了,也可以换一台新的了,但真要换还是有点肉疼的,看了一下价格基本上要1w左右。考虑了几分钟,决定还是先回去吧,换电脑的事往后稍稍。

哎,从4月份买了4k显示屏到现在一共用了差不多3个月,结果跟随我6年半的电脑没了。我想了下烧坏的原因大概有这么几点:

  1. 电脑确实有点旧了。就是不外接显示器,浏览器看视频多打开几个,电脑都有些烫手。外接显示器后属实勉强带动,散热跟不上。
  2. 电脑连接到外接显示器链路太长。电脑—转接头—扩展坞—显示器。因为买扩展坞的时候没注意接口的问题,导致中间还接了根转接头,链路太长各设备的稳定性,散热,兼容性问题隐患就会很大。
  3. 扩展坞不是苹果原装的,用的国产的绿联。怎么说呢有钱的话扩展坞之类的最好用原装的,国产的除了便宜其他真不好说,把电脑烧坏了是真伤不起。后来搜了一下,用绿联外接显示器烧坏主机的不是什么稀奇事,慎重。

总结:

  1. 电脑如果用了4,5年了最好不要再外接啥设备了,特别是电脑稍微多开几个应用就发热厉害的话最好别外接了。
  2. 外接设备时链路最好最短,最好是电脑—线—显示器就完了。
  3. 如果真要扩展坞啥的首选原装,次选国外大品牌比如moshi啥的。

后记:8月份换了新电脑,因为穷买的是低配版,接口一点都不丰富,就两个雷电口,于是买了个苹果原装的转接头,之前外接是扩展坞,电脑都烫手,现在除了转接头有点发热,电脑已经不发热了。现在外接显示器终于是4k@60hz了,纵享丝滑。

首先确保你登录的账号权限要能够修改证书。

导出p12文件

打开钥匙串访问,点“登陆—>我的证书”,选中刚安装的通用证书(不带development的是通用环境(开发,生产都可以用)),导出为p12,设置好密码,上传到个推网站上。也可以同时上传专门用于开发的p12。

更新profiles文件

更新推送证书后别忘了更新profile文件。选中侧边栏Profiles,选中对应profile,进入后点击edite,然后勾选select all,点击保存。

参考

iOS - 推送证书更新(Apple Development和极光)

iOS 推送证书加入到钥匙串后,无法导出P12文件

为什么要让外接显示器达到4K@60Hz的刷新率?

因为4K@30Hz的刷新率鼠标移动时会有明显的延迟感。刷新率越高画面才能越丝滑。4K的显示屏至少需要60Hz的刷新率,当然如果支持144Hz的那更好。对于一般用户60Hz基本足够。

外接显示器达到4K@60Hz刷新率的条件

主机支持(主要是主机的显卡),显示器支持,线材支持。该条件符合木桶原理只要其中一个不满足就无法达到4K@60Hz的刷新率。为了到达这个目标需要我们对主机的端口,显示器参数,线材等有一个基本的了解。下面一个个介绍。

主机支持

我的电脑是MacBook Pro 13寸 2014mid的。可以从苹果官网 macbook pro 2014mid 查到这款电脑的参数说明,如下端口部分是我们重点关注的:

可以看到HDMI端口只能输出60Hz的1080p。而4K的只能到30Hz或24Hz。所以我们就不能使用该端口作为视频输出。相应的我们就不能买HDMI(主机)—HDMI(显示器)线或HDMI—DP线材。不管这根HDMI线支持的版本是多少都不能买。(ps:这里线材使用”主机端口—显示器端口“表示,下同)

而对于Thunderbolt2端口支持原生MiniDP输出,但没有写明能否支持到4K@60Hz。搞得我一度以为通过MiniDP—DP能达到4K@60Hz,然而换了几次线后发现还是不行,最后又搜索了好久的资料基本可以确定这款机器不管通过什么端口都不能达到4K@60Hz。所以如果技术规格里没写明的话基本上是支持不了的。

让我们看一下2015年中的15寸的mbp的技术规格:

阅读全文 »

最近在折腾外接显示器,一直达不到4k@60hz。然后稀里糊涂安装了SwitchResX。SwitchResX是干什么的这里就不多说了,本文主要讲一下怎么卸载。

在系统偏好设置中点击移除就完事了,但事实并没有这么简单。虽然卸载了但是打开显示器会发现显示器的名称中还是带有SwitchResX而不是默认的:

image.png

这说明SwitchResX虽然卸载了但是还残留了一些文件。那么如何恢复显示器的默认名称呢?这个问题实在是有点冷门,花了我好几个小时才搜到解决办法,主要是不知道该怎么搜问题的关键字。搜个卸载出来的都是让你安装清理软件APP的。

最终解决办法如下:

  1. 确保已经退出SwitchResX,然后在系统偏好设置中点击移除删除APP包
  2. /Library/Displays/Contents/Resources/Overrides 里的文件全部删掉
  3. /Library/Colorsync/Profiles/Displays 删掉SwitchRes开头的
  4. 重启电脑

ps:连带的MonitorControl突然无法使用提示“未发现支持的显示器”的问题也解决了(一度以为是升级到Big Sur系统导致)。真正原因估计就是SwitchResX改了显示器的名称导致。

总结:
1.Mac中删除一个APP,系统只是删掉了这个应用,但是这个应用在使用的过程中产生的一些文件并不会被删除。
一个APP在使用过程中或多或少会产生一些文件比如缓存,但这些缓存一般对我们的使用不会产生影响顶多占用了一些磁盘空间,但有些APP会产生一些配置文件这就导致即使删除了这个APP,只要它的配置文件还在那么就会存在副作用。因此要想彻底卸载一个应用除了删掉这个应用还要把它使用过程中产生的文件也要删除掉才行。至于APP会在哪些目录产生哪些文件那就只能网上搜索了。
2.如果你想设置分辨率的话还不如用EasyRes,目前还没看到有啥问题。
3.英语好还是很有用的。在搜一些问题的时候直接上英文关键字比中文的好多了。用中文搜可能来来回回都是那几篇还是抄来抄去的。

参考
how reset my display profile it’s show “SwitchResX4 - Color LCD”
有一个人的回答:

1
2
3
4
5
6
It's not a problem. SwitchResX is doing that on purpose, and for absolutely no good reason.

The app doesn't do anything useful. Correctly and entirely delete SwitchResX according to the instructions
on their site and your issue should be solved. Though after its removal, you'll have to empty out the Displays
profile folder again so the OS can pull another new copy of the default display profile that won't be tagged
with their application name.
阅读全文 »