開發隨筆記錄

1.異步

昨天別人給了同事一個簡單的demo,問題是UITableViewController上有內有textfield的cell,在textfield被選中,彈出鍵盤,界面會自動滾動,就和咱們平時作textfield輸入時不要被鍵盤擋住那樣的滾動。對方是想要不要這個自動滾動,由於那個demo滾動的位置不對,查了一下,發現不知道何時起,UITableViewController自動適配了這個需求,完成不滾動的要求只有兩個作法:(1)把UITableViewController換成UIViewController;(2)重載viewWillAppear方法,但不要繼承[super viewWillAppear]async

 

2.oop

你們都知道字典類NSDictionary和NSMutableDictionary在寫入的時候,value對應的key只要實現copy協議就能夠。上次碰到要把一個字典的數據存到沙盒的plist表裏,發現寫入失敗。作了些對比後發現,由於字典類中,有一個key是number類型的,而plist表去查看source code,發現它的key都是同種類型,string的,因此寫入失敗。測試

 

3.動畫

在CoreText裏獲取文本被點擊位置的文本索引,通常用的是方法CTLineGetStringIndexForPosition,但屢次測試會發現,你點某一個字的前半部分,輸出是前一個字的索引,點擊後半部分才輸出正確,看了下官方文檔裏的解釋,大概意思是這個方法是將點擊位置轉換爲最近的字符插入處(其實就是光標),因此纔會形成這樣。修正的代碼應該是這樣的:spa

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.code

關於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。

相關文章
相關標籤/搜索