[BS] 小知识点总结-04

时间:2023-03-09 15:56:39
[BS] 小知识点总结-04

1. ios新知识学习思路:

  在开发过程中如果遇到某种新需求以前从未做过,例如改变textField的Placeholder颜色,有如下思路和途径:

  1.1 在Storyboard/Xib辅助编辑器Attributes inspector查找是否有相关设置。

  1.2 查看UITextField.h头文件,查找是否有相关属性、方法、遵从的协议、代理的方法。

  1.3 去父类UIControl.h中,查找是否有相关属性、方法、遵从的协议、代理的方法。

  1.4 使用Baidu/Google搜索,论坛Stack Overflow等,或者去开发QQ/微信群求助;

  1.5 使用runtime或者打断点Breakpoint查看UItextField类中所有的实例变量,通过KVC设值。

  1.6 使用runtime或者Breakpoint找到相关实例变量,自定义继承UITextField类,重新设置属性值。

2.

凡是不可变类的对象(如NSString,富文本NSAttibuteString,NSArray,NSDictionary等),在创建时就必须存入内容,之后不可变对象存在于堆内存中,再也不能对其进行修改了。非要修改,只能将原来的销毁,然后重新创建一个新的不可变对象。

3.

NSMutableAttributedString(富文本: 颜色/大小/样式丰富多彩)用法 

NSMutableAttributedString *attrString = [[NSMutableAttributedString alloc]initWithString:@"这是一串五颜六色的文字" attributes:@{NSForegroundColorAttributeName:[UIColor whiteColor]}];

self.phoneTextField.attributedPlaceholder = attrString;

哪些常见的控件可以设置富文本属性呢?

A. textField.attributedPlaceholder = attrString;

B. 所有的UILabel*lbl; 都可以设置富文本lbl.attributedText = attrString;

C. 字符串可以通过绘图方法 [str drawInRect: withAttributes:] 和 str drawAtPoint: withAttributes:

4.

对于苹果封装好的标准UI控件,如果想要调整其内部子控件的布局,需要新建子类,然后在子类.m中重写layoutSubviews方法。

5.

苹果封装好的类,只要在其头文件中暴露出来的属性/方法,都可以新建子类对其进行自定义。对于未暴露出来的内部私有属性,可以通过Runtime/或运行时打断点获取,然后通过自定义子类来修改。

6.

ios开发中常用的纯C语言库有:Core Foundation/GCD/Runtime, 都是纯C语言,ARC只对OC有效,所以使用C语言,必须自己管理内存。

C语言中malloc, calloc, realloc, new, copy出来的指针指向的内存空间,使用完均需使用free(p)进行释放,并让p=Null,防止野指针

运行时(Runtime): 是苹果官方的一套纯C语言库。可以做很多底层的操作,如查看类的所有成员变量(包含私有的),属性,方法,协议等。

- (void)viewDidLoad {
[super viewDidLoad]; unsigned int count1 = ;
Ivar *p; //定义Ivar类型的指针p,指向装着实例变量的数组
p = class_copyIvarList([UITextField class], &count1); //返回的是一个Ivar类型的数组的指针(首地址),就像 Ivan a[count1];中存储的都是Ivar类型,然后让指针p = a; for (int i = ; i < count1; i++) {
//p指向Ivar类型的数组,使用Ivar类型变量取出
Ivar var = *(p+i);
//Ivar类的getName方法, C语言字符串使用%s
NSLog(@"%s", ivar_getName(var));
} //C语言自己管理内存copy
free(p);
p = NULL; //防止也指针 }

7.

在类的内部,通过self调用的方法只能是类方法,不能调用内部的对象方法。因为在类方法中self代表着本类。self在对象方法中代表当前类的对象。

8.

如下方法可以获取info.plist中的key-value:

NSString *currentVersion = [NSBundle mainBundle].infoDictionary[@"CFBundleShortVersionString"]; //本次打开应用的版本

应用启动后一般会有多个window,他们存在单例[UIApplication sharedApplication].windows数组中,通过如下方法可获取keyWindow

  UIWindow *window = [UIApplication sharedApplication].keyWindow;

9.

  数组/字典的addObject:方法的参数都不能为nil,因为数组/字典是以nil作为结尾的。[UIImage imageNamed:str];的str也不能为nil,否则直接崩溃。

  一劳永逸的解决方案:可以用runtime交换方法(Method Swizzle “ios黑魔法”)实现的思路,对以上方法进行拦截,然后对传入的对象做判断,如果为nil,就不添加。

10.

  NSString、NSArray、NSDictionary、NSData可以将数据直接写入本地文件中(文件格式随意写,只是用来告诉系统用什么软件打开)

//发送请求
[[AFHTTPSessionManager manager] GET:@"http://www.badung.com.php" parameters:params progress:nil success:^(NSURLSessionDataTask * _Nonnull task, id _Nullable responseObject) {
//先将id改为NSDictionary*类型, 将服务器返回数据写入本地plist文件
[(NSDictionary *)responseObject writeToFile:@"/Users/wz/Desktop/Words.plist" atomically:YES]; } failure:^(NSURLSessionDataTask * _Nullable task, NSError * _Nonnull error) { }];

  NSData *data = [[NSData alloc]init];

[data writeToFile:@"/Users/wz/Desktop/data.arc" atomically:YES];

NSString *string = [[NSString alloc]init];

[string writeToFile:@"/Users/wz/Desktop/string.text" atomically:YES encoding:NSUTF8StringEncoding error:nil];

NSArray *array = @[@"123",@"456",@"789"];

[array writeToFile:@"/Users/wz/Desktop/array.text" atomically:YES];

11.

写第三方框架的人为什么很多方法需要带个前缀?如sd_setImageWithURL: , mj_footer等。

答案: sd_setImageWithURL:方法是定义在#import “UIImageView+WebCache.h”中,一般给系统提供的框架类建分类时,可能会重写系统类原有的方法,因为分类的优先级高于原类,这样就会将原来的方法覆盖掉,如有想用原来方法时就没法调用了。为了区别分类和原类中方法,所以在给系统类增加分类时,分类中方法一般会带前缀。

补11. 减少不稳定的第三方框架的风险:

//此处做法是为了减少不稳定的第三方框架的风险。通过自定义类继承自第三方框架,我们应用内部全部导入自定义类的头文件,并使用我们重写过的方法,这样如果哪天不想用DA这个框架,想换成M13框架,只需要在自定义这个类中将继承的父类换为M13即可。我们App内部的所有头文件和方法名都不会报错,也不用去修改。

@interface WZTopicPictureProgressView : DALabeledCircularProgressView

@end

12.

a&b和a&&b运算结果相同,但a&&b效率更高。一旦发现a的值为false,就停止运算,直接得出结果为false。只有当a为true,才会计算b的值。

a|b和a||b运算结果相同,但a||b效率更高。一旦发现a的值为true,就停止运算,直接得出结果为true。只有当a为false,才会计算b的值。

[BS] 小知识点总结-04

13. UTC 和 GMT的区别是什么啊?

GMT:格林尼治标准时间Greenwich Mean Time(也称universal time),是指位于伦敦郊区的皇家格林尼治天文台的标准时间,因为在通过那里的经线被定义本初子午线。地球每天的自转是有些不规则的,而且正在缓慢减速。所以,格林尼治时间已经不再被作为标准时间使用。

UTC:协调世界时Coordinated Universal Time由原子钟提供,是现在全世界的标准时间。这套时间系统被应用于许多互联网和万维网的标准中,例如,网络时间协议就是协调世界时在互联网中使用的一种方式。*、中国香港、中国澳门、中国*、蒙古国、新加坡、马来西亚、菲律宾、西澳大利亚州的时间与UTC的时差均为+8,也就是UTC+8。

使用Xcode打印【NSDate date】时间与实际相差8个小时解决方案:

- (void)printDate: (NSDate *)date {

NSTimeZone *zone = [NSTimeZone systemTimeZone];

NSInteger interval = [zone secondsFromGMTForDate: date];

NSDate *localDate = [date  dateByAddingTimeInterval: interval];

NSLog(@"\n     date:%@\nlocalDate:%@",date,localDate);

}

14.

- (NSDateComponents *)components:(NSCalendarUnit)unitFlags
                        fromDate:(NSDate *)startingDate   
                          toDate:(NSDate *)resultDate
                         options:(NSCalendarOptions)options

The resulting component values may be negative if resultDate(小) is before startDate(大). //如果resultDate小,startDate大,就会出现负值。

可见以上方法,从fromDate 到 toDate,是用toDate减去fromDate. 

15.

不直接在tableView代理方法heightForRowAtIndexPath中计算cell高度的原因:

1. heightForRowAtIndexPath方法和cellForRowAtIndexPath方法的调用频率一样,当一个Row滚出屏幕再次滚回屏幕,就需要调用这两个方法。将计算cell高度的代码放在这里,同一Row每次出现时都要计算一次需要的cell高度。这样比较浪费。可以在模型中新增rowHeight属性(模型本来就代表需要展示的Row),在cellHeight属性的get方法通过懒加载将计算出来的Row需要Cell高度存放在模型的_cellHeight实例变量中。这样可以保证每个Row(模型)只计算一次cell高度。

2. Cell的高度虽然是tableView的属性,但是真正的高度是由model中的文字/图片等内容的数量/字体等决定的,当模型中加载完数据,其实就可以将该模型(Row)展示时需要的cell的高度计算好,并存储在模型(Row)的cellHeight属性中。需要展示该模型时,直接通过model.cellHeight取出所需cell的高度即可。

eg1. 在代理方法heightForRowAtIndexPath中计算cell高度

#pragma mark - Table view delegate
- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath { //获取模型,根据模型文字来计算Label的高度
WZTopic *topic = self.topics[indexPath.row];
//计算模型文字块高度
//[topic.text sizeWithAttributes:@{NSFontAttributeName:[UIFont systemFontOfSize:14]}].height; //只能计算一横行文字的长度
//[topic.text sizeWithFont:[UIFont systemFontOfSize:14] constrainedToSize:CGSizeMake([UIScreen mainScreen].bounds.size.width, MAXFLOAT)].height;//提示ios7已弃用,使用boundingRectWithSize方法
CGFloat textH = [topic.text boundingRectWithSize:CGSizeMake([UIScreen mainScreen].bounds.size.width - 4 *WZTopicCellMargin, MAXFLOAT) options:NSStringDrawingUsesLineFragmentOrigin attributes:@{NSFontAttributeName:[UIFont systemFontOfSize:14]} context:nil].size.height; //计算并返回cell(只有文字时)高度
//CGFloat cellH = WZTopicCellTextY + textH +WZTopicCellBottomBarH;//此处算好的cellH,会被WZTopicCell.m中的setFrame拦截,并将高度减去一个margin,故此处需故意多加个margin抵消减去,才能让cell中的内容都完全显示出来,否则text将有一个margin的高度显示在底部工具条上。
//CGFloat cellH = WZTopicCellTextY + textH +WZTopicCellBottomBarH + WZTopicCellMargin; //刚刚好,紧贴底部工具条最上边
CGFloat cellH = WZTopicCellTextY + textH +WZTopicCellBottomBarH + WZTopicCellMargin + WZTopicCellMargin; //文字下边缘与底部工具条最上边留一个margin return cellH;
}

eg2.在模型(需要展示的Row)的cellHeight属性中计算cell高度

//WZModel.h文件
@property (assign,nonatomic) CGFloat cellHeight; //WZModel.m文件
- (CGFloat)cellHeight { if (_cellHeight == ) { //模型第一次展示时==0,需要计算,否则直接返回 //计算模型文字块高度
CGFloat textH = [self.text boundingRectWithSize:CGSizeMake([UIScreen mainScreen].bounds.size.width - *WZTopicCellMargin, MAXFLOAT) options:NSStringDrawingUsesLineFragmentOrigin attributes:@{NSFontAttributeName:[UIFont systemFontOfSize:]} context:nil].size.height; //计算cell(只有文字时)高度
_cellHeight = WZTopicCellTextY + textH +WZTopicCellBottomBarH + WZTopicCellMargin + WZTopicCellMargin;
} return _cellHeight; //第二次/第三次展示,直接返回cell高度
} #pragma mark - Table view delegate
- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath { //获取模型
WZTopic *topic = self.topics[indexPath.row]; //返回模型中算好的cell高度
return topic.cellHeight; }

16.

  (1). @property name; 声明的属性会自动生成_name实例变量和get/set方法的声明和实现部分。但是如果声明了@property name; 由同时重写了_name的get和set方法,则@property不帮忙生成_name了。如果只重写get/set其中的一个方法,@property仍然帮忙生成_name;

  (2). 如果声明了@property(readonly)name; 编译器会自动帮忙生成_name和get方法。但此时如果重写get方法,@property不帮忙生成_name;

17.

利用MJExtension替换模型中的服务器返回的不合适的property(如id,description等)的方法

#import <MJExtension.h>

@implementation WZTopic {

//替换模型中的原有key(给NSObject增加的非正式协议)

+ (NSDictionary *)mj_replacedKeyFromPropertyName {

return @{ @"ID":@"id" };

}

//可以批量替换所有的property(给NSObject增加的非正式协议)

+ (NSString *)mj_replacedKeyFromPropertyName121:(NSString *)propertyName {

if ([propertyName isEqualToString: @"id"])  return @"ID";

return propertyName;// 其他情况直接返回

}

18.  在不知道图片扩展名的情况下,如何知道图片的真实类型?

  (1) 根据图片扩展名判断(不一定准确)。BOOL isGif = [imagePath.pathExtension.lowercaseString isEqualToString:@"gif"];

(2) 最准确的做法:取出图片二进制数据的第一个字节,进行判断。(图片的类型存储在二进制数据的第一个字节中)。 图片类型判断可参考SDWebImage中的"NSData+ImageContentType.h"分类

#import "NSData+ImageContentType.h"

@implementation NSData (ImageContentType)

+ (NSString *)sd_contentTypeForImageData:(NSData *)data {
uint8_t c;
[data getBytes:&c length:];
switch (c) {
case 0xFF:
return @"image/jpeg";
case 0x89:
return @"image/png";
case 0x47:
return @"image/gif";
case 0x49:
case 0x4D:
return @"image/tiff";
case 0x52:
// R as RIFF for WEBP
if ([data length] < ) {
return nil;
}
NSString *testString = [[NSString alloc] initWithData:[data subdataWithRange:NSMakeRange(, )] encoding:NSASCIIStringEncoding];
if ([testString hasPrefix:@"RIFF"] && [testString hasSuffix:@"WEBP"]) {
return @"image/webp";
}
return nil;
}
return nil;
}
@end @implementation NSData (ImageContentTypeDeprecated)
+ (NSString *)contentTypeForImageData:(NSData *)data {
return [self sd_contentTypeForImageData:data];
}
@end

19.

iOS中通过核心动画和UIView都可以做动画,但是二者有什么区别呢?

[BS] 小知识点总结-04

20.

//1.字符串创建

NSString *str = [NSString stringWith...];

NSString *str = [[NSString alloc] initWith...];

//2.字符串常用的处理方法

NSString *str = @"5445546564";

NSString *newStr = [str stringBy....];

//3.String和subString

NSString *str = @"5445546564";

NSString *newStr = [str substring...];

//4.字符串替换

NSString *str = @"h-e-l-l-o, w-o-r-l-d";

NSString *newStr = [str stringByReplacingOccurrencesOfString:@"-" withString:@""];

NSLog(@"str:%@,newStr:%@",str,newStr);

打印结果:str:h-e-l-l-o, w-o-r-l-d,newStr:hello, world

21.

利用UIGestureRecognizer,能轻松识别用户在某个view上面做的一些常见手势。UIGestureRecognizer是一个抽象的基类,定义了所有手势的基本行为,不能直接使用。需使用UIGestureRecognizer的子类才能处理具体的手势,具体如下:

UITapGestureRecognizer //(敲击)  //较常用

UIPinchGestureRecognizer //(捏合,用于缩放)

UIPanGestureRecognizer //(拖拽)

UISwipeGestureRecognizer //(轻扫)

UIRotationGestureRecognizer //(旋转)

UILongPressGestureRecognizer //(长按)

注意:UIView,UIImgeView默认是不接收手势事件的,在添加手势前,需设置view.userInteraction = YES; UIButton该属性默认是YES,不要设置。

22.

在自定义的UIView中,如果想push/present modally视图控制器,因为self是UIView类不能弹出控制器(只有ViewController才能弹出控制器),通常做法是通过UIApplication单例获取根视图控制器,用它在自定义View中弹出视图控制器。

//通过单例拿到根控制器来显示vc

[[UIApplication sharedApplication].keyWindow.rootViewController presentViewController:vc animated:YES completion:nil];

23. 自定义view的xib不要和控制器重名

[BS] 小知识点总结-04

24.

注意:以后凡是看到系统自带的类xxxItem,肯定是苹果封装好的Model模型,它并不是UI控件,而是继承自NSObject的模型。 例如:navigationItem是用来给navigationBar提供显示数据的模型,UIBarButtonItem是用来给UINavigationItem提供数据的模型,他们是继承自NSObject的模型,并不是UI控件。(猜想:苹果在UIViewController.h中增加navigationItem模型属性,在vc.navigationItem.title方法的内部实现,肯定是调用了vc.navigationController.navigationBar.titleLabel.text来设置的)。