美文网首页iOS developiOS Developer
iOS开发琐碎知识点总结

iOS开发琐碎知识点总结

作者: 夜幕青雨 | 来源:发表于2017-09-23 09:35 被阅读48次
    1. bringSubViewToFront 不起作用的问题
    bringSubViewToFront问题.png

    bringSubViewToFront方法只对该view的childView起作用,而对grandView不起作用,可以用上面的方法,把grandView前置。

    最近项目中遇到一个坑,有一个父view,该父view中添加了第三方SDK中的view,所以没有准确判断view的层级关系,导致我添加的一个label无论怎样调用bringSubViewToFront都无法显示在最前面。后来发现原因是,我的label添加后,调用bringSubViewToFront过早,因为这个时候第三方view还没加载上去,所以调用bringSubViewToFront也不会将label显示在第三方veiw的前面。要在第三方view加载完成后,才会起作用。

    总结:bringSubViewToFront方法只会将某个view放在其父view的所有已存在的子view的前面。后来添加的view依然有可能遮挡住这个view.

    2. 强制某个 viewController 横屏竖屏
    /*方法*/
    - (void)interfaceOrientation:(UIInterfaceOrientation)orientation
    {
      if ([[UIDevice currentDevice] respondsToSelector:@selector(setOrientation:)])  {
    
          SEL selector = NSSelectorFromString(@"setOrientation:");
    
          NSInvocation *invocation = [NSInvocation invocationWithMethodSignature:[UIDevice instanceMethodSignatureForSelector:selector]];
    
          [invocation setSelector:selector];
    
          [invocation setTarget:[UIDevice currentDevice]];
    
          int val  = orientation;
    
          [invocation setArgument:&val atIndex:2];
    
          [invocation invoke];
      }
    }
    

    举个栗子:

    // 横屏
    - (IBAction)landscapAction:(id)sender {
        [self interfaceOrientation:UIInterfaceOrientationLandscapeRight];
    }
    
    // 竖屏
    - (IBAction)portraitAction:(id)sender {
        [self interfaceOrientation:UIInterfaceOrientationPortrait];
    }
    

    3. nib 文件加载过程

    Outlets are set after -init and before -awakeFromNib. If you want to access outlets, you need to do that in -awakeFromNib or another method that’s executed after the outlets are set (e.g. -[NSWindowController windowDidLoad]).

    When a nib file is loaded:

    1. Objects in the nib file are allocated/initialised, receiving either -init, -initWithFrame:, or -initWithCoder:
    2. All connections are reestablished. This includes actions, outlets, and bindings.
    3. -awakeFromNib is sent to interface objects, file’s owner, and proxy objects.

    4. table view 优化

    tableView优化.png

    5. xib 中使用 autolayout 布局

    关于 xib 中使用 autolayout 布局的问题,下面两张图应该说明的很明白了。

    autolayout image.png

    6. 使用 setValue:forKey:设置对象属性值

    在使用 setValue:forKey: 设置对象属性值时,不管该属性是否为只读的、不管在 .h 或者 .m 文件中,都能够成功设置! 果如你重写了这个属性的 setter 方法,那么也会走该属性的 setter 方法。

    7. Class / id / objc_object

    Objective-C类是由 Class 类型来表示的,它实际上是一个指向
    objc_class 结构体的指针。Class 的定义如下:

    typedef struct objc_class *Class;
    

    查看 objc/runtime.h 中 objc_class 结构体的定义如下:

    struct objc_class {
        Class _Nonnull isa  OBJC_ISA_AVAILABILITY;
    
    #if !__OBJC2__
        Class _Nullable super_class                              OBJC2_UNAVAILABLE;
        const char * _Nonnull name                               OBJC2_UNAVAILABLE;
        long version                                             OBJC2_UNAVAILABLE;
        long info                                                OBJC2_UNAVAILABLE;
        long instance_size                                       OBJC2_UNAVAILABLE;
        struct objc_ivar_list * _Nullable ivars                  OBJC2_UNAVAILABLE;
        struct objc_method_list * _Nullable * _Nullable methodLists                    OBJC2_UNAVAILABLE;
        struct objc_cache * _Nonnull cache                       OBJC2_UNAVAILABLE;
        struct objc_protocol_list * _Nullable protocols          OBJC2_UNAVAILABLE;
    #endif
    
    } OBJC2_UNAVAILABLE;
    

    由 objc_class 结构体可知,在 objective-C 中,所有的类自身也是一个对象,里面有一个 Class 类型的 isa 指针,指向 metaClass(元类)。

    super_class:指向该类的父类,如果该类已经是最顶层的根类(如NSObject或NSProxy),则super_class为NULL。

    cache:用于缓存最近使用的方法。一个接收者对象接收到一个消息时,它会根据isa指针去查找能够响应这个消息的对象。在实际使用中,这个对象只有一部分方法是常用的,很多方法其实很少用或者根本用不上。这种情况下,如果每次消息来时,我们都是methodLists中遍历一遍,性能势必很差。这时,cache就派上用场了。在我们每次调用过一个方法后,这个方法就会被缓存到cache列表中,下次调用的时候runtime就会优先去cache中查找,如果cache没有,才去methodLists中查找方法。这样,对于那些经常用到的方法的调用,提高了调用效率。

    objc_object 是表示一个类的实例的结构体,它的定义如下(objc/objc.h):

    /// Represents an instance of a class.
    struct objc_object {
        Class _Nonnull isa  OBJC_ISA_AVAILABILITY;
    };
    
    /// A pointer to an instance of a class.
    typedef struct objc_object *id;
    

    id 类型是一个指向 objc_object 结构体类型的指针。它的存在可以让我们实现类似于C++中泛型的一些操作。该类型的对象可以转换为任何一种对象,有点类似于C语言中void *指针类型的作用。

    在 Objective-C,一个对象的类由它的 isa 指针决定。isa 指针指向这个对象的 Class。在 Objective-C 中,对象的一个重要的特性是,你可以向它们发送消息:

    1. 当你向一个对象发送消息,就在那个对象的方法列表中查找那个消息。
    2. 当你想一个类发送消息,就再那个类的 meta-class 中查找那个消息。

    meta-class 是一个类对象的类

    当我们向一个对象发送消息时,runtime会在这个对象所属的这个类的方法列表中查找方法;而向一个类发送消息时,会在这个类的meta-class的方法列表中查找。

    meta-class 之所以重要,是因为它存储着一个类的所有类方法。每个类都会有一个单独的 meta-class ,因为每个类的类方法基本不可能完全相同。

    meta-class 是必须的,因为它为一个 Class 存储类方法。每个类都必须有一个唯一的 meta-class,因为每个 Class 都有一个可能不一样的类方法。

    meta-class,如之前的 Class,同样是个对象。这就意味着你也可以在它上面调用方法。自然的,这就意味着它也必须有一个类

    所有的 meta-class 使用它们基类的 meta-class (继承层次中最顶层的 Class 的 meta-class)作为它们自己的类。这就是说所有继承自 NSObject 的类(大部分的类),以 NSObject 的 meta-class 作为自己的 meta-class 的类。

    遵循这个规则,所有的 meta-class 使用基类的 meta-class 作为他们的类,任何基类的 meta-class 将会是他们自己(它们的 isa 指向他们自己)。这就是说 NSObject 的 meta-class 的 isa 指针指向它们自己(是自己的一个实例)。

    8.nullable、__nullable、_Nullable 究竟有什么区别

    1. 对于属性、方法返回值、方法参数的修饰,使用: nonnull/nullable

    2. 对于 C 函数的参数、Block 的参数、Block 返回值的修饰,使用: _Nonnull/_Nullable , 建议弃用 __nonnull/__nullable

    如果需要每个属性或每个方法都去指定 nonnull 和 nullable ,将是一件非常繁琐的事。苹果为了减轻我们的工作量,专门提供了两个宏:NS_ASSUME_NONNULL_BEGINNS_ASSUME_NONNULL_END 。在这两个宏之间的代码,所有简单指针对象都被假定为 nonnull ,因此我们只需要去指定那些 nullable 指针对象即可。

    疑问:为什么已经有了 nonnull/nullable ,为什么还要增加 _Nonnull/_Nullable ?

    9. 使用pathForResource获取不到 bundle 里的资源,返回nil的问题

    通过 右键->add files to 的方式将 Bundle 添加到工程里面,但是使用[[NSBundle mainBundle] pathForResource:@"name" ofType:@"type"]时,无论如何都找不到文件,经过了重启工程 、clear工程以及重启电脑等方式都无法解决问题。经过思考和测试,感觉这可能是xcode的一个bug。

    解决办法及原理是这样的,[NSBundle mainBundle]其获取的路径是你程序的安装路径下的资源文件位置。 在xcode中采用add file to 方式添加文件时,一般情况下xcode会自动将文件添加到你的资源文件,而且,这些文件在你工程的 build Phases 中的 copy Bundle Resources 中可以查看到。但是有时候,由于xcode的问题,采用add files to 不能自动添加到你的资源文件中,这时,可以采用copy Bundle Resources下面的“+”号,手动将文件添加到你的资源文件中,这样就可以解决问题了。

    10. 新建 window 并设置它的 rootViewController 遇到的状态栏问题

    新建 window 并设置它的 rootViewController ,在 rootViewController 中想要自定义状态栏样式或者隐藏状态栏,遇到的问题是,如果该 window 的 frame 不等于 [UIScreen mainScreen].bounds ,那么在 rootViewController 中想要使用下面两个方法设置状态栏时,是无效的,因为此时这两个方法不会调用:

    - (UIStatusBarStyle)preferredStatusBarStyle;
    - (BOOL)prefersStatusBarHidden
    

    Apple 这样做是有理由的,比如新建 window 不能覆盖整个屏幕,只是一个很小的悬浮框,此时的 rootViewController 不需要控制状态栏样式。

    当不设置 window 的 frame 或者设置 window 的 frame 为 [UIScreen mainScreen].bounds 时,在 rootViewController 中才能够设置状态栏样式。

    能够正确设置状态栏的代码:
            UIWindow *delegateWindow = [UIApplication sharedApplication].delegate.window;
            CGRect rect = [UIScreen mainScreen].bounds;
            self.window = [[UIWindow alloc] init];
            self.window.rootViewController = self.rootVC;
            [self.window makeKeyAndVisible];
            self.window.frame = CGRectMake(0, -rect.size.height, rect.size.width, rect.size.height);
            // 保持原先的keyWindow,避免一些奇怪的bug
            [delegateWindow makeKeyWindow];
            [UIView animateWithDuration:0.3 animations:^{
                self.window.frame = rect;
            } completion:NULL];
    
    

    11. 当 present 一个viewController并且设置这个viewController背景透明度时,背景色变黑的问题

    想模态展示一个VC窗口,设置它的背景透明度为0.5,却发觉prsent后的背景色变为黑色。

    dd

    原因是:

    NavigationController and the View Controllers are designed in such a way that only one view controller may show at a time. When a new view controller is pushed/presented the previous view controller will be hidden by the system. So when you reduce the modal view's alpha you will possibly see the window's backgroundColor (the black color you see now).

    If you want a translucent view to slide-in over the main view, you can add the view as the subView of main view and animate it using UIView Animations.

    解决方法可以是 :直接动画添加view
    或者 设置模态VC的属性 modalPresentationStyle 为
    UIModalPresentationCustom

    12. App支持从4种来源去打开一个VC页面

    • Push推送
    • App外部网页打开
    • App内部网页打开
    • 应用内点击打开
    页面打开来源

    这四种方式均跳转到 DetailViewController 界面。普通的跳转依然可以满足该场景, 最简单的解决方案是在四个不同的地方都写一个独立的界面打开逻辑。作为一名有追求的开发者, 这么冗余的四份入口代码显然不合适。

    一种解决方案是采用 URL 协议统一跳转。每个 viewController 页面定义与之对应的 URL,在各个入口只需要调用打开该URL的方法即可完成页面的创建以及跳转。

    基于URL的路由方案:

    SNMediator 是用于 iOS 应用进行模块化拆分的中间件框架,它不依赖任何第三方库,基于 URL 协议实现三端(iOS, Android, H5)统一的页面跳转方式。

    例如:你的 APP DetailViewController 界面对应URL定义为:
    myapp://businessModule/goodsdetails/?id=100
    其中,scheme为 myapp,host为 businessModule,path为 goodsdetails,携带参数 id=100.

    使用 SNMediator 跳转页面方法如下:

    + (BOOL)routeURL:(nonnull NSURL *)URL withParams:(nullable NSDictionary *)params completion:(void(^ _Nullable)(id _Nullable result))completion;
    
    [SNMediator routeURL:@"myapp://businessModule/goodsdetails/?id=100" params:nil completion:NULL];
    

    SNMediator 支持通过字典传递额外的自定义复杂对象,也支持URL自身携带参数。所以4种入口都可以通过这一句代码调用完成页面跳转,保持了不同入口跳转同一界面的代码一致性。

    13. iOS 中一个viewController只能 present 出来唯一一个其他viewController

    如果你要在同一个 viewController 中上同时 present 两个viewController,比如:

        SNViewControllerOne *oneVC = [[SNViewControllerOne alloc] init];
        [self presentViewController:oneVC animated:YES completion:^{
            SNViewControllerTwo *twoVC = [[SNViewControllerTwo alloc] init];
            [self presentViewController:twoVC animated:YES completion:NULL];
        }];
    

    此时界面上只会显示 oneVC 的视图,不会显示 twoVC 并且 twoVC 也不存在于视图栈中。这是因为当一个新的 viewController 被 push/present 时,先前的那个 viewController 就会被系统隐藏,所以不会出现在视图栈中。

    When a new view controller is pushed/presented the previous view controller will be hidden by the system.

    并且在控制台还会给出警告:

    Warning: Attempt to present < SNViewControllerTwo: 0x7ff813c5c760> on <SNRootViewController: 0x7ff813e307c0> whose view is not in the window hierarchy!
    

    告诉你当 SNRootViewController present 出来 SNViewControllerOne 后,再试图 present SNViewControllerTwo 时,SNRootViewController 已经被隐藏,不再存在于 window 的视图层级中,所以也就无法在 SNRootViewController 基础上继续 present 另一个视图。

    14. UIGestureRecognizerState 各个状态的变化

    UIGestureRecognizerState的定义如下:

    typedef NS_ENUM(NSInteger, UIGestureRecognizerState) {
        UIGestureRecognizerStatePossible,  
        UIGestureRecognizerStateBegan,    
        UIGestureRecognizerStateChanged,    
        UIGestureRecognizerStateEnded,   
        UIGestureRecognizerStateCancelled, 
        UIGestureRecognizerStateFailed,    
        UIGestureRecognizerStateRecognized = UIGestureRecognizerStateEnded 
    };
    

    Remarks:
    This describes the state of a UIGestureRecognizer. All of UIGestureRecognizers start in the Possible state. Once one or more touches has been received, the recognizers transition to the Began state. For one-shot patterns (like Tap), this will either transition into the Recognized state or the Failed state. For continuous gestures (like panning, pinching, rotating) the recognizer will transition to the Changed state and emit multiple calls back to the action and finally transition to either the Ended or Cancelled states.

    相关文章

      网友评论

        本文标题:iOS开发琐碎知识点总结

        本文链接:https://www.haomeiwen.com/subject/zldqextx.html