iOS | 常用技术点小记(3)

TabBar设置shadowImage不起作用问题

要同时设置backgroundImageshadowImage才行

tabBar.backgroundImage = .init()
tabBar.shadowImage = .init()

切换导航栏透明效果

func updateNavBar(tintColor: UIColor?, barColor: UIColor?, isTranslucent: Bool) {
        let bar = navigationController?.navigationBar
        bar?.tintColor = tintColor
        bar?.setBackgroundImage(barColor?.renderToImage(), for: .default)
        var attrs = bar?.titleTextAttributes
        attrs?[NSAttributedString.Key.foregroundColor] = tintColor
        bar?.titleTextAttributes = attrs
        bar?.isTranslucent = isTranslucent
        bar?.barTintColor = barColor
    }

设置导航栏translucent跳动问题

/// VC里设置
extendedLayoutIncludesOpaqueBars = true
edgesForExtendedLayout = [.all]

HandyJSON deserialize(from:designatedPath:)失败

extension String: _BuiltInBasicType {

    static func _transform(from object: Any) -> String? {
        switch object {
        case let str as String:
            return str
        case let num as NSNumber:
            // Boolean Type Inside
            if NSStringFromClass(type(of: num)) == "__NSCFBoolean" {
                if num.boolValue {
                    return "true"
                } else {
                    return "false"
                }
            }
            return formatter.string(from: num)
        case _ as NSNull:
            return nil
        case _ as NSObject:
            do {
                let data = try JSONSerialization.data(withJSONObject: object, options: .fragmentsAllowed)
                return String(data: data, encoding: .utf8)
            }catch {
                fatalError(error.localizedDescription)
            }
        default:
            fatalError("HandyJSON 数据解析失败,请检查数据格式!")
        }
    }

    func _plainValue() -> Any? {
        return self
    }
}

Collection View头部悬停

flowLayout.sectionHeadersPinToVisibleBounds = true
flowLayout.sectionFootersPinToVisibleBounds = true

Swift方法OC指定

@objc(registerClass:forCellWithReuseIdentifier:)
    open func register(_ cellClass: Swift.AnyClass?, forCellWithReuseIdentifier identifier: String) {
        self.collectionView.register(cellClass, forCellWithReuseIdentifier: identifier)
    }

Xib构建视图

#if TARGET_INTERFACE_BUILDER
    open override func prepareForInterfaceBuilder() {
        super.prepareForInterfaceBuilder()
        self.contentView.layer.borderWidth = 1
        self.contentView.layer.cornerRadius = 5
        self.contentView.layer.masksToBounds = true
        self.contentView.frame = self.bounds
        let label = UILabel(frame: self.contentView.bounds)
        label.textAlignment = .center
        label.font = UIFont.boldSystemFont(ofSize: 25)
        label.text = "FSPagerView"
        self.contentView.addSubview(label)
    }
#endif

更改Info.plist,报错:Multiple commands produce...

BuildPhases->Copy Bundle Resources-> 删除Info.plist文件即可

prefersStatusBarHidden问题

在iOS7中,实际上有一个称为modalPresentationCapturesStatusBarAppearance的UIViewController的新属性,Default value is NO.

When you present a view controller by calling the presentViewController:animated:completion: method, status bar appearance control is transferred from the presenting to the presented view controller only if the presented controller’s modalPresentationStyle value is UIModalPresentationFullScreen. By setting this property to YES, you specify the presented view controller controls status bar appearance, even though presented non–fullscreen.

The system ignores this property’s value for a view controller presented fullscreen.

Swift图片尺寸适配

extension CGSize {
    func fitMaxSize(_ maxSize: CGSize = .init(width: UIScreen.main.bounds.width / 2, height: UIScreen.main.bounds.width / 2))->CGSize {
        var w = width
        var h = height
        let ratio = w / h
        let maxW = maxSize.width
        let maxH = maxSize.height
        if w > maxW {
            w = maxW
            h = w / ratio
        }
        if h > maxH {
            h = maxH
            w = h * ratio
        }
        /// 如果上述缩放后还是有任一边超过最大值,继续缩放
        if w > maxW || h > maxH {
            return fitMaxSize()
        }
        return .init(width: w, height: h)
    }
}

每次编译修改build号

#!/bin/bash
buildNumber=$(date +%Y%m%d.%H%M)
/usr/libexec/PlistBuddy -c "Set :CFBundleVersion $buildNumber" "$INFOPLIST_FILE"

真机调试报错libclang_rt.asan_ios_dynamic.dylib __asan::AsanDie:

Scheme里取消勾选

Xcode里alt复制文件

注意在xcode里工程目录里如果alt拖动复制的文件如xib等,默认是没有勾选进target的,所以需要手动勾选,否则会出现xib未被load的情况,导致cell的register和dequeue失败。

github代理设置

open ~/.gitglobalconfig,添加:

[http]
    proxy = 127.0.0.1:7890(端口号不同的代理软件可能不一样)

git终端代理设置

export https_proxy=http://127.0.0.1:7890 http_proxy=http://127.0.0.1:7890 all_proxy=socks5://127.0.0.1:7890

然后开启VPN,再执行git clone就会非常快

Flutter SDK环境变量设置

export PATH=$PATH:/Volumes/Document/Flutter/SDK/flutter/bin

  • 注意路径不能有"号

CodeSnippets路径

~/Library/Developer/Xcode/UserData/CodeSnippets

UIScrollView实时滚动方向判断

/// 这里存储上一次偏移量
- (void)scrollViewWillBeginDragging:(UIScrollView *)scrollView{
    self.lastScrollOffsetY = scrollView.contentOffset.y;
}

/// 这里进行实时滚动方向判断
- (void)scrollViewDidScroll:(UIScrollView *)scrollView{
    CGFloat y = scrollView.contentOffset.y;
    CGFloat Y = self.lastScrollOffsetY;
    BOOL isScrollDown = y-Y < 0;
}

NSDictionary全类型

/// __NSDictionaryI 有初始值的
    NSDictionary *d1 = @{@"name": @"1", @"age": NSNull.null};
    NSDictionary *d6 = [NSDictionary dictionaryWithObjectsAndKeys:@1,@"2",@2,@"",nil];
    /// __NSDictionary0 空字典
    NSDictionary *d2 = [NSDictionary new];
    NSDictionary *d3 = [NSDictionary dictionary];
    NSDictionary *d4 = [NSDictionary dictionaryWithDictionary:@{}];
    NSDictionary *d7 = @{}.copy;
    /// __NSSingleEntryDictionaryI 单容量
    NSDictionary *d5 = [NSDictionary dictionaryWithObject:@1 forKey:@"2"];
    /// __NSArrayM 可变字典
    NSDictionary *d8 = [NSMutableArray new];
/// __NSFrozenDictionaryM

Charles抓包https显示unknown

1、手机浏览器输入chls.pro/ssl安装证书(通用->描述文文件与设备管理->选中Charles证书安装)。
2、安装好之后要设置信任该证书(通用->关于本机->设置信任该证书)

获取启动storyboard的入口vc

    id name = NSBundle.mainBundle.infoDictionary[@"UILaunchStoryboardName"];
    UIStoryboard *sd = [UIStoryboard storyboardWithName:name bundle:nil];
    UIViewController *vc = sd.instantiateInitialViewController;

OC runtime方法交换的正确用法

/// 方法交换
+ (void)zhlxib_swizzleMethod:(SEL)sel1 method2:(SEL)sel2{
    Method m1 = class_getInstanceMethod(self, sel1);
    Method m2 = class_getInstanceMethod(self, sel2);
    if (m1 && m2) {
        BOOL m1Added = class_addMethod(self, sel1, method_getImplementation(m2), method_getTypeEncoding(m2));
        if (m1Added) {
            class_replaceMethod(self, sel2, method_getImplementation(m1), method_getTypeEncoding(m1));
        }
        else{
            method_exchangeImplementations(m1, m2);
        }
    }
}

iOS转屏

  • modal模式只支持fullscreen

    • 设置shouldAutoRotate为YES
    • 设置supportedInterfaceOrientations
    • 设置preferredInterfaceOrientationForPresentation
  • push模式只能通过强制转屏来实现(设置UIDevice的orientation)

    • 这种情况只能present出来一个nav的vc,然后在vc设置横屏并且全屏

DoKit

https://www.dokit.cn/#/index/home

Lookin

https://lookin.work/

EchoSDK

https://github.com/didi/echo

关于布局priority

需要注意的是,只能修改可选约束的优先级,也就是说:

  • 不允许将优先级由小于1000的值改为1000
  • 不允许将优先级由1000修改为小于1000的值

如果将优先级由250修改为1000,则会抛出异常,所以要做布局的切换时,只能从high和low之间设置;

NSUserDefaults.standardUserDefaults().registerDefaults(["maxCount": 3])

设置key不存在时,返回的默认值,但registerDefaults 设置的默认值是不会持久化存储的
参考

iOS使用.svg 参考

Xcode默认不支持.svg文件,若将文件拖入Assets.xcassets则Xcode自动将该文件当成SymbolImage处理,变成.symbolset,但可以通过修改Contents.json文件取巧

// 增加properties属性
{
  "images" : [
    {
      "filename" : "menu.svg",
      "idiom" : "universal"
    }
  ],
  "info" : {
    "author" : "xcode",
    "version" : 1
  },
  "properties" : {
    "preserves-vector-representation" : true
  }
}

代码使用

UIImage *img = [UIImage imageNamed:@"menu"];
    CGFloat ration = MIN(img.size.height/img.size.width, img.size.width/img.size.height);
    CGRect rect = CGRectMake(0, 0, self.view.bounds.size.width, self.view.bounds.size.height*ration);
    
    UIImageView *iv = [[UIImageView alloc] initWithFrame:rect];
    iv.backgroundColor = UIColor.greenColor;
    iv.image = [img imageWithTintColor:UIColor.redColor];
    
    [self.view addSubview:iv];

NSString中截取数字的2种方法

  • 官方方法,NSScanner,但有点绕
        NSMutableString *noStr = [NSMutableString new];
        NSString *tmpStr;
        NSScanner *sc = [NSScanner scannerWithString:appNo1];
        while (!sc.isAtEnd) {
            /// 丢弃数字之前的
            [sc scanUpToCharactersFromSet:NSCharacterSet.decimalDigitCharacterSet intoString:NULL];
            /// 保留数字部分
            [sc scanCharactersFromSet:NSCharacterSet.decimalDigitCharacterSet intoString:&tmpStr];
            [noStr appendString:tmpStr];
            tmpStr = @"";
        }

  • 循环判断截取,简单容易理解
NSMutableString *noStr = [NSMutableString new];
        for (int i=0; i

UISlider拖动事件监听

[self.slider addTarget:self action:@selector(sliderValueChanged:event:) forControlEvents:UIControlEventValueChanged];

- (void)sliderValueChanged:(UISlider *)sender event:(UIEvent *)event{
    UITouch *touch = event.allTouches.anyObject;
    self.isDragging = YES;
    switch (touch.phase) {
        case UITouchPhaseBegan:
            self.pausePlayBlock ? self.pausePlayBlock() : nil;
            break;
        case UITouchPhaseMoved:
            self.adjustProgressBlock ? self.adjustProgressBlock(self.slider.value) : nil;
            break;
        case UITouchPhaseEnded:
        case UITouchPhaseCancelled:
            self.playPlayBlock ? self.playPlayBlock() : nil;
            self.isDragging = NO;
            break;
        default:
            break;
    }
}

UIStackView布局

  • 先指定sv的frame布局,sv内部会自动布局所有subviews,自动设置subview的frame布局(自动伸缩布局),如果你先自定义subview内部subviews的布局,就必须再包裹一层uiview进行布局;
  • 如果你不指定sv的frame宽高,则sv会根据它的subviews的宽高计算出它自己的宽高,这时候要求subview的宽高是明确的可以计算出来的;
  • UIStackView只参与布局的计算,不会被渲染显示出来;

数组越界runtime处理

//
//  NSArray+ZHL.m

#import 
#import "NSArray+ZHL.h"

@implementation NSArray (ZHL)

+ (void)load{
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        
        id list = @[
            @{
                @"class": @"__NSArray0",
                @"selector_suffix": @"0"
            },
            @{
                @"class": @"__NSArrayI",
                @"selector_suffix": @"I"
            },
            @{
                @"class": @"__NSSingleObjectArrayI",
                @"selector_suffix": @"SingleObject"
            },
            @{
                @"class": @"__NSArrayM",
                @"selector_suffix": @"M"
            },
            @{
                @"class": @"__NSFrozenArrayM",
                @"selector_suffix": @"FrozenM"
            },
        ];
        
        for (id item in list)
        {
            id sel1 = @"objectAtIndex";
            id sel2 = @"objectAtIndexedSubscript";
            Class class = NSClassFromString(item[@"class"]);
            id suffix = item[@"selector_suffix"];
            
            SEL SEL1 = NSSelectorFromString([NSString stringWithFormat:@"%@:",sel1]);
            SEL _SEL1 = NSSelectorFromString([NSString stringWithFormat:@"zhl_%@%@:",sel1,suffix]);
            
            SEL SEL2 = NSSelectorFromString([NSString stringWithFormat:@"%@:",sel2]);
            SEL _SEL2 = NSSelectorFromString([NSString stringWithFormat:@"zhl_%@%@:",sel2,suffix]);
            
            [self exchangeSelector1:SEL1 class1:class selector2:_SEL1 class2:self];
            [self exchangeSelector1:SEL2 class1:class selector2:_SEL2 class2:self];
        }
    });
}

#pragma mark 方法交换
+ (void)exchangeSelector1:(SEL)sel1 class1:(Class)cls1 selector2:(SEL)sel2 class2:(Class)cls2{
    Method m1 = class_getInstanceMethod(cls1, sel1);
    Method m2 = class_getInstanceMethod(cls2, sel2);
    if (m1 && m2) {
        method_exchangeImplementations(m1, m2);
    }
}

#pragma mark - index方法
- (id)zhl_objectAtIndexFrozenM:(NSUInteger)index{
    NSAssert(index

Pods中使用Swift和Objective-C混编-编译不通过的原因-ld: symbol(s) not found for architecture arm64

原因:一般人在Objective-C项目和Swift混编时,会创建桥接文件:项目名-Bridging-Header.h .
在Objective-C项目中新建Swift文件时会自动提示是否创建桥接文件,点击蓝色按钮同意就行了.
而出现这个问题的原因是,桥接文件创建成功之后我把我创建的Swift文件给删除了(因为个人认为用不到),如是就提示上面的警告,并且编译不通过。

解决:在项目中新增一个.swift文件即可

自定义NavigationController

let nav = UINavigationController(navigationBarClass: CustomNavigationBar.self, toolbarClass: nil)
nav.pushViewController(FeedViewController(), animated: false)

class CustomNavigationBar: UINavigationBar {
  let titleLabel: UILabel = {
    let label = UILabel()
    label.backgroundColor = .clear
    label.text = "MARSLINK"
    label.font = AppFont()
    label.textAlignment = .center
    label.textColor = .white
    return label
  }()
  
  let statusLabel: UILabel = {
    let label = UILabel()
    label.backgroundColor = .clear
    label.text = "RECEIVING"
    label.font = AppFont(size: 13)
    label.textAlignment = .center
    label.textColor = UIColor(hex6: 0x42c84b)
    label.sizeToFit()
    return label
  }()
  
  let statusIndicator: CAShapeLayer = {
    let layer = CAShapeLayer()
    layer.strokeColor = UIColor.white.cgColor
    layer.lineWidth = 1
    layer.fillColor = UIColor.black.cgColor
    let size: CGFloat = 8
    let frame = CGRect(x: 0, y: 0, width: size, height: size)
    layer.path = UIBezierPath(roundedRect: frame, cornerRadius: size / 2).cgPath
    layer.frame = frame
    return layer
  }()
  
  let highlightLayer: CAShapeLayer = {
    let layer = CAShapeLayer()
    layer.fillColor = UIColor(hex6: 0x76879D).cgColor
    return layer
  }()

  var statusOn = false
  
  override init(frame: CGRect) {
    super.init(frame: frame)
    layer.addSublayer(highlightLayer)
    layer.addSublayer(statusIndicator)
    addSubview(titleLabel)
    addSubview(statusLabel)
    barTintColor = .black
    updateStatus()
  }
  
  required init?(coder aDecoder: NSCoder) {
    fatalError("init(coder:) has not been implemented")
  }
  
  override func layoutSubviews() {
    super.layoutSubviews()
    let titleWidth: CGFloat = 130
    let borderHeight: CGFloat = 4
    
    let path = UIBezierPath()
    path.move(to: .zero)
    path.addLine(to: CGPoint(x: titleWidth, y: 0))
    path.addLine(to: CGPoint(x: titleWidth, y: bounds.height - borderHeight))
    path.addLine(to: CGPoint(x: bounds.width, y: bounds.height - borderHeight))
    path.addLine(to: CGPoint(x: bounds.width, y: bounds.height))
    path.addLine(to: CGPoint(x: 0, y: bounds.height))
    path.close()
    highlightLayer.path = path.cgPath
    
    titleLabel.frame = CGRect(x: 0, y: 0, width: titleWidth, height: bounds.height)
    statusLabel.frame = CGRect(
      x: bounds.width - statusLabel.bounds.width - CommonInsets.right,
      y: bounds.height - borderHeight - statusLabel.bounds.height - 6,
      width: statusLabel.bounds.width,
      height: statusLabel.bounds.height
    )
    statusIndicator.position = CGPoint(x: statusLabel.center.x - 50, y: statusLabel.center.y - 1)
  }
  
  func updateStatus() {
    statusOn.toggle()
    CATransaction.begin()
    CATransaction.setValue(kCFBooleanTrue, forKey: kCATransactionDisableActions)
    statusIndicator.fillColor = (statusOn ? UIColor.white : UIColor.black).cgColor
    CATransaction.commit()
    DispatchQueue.main.asyncAfter(deadline: DispatchTime.now() + 0.6) {
      self.updateStatus()
    }
  }

}

文本高度计算并缓存

public struct TextSize {
  private struct CacheEntry: Hashable, Equatable {
    let text: String
    let font: UIFont
    let width: CGFloat
    let insets: UIEdgeInsets
    
    func hash(into hasher: inout Hasher) {
      hasher.combine(text)
      hasher.combine(width)
      hasher.combine(insets.top)
      hasher.combine(insets.left)
      hasher.combine(insets.bottom)
      hasher.combine(insets.right)
    }
    
    static func ==(lhs: TextSize.CacheEntry, rhs: TextSize.CacheEntry) -> Bool {
      return lhs.width == rhs.width && lhs.insets == rhs.insets && lhs.text == rhs.text
    }
  }
  
  private static var cache: [CacheEntry: CGRect] = [:] {
    didSet {
      assert(Thread.isMainThread)
    }
  }
  
  public static func size(_ text: String, font: UIFont, width: CGFloat, insets: UIEdgeInsets = .zero) -> CGRect {
    let key = CacheEntry(text: text, font: font, width: width, insets: insets)
    if let hit = cache[key] {
      return hit
    }
    
    let constrainedSize = CGSize(width: width - insets.left - insets.right, height: .greatestFiniteMagnitude)
    let attributes = [NSAttributedString.Key.font: font]
    let options: NSStringDrawingOptions = [.usesFontLeading, .usesLineFragmentOrigin]
    var bounds = (text as NSString).boundingRect(with: constrainedSize, options: options, attributes: attributes, context: nil)
    bounds.size.width = width
    bounds.size.height = ceil(bounds.height + insets.top + insets.bottom)
    cache[key] = bounds
    return bounds
  }
}

快捷转换hex6颜色到RGBA

/// hex6 color
UIColor *_HEXColor(UInt32 hex6){
    CGFloat divisor = 255;
    CGFloat red     = (CGFloat)((hex6 & 0xFF0000)>>16) / divisor;
    CGFloat green   = (CGFloat)((hex6 & 0x00FF00)>>8) / divisor;
    CGFloat blue    = (CGFloat)(hex6 & 0x0000FF) / divisor;
    return [UIColor colorWithRed:red green:green blue:blue alpha:1];
}

第一次安装CocoaPods后拉去Specs慢的问题

用lantern开启代理,然后执行pod setup大概2个小时左右就能clone完成;
这个问题主要在每次pod版本升级后都会遇到,同样的方法;
查看specs的大小https://api.github.com/repos/CocoaPods/Specs

iOS禁用黑暗模式

Info.plist

pod repo注意

当你在Podfile里指定source '[email protected]:flytoo/PTSpecs.git,执行pod install时,pod会自动同步指定的仓库索引到/Users/zhuxuhong/.cocoapods/repos/gitee-ptspecs文件夹;但是如你在pod repo push PTSpecs时指定的不是gitee-ptspecs,那么在你执行pod install时,就会报错误Unable to find a specification for xxx

正确顺序应该为

  1. pod repo add PTSpecs [email protected]:flytoo/PTSpecs.git
  2. pod repo push PTSpecs *.podspec --allow-warnings --verbose
  3. pod repo update gitee-ptspecs
  4. pod update --no-repo-update --verbose

.podspec文件路径注意

s.source_files = 'DemoPodLibs/Classes/Sub1/**/*'

或者

s.source_files = 'DemoPodLibs/Classes/Sub1/*.{h,m}'

Debug模式下无法断点调试Pods库

改为如图,默认是None

将JSON对象中的null替换为@{}

使用

id str = @"{\"status\":0, \"data\":\{\"list\": [], \"user\": null }, \"msg\": \"\"}";
    id json = [NSJSONSerialization JSONObjectWithData:[str dataUsingEncoding:NSUTF8StringEncoding] options:NSJSONReadingMutableContainers error:nil];
/// 这样取值会崩溃,因为null对象不等于nil指针,null对象无法调用方法(NSNull.null为NSNull的一个单例,并没有下标方法,它是一个对象,而非nil指针)
    NSLog(@"%@",json[@"data"][@"user"][@"name"]);
/// 这样不会崩溃,因为nil取值(调用下标方法无效但不崩溃)
    NSString *userName = [JSONTool nullsReplaced:json][@"data"][@"user"][@"name"];
    NSLog(@"after: %d",(userName == nil));

代码

+ (id)nullsReplaced: (id)object{
    if ([object isKindOfClass:[NSDictionary class]]){
        return [self nullDic:object];
    }
    else if([object isKindOfClass:[NSArray class]]){
        return [self nullArr:object];
    }
    else if([object isKindOfClass:[NSString class]]){
        return [self stringToString:object];
    }
    else if([object isKindOfClass:[NSNull class]]){
        return [self nullToDictionary];
    }
    return object;
}

/// 处理字典类型
+ (NSDictionary *)nullDic: (NSDictionary *)myDic{
    NSArray *keyArr = [myDic allKeys];
    NSMutableDictionary *resDic = [[NSMutableDictionary alloc]init];
    for (int i = 0; i < keyArr.count; i ++){
        id obj = [myDic objectForKey:keyArr[i]];
        obj = [self nullsReplaced:obj];
        [resDic setObject:obj forKey:keyArr[i]];
    }
    return resDic;
}

/// 处理数组类型
+ (NSArray *)nullArr: (NSArray *)myArr{
    NSMutableArray *resArr = [[NSMutableArray alloc] init];
    for (int i = 0; i < myArr.count; i ++){
        id obj = myArr[i];
        obj = [self nullsReplaced:obj];
        [resArr addObject:obj];
    }
    return resArr;
}

+ (NSString *)stringToString:(NSString *)string{
    return string;
}

/// 将`null`类型的项目转化成 @{}
+ (NSDictionary *)nullToDictionary{
    return @{};
}

柔和的弹跳缩放动画

self.wrapperView.transform = CGAffineTransformMakeScale(0.3, 0.3);
[UIView animateWithDuration:0.65 delay:0 usingSpringWithDamping:0.65 initialSpringVelocity:1 options:UIViewAnimationOptionCurveEaseInOut animations:^{
        self.wrapperView.alpha = 1;
        self.wrapperView.transform = CGAffineTransformMakeScale(1, 1);
    } completion:nil];

NSPredicate使用

_goodsList = @[
  @{
  @"status": @1
},@{
  @"status": @2
}
];
id pre = [NSPredicate predicateWithFormat:@"self.status != '2'"];
_goodsList = [_goodsList filteredArrayUsingPredicate:pre];

github访问加速

Cocoapods master快速版本

git clone https://git.coding.net/CocoaPods/Specs.git ~/.cocoapods/repos/master

Cocoapods 安装指定版本

sudo gem install cocoapods  -v 1.7.5 -n /usr/local/bin

Xcode中注释+代码分割标记

///MARK: 这是方法注释,又起到分割作用
+ (void)testFunc{
}
///MARK: - 这是方法注释,又起到带横线分割作用
效果

setObject:forkey:与setValue:forKey:的区别

  • setObject:forkey: 中 object 是不能够为 nil 的,不然会报错。
  • setValue:forKey: 中 value 能够为 nil,但是当 value 为 nil 的时候,会自动调用removeObjectForKey:方法。
  • setValue:forKey:中 key 的只能是 NSString类型,
  • setValue:forKey:可以应用于任意NSObject,与setValue: forKeyPath相似,同为为KVC方法;
  • setObject:forkey:中的 key是id类型。
  • setObject:forkey:有相对方法removeObjectForKey:

pod lint错误unknown: Encountered an unknown error (Could not find a ios simulator (valid values:).

升级pod或卸载重装即可

// 升级
sudo brew upgrade cocoapods
// 安装指定版本
sudo gem install cocoapods --version x.x.x
// 卸载
sudo gem uninstall cocoapods

Swift4.0变化的ABI

  • 重写父类中的NSObject方法时,必须要在父类方法前加上@obj;
  • Notification.Name.UITextField.textDidChangeNotification等系统组件通知名字相关的改为了UITextField.textDidChangeNotification;
  • String.characters.count改为了String.count;
  • 支持多行字符串定义''' 多行 ''',三个'号标识;
  • extension中可以调用private方法;
  • vc.childViewControllers改为vc.children;
  • vc.removeFromParentViewController()改为vc.removeFromParent();
  • vc.addChildViewController(:)改为vc.addChild(:);
  • UIAlertControllerStyle改为UIAlertController.Style;
  • UIApplicationLaunchOptionsKey改为UIApplication.LaunchOptionsKey;
  • 闭包中如果无参数,则由(Void)改为(),调用时由{_ in}改为{};
  • UIFont.Weight.light;
  • NSAttributedString.Key.foregroundColor等;
  • @Inspectable不可用;

用do{}while(0)来定义宏

Linux和其它代码库里的宏都用do/while(0)来包围执行逻辑,因为它能确保宏的行为总是相同的,而不管在调用代码中使用了多少分号和大括号

#define foo(x) do{ bar(x); baz(x); } while(0) 

iOS网络变化监听

// 添加观察者
CFNotificationCenterAddObserver(CFNotificationCenterGetDarwinNotifyCenter(), //center
                                NULL, // observer
                                onNotifyCallback, // callback                            CFSTR("com.apple.system.config.network_change"), // event name
                                NULL, // object
 CFNotificationSuspensionBehaviorDeliverImmediately);

// 回调
static void onNotifyCallback(CFNotificationCenterRef center, void *observer, CFStringRef name, const void *object, CFDictionaryRef userInfo)
{
    NSString* notifyName = (NSString*)name;
    // this check should really only be necessary if you reuse this one callback method
    //  for multiple Darwin notification events
    if ([notifyName isEqualToString:@"com.apple.system.config.network_change"]) {
        // use the Captive Network API to get more information at this point
        //  https://stackoverflow.com/a/4714842/119114
    } else {
        NSLog(@"intercepted %@", notifyName);
    }
}

nullable/nonnull、__nullable/__nonnull、_Nullable/_Nonnull用法

  - (nullable NSString*)method;
  - (NSString* __nullable)method;
  - (NSString* _Nullable)method;

  - (nonnull NSString*)method;
  - (NSString* __nonnull)method;
  - (NSString* _Nonnull)method;

几个可以用的RTMP电视直播源

  • 香港卫视,rtmp://live.hkstv.hk.lxdns.com/live/hks
  • 香港财经,rtmp://202.69.69.180:443/webcast/bshdlive-pc
  • 韩国GoodTV,rtmp://mobliestream.c3tv.com:554/live/goodtv.sdp
  • 韩国朝鲜日报,rtmp://live.chosun.gscdn.com/live/tvchosun1.stream
  • 美国1,rtmp://ns8.indexforce.com/home/mystream
  • 美国2,rtmp://media3.scctv.net/live/scctv_800
  • 美国中文电视,rtmp://media3.sinovision.net:1935/live/livestream
  • 湖南卫视,rtmp://58.200.131.2:1935/livetv/hunantv

http://ivi.bupt.edu.cn/

实现UITextView滚动到底部

_textView.layoutManager.allowsNonContiguousLayout = NO;
[_textView scrollRangeToVisible:NSMakeRange(_textView.text.length, 1)];

OC实现Swift Class中static var value: String?

// .h
@property(nonatomic, class, copy)NSString *value;

// .m 需实现类属性的setter和getter方法

// 定义私有静态全局变量
static NSString *_kValue;

+ (void)setValue:(NSString *)value{
    _kValue = value;
}

+ (NSString *)value{
    return _kValue;
}

Mac允许任何来源程序安装

sudo spctl --master-disable

@synchronized (self){}

  • 这个主要是考虑多线程的程序,这个指令可以将{}内的代码限制在一个线程执行,如果某个线程没有执行完,其他的线程如果需要执行就得等着。
  • @synchronized 的作用是创建一个互斥锁,保证此时没有其它线程对self对象进行修改,作用类似于NSLock

NSArrayenumerateObjectsUsingBlock:

__block NSInteger index = -1;
    [self.streamLines enumerateObjectsUsingBlock:^(TALStreamInfo * _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) {
        if ([streamInfo.lineId isEqualToString:obj.lineId]) {
            index = idx;
            *stop = YES;
        }
    }];
    return index;

iOS单例方法SDK约束

+ (instancetype)allocWithZone:(struct _NSZone *)zone OBJC_UNAVAILABLE("allocWithZone not available, call sharedInstance instead");
+ (instancetype)alloc OBJC_UNAVAILABLE("alloc not available, call sharedInstance instead");
- (instancetype)init OBJC_UNAVAILABLE("init not available, call sharedInstance instead");
+ (instancetype)new OBJC_UNAVAILABLE("new not available, call sharedInstance instead");
- (instancetype)copy OBJC_UNAVAILABLE("copy not available, call sharedInstance instead");

防止CAAnimationDelegate设置引起的内存泄漏

// 注意delegate是strong类型的,在设置duration后动画开始执行,
// 那么对于delegate是进行了strong持有,需要注意及时self释放
@property(nullable, strong) id  delegate;

防止[self performSelector:withObject:afterDelay:]引起的内存泄漏

- (void)viewWillDisappear:(BOOL)animated{
    [super viewWillDisappear:animated];
    
    [NSObject cancelPreviousPerformRequestsWithTarget:self];
}

AppleDoc文档导出

  • 安装
终端命令运行
git clone git://github.com/tomaz/appledoc.git
cd ./appledoc
sudo sh install-appledoc.sh
  • 使用
cd 到类文件目录
appledoc --project-name "DemoDoc" --project-company "TAL.inc" --no-create-docset ./
  • 效果


    html

AppleDoc注释规范

AppleDoc规范
/**
 * @brief 这是一个AppDoc规范示例方法
 * @param param1 第一个参数
 * @param param2 第二个参数
 * @return BOOL值
 */
- (BOOL)thisIsADemoFuncWithParam1: (NSString *)param1
                            pram2: (NSInteger)param2{
    return NO;
}
效果

XCode代码注释

效果
//MARK: mark here

//TODO: todo here

//FIXME: fix me later

//???: What is this shit

//!!!: DO NOT TOUCH MY CODE

#pragma mark - 带分割线

.ips日志文件分析

  • 终端命令find /Applications/Xcode10.app -name symbolicatecrash -type f找到symbolicatecrash工具,选择xxx/DVTFoundation.framework/Versions/A/Resources/symbolicatecrash这个
  • 将崩溃App的.dSYM文件.ips文件拷贝到symbolicatecrash工具同目录下
  • .ips文件后缀改为.crash
  • 终端运行命令./symbolicatecrash /绝对路径/xxxx.crash /绝对路径/xxx.app.dSYM > xxx.crash
    • 如果提示错误Error: "DEVELOPER_DIR" is not defined at ./symbolicatecrash line 69.
    • 接着运行终端命令:export DEVELOPER_DIR="/Applications/Xcode10.app/Contents/Developer"(注意这个地方的Xcode要指定为xcode-select --switch的对应版本)

Xcode显示编译用时

终端运行命令defaults write com.apple.dt.Xcode ShowBuildOperationDuration -bool YES

实际效果

改善Swift项目编译时间

终端运行命令defaults write com.apple.dt.Xcode BuildSystemScheduleInherentlyParallelCommandsExclusively -bool NO

  • 注意该方法只对Xcode9.2及以上版本生效

你可能感兴趣的:(iOS | 常用技术点小记(3))