Fork me on GitHub

iOS知识点总结

单例

Objective-C 写法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
@interface IHUserManager : NSObject
+ (instancetype)sharedInstance;
@end
@implementation IHUserManager
+ (instancetype)sharedInstance {
static IHUserManager *sharedInstance = nil;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
sharedInstance = [[self alloc] init];
});
return sharedInstance;
}

@end

Swift 写法

1
2
3
4
import Foundation
class IHUserManager {
static let sharedInstance = IHUserManager()
}

block和闭包在对象绑定时的坑

分析

oc 中,使用 runtime 的对象绑定相信大家都使用过,而且在 block 上的使用也经常看到,在 oc 中可以把 block 直接使用 runtime 通过 key 和其他的对象绑定起来,但在 swift 中闭包不是:AnyObject 类型 使用在进行对象绑定时无法进行。
参考:
1.stackoverflow.com
2.http://nshipster.cn

需要使用:
public func unsafeBitCast<T, U>(x: T, _: U.Type) -> U
函数

代码实现

对 UIControl 增加 Block 属性和 Closure 属性

swift 具体的实现如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
typealias IHClosure = @convention(block)() -> ()

extension UIControl {

private struct ClosureKey {
static var touchUpInsideKey = "touchUpInsideKey"
}

func ih_addTouchUpInsideClosure(closure: IHClosure) {
let obj: AnyObject = unsafeBitCast(closure, AnyObject.self)
objc_setAssociatedObject(self, &ClosureKey.touchUpInsideKey, obj, .OBJC_ASSOCIATION_COPY_NONATOMIC)

self.addTarget(self, action: #selector(UIControl._touchUpInside), forControlEvents: .TouchUpInside)
}

@objc private func _touchUpInside() {
let obj: AnyObject = objc_getAssociatedObject(self, &ClosureKey.touchUpInsideKey)
let closure: IHClosure = unsafeBitCast(obj, IHClosure.self)
closure()
}
}

Objective-C 具体的实现如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
#import "UIControl+Block.h"
#import <objc/runtime.h>

const char kTouchUpInside = '\0';

@implementation UIControl (Block)

- (void)ih_addTouchUpInsideWithBlock:(dispatch_block_t)block {
objc_setAssociatedObject(self, @selector(_touchControlEvents), block, OBJC_ASSOCIATION_COPY_NONATOMIC);
[self addTarget:self action:@selector(_touchControlEvents) forControlEvents:UIControlEventTouchUpInside];
}

- (void)_touchControlEvents {
dispatch_block_t block = objc_getAssociatedObject(self, _cmd);
if (block) block();
}
@end

swift 和 oc 之 load方法不同以及处理方案

分析

在 oc 开发中,APP统计时,自然会使用到 方法的替换,下面方法

OBJC_EXPORT void method_exchangeImplementations(Method m1, Method m2) __OSX_AVAILABLE_STARTING(__MAC_10_5, __IPHONE_2_0);

通常会为 vc 添加一个 category 同时在 + load 方法里面的其做相应的处理。但在 swift 中系统不允许使用 + load 方法。那么我们可以想办法使用:
+ initialize 方法

代码实现

oc 具体的实现如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
#import "UIViewController+Swizzle.h"
#import <objc/runtime.h>

@implementation UIViewController (Swizzle)

+ (void)load {
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{

// 获取系统的方法的 方法对象
Method orignViewDidLoad = class_getInstanceMethod([UIViewController class], @selector(viewDidLoad));

// 获取准备用来替换的 方法对象
Method replacingMethod = class_getInstanceMethod([UIViewController class], @selector(_viewDidLoad_Swizzling));


BOOL didAddMethod = class_addMethod([self class], @selector(viewDidLoad), method_getImplementation(replacingMethod), method_getTypeEncoding(replacingMethod));

if (didAddMethod) {
// 替换一下方法地址
class_replaceMethod([self class], @selector(_viewDidLoad_Swizzling), method_getImplementation(orignViewDidLoad), method_getTypeEncoding(orignViewDidLoad));
}else{
// 直接交换的调用
method_exchangeImplementations(orignViewDidLoad, replacingMethod);
}
});
}

+ (void)_viewDidLoad_Swizzling {
[self _viewDidLoad_Swizzling];
}
@end

swift 具体的实现如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
extension UIViewController {

public override class func initialize() {

struct Static {
static var token: dispatch_once_t = 0
}

if self !== UIViewController.self {
return
}

dispatch_once(&Static.token) {
let originalSelector = #selector(UIViewController.viewWillAppear(_:))
let swizzledSelector = #selector(UIViewController.nsh_viewWillAppear(_:))

let originalMethod = class_getInstanceMethod(self, originalSelector)
let swizzledMethod = class_getInstanceMethod(self, swizzledSelector)

let didAddMethod = class_addMethod(self, originalSelector, method_getImplementation(swizzledMethod), method_getTypeEncoding(swizzledMethod))
if didAddMethod {
class_replaceMethod(self, swizzledSelector, method_getImplementation(originalMethod), method_getTypeEncoding(originalMethod))
} else {
method_exchangeImplementations(originalMethod, swizzledMethod);
}
}
}

func nsh_viewWillAppear(animated: Bool) {
self.nsh_viewWillAppear(animated)
}
}

swift 和 oc 之 Log 处理

  • 在 oc 中为: NSLog
  • 在 swift 中为: print

我们在平常的开发中肯定会对 oc 的 NSLog 做一个特殊处理,打印更多的信息,在上线以后关闭日志输出,最简单的实现方式如下:

1
2
3
4
5
#ifdef DEBUG
#define DLog(fmt, ...) NSLog((@"%s [Line %d] " fmt), __PRETTY_FUNCTION__, __LINE__, ##__VA_ARGS__);
#else
#define DLog(...)
#endif

但是在 swift 中没有宏定义,使用也没 oc中的 DEBUG.

简单的代码如下:

1
2
3
4
5
func IHLog<T>(message: T ,
file: String = (#file as NSString).lastPathComponent,
funcName: String = #function, linNUm: Int = #line) -> () {
print("\(file) [\(funcName)] \(linNUm) : \(message)")
}

添加环境控制:

第1步

第2步.png

第3步.png

优化的代码如下:

1
2
3
4
5
6
7
 func IHLog<T>(message: T ,
file: String = (#file as NSString).lastPathComponent,
funcName: String = #function, linNUm: Int = #line) -> () {
#if IS_DEBUG
print("\(file) [\(funcName)] \(linNUm) : \(message)")
#endif
}

PS: IS_DEBUG 是你随便取的哦 在添加的地方必须为: -D+名称,这里我取为 IS_DEBUG

大功告成,release 环境下不会有 log 信息了、O(∩_∩)O哈哈~

Github源码


交换2个值

OC使用的方法就不描述了

Swift 使用元组

1
2
3
var obj1 = "1"
var obj2 = "2"
(obj1, obj2) = (obj2, obj1)

GCD延时函数

1
2
3
4
func delay(_ delay:Double, closure:@escaping ()->()) {
let when = DispatchTime.now() + delay
DispatchQueue.main.asyncAfter(deadline: when, execute: closure)
}
1
2
3
delay(1) {
//...
}

SEL

1
2
3
#selector(UIViewController.present(_:animated:completion:))
let vc = UIViewController()
#selector(vc.self.present(_:animated:completion:))

获取类 class

1
2
self.class
self.self

是否属于一个类型

1
2
3
4
5
6
7
8
9
// 是否属于一个类型
if self is UIViewController {
}
let obj = NSObject()
if obj is [String] {
print("c")
}
if obj is [Any] {
}

copy mutableCopy

1
2
3
4
5
6
// copy mutableCopy
var arr = [String]()
let arr1 = (arr as NSArray).mutableCopy()
let arr2 = (arr as NSArray).copy()
arr = arr1 as! Array<String>
arr = arr2 as! Array<String>

for in

1
2
3
4
5
6
7
8
9

for i in 1...2 {}
for i in 1..<2 {}
for obj in array{}


for (i, obj) in (array?.enumerated())! {
array?[i] = (obj as! NSArray).mutableCopy() as! [Any]
}

属性

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
class Person: NSObject {
var name = ""
}

class BMPerson: Person {

/// 存储属性
var name1 = ""
var name2: String = ""
var name3: String? = ""
var name4: String?

/// 懒加载,swift lazy 和 oc懒加载不同,swift的懒加载永远只会走一次,不是和oc一样做非nil 判断
lazy var name6 = "c"
lazy var name7: String = "c"
lazy var view1: UIView = {
let view = UIView()
view.frame = CGRect.init(x: 0, y: 0, width: 100, height: 100)
view.backgroundColor = .red
return view
}()

/// 计算属性,不会生成功成员变量,等于oc中重写了set get 方法
var name8: String? {
get {
print("get")
return ""
}
set {
print("set")
}
}

/// 重写set get 方法 并且增加私有的成员变量
private var _name9: String?
var name9: String? {
get {
print("get")
return _name9
}
set {
print("set")
_name9 = newValue
}
}

var name10: String? {
willSet {
print("get")
}
didSet {
print("set")
}
}

var name11: String? {
willSet {
print("get")
}
}

/// 初始化方法对属性的设定,以及在 willSet 和 didSet 中对属性的再次设定都不会再次触发属性观察的调用,一般来说这会是你所需要的行为,可以放心使用能够。
var name12: String? {
didSet {
print("set")
var FA = false
FA = arc4random() % 2 == 0
if FA {
print("NO")
name12 = "CC"
}
}
}

/// 监控分类继承的属性变化
override var name: String {
willSet {
print("分类的属性 将要修改")
}
didSet {
print("分类的属性 将已经修改")
}
}

/// 只读属性
private var _name8 : String?
var name8: String? {
get {
return _name8
}
}
}

元组

在 Swift 中创建元组的方式很简单,语法类似数组,但需要把方括号替换为圆括号:

0x00

1
let firstHighScore = ("Mary", 9001)
  • 与数组不同的是,元组中的元素可以是任意类型。上面代码中 firstHighScore 元组就包含一个 String 类型的元素和一个 Int 类型的元素。

    0x01

  • 另外,在创建元组时你还可以给元组中的元素命名:

    1
    let secondHighScore = (name: "James", score: 4096)

    这样可以让我们在使用元组的时候明确的指定某个元素,非常有用。在后面的文章中大家可以看到给元素命名的好处。

    以上就是创建元组的两种方式,非常简单和简洁。你不需要像创建 struct 一样写出它的结构和内部属性,也不需要像创建 class 一样要写初始化方法。你只需要把你想用的、任何类型的值放在圆括号内,用逗号隔开即可。如果你愿意你还可以给每个元素命名,提高元组使用效率。

    从元组中读元素

    从元组中读取元素有几种方式,但一般我们会选择最适合当前应用场景的方式,并且确保选择的方式是在当前情况下最简单的一种。

    0x00

    如果我们没有给元组的元素命名,我们可以用点语法,通过定义好的元组变量或常量获取它的第 1 个到第 n 个元素:

    1
    2
    3
    let firstHighScore = ("Mary", 9001)
    firstHighScore.0 // Mary
    firstHighScore.1 // 9001

    0x01

    如果你觉得上述这种方法会造成语义的不明确,那么我们还可以将元组赋值给一个带有元素名称的元组(元素名称个数要对应):

    1
    2
    3
    let (firstName, firstScore) = firstHighScore
    firstName // Mary
    firstScore // 9001

    0x02

    如果你只想读取 firstHighScore 元组中的分数,那么你可以这样写:

    1
    2
    let (_, firstScore) = firstHighScore
    firstScore // 9001

    0x03

    元组元素有命名
    如果我们已经给元组中的元素命名了名称,那么我们可以这样写:

    1
    2
    3
    4
    let secondName = secondHighScore.name
    let secondScore = secondHighScore.score
    secondName // James
    secondScore // 4096

    0x04

    将元组作为函数返回值
    我们可以将元组作为函数的返回值,下面这个函数的返回值就是我们之前定义过的 secondHighScore 元组:

    1
    2
    3
    4
    5
    6
    func getAHighScore() -> (name: String, score: Int)
    {
    let theName = "Patricia"
    let theScore = 3894
    return (theName, theScore)
    }

    为什么说上述函数的返回值是 secondHighScore 元组呢?因为 getAHighScore 函数返回的元组元素个数、元素名称、元素类型均和 secondHighScore 相同。

    其实将元组作为函数的返回值时也可以不必对元素进行命名,只要你明白每个元素代表的含义即可:

    1
    2
    3
    4
    5
    6
    func getAHighScore() -> (String, Int)
    {
    let theName = "Patricia"
    let theScore = 3894
    return (theName, theScore)
    }
  • 如果你不确定返回的元组一定不为 nil ,那么你可以返回一个可选的元组类型:

    1
    2
    3
    4
    func maybeGetHighScore() -> (String, Int)?
    {
    return nil
    }
  • 因为是可选的元组类型,所以当返回的元组不为 nil 时,你需要对元组进行解包:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    if let possibleScore = maybeGetHighScore()
    {
    possibleScore.0
    possibleScore.1
    }
    else
    {
    println("Nothing Here")
    }
  • 注意:当你定义了一个没有返回值的函数时,其实该函数是返回一个空的元组 ()。

    元组的访问级别
    元组的访问级别取决于它包含的元素。比如元组里的元素都是 private 级别的,那么该元组也是 private 级别的。但这里有一个遵循最小的原则,也就是说如果一个元组中有两个元素,一个为private级别,另一个为 public 级别,那么该元组遵循最小原则,它的访问级别为 private 。

    元组是值类型
    关于值类型和引用类型的知识这里不再累赘,我们通过一个代码示例来看看元组是哪种类型:

    1
    2
    3
    4
    5
    6
    var someScore = ("John", 55)
    var anotherScore = someScore
    anotherScore.0 = "Robert"

    println(anotherScore.0) //Outputs: "Robert"
    println(someScore.0) //Outputs: "John"

    通过上述的代码示例可以看出,我把 someScore 元组赋值给了 anotherScore ,然后修改了 anotherScore 的第1个元素的值,最后分别打印了 someScore 和 anotherScore 第 1 个元素的值。 someScore 元组第一个元素的值为 Robert ,而 anotherScore 元组第一个元素的值仍然为 John 。由此可见元组是值类型。


Swift init 方法

在我们刚刚接触 Swift 时,可能会遇到 init 构造方法 的各种坑,各种报错,那么 Apple 为什么要这样做呢?难道是无聊虐开发者吗?当然不是,其实我们在使用 Objective-C 编码时,对于新手来说太多坑了,比如:在 init 中调各种方法,访问各种属性等,在 Objective-C 中其实 Apple 是不推荐在 init 中访问属性的,因为此时可能部分属性根本没有初始化,可能导致一系列的问题存在。到了 Swift 时,Apple 做了强制措施,彻底解决类似的问题。

AppleSwift 中的 init 方法做各种限制的终极目标就是为了 保证使用者不关使用什么方式创建对象,均保证在初始化完成前所有的属性都被初始化,明白这目的时,对 Swiftinit 就比较好理解了,具体的可以参考下面的文章,就不再一一分析了哈,逃.

iOS的一点调试技巧


下面使用GIF简单快速介绍几种断点调试

copy mutableCopy 对 NSString

写在前面

iOS开发中可能有时候会接触到copymutableCopy这两个方法,而且其相关的内存问题经常出没于面试题中,本文简单介绍下mutableCopycopyNSString和集合类的相关问题。

copy, mutableCopy对NSString操作

在对NSString进行copy或者mutableCopy操作时可以使用下图的内存分配情况来表述

未命名.001.jpeg

未命名.002.jpeg

由上可得,

  • copyNSString 是浅拷贝,只拷贝指

NSString为什么要使用copy修饰

写在前面

NSString 属性为什么应该使用copy修饰,使用strong为什么不行?我们可能都知道NSString/Blocks类型的属性使用copy来修饰,why?最近看到这篇blog感觉讲得较完善,那么我也把自己的一点体会和理解记录下吧,欢迎指正。

  • 我们先定义一个测试类BMPerson
1
2
3
4
5
6
7
@interface BMPerson : NSObject
@property (strong, nonatomic) NSString *strStrong; ///< strStrong
@property (copy, nonatomic) NSString *strCopy; ///< strCopy
@end

@implementation BMPerson
@end
  • main函数中执行如下代码:
1
2
3
4
5
6
7
NSString *str = @"abc";
BMPerson *per = [BMPerson new];
per.strStrong = str;
per.strCopy = str;
NSLog(@"修改str前:per.strStrong = %@ per.strCopy = %@", per.strStrong, per.strCopy);
str = @"qwe";
NSLog(@"修改str前:per.strStrong = %@ per.strCopy = %@", per.strStrong, per.strCopy);

会输出如下的log

1
2
修改str前:per.strStrong = abc per.strCopy = abc
修改str前:per.strStrong = abc per.strCopy = abc
  • 似乎是strongcopy没有任何区别,那么我们换一种方式看看,使用下面的代码测试
1
2
3
4
5
6
7
NSMutableString *str = [NSMutableString stringWithFormat:@"abc"];
BMPerson *per = [BMPerson new];
per.strStrong = str;
per.strCopy = str;
NSLog(@"修改str前:per.strStrong = %@ per.strCopy = %@", per.strStrong, per.strCopy);
[str appendString:@"+a"];
NSLog(@"修改str前:per.strStrong = %@ per.strCopy = %@", per.strStrong, per.strCopy);

会输出如下的log

1
2
修改str前:per.strStrong = abc per.strCopy = abc
修改str前:per.strStrong = abc+a per.strCopy = abc

我们发现本来给 per.strStrong 赋值的是 @“abc”,但在修改了str后发现per.strStrong的值也变了,但是使用copy修饰的并不会变化,在这篇blog的基础上简单画了下内存分配图如下:

  • 第一种方式

  • 第二种方式

  • 由上面的图我们应该可以发现问题所在(如果不清楚可以下方留言哈),所以NSSring类型的属性时我们尽量使用copy修饰吧。

最后

参考


iOS逆向之获取第三方App头文件

之前随便看了下iOS逆向的知识,下面的一个最简单的使用,获取第三方App头文件,如:微信支付宝等。

操作步骤

  • 首先需要用一个工具class-dump点我下载
  • 下载好以后解压如下图:
  • class-dump-3.5文件夹下的class-dump文件夹拖入/usr/local/bin 目录下
  • 打开终端 cd /usr/local/bin
  • 再 执行 sudo chmod 777/usr/local/bin/class-dump
  • 输入密码
  • 下载PP助手或者其他可以下载越狱应用的应用(正版需要砸壳),下载越狱应用后解压
  • 执行 class-dump -H /Users/daredos/Desktop/微信-6.3.23\(越狱应用\)/Payload/WeChat.app -o /Users/daredos/Desktop/wx

/Users/daredos/Desktop/微信-6.3.23\(越狱应用\)/Payload/WeChat.app 表示App全路径

/Users/daredos/Desktop/wx 表示获取到的.h 存放的路径

效果图

在Storyboard 及 Xib 中为 TableView 设置 HeaderView 和 FooterView

本项目主要是介绍在Storyboard(下面检测SB)及Xib中给UITableView设置tableHeaderViewtableFooterView的方法。默认认为已经基本熟悉SB/Xib的使用哈。

在平时的项目开发中,可能使用SB或者Xib会特别快,当然喜欢代码的就可以忽略了哈。我们在用UITableView可能会使用到tableHeaderViewtableFooterView这2个属性,在有一些需求来说是得心应手。之前都是使用代码添加UITableView的tableHeaderViewtableFooterView,虽然也挺方便,那么是否可以使用xib直接添加呢?在网上搜了下,答案是可以的哈,下面的简单介绍下添加的方式。

SB 中给UITableView设置tableHeaderViewtableFooterView



Xib 中给UITableView设置tableHeaderViewtableFooterView



运行结果



怎么样自适应高度

可以添加如下代码完成自适应,参考于这里

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
- (void)viewDidLayoutSubviews {
[super viewDidLayoutSubviews];
{
CGFloat height = [self.tableView.tableFooterView systemLayoutSizeFittingSize:UILayoutFittingCompressedSize].height;
CGRect footerFrame = self.tableView.tableFooterView.frame;
footerFrame.size.height = height;
self.tableView.tableFooterView.frame = footerFrame;
self.tableView.tableFooterView = self.tableView.tableFooterView;
}
{
CGFloat height = [self.tableView.tableHeaderView systemLayoutSizeFittingSize:UILayoutFittingCompressedSize].height;
CGRect headerFrame = self.tableView.tableHeaderView.frame;
headerFrame.size.height = height;
self.tableView.tableHeaderView.frame = headerFrame;
self.tableView.tableHeaderView = self.tableView.tableHeaderView;
}
}

项目源码

参考

iOS开发之@weakify, @strongify

为什么在使用Block时需要注意使用 @weakify@strongify

不使用 @weakify

我们在使用MJRefresh时如果不使用@weakify且如下编写代码

1
2
3
4
5
6
7
self.tableView.mj_header = [MJRefreshNormalHeader headerWithRefreshingBlock:^{
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(1 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
// 这里使用了 self 没有使用__weak
[self.tableView.mj_header endRefreshing];
[self.tableView reloadData];
});
}];

可以使用腾讯微信读书开源的MLeaksFinder很容易检测到内存泄露



不使用 @strongify

下面举一个不使用 @strongify 会出现的问题,代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
__weak typeof(self) wself = self;
dispatch_async(dispatch_get_global_queue(0, 0), ^{
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(1 * NSEC_PER_SEC)), dispatch_get_global_queue(0, 0), ^{
if (self) {
sleep(2);
NSLog(@"%@", @[wself]);
}
});
});

dispatch_async(dispatch_get_global_queue(0, 0), ^{
sleep(2);
dispatch_async(dispatch_get_main_queue(), ^{
[self.navigationController popViewControllerAnimated:YES];
});
});

会直接崩溃,而且我们这里做了判空处理。



总结

由上面的问题可见在使用Block时注意使用@weakify@strongify 是如此的重要,那么上面的代码安全的方式应该是如下代码.

1
2
3
4
5
6
7
8
9
10
__weak typeof(self) wself = self;
self.tableView.mj_header = [MJRefreshNormalHeader headerWithRefreshingBlock:^{
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(1 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
__strong typeof(wself) self = wself;
if (self) {
[self.tableView.mj_header endRefreshing];
[self.tableView reloadData];
}
});
}];
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
__weak typeof(self) wself = self;
dispatch_async(dispatch_get_global_queue(0, 0), ^{
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(1 * NSEC_PER_SEC)), dispatch_get_global_queue(0, 0), ^{
__strong typeof(wself) self = wself;
if (self) {
sleep(2);
NSLog(@"%@", @[self]);
}
});
});

dispatch_async(dispatch_get_global_queue(0, 0), ^{
sleep(2);
dispatch_async(dispatch_get_main_queue(), ^{
__strong typeof(wself) self = wself;
if (self) {
[self.navigationController popViewControllerAnimated:YES];
}
});
});

使用我们在使用Block如果使用了self而且可能有相互引用的情况,那么应该注意 @weakify@strongify 以及 必要的判空操作

iOS开发之自定义View的一些坑

我们做几个简单的例子哈

自定义一个View

View的m文件中有代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
@implementation BMView

- (instancetype)init {
if (self = [super init]) {
NSLog(@"init");
}
return self;
}

- (instancetype)initWithFrame:(CGRect)frame {
if (self = [super initWithFrame:frame]) {
NSLog(@"initWithFrame");
}
return self;
}

@end

使用如下代码:

1
2
3
4
5
6
7
8
9
10
11
NSLog(@"new ");
[BMView new];
NSLog(@"\n ");

NSLog(@"alloc init");
[[BMView alloc] init];
NSLog(@"\n ");

NSLog(@"alloc initWithFrame");
[[BMView alloc] initWithFrame:CGRectMake(100, 100, 100, 100)];
NSLog(@"\n ");

输出如下:

1
2
3
4
5
6
7
8
9
10
11
12
02.165 ViewDemo[32765:1250187] new 
02.165 ViewDemo[32765:1250187] initWithFrame
02.165 ViewDemo[32765:1250187] init
02.166 ViewDemo[32765:1250187]

02.166 ViewDemo[32765:1250187] alloc init
02.166 ViewDemo[32765:1250187] initWithFrame
02.166 ViewDemo[32765:1250187] init
02.166 ViewDemo[32765:1250187]

02.167 ViewDemo[32765:1250187] alloc initWithFrame
02.167 ViewDemo[32765:1250187] initWithFrame

我们发现

  • 使用newalloc init会调用 initWithFrame init方法
  • 使用initWithFrame会只会调用 initWithFrame方法

自定义BMTableView

UITableView1的m文件中有代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
@implementation BMTableView

- (instancetype)init {
if (self = [super init]) {
NSLog(@"init");
}
return self;
}

- (instancetype)initWithFrame:(CGRect)frame {
if (self = [super initWithFrame:frame]) {
NSLog(@"initWithFrame");
}
return self;
}

- (instancetype)initWithFrame:(CGRect)frame style:(UITableViewStyle)style {
if (self = [super initWithFrame:frame style:style]) {
NSLog(@"initWithFrame style");
}
return self;
}

@end

使用如下代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
NSLog(@"new");
[BMTableView new];
NSLog(@"\n");

NSLog(@"alloc init");
[[BMTableView alloc] init];
NSLog(@"\n");

NSLog(@"alloc initWithFrame");
[[BMTableView alloc] initWithFrame:CGRectMake(100, 100, 100, 100)];
NSLog(@"\n");

NSLog(@"alloc initWithFrame style");
[[BMTableView alloc] initWithFrame:CGRectMake(100, 100, 100, 100) style:UITableViewStylePlain];
NSLog(@"\n");

输出如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
43.615 ViewDemo[32847:1259789] new
43.637 ViewDemo[32847:1259789] initWithFrame style
43.637 ViewDemo[32847:1259789] initWithFrame
43.637 ViewDemo[32847:1259789] init
43.638 ViewDemo[32847:1259789]
43.638 ViewDemo[32847:1259789] alloc init
43.639 ViewDemo[32847:1259789] initWithFrame style
43.639 ViewDemo[32847:1259789] initWithFrame
43.640 ViewDemo[32847:1259789] init
43.640 ViewDemo[32847:1259789]
43.640 ViewDemo[32847:1259789] alloc initWithFrame
43.641 ViewDemo[32847:1259789] initWithFrame style
43.641 ViewDemo[32847:1259789] initWithFrame
43.642 ViewDemo[32847:1259789]
43.642 ViewDemo[32847:1259789] alloc initWithFrame style
43.642 ViewDemo[32847:1259789] initWithFrame style

我们发现

  • 使用newalloc init会调用 initWithFrame style initWithFrame init方法
  • 使用initWithFrame会调 initWithFrame style initWithFrame方法
  • 使用initWithFrame style会只会调用 initWithFrame style 方法

why

为什么我们只是单纯的调用了 new 或者 init 就会跑到了各种构造方法中呢?感觉有一些奇怪,其实是见怪不怪。我们可以通过Chameleon查看UIViewUITableView的底层实现便一目了然。

UIView.m

UIViewm文件中可看到如下代码UIView.m

1
2
3
4
5
6
7
8
9
10
11
12
- (id)init
{
return [self initWithFrame:CGRectZero];
}

- (id)initWithFrame:(CGRect)theFrame
{
if ((self=[super init])) {
// ...
}
return self;
}

UITableView.m

UITableViewm文件中可看到如下代码: UITableView.m

1
2
3
4
5
6
7
8
9
10
11
12
- (id)initWithFrame:(CGRect)frame
{
return [self initWithFrame:frame style:UITableViewStylePlain];
}

- (id)initWithFrame:(CGRect)frame style:(UITableViewStyle)theStyle
{
if ((self = [super initWithFrame:frame])) {
// ...
}
return self;
}

看到源码后当然就一目了然了,所以我们在自定义一些UI控件时不要盲目的重写各种构造方法,感觉好高大上,其实就是一个大坑😀,之前博主也踩过😭(可能是我孤陋寡闻了)。感谢团队中阿海的指出。随便提醒下,我们可以通过https://github.com/BigZaphod/Chameleon知道天天使用的UITableView为什么那么纵享丝滑,或者各种好玩的东西。

如何编写initWith... 方法

自定义一个类BMObj,编写如下代码,是否有不合适的地方?

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
- (instancetype)init {
if (self = [super init]) {
//...
}
return self;
}

- (instancetype)initWithName:(NSString *)name age:(int)age {
if (self = [super init]) {
_name = name;
_age = age;
//...
}
return self;
}

上面的写法其实有一些问题,正确的编写如下:

1
2
3
4
5
6
7
8
9
10
11
- (instancetype)init {
return [self initWithName:nil age:0];
}

- (instancetype)initWithName:(NSString *)name age:(int)age {
if (self = [super init]) {
_name = name;
_age = age;
}
return self;
}

之所以这样编写init...是为了达到和系统init...一样的目的,可移步这里查看究竟。同时可以查看一些第三方的写法也如此YYCache

参考

一些小知识点

结构体

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
struct BMerson {
int age;
double height;
};

typedef struct BMerson BMerson;

BMerson BMersonMake1(int a, double h) {
BMerson p = {1, 1.0};
return p;
}

BMerson BMersonMake2(int a, double h) {
BMerson p = {
.age = a,
.height = h,
};
return p;
}

const关键字

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
{
int a1 = 0;
a1 = 10;

const int a2 = 0;
printf("%d", a2);
// error a2 = 10;

int const a3 = 0;
printf("%d", a3);
// error a3 = 10;
}

{
int a1;
int *p1;
p1 = &a1;
*p1 = 10;

const int *p2;
const int a2 = 10;
p2 = &a2;
p2 = &a1;
// error *p2 = 10;

int const *p3;
p3 = &a2;
p3 = &a1;
// error *p3 = 10;

int * const p4 = NULL;
// error p4 = &a2;
// error p4 = &a1;
*p4 = 10;

const int * const p5;
printf("%p", p5);
// error p5 = &a2;
// error p5 = &a1;
// error *p5 = 10;
}

iOS中的常量

常量

  • 通知名
1
2
3
4
UIKIT_EXTERN NSNotificationName const BMLoginSuccessNotification;
// NSNotificationName const BMLoginSuccessNotification = @"BMLoginSuccessNotification";
extern NSNotificationName const BMLoginFailureNotification;
// NSNotificationName const BMLoginFailureNotification = @"BMLoginFailureNotification";
  • NSString常量
1
2
3
4
UIKIT_EXTERN NSString *const BMUserName;
// NSString *const BMUserName = @"BMUserName";
extern NSString *const BMUserKey;
// NSString *const BMUserKey = @"BMUserKey";
  • 值常量
1
2
3
4
UIKIT_EXTERN const CGFloat BMNavHeight;
const CGFloat BMNavHeight = 10.0f;
extern const CGFloat BMNavWidth;
const CGFloat BMNavWidth = 10.0f;

Apple 尺寸

设备型号 屏幕尺寸 开发尺寸 像素尺寸 倍图
4/4S 3.5英寸 320*480 640*960 @2X
5/5S/5C/SE 4.0英寸 320*568 640*1136 @2X
6/6S/7/8 4.7英寸 375*667 750*1134 @2X
6P/6SP/7P/8P 5.5英寸 414*736 1242*2208 @3X
X 5.8英寸 375*812 1125*2436 @3X
iPad Mini 4 / iPad Air 2 / iPad Pro (9.7) 9.7英寸 768*1024 1536*2048 @2X
iPad Pro(12.9) / iPad Pro(12.9) (第二代) 12.9英寸 1024*1036 2048*2732 @2X
iPad Pro(10.5) 10.5英寸 1112*834 2224*1668 @2X

最后

- END -
扫一扫上面的二维码图案,加我微信

文章作者:梁大红

特别声明:若无特殊声明均为原创,转载请注明,侵权请联系

版权声明:署名-非商业性使用-禁止演绎 4.0 国际