zoukankan      html  css  js  c++  java
  • UI-1

    ---恢复内容开始---

    1 [TOC] # 一-开发之前

    1.学习大纲

    -w1000
    技术目标:

    • 强化编程思想:抽取封装能力,思考解决能力,框架设计能力
    • 强化自我能力:解决问题能力,自学能力,知识存储记录能力

    2.项目启动过程

    运行就是打包的过程,把包放到模拟器中,此时设置一系列的内容,找到程序的入口(MainInterface),依次按照步骤来.

    3.拖拽的控件

    • 为什么用weak?
      拖拽的控件所在的控制器都是被强引用所存在的,这个控制器被强引用,控制器的view被控制器强引用,view的控件被view强引用。所以外部拖线这件事其实就是我拿到这个控件,而不需要strong再引用了。

    • 什么可以单击事件?
      一般继承UIControl的都有单击事件

    • 常见错误

      • 属性连线出问题了(找不到属性或者属性多了):this class is not key value coding-compliant for the key xxxxxx
      • 方法出问题了(找不到方法了): unrecoginzed selector sent to instance

    二-UI

    UIView

    1. 概念
      屏幕上所有的UI元素都叫做控件,所有控件都继承自UIView,哪怕是UIControl也是继承UIView.这些控件的基本属性都放到了公共的UIView中.

    2. 常见属性/方法

      • superView: 父控件
      • subViews: 子控件
      • tag: 标识
      • frame: 控件在父控件的位置尺寸(以父控件左上角为00)
      • bounds : 控件的位置尺寸(以自己左上角为00)
        • 所以一般只改尺寸,xy都是0 (一般layer层用)
      • center : 控件中心(以父控件左上角为00)
      • addSubView: 添加
      • removeFromsuperView: 从父控件移除
      • viewWithTag:根据tag找到View
        • 尽量少使用tag,因为效率差,很乱
    3. ViewDidLoad:

      • 系统调用
      • 控制器的View加载完毕调用
    4. 常用控件:

    UILabel

    用于显示文字

    1. 属性
      • numberofLines: 文字行数,等于0的时候自动换行
      • lineBreakMode: 换行模式,是头部省略还是尾部省略还是中间省略
      • font: 字体

    UIImageView

    用于显示图片

    1. 属性

      • contentMode: 填充模式
        带有scale的会缩放,不带的不会缩放,但是需要裁剪
        

      typedef NS_ENUM(NSInteger, UIViewContentMode) {
      UIViewContentModeScaleToFill,// 完全压缩或拉伸
      UIViewContentModeScaleAspectFit, // 宽高比不变,不会变形
      UIViewContentModeScaleAspectFill,// 宽高比不变,填充
      UIViewContentModeRedraw,// 重新绘制
      UIViewContentModeCenter,
      UIViewContentModeTop,
      UIViewContentModeBottom,
      UIViewContentModeLeft,
      UIViewContentModeRight,
      UIViewContentModeTopLeft,
      UIViewContentModeTopRight,
      UIViewContentModeBottomLeft,
      UIViewContentModeBottomRight,
      };

      
      - `clipsToBounds`: 裁剪多余部分
      - `animationImages`: 设置动画图片(放一个数组)
      - `animationRepeatCount`: 播放次数
      - `animationDuration`: 播放时间
      
      
    2. 设置毛玻璃效果
      由于UIToolBar自带毛玻璃效果,所以可以给UIImageView增加UIToolBar的方法设置毛玻璃的简单效果。UIToolBar有一个barStyle枚举可以设置毛玻璃样式,配合alpha透明度可以做设置。

    3. 序列帧动画

    • 加载图片的两种方式:
      1. UIImageNamed
        • 就算指向他的指针被销毁,该资源也不会从内存销毁
        • 放到Assets里的蹄片,只能imageNmaed加载,默认就有缓存的
        • 所以是图片经常使用的时候,就把图片放Assets里,用imageNamed方法
      2. imageWithContentsOfFile
        • 指向他的指针被销毁,资源就从内存中小时

        • 无法缓存,用于不经常使用的大批量的图片

        • 从资源路径中找(SandBox)

        • 手机里的缓存数据都是在沙盒中

        • 图片放到Assets里就拿不到路径了,放到项目中可以。具体可以在查看沙盒的时候找Bundle包,里面有的图片资源就是项目中的,没有的就是放到Assets里面的

        • 拿到图片路径:

        NSString *path = [[NSBundle mainBundle] pathForResource:@"资源名字" ofType:@"资源类型"];
        self.imgView.image = [UIImage imageWithContentsOfFile:path];
        ```
    • 关于颜色:
      • 白色的RGB都是255
      • 黑色的RGB都是0
      • 灰色的RGB都一样,靠近0就是深灰,靠近255就是浅灰
      • 还有什么hex格式,argb格式等

    UIButton

    按钮既能显示文字又能显示图片,还能随时调整内部图片文字的位置

    1. 状态

      • normal:一般
      • hightlighted:高亮
      • disabled:不可点击
    2. 内容图片和背景图片

      • 内容图片不会随按钮变大而变化
      • 背景图片会随着按钮变化而拉伸
    3. 属性

      • 文字,图片等设置都需要用set方法,因为要区分状态
      • 设置按钮字体:button.titleLabel.font
      • 获得按钮的文字,文字颜色,图片,背景图片 :xxxForState
    4. button不同于imageView和label,它是用addTarget监听的,但是其实都可以通过手势添加监听。

    5. 调整按钮内部子控件位置:默认的是图片左边,文字右边,但是开发是各种需求的

      当自己算完按钮每部距离什么时候,可能出现文字显示不全问题,这时候只需要sizeToFit,但是有时会出现文字错位问题!因为这个方法可能会修改center,那么可以先sizetofit算,然后再修改center即可。

      方法1: UIButton有两个方法返回CGRect,可以自定义Button重写这两个方法

      - (CGRect)titleRectForContentRect:(CGRect)contentRect;
      
    • (CGRect)imageRectForContentRect:(CGRect)contentRect;
      方法2: 其实既然都重写了,不如直接在`layoutSubViews`方法里面修改
      方法3: 不需要重写一个新的Button,直接修改内边距
      ```objc{
      CGFloat labelWidth = modelButton.titleLabel.frame.size.width;  
      

    CGFloat imageWith = modelButton.imageView.frame.size.width;  
    modelButton.imageEdgeInsets = UIEdgeInsetsMake(0, labelWidth, 0, -labelWidth);  
    modelButton.titleEdgeInsets = UIEdgeInsetsMake(0, -imageWith, 0, imageWith);

    ```
    
    ```objc
    /*
    

    一、按钮的状态
    1.UIControlStateNormal
    1> 除开UIControlStateHighlighted、UIControlStateDisabled、UIControlStateSelected以外的其他情况,都是normal状态
    2> 这种状态下的按钮【可以】接收点击事件

    2.UIControlStateHighlighted
    1> 【当按住按钮不松开】或者【highlighted = YES】时就能达到这种状态
    2> 这种状态下的按钮【可以】接收点击事件

    3.UIControlStateDisabled
    1> 【button.enabled = NO】时就能达到这种状态
    2> 这种状态下的按钮【无法】接收点击事件

    4.UIControlStateSelected
    1> 【button.selected = YES】时就能达到这种状态
    2> 这种状态下的按钮【可以】接收点击事件

    二、让按钮无法点击的2种方法
    1> button.enabled = NO;
    *【会】进入UIControlStateDisabled状态

    2> button.userInteractionEnabled = NO;
    *【不会】进入UIControlStateDisabled状态,继续保持当前状态

    */

    ```
    
    1. 图片拉伸
      在一些聊天软件中,那些聊天气泡,美工都是准备一张图的,这时候这个Button的背景就是图片,但是Button很大的时候拉伸很难看。此时需要代码进行拉伸。这种图片一般都是要保护一部分不被拉伸,例如四个角什么的

      • 代码拉伸
      // 拉伸
      UIImage *img = [UIImage imageNamed:@"1"];
      // 方法一:返回一张受保护并且拉伸平铺的图片(edge是受保护区域),此时就留下了图片最中心1,1的像素点无线平铺
      UIImage *resizableImage = [img resizableImageWithCapInsets:UIEdgeInsetsMake(img.size.height * 0.5, img.size.width * 0.5, img.size.height * 0.5 - 1, img.size.width * 0.5 - 1)];
      // 方法二:左边和右边需要保护的区域
      UIImage *strechImage = [img stretchableImageWithLeftCapWidth:img.size.width * 0.5 topCapHeight:img.size.height * 0.5]    
      [self.btn setBackgroundImage:resizableImage forState:UIControlStateNormal];
      

      其实这些可以写在分类里,返回一张受保护的图片(写一个UIImage分类)

      • 直接拉伸
        选择Slicing,会自动计算最合适的保护区域,要不自己拽。

    懒加载

    访问数据就是重写get方法,一般的控件,数组等都应该用懒加载

    plist

    开发中有一些小数据可以存储在plist中。例如很多页面用的数据,NSArray或者NSDictionary之类的数据。

    1. 创建
      • 生成plist只有可能添加的是字典或者数组。一般是直接在plist中添加,或者代码写好字典数组,然后writeToFile到本地,然后拽到项目中
    2. 解析
      // 写入
      NSArray *array = @[@"1",@"2",@{@"aaa":@"aa"}];
      [array writeToFile:@"/Users/aixiaoxin/Desktop/study.plist" atomically:YES];
      
      // 读取
      
      NSArray *arr = [NSArray arrayWithContentsOfFile:[[NSBundle mainBundle] pathForResource:@"study" ofType:@"plist"]];
      NSLog(@"%@",arr);
      
      

    字典转模型

    所谓模型,就是专门用来存放数据的对象。

    方法:

    1. 纯手工:自己创造模型,模型新增构造方法initWithDict等,然后外界赋值,内部实现.
      -(instancetype)initWithDict:(NSDictionary *)dict {
      
      if (self == [super init]) {
         
         self.name = dict[@"name"];
         
         self.icon = dict[@"icon"];
      }
      return self;
      

    }
    ```

    1. kvc字典转模型:

    2. YYModel:

    自定义控件

    • 代码创建

    自定义UIView,一般在init初始化方法中添加自己的子控件(initWithFrame中写就可以,这个方法init也会调用),在layoutsubView中布局frame(这里能拿到self.frame),再引入模型接口,在模型的set方法中赋值模型,设置数据(或者提供接口方法直接设置)

    • xib创建
    1. xib:是轻量级的,用来描述局部的UI界面 .xib打包后就是.nib。所以加载是通过nib加载的。
    2. 加载xibView:

    // 读取xib
    // last是最后一个数组,因为一个xib可能拽了很多view,其实一般情况下就一个,便于开发
    UIView *xibView = [[[NSBundle mainBundle] loadNibNamed:@"Play" owner:nil options:nil] lastObject];
    xibView.frame = CGRectMake(100, 100, 300, 100);
    [self.view addSubview:xibView];

    ```
    
    1. xib的绑定和连线
      和代码类似,关联类,然后传入模型等。
    2. 注意
      • 如果xib各处都用,那应该提供快速的创建方法

        // 快速加载xib
        

    +(instancetype)play {
    return [[[NSBundle mainBundle] loadNibNamed:@"Play" owner:nil options:nil] lastObject];
    }

    • 如果xib的.m中,还想用代码创建子控件,此时和纯代码是不一样的,纯代码是init和initWithFrame方法。xib是initWithCoder:,在这里创建子控件就可以了。
    • 如果子控件是从xib中创建的initWithCoder中创建的,此时控件是处于为唤醒状态,此时要用到awakeFromNib方法,在这里可以添加xib中创建的子控件的子控件。其实如果想在xib中再加控件,统一这个方法就行。
    • xib创建的是不可以用allocinit直接加载的。
    1. 加载原理
      xib转化成xml代码,里面有控件所有的设置。

    UIView渐变动画

    方法

    KVC

    1. KVC 可以取值/赋值/字典转模型等。

      • 赋值:setValueForKey:可以进行自动类型转换,如果用KVC和网络搭建,非常方便。一般一个对象KVC赋值属性值是可行的。而 setValueForKeyPath: keyPath是可以嵌套对象的,假如赋值一个人的狗属性的名字(点语法点出来),就可以 person setValue: @"旺财 forKeyPath:@"dog.name"。其实后者包含前者全部功能,所以直接用setValueForKeyPath就行。

      • 改变类的私有变量: 一般私有类都是在.m的{}中,此时是点不出来的,这意味着我定义了一个_age,而我直接可以setValueForKeyPath:@"_age"来修改这个值(写@"age"都行,他会去找下划线和不带下划线的)。

      • 字典转模型:

      -(instancetype)initWithDict:(NSDictionary *)dict {
      
      if (self == [super init]) {
          
          // 废弃
      

    // self.name = dict[@"name"];
    // self.icon = dict[@"icon"];

        // KVC字典转模型
        [self setValuesForKeysWithDictionary:dict];
    }
    return self;
    

    }
    -(void)setValue:(id)value forUndefinedKey:(NSString *)key {
    }```
    KVC字典转模型的问题:如果有模型嵌套,就会出问题,因为是暴力的键值对赋值。当然,此时就会用到框架(YYModel,MJExtension)

    • 取值:valueForKeyPath方法,开发一般不怎么用,因为点语法就行了
    • 运行时的时候KVC非常强大

    KVO

    Key Value Observing(键值监听)当某个对象的属性发生改变时进行监听。假如监听一个对象的名字改变,scrollView的contentOffSet改变等。
    如果一个类用KVO监听,苹果会自动为这个类生成一个子类(查看isa指针的时候就是NSKVONotifying_XXX类型),所以用的多了性能很差


    监听属性,就要给属性的对象增加观察者

        MYModel *model = [MYModel new];
        // 增加监听观察后,那么应该有个方法来进行 监听完成后的作为
        [model addObserver:self forKeyPath:@"name" options:NSKeyValueObservingOptionNew context:nil];
        model.name = @"改变前";
        model.name = @"改变后";
        [model removeObserver:self forKeyPath:@"name"];
    
    /**
     监听属性值改变后
    
     @param keyPath 要改变的属性
     @param object 要改变的属性所属的对象
     @param change 改变的内容
     @param context 上下文
     */
    -(void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSKeyValueChangeKey,id> *)change context:(void *)context{
        
        NSLog(@"%@==%@===%@",keyPath,object,change);
        
    }
    

    结果:

    2017-03-20 18:10:22.341 button[6755:1544348] name==<MYModel: 0x60800022d7e0>==={
        kind = 1;
        new = "U6539U53d8U524d";
    }
    2017-03-20 18:13:10.575 button[6785:1563494] name==<MYModel: 0x618000030500>==={
        kind = 1;
        new = "U6539U53d8U540e";
    }
    

    UIScrollView

    设备的屏幕大小是有限的,普通的View不具备滚动功能,UIScrollView可以通过滚动展示大量内容。


    1. 基本使用

      • 将需要展示的内容添加到UIScrollViez中
      • 如果想要滚动,设置contentSize属性,也就是滚动范围(可以远大于本体scrollView的size),所以当contentSize尺寸减去本体size是负数的话就无法滚动了,怎么也要大于本体尺寸
      • 默认scroll就设置了clipsToBounds = YES,也就是内容超出边框后就隐藏
      • 默认scroll设置了弹簧效果
      • 无法滚动?
        • 没有设置contentSize
        • scrollView.scrollEnabled设置为NO(只是不能滚动)
        • scrollView.userInteraction设置为NO(失去任何交互能力)
        • scrollView.subViews自带两个滚动条
        • 滚动就是修改contentOffset,此时可以用set方法加一个动画
    2. 常见属性

      • bounces默认YES,弹簧效果
      • alwaysBouncesHorizontal默认NO,和下一个方法一样,设置YES的时候可以让没有contentsize的scrollView滚动(只是一个弹簧效果)
      • alwaysBouncesVertical默认NO,这两个值就算设置YES也没区别,因为这时候已经设置了contentSize。当没有设置contentSize的时候,如果设置这两个是YES就可以滚动了,就像网络图片还没下载完成,只有下拉才能下载,然后加载contentsize,这时候就需要用到这两个属性了
      • showHorizontalScrollIndicator展示水平滚动条
      • showVerticalScrollIndicator展示垂直滚动条
          public var contentOffset: CGPoint // 滚动视图偏移量
          public var contentSize: CGSize // 滚动视图的内容大小
          public var contentInset: UIEdgeInsets // scrollview的contentview的顶点相对于scrollview的位置
          
          public var directionalLockEnabled: Bool // default NO. if YES, try to lock vertical or horizontal scrolling while dragging
          public var bounces: Bool // 是否开启回弹效果
          public var alwaysBounceVertical: Bool // 是否始终水平回弹
          public var alwaysBounceHorizontal: Bool // 是否始终垂直回弹
          public var pagingEnabled: Bool // 是否分页(开启后滑动有自动定位功能)
          public var scrollEnabled: Bool // 是否可以滚动
          public var showsHorizontalScrollIndicator: Bool // 是否显示水平滚动条
          public var showsVerticalScrollIndicator: Bool // 是否显示垂直滚动条
          public var indicatorStyle: UIScrollViewIndicatorStyle // 滚动条的样式(黑色/白色/默认) 
          public var scrollsToTop: Bool // 默认YES,单击上方状态栏(服务商和电池那个地方),会自动回到顶端  
      
      
      
    3. 重要属性

      • contentOffSet,是一个CGPoint属性,是一个偏移量
        • 可以控制内容滚动的位置
        • 可以得知内容滚动的位置(get)
      • contentInset,是UIEdgeInset属性,是内边距,分别是上左下右,滚动的时候会多出来一块儿不显示的部分就是内边距效果,会变相的增加额外的滚动距离
    4. 三者区别

    5. 代理(监听行为)

    在UIScrollView滚动时候就会告诉代理,代理发送一系列消息。

     public protocol UIScrollViewDelegate : NSObjectProtocol {
    @available(iOS 2.0, *)
    optional public func scrollViewDidScroll(scrollView: UIScrollView) // 滚动的时候
    @available(iOS 3.2, *)
    optional public func scrollViewDidZoom(scrollView: UIScrollView) // 滚动视图已经缩放时候
    @available(iOS 2.0, *)
    optional public func scrollViewWillBeginDragging(scrollView: UIScrollView) // 将要开始拖动视图调用
    @available(iOS 5.0, *)
    optional public func scrollViewWillEndDragging(scrollView: UIScrollView, withVelocity velocity: CGPoint, targetContentOffset: UnsafeMutablePointer<CGPoint>) // 将要结束拖拽视图调用
    @available(iOS 2.0, *)
    optional public func scrollViewDidEndDragging(scrollView: UIScrollView, willDecelerate decelerate: Bool) // 已经结束拖拽视图
    @available(iOS 2.0, *)
    optional public func scrollViewWillBeginDecelerating(scrollView: UIScrollView) // 即将减速的时候
    @available(iOS 2.0, *)
    optional public func scrollViewDidEndDecelerating(scrollView: UIScrollView) // 减速停止的时候
    @available(iOS 2.0, *)
    optional public func scrollViewDidEndScrollingAnimation(scrollView: UIScrollView) // 滚动动画结束时候
    @available(iOS 2.0, *)
    optional public func viewForZoomingInScrollView(scrollView: UIScrollView) -> UIView? // 设置进行缩放的子视图(谁缩放)
    @available(iOS 3.2, *)
    optional public func scrollViewWillBeginZooming(scrollView: UIScrollView, withView view: UIView?) // 将要进行缩放时候
    @available(iOS 2.0, *)
    optional public func scrollViewDidEndZooming(scrollView: UIScrollView, withView view: UIView?, atScale scale: CGFloat) // 完成缩放的时候
    @available(iOS 2.0, *)
    optional public func scrollViewShouldScrollToTop(scrollView: UIScrollView) -> Bool // return a yes if you want to scroll to the top. if not defined, assumes YES
    @available(iOS 2.0, *)
    optional public func scrollViewDidScrollToTop(scrollView: UIScrollView) // 点击状态栏回到顶部,仅针对可上下滚动的scrollview 有效}
    

    注意: 任何OC对象都可以成为代理,哪怕你SCrollView滚动,让一条狗遵守ScrollViewDelegate并且实现方法成为代理,那再狗的.m中就可以监听到scrollView滚动

    6.代理(监听行为)

    scrollView可以通过手势完成内容缩放放大
    1. 用代理找到要缩放的scrollViewviewForZoomingInScrollView
    2. 设置缩放比例:

    ```
        scrollView.maximumZoomScale = 2.0
        scrollView.minimumZoomScale = 0.5
        scrollView.bouncesZoom = true // 缩放属性是否回弹 
    ```
    

    7.分页功能
    - 就像苹果手机开机的分页页面,当你滚动的时候,左边比较多右边比较少,那么就自动滚动到左边,这就是分页功能。很多App的分页滚动功能,也是依据分页来实现的。
    其实就是一个属性pageingEnable开启后就可以了。系统分页的判断是按照scrollView的大小来分的。如果要添加分页控件再自行添加UIpageControl
    - 很多分页的点都用得图片,这样看起来很好看,但是其实观察UIpageControl头文件是没有图片选择的,它隐藏在.m的私有拓展中有一个属性_currentPageImage,_otherPageImage,此时可以通过KVC来转化成自己喜欢的图片
    - 分页定时器:每隔一定的时间,希望scrollView做一些事情,去滚动,要用到定时器NSTimer。在用户即将拖拽scrollView的时候停止定时器,不在拖拽的时候开启定时器。

    1.一个定时器要不永久开启,只要一停止就自动销毁了,只能创建一个新的
    2.程序一启动会默认开启一条线程,是主线程,主线程显示刷新UI界面,处理用户交互界面,所以主页面在进行别的操作的时候,定时器可能会卡住。所以要把`NSTimer`添加到运行循环(修改定时器在runloop的模式,目的是不管主线程在做什么,都会分配一定时间处理事情)。
    // 添加到运行循环
    [[NSRunLoop mainRunLoop] addTimer:self.timer forMode:NSRunLoopCommonModes];
    

    常见控件监听

    随便一个控件,点开头文件,如果继承自UIControl就都可以通过addTarget来监听,有些特殊的提供delegate就可以通过代理来监听。像TextField这类比较特殊,它既有代理也继承UIControl,此时两者都可以监听开始编辑,文字改变,结束编辑等状态的。

    屏幕适配

    1. 程序员只需关注的是点,而不是像素,一个点所容纳的像素越多,越清晰。在iphone4S时代,屏幕尺寸固定,那么固定值算就可以适配。在5和5s时代尺寸变化了,此时还无法用autolayout(因为还要适配ios5),此时用的是AutoresizingMask,在iphone6时代开始用AutoLayOut

    2. Autoresizing

      • Autoresizing和AutoLayout是不兼容的,想要使用现在需要去掉AutoLayout。我只在xib中用过,就是六根线。外面四根线是和父亲的关系,里面两根线是子类的关系(选择了会跟随父控件伸缩)
      • 代码实现就是有个AutoResizingMask枚举,可以选择是上左下右怎么适配
      • 只能解决子控件和父控件之间的关系
      • AutoLayout比Autoresizing强得多
    3. AutoLayout

      • ios6 开始引用,不过真正从xcode5开始大量使用,也是现在的主流

      • 可以解决任何控件之间的关系

      • 用自动布局,就不要在设置frame

      • 利用约束参照完成布局

      • 左对齐/右对齐/顶部对齐/底部对齐

      • 中心点x和父控件一样/中心点y和父控件一样

      • 两个控件中心点x一样/中心点y一样

      • 在做pad留言板的时候,因为用的是autoResizing,对于UILabel总是文字居中显示,这时候上下端会出现很多空缺,解决办法只能计算文字到底多高,在固定高度,而用了AutoLayout就可以完美解决这个问题。UILabel只需要给一个位置就不报错,尺寸会自动根据文字内容而计算宽高,此时应该设置下最大宽度(不是真实宽度)Relation->LessThan,告诉UILabel什么时候开始换行。那么固定位置和宽度,UILabel就会自动剔除上下两端的空格了。

      • 让父控件的高度跟随子控件伸缩:例如很多表格类型的app,每个cell高度都不同,因为里面的子控件大相径庭。 其实这时候cell 就不能直接设置高度了,而是要设置自己底部和会影响到你的子类的关系,例如你的子类最后一个控件的底永远和父类的底差10个点,那么不管子类怎么变高,父容器也会跟着变。

    4. 约束优先级:两个互相冲突的约束如果修改优先级,那么优先级较高的生效,当优先级较高的失效了或者删除了,优先级较低的才会生效。例如三个方块并排放各自距离20,当中间的删除后,让第三个自动到中间位置,这时候就用到优先级了。

    代码实现AutoLayout

    1. 禁止autoresizingself.myView.translatesAutoresizingMaskIntoConstraints = NO;

    2. 创建NSLayoutconstraint类创建约束关系

      • 宽高什么的添加到自己上
      • 和父控件之间控制位置的约束添加到父控件上
      • 两个平等的控件(都是儿子)之间的约束添加到父控件上
    3. VFL可视化语言

    4. 修改约束:

          // 修改约束
      self.myWidth.constant = 25;
      [UIView animateWithDuration:2.0 animations:^{
          // 需要强制更新
          [self.view layoutIfNeeded];
      }];
      

    Masonry

    - 目前最流行的第三方框架
    - [Masonry/SnipKit](https://github.com/SnapKit/Masonry)
    - 两个pch上面的宏定义可以让`mas_`全部省略
    - `makeConstraints`是添加新的约束
    - `updateConstraints`是更新约束
    - `remakeConstraints`是删除之前所有约束,然后添加新的约束
    

    UITableView

    基本

    1. TableView分为PlainGroup两种style类型,这个是只读的,意味着只有在创建tableView的时候可以设置。
    2. indexPath分为sectionrow两个属性,分别是组和行,根据这个可以确定唯一的一行
    3. cell.accessoryView可以设置系统cell右侧的那个按钮状态样式
    4. UITableView内部自动封装了一套复用机制。会让空闲的cell进入可重用线程池,当有新的cell出现会先去线程池中找有没有可复用的,没有才会创建。假如有100组数据,需要100个cell,但是手机上每屏只能放下10个,其实这时候只需创建11个cell就够用了。每一个数据模型就是一个cell。通过数据源方法来对每个cell进行数据设置。通过代理方法设置关于tableView的头,尾等视图设置

    UITableView常见属性

    1. rowHeight:每一行cell高度
    2. sectionHeader(Footer)Height:每一组头尾高度
    3. separateStyle:分割线样式(删除分割线separateNone)
    4. tableHeader(footer)View:整个tableView的头尾部,上面的是每一组的头尾,这个是整个的(插播个广告什么的)

    Cell常见属性

    1. accessoryView--accessoryType:都是设置右边控件的(View优先级更大)
    2. selectedStyle:选中样式(有Blur,Gray,None等,ios6之前有效果,7之后全部都是灰色)
    3. backgroundView:cell背景可以是好看的图片,selectedBackgroundView:选中的背景图片
    4. contentView:cell上添加的东西都是添加在contentView上的。因为有时候cell删除,滑动,整个cell都向左移动了,此时如果移动cell非常麻烦,但是移动contentView就方便多了
    5. 自带cell中的imageView,textLabel等属性都是延迟加载的,是懒加载的,用到才会加载

    数据源方法

    • 提供几组,几行,每一个cell什么样子,数据是什么

    代理方法

    • 提供cell单机事件
    • 提供cell的头/尾视图设置,头/尾高度设置,设置了这些,前面属性设置的高度就失效了。
    • 提供了每一行的高度,rowHeight是有局限性的,这个可以让每个cell高度不同

    UITableViewController

    • 每一个控制器都有View,在UITableViewController中:self.viewself.tableView是一个对象。
    • 当数据比较少,还想去掉没有数据的分割线时:可以设置self.tableView.tableFooterView = [UIView new]

    性能优化

    • cell标准写法.注册方法

    自定义等高的cell

    • 无论是系统自带cell还是自定义创建注册的cell,这个[cell class]都是通过cell alloc initWithStyle方法实现的
    -(instancetype)initWithStyle:(UITableViewCellStyle)style reuseIdentifier:(NSString *)reuseIdentifier {
        
        
        if (self = [super initWithStyle:style reuseIdentifier:reuseIdentifier]) {
            
            _name = [UILabel new];
             [self.contentView addSubview:_name];
            
        }
        return self;
        
        
    }
    
    • 至于cell内部自然要用约束来控制。当需要用frame计算的时候一般会写在layoutifNeed里面,因为这里才能拿到self的frame.但是用约束计算的话直接写在cell alloc initWithStyle方法里就行(写在addSubView以后),因为这是根据父控件的约束,不管父控件如何
    • xib创建cell时候注册nib
    [self.tableView registerNib:[UINib nibWithNibName:NSStringFromClass([MyTableViewCell class]) bundle:nil] forCellReuseIdentifier:@"cell"];
    
    • 一个tableView显示不同的cell:由于cellforRowAtIndexpath方法返回一个cell,所以可以根据indexpath改变cell。说白了就是注册N个cell和reusedId,根据indexPath来dequeue不同的cell。广告cell就这么来的
    • 自定义分割线:添加高度为1的UIView

    静态cell和动态cell

    • 动态cell是数据.数量.高度等等都是动态获取的
    • 静态cellStatic Cells是写死的,在sb中/xib中直接修改cell类型然后绘制出来的。可以做一些设置界面,然后通过代理监听
    • 静态动态只是创建区别,功能都是实现cell
    • 静态cell 也是可以自定义的,也可以画画然后关联类,然后从waakefromNib读取关联的cell

    字典转模型

    • 字典转模型无非就是遍历数组,然后拿到数组里的每一个字典,然后给模型用KVC赋值,这是一个重复的过程,所以要省事,用插件。而且有的模型很复杂,嵌套模型很多,这么写起来更麻烦。

    • MJExtension
      GitHubMJExtension

      model-objetArrayWith...

    • YYModel
      GitHubYYModel

      model-yy_modelWithJson..


    自定义不等高的cell

    • (计算不等高UILabel)如果frame计算,需要注意UILabel内容不一样的时候,要想计算它的高度,需要
        
        UILabel *lab = [[UILabel alloc] init];
        // 100是固定宽度 后者是最大宽度,不限制
        CGSize size = CGSizeMake(100, MAXFLOAT);
        // 计算出高度
        CGFloat height = [lab.text sizeWithFont:[UIFont systemFontOfSize:14] constrainedToSize:size].height;
    
    • 一些app都是frame计算的,假如需求是frame来计算不等高度的cell。控制器在heightForRowAtIndexpath方法中可以拿到模型方便计算,但是在这里计算要调用无数次,性能很差,这时候还必须先返回一个高度给cell,所以一开始觉得应该给数据模型增加所有的cell子控件frame 和总高度frame属性,在总高度属性中,利用懒加载来计算 高度/各个frame,然后外界heightForRowAtIndexpath直接设置高度。但是这么做,重用刷新就会出现问题,因为刷新的时候高度大于0,但是不是你想要的高度。真正做法是刷新的时候,拿到json的时候,字典转模型的时候,创建一个新的frame模型,里面存放着高度和各个cell子控件的frame,每次刷新数据就绑定好,然后heightForRowAtIndexpath直接拿frame模型。
    • 打印各种方法执行顺序,假如10条数据在页面,其实是先执行10次heightForRowAtIndexpath才会执行cellForRowAtIndexPath,说白了就是cell展示前,所有高度早就计算好了。
    • masonry每次都会重新计算,在一些很复杂很复杂的cell控件中,其实frame算性能比较好。(一哥们亲身经历)
    • cell内部计算的时候也不一定写在layoutsubVies,写在模型的set方法也可以,因为这时候是肯定有frame的
    • self.tableView.estimatedRowHeight:预估高度,会优化性能,让heightForRowAtIndexpath执行次数少很多。而且没有估算的时候,是先执行heightForRowAtIndexpath后执行cellForRowAtIndexPath。当写了估算行高后,反过来了,因为已经有一个默认高度了。

    数据刷新

    • 全局刷新:reloadData:就是重新调用数据源方法
    • 局部刷新:reloadRowsAtIndexPath:适合模型数量不变
    • 添加刷新: insertRowsAtIndexPath
    • 删除刷新:deleteRowsAtIndexPath

    左滑删除

    • 实现代理方法就可以出现滑动删除
    -(void)tableView:(UITableView *)tableView commitEditingStyle:(UITableViewCellEditingStyle)editingStyle forRowAtIndexPath:(NSIndexPath *)indexPath{
        // 删除模型,动画单行刷新
        [self.dataSource removeObjectAtIndex:indexPath.row];
        [self.tableView deleteRowsAtIndexPaths:@[indexPath] withRowAnimation:UITableViewRowAnimationTop];   
    }
    -(NSString *)tableView:(UITableView *)tableView titleForDeleteConfirmationButtonForRowAtIndexPath:(NSIndexPath *)indexPath{
        return @"删除";
    }
    
    • 左滑出现多个按钮(例如微信,滑动出现删除/关注),此时只需要一个新方法,如果新增了这个方法,上面的return 删除监听就实效了。
    -(NSArray<UITableViewRowAction *> *)tableView:(UITableView *)tableView editActionsForRowAtIndexPath:(NSIndexPath *)indexPath{
        
        UITableViewRowAction *action1 = [UITableViewRowAction rowActionWithStyle:UITableViewRowActionStyleDefault title:@"关注" handler:^(UITableViewRowAction * _Nonnull action, NSIndexPath * _Nonnull indexPath) {
            
            // 关注
    
        }];
        action1.backgroundColor = [UIColor orangeColor];
        
        UITableViewRowAction *action2 = [UITableViewRowAction rowActionWithStyle:UITableViewRowActionStyleDefault title:@"删除" handler:^(UITableViewRowAction * _Nonnull action, NSIndexPath * _Nonnull indexPath) {
            
            
            // 删除模型,动画单行刷新
            [self.dataSource removeObjectAtIndex:indexPath.row];
            
            [self.tableView deleteRowsAtIndexPaths:@[indexPath] withRowAnimation:UITableViewRowAnimationTop];
            
        }];
        action2.backgroundColor = [UIColor redColor];
        
        return @[action1,action2];
    }
    

    如果想点击后退出编辑模式,tableView.editing = no

    • 编辑模式tableView.editing = yes时,左侧就会出现删除按钮,其实就是上方的那个编辑模式。而且编辑模式有三种
    // 设置cell的编辑模式,有NONE,插入,删除三种
        override func tableView(tableView: UITableView, editingStyleForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCellEditingStyle {
            
            return UITableViewCellEditingStyle.Insert
            
        }
    
    • 批量删除
      有俩属性,编辑模式下就可以批量勾选等等

    UICollectionView

    • 流水布局UICollectionViewFlowLayout:说白了就是当里面内容变宽了变窄了这个那个了,但是整个布局会改变。就像手机界面,删除一个app,后面的会滑上去。

    • self.collrctionView 不等于 self.view,不像tableView,collectionView是添加到view上的。

    • flowLayout布局的时候可以设置item的大小,行间距,item间距,滚动方向,每一组内边距(sectionInset),

    • self.collectionView可以设置分页(pagingEnable),弹簧效果(bounces),隐藏滚动条(showH..)等操作,其实很多都是UIScrollView的属性

    • 相册照片浏览器

    // 必须要有布局,自带循环机制,每一个item就是每一个cell
    // 必须要注册cell,并且必须要自定义cell,因为collectionView的默认cell什么都没有!
    // 调整尺寸什么的都是布局的事,cell自定义后通过代理来完成注册,数量,模型内容
    // 当要做的内容例如item大小不一的时候,系统无法满足,此时要自定义流水布局
    
    #import "FlowLayout.h"
    /*
        自定义布局:只要了解5个方法
     
     - (void)prepareLayout;
     
     - (nullable NSArray<__kindof UICollectionViewLayoutAttributes *> *)layoutAttributesForElementsInRect:(CGRect)rect;
     
     - (BOOL)shouldInvalidateLayoutForBoundsChange:(CGRect)newBounds;
     
     - (CGPoint)targetContentOffsetForProposedContentOffset:(CGPoint)proposedContentOffset withScrollingVelocity:(CGPoint)velocity; // return a point at which to rest after scrolling - for layouts that want snap-to-point scrolling behavior
    
     - (CGSize)collectionViewContentSize;
    
     */
    @implementation FlowLayout
    
    /*
     UICollectionViewLayoutAttributes:确定cell的尺寸
     一个UICollectionViewLayoutAttributes对象就对应一个cell
     拿到UICollectionViewLayoutAttributes相当于拿到cell
     */
    
    
    // 重写它方法,扩展功能
    
    // 什么时候调用:collectionView第一次布局,collectionView刷新的时候也会调用
    // 作用:计算cell的布局,条件:cell的位置是固定不变
    // - (void)prepareLayout
    //{
    //    [super prepareLayout];
    //    
    //    NSLog(@"%s",__func__);
    //    
    //}
    
    
    // 作用:指定一段区域给你这段区域内cell的尺寸(就是这个rect,一开始系统固定的,之后超过这个值就新增)
    // 可以一次性返回所有cell尺寸,也可以每隔一个距离返回cell
    - (nullable NSArray<__kindof UICollectionViewLayoutAttributes *> *)layoutAttributesForElementsInRect:(CGRect)rect
    {
    //    NSLog(@"%s",__func__);
    // 这时候就返回所有了
        NSArray *attrs = [super layoutAttributesForElementsInRect:CGRectMake(0, 0, MAXFLOAT, MAXFLOAT)];
        
        return attrs;
        
    }
    
    // 什么时候调用:用户手指一松开就会调用
    // 作用:确定最终偏移量
    - (CGPoint)targetContentOffsetForProposedContentOffset:(CGPoint)proposedContentOffset withScrollingVelocity:(CGPoint)velocity{
    
        // 拖动比较快 最终偏移量 不等于 手指离开时偏移量
        
        // 最终偏移量
        CGPoint targetP = [super targetContentOffsetForProposedContentOffset:proposedContentOffset withScrollingVelocity:velocity];
        
        // 获取collectionView偏移量
        NSLog(@"%@ %@",NSStringFromCGPoint(targetP),NSStringFromCGPoint(self.collectionView.contentOffset));
        
        
        return CGPointZero;
    }
    
    // Invalidate:刷新
    // 在滚动的时候是否允许刷新布局
    - (BOOL)shouldInvalidateLayoutForBoundsChange:(CGRect)newBounds{
        return YES;
    }
    
    // 计算collectionView滚动范围
    //- (CGSize)collectionViewContentSize{
    //    return [super collectionViewContentSize];
    //}
    
    
    

    MVC

    • M是模型,V是视图,C是控制器。View显示什么取决于M,控制器负责把数据模型给View。MVC因为模型的存在解决cell循环利用表格出bug了。只需要记住,不要随意直接修改cell上的值,要改,就改模型。

    通知

    • 每一个应用程序都有一个NSNotificationCenter实例,负责不同对象之间消息传递。
    • 我做一件事要让外界知道,我就发布通知(post),谁来监听谁就(post),这个过程是一对多的,我广播发布,谁注册了监听就把通知给谁。
    • NSNotification一个通知有三个属性
      1. name:通知名字
      2. object:通知发布者(id)
      3. userInfo:传递内容信息(字典)
        // 发布通知(object是谁发送)
        [[NSNotificationCenter defaultCenter] postNotificationName:@"RemoveNotification" object:self userInfo:@{@"money":self.wine.money}];
        // 监听通知
        [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(remove:) name:@"RemoveNotification" object:nil];
    
    - (void)remove:(NSNotification*)noti {
        
        NSLog(@"%@---%@---%@",noti.name,noti.object,noti.userInfo);
        int money = [[noti.userInfo objectForKey:@"money"] intValue];
        self.totalPriceLabel.text = [NSString stringWithFormat:@"%d",[self.totalPriceLabel.text intValue]-money];
        
    }
    
    

    object谁发布的name什么通知。可以不写name,那就是监听这个object的所有通知。

    • 移除通知remove,当对象被销毁,就删除
    • 通知是有顺序的,要先监听add,再发出post,否则兼听不到
    • add通知的时候还有一个方法会让填写一个队列和block,队列不需要的时候写nil,这个也是可以用的。当队列需要,填写的队列就决定block在哪里执行。而且这个通知的移除不是一般的removeself!因为是系统观察的,方法你都没写!
    @property (nonatomic, weak) id observe;
    @end
    
    @implementation ViewController
    - (void)test
    {
        // 方式一:
        // Observer:观察者
        // selector:只要一监听到通知,就会调用观察者这个方法
        // Name:通知名称
        // object:谁发出的通知
        [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(reciveNote) name:@"note" object:nil];
    
    }
    
    - (void)test2
    {
        // Name:通知名称
        // object:谁发出的通知
        // queue:决定block在哪个线程执行,nil:在发布通知的线程中执行
        // [NSOperationQueue mainQueue]:一般都是使用主队列
        // usingBlock:只要监听到通知,就会执行这个block
        // 注意:一定要记得移除
        _observe = [[NSNotificationCenter defaultCenter] addObserverForName:@"note" object:nil queue:nil usingBlock:^(NSNotification * _Nonnull note) {
            
            // 只要监听到通知 就会调用
            NSLog(@"%@",[NSThread currentThread]);
            
            NSLog(@"%@",self);
            
        }];
    }
    
    
    // 一个对象即将销毁就会调用
    - (void)dealloc
    {
        // 移除通知
        [[NSNotificationCenter defaultCenter] removeObserver:_observe];
    }
    // 异步:监听通知 主线程:发出通知 接收通知代码在主线程
    // 主线程:监听通知 异步:发出通知 接收通知代码在异步
    // 注意:在接收通知代码中 可以加上主队列任务
    
    

    UIDevice通知

    • UIDevice提供一个单例UIDevice-currentDevice,代表设备,可以获取一些设备信息,例如电池电量,电池状态,设备类型,设备系统等等。
    • 以前的代码会判断系统版本sytemVersion来进行代码不同的写法,可以用于系统适配
    • UIDevice会不间断的发布一些通知,例如
      1. 设备旋转
      2. 电池状态改变
      3. 电池电量
      4. 近距离传感器(例如接电话后屏幕变黑)

    键盘通知

    • 键盘也有一些系统的通知,来判断键盘状态

      -w500

    UI进阶(多控制器就是进阶)

    基础

    启动页LaunchScreen

    • 可以用自带的sb设置,也可以用Assset里面的启动页设置。
    • sb设置的底层实现:把LauchScreen里的内容生成了一张图片储存在沙盒中

    info.plist

    • 作用:设置应用程序配置信息。最外层节点是一个字典
    • Bundle name:应用程序名称(项目名称一般不是中文的,因为要打包,但是app很多中文名字,这时候在这里改这个选项是中文,例如 财务审批
    • Bundle identifier:应用程序唯一标识(上传appStore的时候必须有这个,做推送的时候也必须有这个(推送是把消息发送给苹果服务器,服务器根据你手机的Bundle identifier来找到你这个app))
    • Bundle version string , short:版本号,软件版本号
    • Bundle version:应用程序打包版本号

    PCH

    • 作用:
      • 自定义Log
      // 定义debugFKLog
      

    ifdef DEBUG

    define FKLog(fmt, ...) NSLog((@"%s [Line %d] " fmt), PRETTY_FUNCTION, LINE, ##VA_ARGS);

    else

    define FKLLog(...)

    endif

    ```
    
    - 存储公共宏
    - 存储公共头文件(自定义的分类等)
    
    • 从xcode6开始苹果不推荐PCH,就是不编译了。现在需要创建pch后,在Build Settings - 搜索prefix - 寻找 LLVM 7.0 Lauguage - pch改YES - 填写pch路径fullPath
    • 原理:app编译时候,把pch所有内容拷贝到所有文件中(如果是混合开发,有c文件的话就会报错了,因为c不识别pch里面你的UI宏,这时候还给在pch做设置)

    UIApplication

    • UIApplication是应用程序的象征。每一个app只有一个,是单例。sharedApplication
    • 一个ios程序启动后创建的第一个对象就是UIApplication,利用它而已做一些应用层设置。例如
      • applicationIconBadgeNumber:设置app右上角红色提醒数字
    1. 设置图标

    2. 注册通知

    3. 设置图标属性

    /**
    取得用户授权
    */

    • (void)badgeNotification:(UIApplication *)application {
      // 取得用户授权显示通知[BadgeNumberUIApplication层]
      id typeBadge = [UIUserNotificationSettings settingsForTypes:UIUserNotificationTypeBadge categories:nil];
      id typeSound = [UIUserNotificationSettings settingsForTypes:UIUserNotificationTypeSound categories:nil];
      id typeAlert = [UIUserNotificationSettings settingsForTypes:UIUserNotificationTypeAlert categories:nil];
      [application registerUserNotificationSettings:typeBadge];
      [application registerUserNotificationSettings:typeSound];
      [application registerUserNotificationSettings:typeAlert];
      }
        // ios 8后需要授权,目前只会写一种授权方式,iOS10有新方法
        NSInteger times = arc4random() % 100;
        [[UIApplication sharedApplication] setApplicationIconBadgeNumber:times];
    
    - `networkActivituIndicatorVisible`:设置联网小菊花的可见性(Bool)一般写在网络方法中
    - `statusBarHidden`:设置状态栏隐藏
    
    ```
    

    /**
    想要使用 默认要在info.plist新增最后一个键值对,选no,这时候控制器设置无效
    全局设置状态栏白色
    全局设置导航栏背景mainColor
    全局设置导航栏标题文字颜色白色,微软雅黑
    全局设置导航栏Item颜色 白色
    */

    • (void)systemSetting {

      [[UIApplication sharedApplication] setStatusBarStyle:UIStatusBarStyleLightContent];
      [[UINavigationBar appearance] setBarTintColor:mainColor];
      [[UINavigationBar appearance] setTintColor:[UIColor whiteColor]];
      [[UINavigationBar appearance] setTitleTextAttributes: [NSDictionary dictionaryWithObjectsAndKeys: [UIColor whiteColor], NSForegroundColorAttributeName, [UIFont fontWithName:@"MicrosoftYaHei" size:18.0], NSFontAttributeName, nil]];
      }

      
      

    // 不是全局 交给每个控制器独立控制,默认是交给控制器的
    -(UIStatusBarStyle)preferredStatusBarStyle {

    return UIStatusBarStyleLightContent;
    

    }
    -(BOOL)prefersStatusBarHidden {

    return YES;
    }
    ```
    - `keyWindow`:应用程序主窗口
    
    
    
    - 强大的`openURL`方法 `[NSURL URLWithString:]`-->`根据协议头tel: sms:http:等来区分`
    	- 可以打电话
    	- 发短信
    	- 发邮件
    	- 打开网络资源
    	- 打开其他app
    
    • UIApplication代理:
      app容易受到干扰,打电话这个那个会切出去。当这些系统干扰出现的时候,app需要做一些事。例如保存数据等。此时UIApplication会有代理监听(也就是AppDelegate里面那一堆方法)
      • 系统来电等
      • 程序启动关闭等
      • 收到内存警告

    应用程序启动原理

    1. 执行main函数->执行UIApplicationMain->创建UIApplication对象->执行代理
    2. 开启运行循环(主运行循环),保证app不退出(死循环)
    3. 加载info.plist(一些基本属性,然后看看有没有main.sb,有就加载,没有就创建)
    4. 启动完毕,进入appDelegate代理

    UIWindow

    UIWindow是一个特殊的UIView,ios启动完毕后,启动的第一个视图控件就是UIWindow,然后创建控制器View,把控制器view加到UIWindow上。所以说没有UIWindow就看不到任何UI界面的。

    • 纯代码写app的时候window是要手动创建的
    • self.window makeKeyAndVisible的作用是显示,把跟控制器的view加载到我的window上。
    • 其实键盘,状态栏也是一个UIWindow
    • 从ios9后,如果添加了多个窗口,例如又创建了UIWindow,makeKey了,状态栏就没了,消失了,解决办法就是让状态栏给应用程序管理。
    • windowLevel:窗口层级(不同窗口谁前谁后可以分层级)Alert > StatusBar > Normal

    控制器如何加载View?

    1. 控制器利用loadView方法,专门创建控制器View。当控制器的View第一次使用的时候调用此方法。那么loadView做了什么?

       1.  判断如果控制器是sb加载的,就找sb的View设置为自己当前View
       2.  如果是xib加载,就把xibView设置自己View
       3.  如果都不是,就会创建一个空白的View
      

      一旦重写了这个方法,那么这个View就需要自己创建了,这样可以自定义控制器最一开始的View。

    2. 控制器View都是懒加载的

    3. 如果一个控件是透明的,那么它不能接收事件。一开始创建的控制器不是透明的,只是颜色是透明的,是可以接收事件的。

    UITexiField

    1. self.inputView:修改文本框弹出键盘类型

    UIPickerView/UIDatePicker

    PickerView

    代理选择设置内容。一般配合UITextField的键盘类型设置为是自定义的PickerView

    DatePicker

    datepickerMode: 日期模式可以选择想要的年月日
    locale:设置区域为localeWithLocaleId:@"zh"中国
    监听日期改变用addTarget-Valuechange

    项目中这些选项都有,例如左边国家右边国旗的界面, 年月日时间的界面, 选择城市(市区)的界面。国家国旗就是一个pickerView,自定义View弄上imageView和label,用代理完成。年月日就如上,选择城市~~
    

    String

    字符串 用copy目的是外界修改了,不会影响到自己。其实,一个字符串属性被赋值的时候,self.name = @"xxx"这个赋值的值,要不就是写死的,要不就是网络获取的,但是一定都是NSString类型。而copy属性呢,会在set方法的时候做一次copy,copy会做一次判断,判断外界给的值得类型是可变的还是不可变的,如果是不可变的,直接赋值,如果是可变的,就copy一份赋值。所以其实app的字符串都是不可变的,所以写strong也是可以的,甚至会少一次判断,性能上会高一些。财务审批NSString非常多,这一点用strong是可行的。

    ---恢复内容结束---

    ![](http://images2015.cnblogs.com/blog/1014815/201707/1014815-20170705105216878-1240811650.jpg)
  • 相关阅读:
    石子游戏2
    礼物的最大价值
    CF512D. Fox And Travelling
    arc099F
    CF504E. Misha and LCP on Tree(长链剖分求k级祖先)
    agc031D
    CF555E. Case of Computer Network
    agc023D
    CF1406E. Deleting Numbers
    CF585F. Digits of Number Pi
  • 原文地址:https://www.cnblogs.com/sgxx/p/7120077.html
Copyright © 2011-2022 走看看