iOS 开发总结(一直更新)

96
alenpaulkevin
2018.04.18 11:08* 字数 2181

1、我能从我的程序员生涯中学到什么?

答:别人一想到程序员,脑海中浮想的除了工资高,应该是思维严谨,逻辑能力强,是的,写程序能让人培养成思维严谨的习惯;但如果我写的程序的问题没有越来越少,那显然是非常失败的,即使你能实现很多炫酷的功能,但只要多出现几个BUG,别人也会认为你是不个不折不扣的菜逼,以前有点不服气,但现在我自己也非常认同,写程序一定要尽量考虑周全,培养自己严谨的思维习惯,培养跨领域的能力才是最重要的

2、我可能从我的程序员生涯中得到什么坏处?

答: 颈椎病,一敲代码就入神,连续几个小时都不离开座位; 缺乏运动和久坐带来的一些综合症状; 不是要自己有多在意这些,但如果一点都不在意,一定会付出代价的, 平时有空就多去运动一下,每天认真做一遍颈椎操,保护好自己的颈椎,腰椎也要留心一下;


3、对于项目中的第三库一定要进行再次封装,包括网络、刷新、提示、模型转换等所有能封装的部分,一直用MJExtension来做字典转模型,突然想用YYModel了,项目中替换很麻烦,刷新也一样,一直用第三方框架,突然想自己写,改起来麻烦的不要不要的;


4、熟悉一下测试的几种方案,例如交叉测试(一个功能正在运行,另一个功能运行对它的影响,例如 扫码时打开灯光,突然退到后台,再回来查看,可能灯光已经熄灭,按钮还没改变状态)等,这样写程序时才知道往哪些方面考虑


5、多写点代码块,写起代码来会很快,我把自定义Cell都封装了代码块,用xib布局cell就没有纯手写这么快了,改起来还麻烦,这就是代码块的好处;


6、主控制器因为代码比较多,结构一定要清晰,才方便寻找,插入的类按 服务工具类+MVC 划分,属性按修饰符划分,下面代码按功能划分,如下
#import "ViewController.h"

// 工具和服务类
#import "Header.h"
#import "Tool1.h"
#import "Tool2.h"

// Model
#import "Model1.h"
#import "Model2.h"
#import "Model3.h"

// View
#import "View1.h"
#import "View2.h"
#import "View3.h"

// Controller
#import "ViewController1.h"
#import "ViewController2.h"
#import "ViewController3.h"

@interface ViewController ()

// 按修饰符划分
@property (nonatomic, assign) NSInteger num1;
@property (nonatomic, assign) NSInteger num2;

@property (nonatomic, strong, nonnull) NSMutableArray<NSString *> *object3;
@property (nonatomic, strong, nonnull) NSMutableArray<NSNumber *> *object4;

@property (nonatomic, copy, nullable) NSString *object5;
@property (nonatomic, copy, nullable) NSString *object6;

@property (nonatomic, weak) UILabel *object1;
@property (nonatomic, weak) UILabel *object2;

@end

@implementation ViewController

- (void)viewDidLoad {
    [super viewDidLoad];
    // Do any additional setup after loading the view, typically from a nib.
}

#pragma mark - 初始化View
- (void)initView
{
    
}

#pragma mark - 系统方法,界面显示到销毁

#pragma mark - 代理

#pragma mark - 按钮点击,通知,定时器

#pragma mark - 辅助方法

#pragma mark - 懒加载


@end


7、定时器不是马上开始的,多久触发一次事件,多久才开始,记得在退出页面的时候释放定时器,否则控制器不会释放;
8、如果错误提示中出现了duplicate这样的字眼,很可能就是引入了.m文件
9、UIView的tag不能为0;
10、字典转JSON字符串;
NSData *data = [NSJSONSerialization dataWithJSONObject:params 
options:NSJSONWritingPrettyPrinted error:nil];
NSString *paramStr = [[NSString alloc] initWithData:data 
encoding:NSUTF8StringEncoding];


11、有时在动画过程中,需要避免用户重复操作,否则很容易崩溃,设置[[UIApplication sharedApplication] beginIgnoringInteractionEvents]; 允许用户操作[[UIApplication sharedApplication] endIgnoringInteractionEvents]


12、如果延时执行事件会被多次触发,那是一件很危险的事情,需要取消前面的延时执行事件
[self performSelector:<#(nonnull SEL)#> 
withObject:<#(nullable id)#> afterDelay:<#(NSTimeInterval)#>]; // 延时执行

[NSObject cancelPreviousPerformRequestsWithTarget:<#(nonnull id)#> 
selector:<#(nonnull SEL)#> object:<#(nullable id)#>]  // 取消延时执行


13、测试某部分代码的运行时间,
NSTimeInterval beginTime = CFAbsoluteTimeGetCurrent();
...... // 执行代码
NSTimeInterval endTime = CFAbsoluteTimeGetCurrent();
time = endTime - beginTime; // 运行时间


14、对某个控件截图
UIGraphicsBeginImageContextWithOptions(view.bounds.size, NO, 0);
[view.layer renderInContext:UIGraphicsGetCurrentContext()];
UIImage *viewImage = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();


15、添加毛玻璃效果
UIBlurEffect *effect = [UIBlurEffect effectWithStyle:UIBlurEffectStyleLight];
UIVisualEffectView *effectView = [[UIVisualEffectView alloc] initWithEffect:effect];
effectView.contentView = 控件;


16、添加长按手势,在手势开始时才执行方法,避免方法被调用两次
- (void)longPress:(UILongPressGestureRecognizer *)longPressGesture
{
     if (UIGestureRecognizerStateBegan != longPressGesture.state) {
         return;
     }

     ... // 执行方法
}


17、输入框有值时才能点击return key;
textField.enablesReturnKeyAutomatically = YES;


18、isKindOfClass 判断对象是否是一个类的成员,或者是派生自该类的成员 isMemberOfClass 确定对象是否是当前类的成员;


19、tableView设置cell的分割线从屏幕左侧边缘开始
** cell.separatorInset = UIEdgeInsetsMake(0, 0, 0, 0);


20、tableView当内容不够时,去掉底部的分割线
self.tableView.tableFooterView = [[UIView alloc] init];


21、UITableView设置为Plain的样式时,你又有多组时,组头就会默认有悬浮效果,停留在上边,如果不想组头悬浮在上边,可以将样式设为Grouped,把足部设置很小,解决这问题;
UITableView *tableView = [[UITableView alloc] initWithFrame:CGRectMake(x, y, w, h)
style:UITableViewStyleGrouped];
tableView.sectionFooterHeight = 0.0001;


22、设置UITableViewCell之间的间距,在自定义cell中重写setFrame方法
- (void)setFrame:(CGRect)frame
{
    frame.origin.y += 5;
    frame.size.height -= 5;

    [super setFrame:frame];
}


23、修改UISearchController上searchBar的风格,遍历子控件(很有用),找到合适的, 设置想要的子控件的颜色和分格;
UIImageView *barImageView = self.searchController.searchBar.subviews[0].subviews[0];
barImageView.layer.borderColor = [UIColor lightGrayColor];
barImageView.layer.borderWidth = 1;

UIView *textView = self.searchController.searchBar.subviews[0].subviews[1];
textView.backgroundColor = [UIColor whiteColor];


24、监测WKWebView的加载进度
[wkWebView addObserver:self forKeyPath:@"estimatedProgress" 
options:NSKeyValueObservingOptionNew context:nil];


25、一个控件获取在某个控件上的坐标点的四种方式
// 获取View在window上的坐标点的四种写法
 // 使用 convertRect:toRect 方法
CGRect rect = [view  convertRect:view.bounds toRect:window]; 
CGRect rect = [view.superView  convertRect:view.frame toRect:window];

 // 使用 convertRect:from 方法
CGRect rect = [window  convertRect:view.bounds from:view]; 
CGRect rect = [window  convertRect:view.frame from:view.superView];


26、iOS11获取最上面的window
// iOS 11 以前
UIView *windowView = [[UIApplication sharedApplication].windows lastObject]; 
// iOS 11 以后
UIView *windowView = [[UIApplication sharedApplication].windows firstObject]; 


27、某个控件有双击和单击时,设置双击失败时,才触发单击
[oneTap requireGestureRecognizerToFail:doubleTap];


28、设置图片捏合缩放,双击放大
// 首先把imageView添加到scrollview中

- (UIView *)viewForZoomingInScrollView:(UIScrollView *)scrollView
{
    return self.imageView;  // 在代理返回当前imageView现
}

- (void)scrollViewDidZoom:(UIScrollView *)scrollView
{
    // 保证绕着中心点缩放,
    CGSize boundsSize = self.scrollView.bounds.size;
    CGRect frameToCenter = self.imageView.frame;
    if (frameToCenter.size.width < boundsSize.width) {
       frameToCenter.origin.x = floorf((boundsSize.width -
           frameToCenter.size.width) * 0.5f);
    } else {
       frameToCenter.origin.x = 0;
    }
    if (frameToCenter.size.height < boundsSize.height) {
       frameToCenter.origin.y = floorf((boundsSize.height - frameToCenter.size.height) * 0.5f);
    } else {
       frameToCenter.origin.y = 0;
    }
    if (!CGRectEqualToRect(self.imageView.frame, frameToCenter)) {
       self.imageView.frame = frameToCenter;
    }
}

-(void)doubleTap:(UITapGestureRecognizer *)tapBgRecognizer
{
    CGFloat scale = 4; // 最大缩放比例
    if (self.self.imageView.frame.frame.size.width < kScreenWidth * scale) {
        CGPoint point = [tapBgRecognizer locationInView:self.imageView.frame];
        CGFloat xSize = kScreenWidth / scale;
        CGFloat ySize = kScreenHeight / scale;
        CGRect zoomRect = CGRectMake(point.x - xSize * 0.5f,
           point.y - ySize * 0.5f, xSize, ySize);
        self.scrollView.maximumZoomScale = scale;
           // 以点击点为中心缩放到最大
        [self.scrollView zoomToRect:zoomRect animated:YES];
    } else {
        [UIView animateWithDuration:0.25 animations:^{
        self.scrollView.zoomScale = 1.0;
        self.scrollView.contentSize = self.scrollView.bounds.size;
        self.self.imageView.frame.frame = self.originFrame;
        }];
    }
}


29、寻找当前控件的控制器
- (UIViewController *)ht_viewController{
    for (UIView* next = self; next; next = next.superview) {
        UIResponder* nextResponder = [next nextResponder];
        if ([nextResponder isKindOfClass:[UIViewController class]]) {
            return (UIViewController*)nextResponder;
        }
    }
    return nil;
}


30、UITextField的inputView属性是指第一响应的不是键盘,而是赋值给inputView的那个view, inputAccessoryView 是指往键盘上添加另一个view;


31、设置键盘在UIScrollView拖动动时消失
scrollview.keyboardDismissMode = UIScrollViewKeyboardDismissModeOnDrag


32、设置UITableViewCell之间的分割线颜色;
self.tableView.separatorColor = [UIColor redColor];


33、iOS9以后点击状态栏,UIScrollView可返回顶部;


34、显示状态栏的网络请求菊花;
 // 显示菊花, NO为关闭菊花
[UIApplication sharedApplication].networkActivityIndicatorVisible = YES;


35、让UILabel的字体适应指定的宽度,当宽度大时,字体不变, 宽度小时, 字体变小适应宽度
label.adjustsFontSizeToFitWidth = YES;


36、监听拖动进度条时的状态
// 监听进度条的拖动
[slider addTarget:self action:@selector(sliderChange:event:) 
forControlEvents:UIControlEventValueChanged];

- (void)sliderChange:(UISlider *)slider event:(UIEvent *)event
{
    UITouch *touch = [[event allTouches] anyObject];
    // 根据状态来做相应的事情,避免拖动时一直调用某些事件
    switch (touch.phase) {
    case UITouchPhaseBegan:    // 开始
    case UITouchPhaseMoved:    // 拖动
    case UITouchPhaseEnded:    // 结束
    default:
        break;
    }
}


37、让UITableView的某一行滚到底部;
 [self.tableView scrollToRowAtIndexPath:indexPath 
    atScrollPosition:UITableViewScrollPositionBottom animated:YES];


38、以Modal的形式push出一个界面;
[UIView  beginAnimations:nil context:NULL];
[UIView setAnimationCurve:UIViewAnimationCurveEaseInOut];
[UIView setAnimationDuration:0.75];
[self.navigationController pushViewController:vc animated:NO];
[UIView setAnimationTransition:UIViewAnimationTransitionFlipFromRight 
   forView:self.navigationController.view cache:NO];
[UIView commitAnimations];


39、阻止设备自动锁屏 [UIApplication sharedApplication].idleTimerDisabled = YES; 在后台不管用,退出当前页面时,记得设为NO


40、为了避免循环引用,在block中我们一般都用弱引用,但是block中的弱引用对象可能会提前释放,造成崩溃,我们需要在block中强引用一下这个弱对象;
__weak __typeof(self)weakSelf = self;
view.callback = ^(ViewStatus status) {
   // 强引用这个对象,避免执行到一半 self释放,造成崩溃;
   __strong __typeof(weakSelf)strongSelf = weakSelf;
   [strongSelf doSomething];
}


41、如果一个参数中需要包含多个枚举值,用NS_OPTIONS,不要用NS_ENUM
// 位运算保证任意组合的枚举值进行或运算能得到唯一的值
typedef NS_OPTIONS(NSUInteger, TestName) {
    TestNameXiaoHua = 1 << 0,    // 小花
    TestNameXiaoBai = 1 << 1,    // 小白
    TestNameXiaoHei = 1 << 2     // 小黑
};
[self eat:TestNameXiaoHua | TestNameXiaoBai]; // 让小花和小白有饭吃;

- (void)eat:(TestName)name
{
    if ((name & TestNameXiaoHua) || (name & TestNameXiaoBai)) {
       NSLog(@"有饭吃");
   }
}


42、快速生成一个带值的可变字典
NSMutableDictionary *mutDic = @{@"key": @"value"}.mutableCopy;


43、忽略编译器警告
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wgnu"  // 设置要忽略的类型 这里是GNU警告
// 代码
#pragma clang diagnostic pop


44、为了避免多线程访问数据库,造成数据混乱,让读写方法都在同一个队列中进行;
static const void * const IOKey = &IOKey;

// 开辟一个单例队列
dispatch_queue_t NTESDispatchIOQueue()
{
    static dispatch_queue_t queue;
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
    //  dispatch_queue_create("", DISPATCH_QUEUE_SERIAL)
    queue = dispatch_queue_create("io.queue", 0);
    dispatch_queue_set_specific(queue, IOKey, (void *)IOKey, NULL); // 设置队列的的标记
    });
    return queue;
}

typedef void(^dispatch_block)(void);
void io_sync_safe(dispatch_block block)
{
    // 如果是自己设置的队列,执行block
    if (dispatch_get_specific(IOKey))
    {
       block();
    }
    else  // 如果不是自己设置的队列,先创建队列,再执行block
    {
        dispatch_sync(NTESDispatchIOQueue(), ^() {
        block();
    });
}


45、监听UITextField值的改变,请使用 下面这个方法
[_textViewaddTarget:self.action:@selector(textFieldDidChangeValue:)
forControlEvents:UIControlEventEditingChanged]

不要去使用下面这个代理方法,这个方法可能会监听不到

 - (BOOL)textField:(UITextField *)textField shouldChangeCharactersInRange:
(NSRange)range replacementString:(NSString *)string {
 },


46、把C++算法库封装在OC文件里形成静态库时,记得把OC的.m文件改成.mm文件


47、如果视图里面存在唯一一个UIScrollView或其子类View时,会主动设置相应的内边距,避免被导航栏遮住,如果我们的导航栏不透明,原点会从我们的导航栏下方算起,导致上方留白,解决这问题:
if (@available(iOS 11.0, *)) {
   self.tableView.contentInsetAdjustmentBehavior = UIScrollViewContentInsetAdjustmentNever;//UIScrollView也适用
}else {
   self.automaticallyAdjustsScrollViewInsets = NO;
}


48、如果数组中的元素是唯一的,或者要查询某个数据是否在该数组中,不要使用NSArray, 而是使用集合NSSet,集合采用的是哈希表,查询速度更快;


49、动画里的万能神器CAShapeLayer,性能特别好,能实现很多神奇的效果,做什么动画前优先考虑它,尤其做股票财经、健康APP类的,经常需要画图,如果遇到性能问题,试试它;


50、swift中为了避免循环引用,我们使用weak或者unowned来解决,但它们是有区别的; unowned更像OC里的unsafe_unretained,当引用对象释放了以后,它仍然会保持对引用对象的一个无效引用,如果尝试调用方法和成员属性的话,程序就会崩溃; weak则会在引用的内容被释放后,它会自动地变成 nil; 因此我们在使用它时,如果你引用对象不会释放, 使用unowned,写起来方便点; 如果你引用对象会被释放,请使用weak,例如网络请求;


51、在swift中用强制解包!一定要非常小心,尽量少用,一不小心程序就崩溃了;


52、在swift中数组和字典都属于值类型,相当于int类型,跟oc不一样,当你把数组赋值给另一个数组,修改数组的值不会影响另一个数组;
iOS