开发随笔记录

1.

昨天别人给了同事一个简单的demo,问题是UITableViewController上有内有textfield的cell,在textfield被选中,弹出键盘,界面会自动滚动,就和我们平时做textfield输入时不要被键盘挡住那样的滚动。对方是想要不要这个自动滚动,因为那个demo滚动的位置不对,查了一下,发现不知道什么时候起,UITableViewController自动适配了这个需求,完成不滚动的要求只有两个做法:(1)把UITableViewController换成UIViewController;(2)重载viewWillAppear方法,但不要继承[super viewWillAppear]

 

2.

大家都知道字典类NSDictionary和NSMutableDictionary在写入的时候,value对应的key只要实现copy协议就可以。上次碰到要把一个字典的数据存到沙盒的plist表里,发现写入失败。做了些对比后发现,因为字典类中,有一个key是number类型的,而plist表去查看source code,发现它的key都是同种类型,string的,所以写入失败。

 

3.

在CoreText里获取文本被点击位置的文本索引,一般用的是方法CTLineGetStringIndexForPosition,但多次测试会发现,你点某一个字的前半部分,输出是前一个字的索引,点击后半部分才输出正确,看了下官方文档里的解释,大概意思是这个方法是将点击位置转换为最近的字符插入处(其实就是光标),所以才会造成这样。修正的代码应该是这样的:

CFIndex idx = CTLineGetStringIndexForPosition(line, relativePoint);
CGFloat glyphStart;
CTLineGetOffsetForStringIndex(line, idx, &glyphStart);
if (relativePoint.x < glyphStart && idx) {
   --idx;
}

 

4.

CALayer类有个方法,检测某个点是否是在它的区域中

/* Returns true if the bounds of the layer contains point 'p'. */
- (BOOL)containsPoint:(CGPoint)p;

方法上面是文档里的说明,用的是layer的bounds属性来比对点p是否在其中,我们都知道bounds属性的坐标是原点,所以使用这个方法,需要把点p先用以下方法转换到要检测是layer坐标系中,再进行检测:

- (CGPoint)convertPoint:(CGPoint)p fromLayer:(CALayer *)l;
- (CGPoint)convertPoint:(CGPoint)p toLayer:(CALayer *)l;

 

5.

关于CALayer,网上一直可以看到这样一段描述:

UIView的layer树形在系统内部,被系统维护着三份copy(这段理解有点吃不准)。
第一份,逻辑树,就是代码里可以操纵的,例如更改layer的属性等等就在这一份。
第二份,动画树,这是一个中间层,系统正在这一层上更改属性,进行各种渲染操作。
第三份,显示树,这棵树的内容是当前正被显示在屏幕上的内容。
这三棵树的逻辑结构都是一样的,区别只有各自的属性。

或者是这样一个很简单的解释:

UIView的layer树形在系统内部,被维护着三份copy。分别是逻辑树,这里是代码可以操纵的;动画树,是一个中间层,系统就在这一层上更改属性,进行各种渲染操作;显示树,其内容就是当前正被显示在屏幕上得内容。

具体是怎样的,没明白,自己找资料,发现有这样两个属性:

- (id)presentationLayer;
- (id)modelLayer;

第一个属性是显示树或者呈现树,第二个属性是模型树。叫法有不同,有叫逻辑树,动画树和显示树,也有叫呈现树,模型树和渲染树。对这两个方法获得的layer,修改其属性是无效的。

呈现树是我们在显示在屏幕上所看到的layer,所以如果下面的测试代码在viewDidLoad里输出这一层,会看到是空的,因为这时候还没有显示在屏幕上。

- (void)viewDidLoad {
    [super viewDidLoad];
    // Do any additional setup after loading the view from its nib.
    
    _colorLayer = [[CALayer alloc] init];
    _colorLayer.bounds = CGRectMake(0, 0, 10, 10);
    _colorLayer.position = CGPointMake(_centerView.bounds.size.width / 2, _centerView.bounds.size.height / 2);
    _colorLayer.backgroundColor = [UIColor blueColor].CGColor;
    
    [_centerView.layer addSublayer:_colorLayer];
    
    _timer = [NSTimer scheduledTimerWithTimeInterval:0.5 target:self selector:@selector(showLogOut) userInfo:nil repeats:YES];
    [_timer fire];
    
}

- (void)showLogOut {
    
    CALayer* layer = _colorLayer.presentationLayer;
    CALayer* layer1 = _colorLayer.modelLayer;
    NSLog(@"present layer %@", layer);
    NSLog(@"%f-------%f", layer.position.x, layer1.position.x);
}

- (IBAction)doAnimation:(id)sender {
    
    CGPoint point = CGPointMake(_centerView.bounds.size.width / 2, _centerView.bounds.size.height / 2);
    
    CABasicAnimation* animation = [CABasicAnimation animationWithKeyPath:@"position"];
    animation.toValue = [NSValue valueWithCGPoint:CGPointMake(point.x + 100, point.y)];
    animation.duration = 10;
    [_colorLayer addAnimation:animation forKey:nil];
    
}

上面的测试代码是做了一个向右平移100的动画,输出可以看出,presentationLayer的属性跟随动画的变化而变化,记录各个时期的layer的状态,但modelLayer却一直不变。动画结束后,就是layer平移到了指定位置后,会自动返回原始的位置,这就是modelLayer所记录初始状态的作用。

还有一点需要注意,上面的定时器输出还输出了 presentationLayer ,而 presentationLayer 每次都是不一样的。

上面一直没提到的渲染树(或者是动画树)是私有的,我们无法访问,渲染树对呈现树的数据进行渲染,为了不阻塞主线程,渲染树的行为都是在其他线程上进行的。

 

6.NSTimer注意事项

NSTimer会自动retain一次target和userinfo的参数,所以在NSTimer不用的时候,要先invalidate,再把NSTimer置nil,而这两步操作不能在dealloc,否则dealloc永远不会执行,因为无法释放。

NSTimer在滑动视图的时候,是停止执行的,因为runloop让给了UITrackingRunLoopMode,想要执行需要加上这个

[[NSRunLoop currentRunLoop] addTimer:_myTimer forMode:NSRunLoopCommonModes];

NSTimer在非主线程的线程是不执行的,除非加入下面的代码

[[NSRunLoop currentRunLoop] addTimer:_myTimer forMode:NSRunLoopCommonModes];
[[NSRunLoop currentRunLoop] run];

 

7.Block

我们都知道,block内部是不能修改外部变量的,如果要修改,需要加上__block。

上面的描述,其实不对,block是不能修改外部的局部变量,但是对于属性和成员变量,是可以修改的。这是为什么呢?

简单来说,普通的局部外部变量,是分配到栈上的,而__block所修饰的变量,会自动复制到堆上。

参考1:http://chun.tips/blog/2014/11/13/hei-mu-bei-hou-de-blockxiu-shi-fu/

参考2:https://www.zhihu.com/question/39980914

 

8.引用计数相关

retainCount 是引用计数,指的是对某一块内存地址的使用情况,如果为0,代表没有使用了,可以释放。

但是比如说 

MyClass* a = [[MyClass alloc] init];

a 是一个指针对象,即 a 这个内存地址里的内容,是存储另一片内存地址,那么 retainCount 是指那一片内存地址的使用情况?

MyClass* aObj = [[MyClass alloc] init];

MyClass* bObj = [aObj copy];

MyClass* cObj = [aObj retain];

NSLog(@"count %d  %d %d", aObj.retainCount, bObj.retainCount, cObj.retainCount);
NSLog(@"%p  %p  %p", aObj, bObj, cObj);
NSLog(@"%p  %p  %p", &aObj, &bObj, &cObj);

跑完上面的测试代码可以得到结果, &aObj, &bObj, &cObj 三者是对应三个对象的内存地址,各不相同,但 aObj, bObj, cObj 是三者的内容,a 和 c 相同, 和 b 不同,所以 retainCount 是指内容的地址。

还有一个情况

NSString* aStr = @"1";
NSString* bStr = [aStr retain];
NSString* cStr = [aStr copy];
NSString* dStr = [aStr mutableCopy];
    
NSLog(@"%p  %p  %p  %p", aStr, bStr, cStr, dStr);
    
NSLog(@"%d  %d  %d  %d", aStr.retainCount, bStr.retainCount, cStr.retainCount, dStr.retainCount);

输出的结果是,a, b, c 都是指向同一片内存,而 d 不同,引用计数上,a, b, c 都是 -1,d 是 1 。

 

9.tableViewHeaderView的高度(tableFooterView同理)

用纯代码设置tableViewHeaderView的话,是没有啥问题的,但是从xib中加载一个view,再指定这个view作为tableHeaderView的话(代码如下),高度展示上就会不合要求。

MYHeaderView* headerView = [[[NSBundle mainBundle] loadNibNamed:@"MYHeaderView" owner:self options:nil] lastObject];
CGRect frame = headerView.frame;
frame.size.height = 100;
headerView.frame = frame;
tableView.tableHeaderView = headerView;

结果是会挡住开头的cell。原因未知,解决的办法有这两种:

1.在外面代码生成一个view做为容器,把从xib加载的加到这个容器中,把容器做为tableHeaderView

UIView* headerContentView = [[UIView alloc] initWithFrame:CGRectMake(0, 0, [UIScreen mainScreen].bounds.size.height, 100)];

MYHeaderView* headerView = [[[NSBundle mainBundle] loadNibNamed:@"MYHeaderView" owner:self options:nil] lastObject];

[headerContentView addSubview:headerView];

tableView.tableHeaderView = headerContentView;

2.在MYHeaderView(xib对应的类)中,重写layoutSubviews方法,指定这个tableHeaderView的宽高

- (void)layoutSubviews
{
    [super layoutSubviews];
    
    self.bounds = CGRectMake(0, 0, [UIScreen mainScreen].bounds.size.height, 100);
}
 
附:这个要配合第一段代码设置好从xib加载出来的view的高度

 

10.textField的KVO监听输入

本来想用KVO来监听UITextField的键盘输入

[textField addObserver:self forKeyPath:@"text" options:NSKeyValueObservingOptionNew | NSKeyValueObservingOptionOld context:nil];

- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSString *,id> *)change context:(void *)context
{
    NSLog(@"change");
}

却发现上面的观察,在键盘输入中是不会触发的,除非手动设置textField.text,一时不解是为什么,查了资料,最终得到个提示,用runtime遍历出UITextField的所有成员变量

unsigned int outCount;
Ivar *IvarArray = class_copyIvarList([UITextField class], &outCount);//获取到UITextField中的所有成员变量
for (unsigned int i = 0; i < outCount; i ++) {
    Ivar *ivar = &IvarArray[i];
    NSLog(@"第%d个成员变量:%s,类型是:%s",i,ivar_getName(*ivar),ivar_getTypeEncoding(*ivar));// 依次获取每个成员变量并且打印成员变量名字和类型
}

发现其中没有text对应的成员变量,但是有几个label

第30个成员变量:_displayLabel,类型是:@"UITextFieldLabel"
第31个成员变量:_placeholderLabel,类型是:@"UITextFieldLabel"
第32个成员变量:_dictationLabel,类型是:@"UITextFieldLabel"
第33个成员变量:_suffixLabel,类型是:@"UITextFieldLabel"
第34个成员变量:_prefixLabel,类型是:@"UITextFieldLabel"
第36个成员变量:_label,类型是:@"UILabel"

如果设置过textField的text或者placeholder,用object_getIvar根据成员变量进行输出,会看到_displayLabel_placeholderLabel会有对应的值,但是如果从键盘输入的话,是不会有对应的值的。因此,在不确定被观察者内部构造的情况下,不适宜使用KVO。

 

11.新线程中更新UI

一般我们如果有某些耗时的操作,比如图片合成之类的,我们会把这个操作新开一个线程去做,完成后异步调用主线程去赋值。但如果在不调用主线程去赋值,直接在新线程中赋值,是得不到更新的。这是因为在新线程中,无法读取到当前的图像上下文的原因。

- (void)drawRect:(CGRect)rect {
    dispatch_queue_t colorQueue = dispatch_queue_create("testMyNewColor", DISPATCH_QUEUE_CONCURRENT);
    dispatch_async(colorQueue, ^{
        CGContextRef context = UIGraphicsGetCurrentContext();
        CGContextSetFillColorWithColor(context, [UIColor blueColor].CGColor);
        CGContextFillRect(context, self.bounds);
    });
}

上面这段测试代码,在运行后,控制台就会报出一些内容,告诉你invalid context,因为在这里的context是空的,去掉外层的新线程就没问题。

ps:当然,对于UI的某些更新是可以的,比如更新Button的title这样的操作,但是这似乎没太多意义。

 

12.hiddenWhenPush

之前只知道这个属性,设置后,在push的时候,会隐藏tabbar,之后push的vc都是隐藏tabbar的。今天调试一个bug发现,由于我把设置hiddenWhenPush = YES的那个vc从viewControllers的堆栈中移除掉,导致后面再push的vc都显示了tabbar。

也就是说每次push出下一个vc的时候,都会去遍历堆栈中,看是否hiddenWhenPush = YES来决定是否隐藏tabbar。

转载于:https://my.oschina.net/u/574245/blog/520322

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值