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。