钟晴

当你的实力撑不起你的梦想,就更应该脚踏实地!


  • Home

  • Tags

  • Categories

  • Archives

  • 读书

  • 电影

  • Search

iOS代码规范

Posted on 2020-05-15 | In Objective-C

编程核心原则:

原则一:代码应该简洁易懂,逻辑清晰

因为软件是需要人来维护的。这个人在未来很可能不是你。所以首先是为人编写程序,其次才是计算机:

  • 不要过分追求技巧,降低程序的可读性。
  • 简洁的代码可以让bug无处藏身

原则二:面向变化编程,而不是面向需求编程。

需求是暂时的,只有变化才是永恒的。 本次迭代不能仅仅为了当前的需求,写出扩展性强,易修改的程序才是负责任的做法,对自己负责,对公司负责。

原则三:先保证程序的正确性,防止过度工程

过度工程(over-engineering):在正确可用的代码写出之前就过度地考虑扩展,重用的问题,使得工程过度复杂。

  1. 先把眼前的问题解决掉,解决好,再考虑将来的扩展问题。
  2. 先写出可用的代码,反复推敲,再考虑是否需要重用的问题。
  3. 先写出可用,简单,明显没有bug的代码,再考虑测试的问题。

Objective-C 编程规范

if 语句不要使用过多分支,分支比较多的情况下要善于使用return来提前返回

推荐:

1
2
3
4
5
6
- (void)someMethod { 
if (!goodCondition) {
return;
}
//Do something
}

不推荐这样写:

1
2
3
4
5
- (void)someMethod { 
if (goodCondition) {
//Do something
}
}

举个典型的例子

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

-(id)initWithDictionary:(NSDictionary*)dict error:(NSError)err{
//方法1. 参数为nil
if (!dict) {
if (err) *err = [JSONModelError errorInputIsNil];
return nil;
    }

    //方法2. 参数不是nil,但也不是字典
    if (![dict isKindOfClass:[NSDictionary class]]) {
        if (err) *err = [JSONModelError errorInvalidDataWithMessage:@"Attempt to initialize JSONModel object using initWithDictionary:error: but the dictionary parameter was not an 'NSDictionary'."];
        return nil;
    }

    //方法3. 初始化
    self = [self init];
    if (!self) {
        //初始化失败
        if (err) *err = [JSONModelError errorModelIsInvalid];
        return nil;
    }
    //方法4. 检查用户定义的模型里的属性集合是否大于传入的字典里的key集合(如果大于,则返回NO)
    if (![self __doesDictionary:dict matchModelWithKeyMapper:self.__keyMapper error:err]) {
        return nil;
    }

    //方法5. 核心方法:字典的key与模型的属性的映射
    if (![self __importDictionary:dict withKeyMapper:self.__keyMapper validation:YES error:err]) {
        return nil;
    }
    //方法6. 可以重写[self validate:err]方法并返回NO,让用户自定义错误并阻拦model的返回
    if (![self validate:err]) {
        return nil;
    }

    //方法7. 终于通过了!成功返回model
    return self;

条件表达式如果很长(超过2个表达式),则需要将他们提取出来赋给一个BOOL值

推荐

1
2
3
4
5
6
7
8

Bool isContains = sessionName.hasPrefix("Swift")
Bool isCurrentYear = sessionDateCompontents.year == 2014

let isSwiftSession = nameContainsSwift && isCurrentYear && user.isLogin
if (isSwiftSession) {
//Do something
}

不推荐

1
2
3
if (sessionName.hasPrefix("Swift") && sessionDateCompontents.year == 2014 && user.isLogin ){
//Do something
}

一个函数的长度必须限制在50行以内

通常来说,在阅读一个函数的时候,如果视需要跨过很长的垂直距离会非常影响代码的阅读体验。如果需要来回滚动眼球或代码才能看全一个方法,就会很影响思维的连贯性,对阅读代码的速度造成比较大的影响。最好的情况是在不滚动眼球或代码的情况下一眼就能将该方法的全部代码映入眼帘。

一个函数只做一件事(单一原则)

每个函数的职责都应该划分的很明确(就像类一样)。

推荐:

1
2
dataConfiguration()
viewConfiguration()

不推荐:

1
2
3
4
5
void dataConfiguration()
{
...
viewConfiguration()
}

函数命名要清晰

命名应该尽可能的清晰和简洁,但在 Objective-C 中,清晰比简洁更重要。由于 Xcode 强大的自动补全功能,我们不必担心名称过长的问题

推荐

1
2
3
- (void) insertObject:(UInteger)atIndex;

- (void) removeObjectAtIndex:(UInteger)atIndex;

不推荐

1
2
- (void) insert:(int)at;
- (void) remove:(int)at;

在 init 和 dealloc 中不要用存取方法访问实例变量
当 init、dealloc 方法被执行时,类的运行时环境不是处于正常状态的,使用存取方法访问变量可能会导致不可预料的结果,因此应当在这两个方法内直接访问实例变量。

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
//正确,直接访问实例变量
- (instancetype)init {
self = [super init];
if (self) {
_bar = [[NSMutableString alloc] init];
}
return self;
}

- (void)dealloc {
[_bar release];
[super dealloc];
}

// 错误,不要通过存取方法访问
- (instancetype)init {
self = [super init];
if (self) {
self.bar = [NSMutableString string];
}
return self;
}
- (void)dealloc {
self.bar = nil;
[super dealloc];
}

不要用点分语法来调用方法,只用来访问属性。这样是为了防止代码可读性问题。

推荐

1
view.backgroundColor = [UIColor orangeColor];

不推荐

1
[view setBackgroundColor:[UIColor orangeColor]];

注释

  1. 公共接口(注释要告诉阅读代码的人,当前类能实现什么功能)。
  2. 涉及到比较深层专业知识的代码(注释要体现出实现原理和思想)。
  3. 容易产生歧义的代码

设计以上三种情况下。都必须写好注释。在xcode中自带快捷注释方法。选好要注释的方法或类。option + command + /

系统常用类作实例变量声明时加入后缀

类型 后缀
UIView View
UILabel Lbl
UIButton Btn
UIImage Img
UIImageView ImgView
NSArray Ary
NSMutableArray Mary
NSDictionary Dict
NSMutableDictionary Mdict
NSString Str
NSMutableString Mstr
NSSet Set
NSMutableSet Mset

CGRect函数

其实iOS内部已经提供了相应的获取CGRect各个部分的函数了,它们的可读性比较高,而且简短,推荐使用

推荐

1
2
3
4
5
6
CGRect frame = self.view.frame; 
CGFloat x = CGRectGetMinX(frame);
CGFloat y = CGRectGetMinY(frame);
CGFloat width = CGRectGetWidth(frame);
CGFloat height = CGRectGetHeight(frame);
CGRect frame = CGRectMake(0.0, 0.0, width, height);

不推荐

1
2
3
4
5
6
CGRect frame = self.view.frame;  
CGFloat x = frame.origin.x;  
CGFloat y = frame.origin.y;  
CGFloat width = frame.size.width;  
CGFloat height = frame.size.height;  
CGRect frame = (CGRect){ .origin = CGPointZero, .size = frame.size };

范型

如果容器元素唯一时,建议在定义NSArray、NSDictionary、NSSet,指定容器的元素

推荐

1
2
@property (nonatimic, readwrite, copy) NSArray <DSRUser*> *userAry;
@property (nonatimic, readwrite, copy) NSDictionary <NSString *,DSRUser*> *userDict

不推荐

1
2
@property (nonatimic, readwrite, copy) NSArray *userAry;
@property (nonatimic, readwrite, copy) NSDictionary * userDict

尽量使用字面量值来创建 NSString , NSDictionary , NSArray , NSNumber 这些不可变对象:

推荐

1
2
3
4
NSArray *nameAry = @[@"zhangsan", @"lishi", @"wangwu"];
NSDictionary *productDict = @{@"iPhone" : @"Kate",
@"iPad" : @"Kamal",
@"Mobile Web" : @"Bill"};

不推荐

1
2
NSArray * nameAry = [NSArray arrayWithObjects:@"zhangsan", @"lishi", @"wangwu", nil];
NSDictionary *productManagers = [NSDictionary dictionaryWithObjectsAndKeys:@"Kate", @"iPhone", @"Kamal", @"iPad", @"Bill"];

属性的命名使用小驼峰

推荐:

1
@property (nonatomic, readwrite, copy) NSString * tipStr;

属性的关键字推荐按照 原子性,读写,内存管理的顺序排列
推荐:

1
2
@property (nonatomic, readwrite, copy) NSString *nameStr;
@property (nonatomic, readonly, assign) NSUInterger old;

Block属性应该使用copy关键字

推荐:

1
2
typedef void (^ErrorCodeBlock) (id errorCode,NSString *message);
@property (nonatomic, readwrite, copy) ErrorCodeBlock errorBlock;

形容词性的BOOL属性的getter应该加上is前缀

推荐

1
@property (nonatomic, getter=isEditable, assign) BOOL editable;

建议尽量把对外公布出来的属性设置为只读,在实现文件内部设为读写,避免多处地方修改属性值,方便管理代码

1
2
在头文件中,设置对象属性为 `readonly`
在实现文件中设置为 `readwrite`

方法名中不应使用and

推荐

1
- (instancetype)initWithWidth:(CGFloat)width height:(CGFloat)height;

不推荐

1
- (instancetype)initWithWidth:(CGFloat)width andHeight:(CGFloat)height;

方法实现时,如果参数过长(参数超过3个),则令每个参数占用一行,以冒号对齐

1
2
3
4
5
6
- (void)doSomethingWith:(NSString *)theFoo
                   rect:(CGRect)theRect
               interval:(CGFloat)theInterval
{
//Implementation
}

方法名前缀

  • 刷新视图的方法名要以refresh为首。
  • 更新数据的方法名要以update为首
  • 点击事件的方法实现要以click为首

推荐

1
2
- (void)refreshHeaderViewWithCount:(NSUInteger)count;
- (void)updateDataSourceWithViewModel:(ViewModel*)viewModel;

类遵循代理过多的时候(超过3个协议),换行对齐显示

1
2
3
4
5
6
7
@interface ShopViewController () <UIGestureRecognizerDelegate,
                                  HXSClickEventDelegate,
                                  UITableViewDelegate,
                                  UITableViewDataSource>{
   ...
                 
}

代理的方法需要明确必须执行和可不执行

代理方法在默认情况下都是必须执行的,然而在设计一组代理方法的时候,有些方法可以不是必须执行(是因为存在默认配置),这些方法就需要使用@optional关键字来修饰:

1
2
3
4
5
6
@protocol HomeCellViewDeleagte <NSObject>

@optional
- (void)switchValueChanged:(id)sender;
- (void)clickButtonToDetail:(id)sender;
@end

使用定时器时。要实现startTimer 和 stopTimer 两个函数;实现函数尽量不要加入其他的业务逻辑

1
2
3
4
5
6
7
8
9
10
11
12
- (void) startTimer
{
if (_timer){
[self stopTimer];
}
_timer = ...
}

- (void) stopTimer{
[_timer invalidate];
_timer = nil;
}

方法与参数名,一般都以小写字母开头;对于缩写字符串,可以大写字符开头

1
+ (NSURL *)URLWithString:(NSString *)URLString;

参考文献

https://juejin.im/post/5940c8befe88c2006a468ea6#heading-29

https://github.com/QianKaiLu/Objective-C-Coding-Guidelines-In-Chinese

iOS面试题Swift部分

Posted on 2020-04-21 | In Swift

Swift基础面试题

1、Class和Struct 的区别

类是引用类型,结构体是值类型。
结构体不可以继承
值类型被赋予给一个变量、常量或者被传递给一个函数时。其值会倍拷贝。
引用类型在被赋予到一个变量、常量或者被传递到一个函数时、其值不会被拷贝。

2、defer的用法。

使用defer代码块来表示在函数返回前,函数中最后执行的代码。无论函数是否会抛出异常,这段代码都会被执行。

defer语句块中的代码,会在当前作用域结束前掉用。每当一个作用域结束就进行该作用域defer执行。

3、什么是高阶函数。

一个函数如果可以一某一个个函数作为参数,或者是返回值。那么这个函数就称之为高阶函数。

4、什么是copy on write时候

写时复制,指的是swift中的值类型,并不会一开始赋值的时候就去复制,只有再需要修改的时候,才去复制。

5、String 与 NSString 的关系与区别

String 是结构体,值类型,NSStirng 是类,引用类型
NSString 与 String之间可以随意转换

6、associatedtype 的作用

简单来说就是protocol 使用的泛型

实现协议的时候,可以使用typealias 指定特定的类型,也可以自动推断

7、final关键字

final用于限制继承和重写,如果只是需要在某一个属性前加一个final。如果需要限制整个类无法被继承,那么可用用类名之前加一个final

8、public 和 open 的区别

这两个都用于模块声明需要对外界暴露的函数,区别在于,public 修饰的类,在模块外无法被继承,而open则可以任意继承,公开度来说,open>public

9、定义静态方法时关键字 static 和 class 有什么区别

static 定义的方法不可以被子类继承,class则可以

10、throws 和 rethrows 的用法与作用

throws 用在函数上,表示这个函数会抛出异常

rethrows 与 throws 类似,不过只适用于参数中有函数,且函数会抛出异常的情况下,rethrows 可以用throws替换,反过来不行.如

11、mutating关键字

结构体和枚举类型中修改 self 或其属性的方法必须将该实例方法标注为 mutating,否则无法在方法里改变自己的变量。

实现协议中的 mutating 方法时,若是类类型,则不用写 mutating 关键字。而对于结构体和枚举,则必须写 mutating 关键字。

12、必包是引用类型吗

必包是引用类型。如果一个必包被分配给一个变量。这个变量复制给另一个变量,那么他们引用的是同一个必包,他们的捕捉列表也会被复制。

iOS面试题Object-C部分

Posted on 2020-04-21 | Edited on 2020-04-28 | In Object-C

iOS面试题 Object-C部分

Object-C 基础题

1、NSSet 一般用什么关键字修饰。为什么?
2、浅拷贝和深拷贝的区别?

浅拷贝:只复制指向对象的指针,而不复制引用对象本身。
深拷贝:复制引用对象本身。内存中存在了两份独立对象本身,当修改A时,A_copy不变。

3、使用Block时需要注意哪些问题。
  1. 在block内部使用外部指针且会造成循环引用情况下,需要用__weak修饰外部指针: __weak typeof(self) weakSelf = self;
  2. 在block内部如果调用了延时函数还使用弱指针会取不到该指针,因为已经被销毁了,需要在block内部再将弱指针重新强引用一下。 __strong typeof(self) strongSelf = weakSelf;
  3. 如果需要在block内部改变外部栈区变量的话,需要在用__block修饰外部变量
4、UIView 和 CALayer 是什么关系?

UIView 继承自UIResponder类,可以响应事件。
CALayer直接继承NSObject类。不可以响应事件。
UIView时CALayer的代理。
UIView主要处理事件,CALayer负责绘制。

5、iOS中数据持久化方案有哪些?

NSUserDefault 简单数据快速读写
Property list (属性列表)文件存储
Archiver (归档)
SQLite 本地数据库

6、单个viewController的生命周期?
  • initWithCoder:(NSCoder *)aDecoder:(如果使用storyboard或者xib)
  • loadView:加载view
  • viewDidLoad:view加载完毕
  • viewWillAppear:控制器的view将要显示
  • viewWillLayoutSubviews:控制器的view将要布局子控件
  • viewDidLayoutSubviews:控制器的view布局子控件完成
  • viewDidAppear:控制器的view完全显示
  • viewWillDisappear:控制器的view即将消失的时候
  • viewDidDisappear:控制器的view完全消失的时候
  • dealloc 控制器销毁
7、Objective-C 如何对内存管理的?

答:Objective-C的内存管理主要有三种方式ARC(自动内存计数)、手动内存计数、内存池

1). 自动内存计数ARC:由Xcode自动在App编译阶段,在代码中添加内存管理代码。

2). 手动内存计数MRC:遵循内存谁申请、谁释放;谁添加,谁释放的原则。

3). 内存释放池Release Pool:把需要释放的内存统一放在一个池子中,当池子被抽干后(drain),池子中所有的内存空间也被自动释放掉。内存池的释放操作分为自动和手动。自动释放受runloop机制影响。

8、继承、分类和类扩展
  1. 分类有名字,类扩展没有分类名字,是一种特殊的分类。
  2. 分类只能扩展方法(属性仅仅是声明,并没真正实现),类扩展可以扩展属性、成员变量和方法。
  3. 继承可以增加,修改或者删除方法,并且可以增加属性。
9、通常对象为啥要用strong?

用strong就是要持有他的指针,指针就是指向内存,就是叫被引用的内存不被释放

10、instancetype和id的异同?

1.相同点
都可以作为方法的返回类型
2.不同点
1.instancetype可以返回和方法所在类相同类型的对象,id只能返回未知类型的对象;
2.instancetype只能作为返回值,不能像id那样作为参数

11、isKindOfClass和isMemberOfClass的区别

isKindOfClass来确定一个对象是否是一个类的成员,或者是派生自该类的成员

isMemberOfClass只能确定一个对象是否是当前类的成员

12、delegate 和 notification 的区别

二者都用于传递消息,不同之处主要在于一个是一对一的,另一个是一对多的。

13、iOS多线程技术有哪几种方式?

pthread、NSThread、GCD、NSOperation

14、什么是懒加载,懒加载有什么作用

用到时才初始化,延迟加载。
延时加载可以避免内存过高

15、有哪些常见的Crash场景
  1. 访问了僵尸对象
  2. 访问了不存在的方法
  3. 数组越界
16、说一下静态库和动态库之间的区别

动态库形式:.dylib 和 .framework

静态库形式:.a和.framework

区别:

  • 静态库:链接时,静态库会被完整的复制到可执行文件中
  • 系统动态库:链接时不复制,程序运行时有系统动态加载到内存,提供内存掉用,系统只加载一次,多个程序共用,节省内存。
17、使用atomic一定是线程安全的吗?

不一定。只有在执行setter或getter方法时是线程安全的。

18、什么是 KVO 和 KVC?

KVC(key-value-coding)键值编码,是一种间接访问实例变量的方法。提供一种机制来间接访问对象的属性

KVO是一种基于KVC实现的观察者模式。当指定的被观察的对象的属性更改了,KVO会以自动或手动方式通知观察者。

19 堆、栈

栈:由系统管理分配和释放。一般存放函数的参数值,和局部变量(函数中的基本数据类型)。栈区的操作方式类似数据结构中的栈(先进后出)

堆:由程序员管理(分配释放),若程序员不释放,程序结束时可能由系统回收。一般由程序员 alloc、new 出来的对象。堆的操作方式与数据结构中的堆不同,操作方式类似于链表

Object-C 进阶题:

1、什么是 RunLoop?
2、说的OC是动态运行时语言是什么意思?

主要是将数据类型的确定由编译时,推迟到了运行时。

简单来说, 运行时机制使我们直到运行时才去决定一个对象的类别,以及调用该类别对象指定方法。

3、isa指针?

对象的isa指针指向所属类。
类的isa指针指向所属的元类。
元类的isa指针指向了根元类;跟元类指向了自己

4.Objective C都有哪些锁机制

1.@synchronized 同步锁
2.NSLock 对象锁
3.NSRecursiveLock 递归锁
4.NSConditionLock 条件锁
5.pthread_mutex 互斥锁(C语言)
6.dispatch_semaphore 信号量实现加锁(GCD)

5、NSNotificationCenter是在哪个线程发送的通知?

6、load和initialize方法分别在什么时候调用的?

7、说一下hash算法。

8、什么是method swizzling?

9、runtime 如何实现 weak 属性

要实现 weak 属性,首先要搞清楚 weak 属性的特点:

weak 此特质表明该属性定义了一种“非拥有关系” (nonowning relationship)。为这种属性设置新值时,设置方法既不保留新值,也不释放旧值。此特质同 assign 类似, 然而在属性所指的对象遭到摧毁时,属性值也会清空(nil out)。

那么 runtime 如何实现 weak 变量的自动置nil?

runtime 对注册的类, 会进行布局,对于 weak 对象会放入一个 hash 表中。 用 weak 指向的对象内存地址作为 key,当此对象的引用计数为0的时候会 dealloc,假如 weak 指向的对象内存地址是a,那么就会以a为键, 在这个 weak 表中搜索,找到所有以a为键的 weak 对象,从而设置为 nil。

10、dispatch_barrier_async的作用是什么?

栏栅函数

计算机基础题

1、get 和 post 的区别
  1. get用来获取数据,post用来提交数据
  2. get参数有长度限制,而post无限制。
  3. get是明文传输,post是放在请求体中
  4. get请求会保存在浏览器历史记录中,还可能保存在web服务器的日志中
1、HTTP与HTTPS的区别?
  1. HTTPS协议需要到CA申请证书
  2. HTTP是明文传输,HTTPS则是具有安全性的SSL加密传输
  3. HTTP和HTTPS使用的端口也不一样,前者是80,后者是443
  4. HTTP的连接很简单,是无状态的;HTTPS协议是由SSL+HTTP协议构建的可进行加密传输、身份认证的网络协议,比http协议安全。

3、说一下TCP和UDP

UDP协议是全称是用户数据协议,在OSI模型中,在第四层网络层。UDP有不提供数据包分组、组装和不对数据包进行排序的缺点,也就是说,当报问发送之后,是无法得知器是否安全完整的达到。

TCP协议全称是传输控制协议。是一种面向连接的、可靠的、基于字节流的传输层通信协议。TCP是面向连接的、可靠的、流协议。流就是指不间断的数据结构,你可以把它想象成排水管里面的水流。

实战应用相关的题目

1、麻烦你设计个简单的图片内存缓存器(移除策略是一定要说的)

2、如何检测内存泄漏

3、如何优化App的性能。

4、抓包工具的使用。

5、苹果掉单问题如何解决

7、有一个数组,我要把里面的元素去重。你会怎么做?

8、Git与SVN之间的比较。

SourceTree记住密码

Posted on 2019-11-15

SoucreTree 每次pull 和 push 的时候都会弹出输入密码的提示框,挺烦的。

简单的设置流程

1、菜单->仓库->仓库设置->远程仓库->选中Url->点击编辑

2、Url路径修改
比如http://xxxx/xxxx.git 改为 http:username:password@xxxx.xxxx.git

3、确定

到这里。以后这个仓库的代码就不用再输入密码了

iOS组件化开发(一):远程私有库的基本使用

Posted on 2019-11-06 | Edited on 2019-11-13 | In iOS组件化学习

随着项目功能的不断增加,越来越多的开发人员,业务主线也随之越来越多,造成耦合越来越严重,编译越来越慢,测试不独立等一系列问题。为了解决此类情况,我们可以考虑组件话开发

  • 概念: 组件话就是将一个单一的工程项目,分解成为各个独立的组件,然后按照某种方式,任意组织成一个拥有完整业务逻辑的工程

  • 优势

  • 独立:独立编写、编译、运行、测试
  • 重用:功能代码的重复使用。比如不同项目使用同一功能模块
  • 高效:任意增删模块,实现高效迭代
  • 组件化还可以配合二进制化,提高项目编译速度
  • 组件分类大体上分为三类:基础组件、功能组件和业务组件
  • 基础组件:也称为公共组件,存放平时定义的宏、常量、协议、分类、对必要的第三方的封装类,以及各种处理工具类,如时间、日期、设备信息、文件处理、沙盒管理等
  • 功能组件:自定义视图控件、一些特定功能的封装(如录音、播放音频封装)
  • 业务组件:各种业务线

步骤归纳

1、创建远程索引库和私有库

2、将远程索引库添加到本地 pod repo add 索引库名称 索引库地址

3、在本地创建一个pod模版 pod lib create 组件名称 将框架的核心代码添加到Classes目录下

4、本地安装测试核心代码是否可用 pod install。修改Spec描述文件,将修改好的模版库上传至远程私有库

5、上传代码

  1. git init
  2. git add .
  3. git commit -m "提交描述"
  4. git remote add origin 远程私有库地址
  5. git push origin master

6、打标签

  1. git tag '0.1.0'
  2. git push --tags

7、提交spec至私有索引库

  1. pod lib lint –private
  2. pod spec lint –private
  3. pod repo push 索引库名称 xx.podspec

8、使用

1
2
source  官方索引库url
source 私有库索引url

一、创建私用索引库

这里以码云为例,创建一个TGSpecs的私有索引库,这玩意的作用如其名,就是用来索引的

二、本地添加私有索引库

一、查看本地索引库

1
pod repo

如图,目前本地仅有github上的那个公有索引库

2、添加私有索引库

将我们刚刚新建的私有索引库TGSpecs添加到本地

1
2
3
4
// pod repo add 索引库名称 索引库地址
// pod repo remove 索引库名称 /*删除本地索引*/

pod repo add TGSpecs https://gitee.com/zhongqing05/TGSpecs.git

再查看一下本索引 pod repo

现在本地就有两个索引库。到这里,索引库的的事情就先放一边了

三、创建组件库

1、创建远程代码仓库

创建TGBase为例,创建一个基础组件

2、本地快速创建模版库

到合适的位置创建一个与组件名相同的文件夹,cd 进去后,使用命令

1
2
3
//pod lib create 组件名

pod lib create TGBase

这里会让你配置一些信息,根据自己的情况自行配置即可。

3、添加组件内容

创建完成后,会自动帮我们打开相应的Example项目,TGBase目录中会出现如图这些文件,我们把基础组件相关的东西丢到Classes文件夹中,并且把ReplaceMe.m 文件删除

note:默认Classes文件夹中存放的文件就是pod install 时要下载下来的文件,当然可以通过修改spec文件的配置来更改位置

4、安装与测试本地库

cd到 Example目录下,打开Podfile文件可以看到

1
pod 'TGBase', :path => '../'

模版库已经默认帮我们在Podfile中指定了TGBase.podspec 的位置,使组件可以正常安装使用和方便测试

1
pod install

如上图安装成功,Xcode打开项目,也能看到刚刚添加的文件

到这里,本地测试组件没问题后,我们接下来要将podspec文件进行修改,然后上传至私有索引库

5、修改Spec

具体的配置说明可以参考Cocoapods创建第三方框架

主要修改内容

1
2
3
4
5
6
7
8
9
  s.name             = 'TGBase'
s.version = '0.1.0'
s.summary = 'TGBase.'
s.description = <<-DESC
TODO: TBase is Tiange Technology Basic component
DESC
s.homepage = 'https://gitee.com/zhongqing05/TGBase'
s.source = { :git => 'https://gitee.com/zhongqing05/TGBase.git', :tag => s.version.to_s }
s.source_files = 'TGBase/Classes/**/*'

四、上传组件代码

1、将代码提交到组件仓库

1
2
3
4
5
6
git add .
git commit -m '提交说明'
git remote add origin https://gitee.com/zhongqing05/TGBase.git
//第一次push如果报错的话可以加上-f
//git push -f origin master
git push origin master

2、打标签

1
2
git tag '0.1.0'
git push --tags

五、提交podspec到私有索引库

在上传spec文件前我们可以做一个验证来节省时间,不然每次推送很久结果还是验证失败,会气死人的

1、本地验证Spec的必填字段

1
2
// 本地验证不会验证 s.source 中的tag
pod lib lint

2、远程验证

1
2
// 远程验证会验证 s.source 中的tag,如果此时没有打上相应的标签则会报错
pod spec lint

如果你刚才没有打标签,并上传至远程私有库进来进行验证,肯定是会报错

note:如果验证的是私有库,则后面加上 --private,否则会有警告,你可以选择 --alllow-warnings 来忽略警告

3、提交podspec

1
2
3
//pod repo push 私有索引库名称  spec名称.podspec

pod repo push TGSpecs TGBase.podspec

这里的操作过程:先将我们的代码直接push到本地索引库TGSpecs,推送后自动会帮我们同步到远程索引库

再来看看码云上的私有索引库TGSpecs

来测试下我们的组件

1
pod search TGBase

六、使用私有库

现在我们可以试试通过pod的形式来添加TGBase,创建一个新项目,cd到工程目录下

1、添加Podfile文件

1
pod init

2、在Podfile的最顶部添加索引库Url

1
2
source 'https://github.com/CocoaPods/Specs.git'
source 'https://gitee.com/zhongqing05/TGSpecs.git'

3、添加使用组件

1
pod 'TGBase'

4、安装组件

1
pod install

查看工程。从远程下载过来的代码

本文参考:https://juejin.im/post/5ac5d5abf265da2396129e63

Xcode 报错:errsecinternalcomponent

Posted on 2019-10-30 | In Error记录

前言

前些天看了一篇技术大佬的博客,关于提升提升mac终端效率的。原文地址:https://juejin.im/post/5d91a07d6fb9a04de96e7cb0。昨天在跟同事的聊天中突然聊到了如何提高百度云盘的效率。所以今天就找到前些天到看的这篇文章,自己尝试弄好之后分享给同事。

百度云破解步骤

1、按照这片文章的这段命令进行操作

1
git clone https://github.com/CodeTips/BaiduNetdiskPlugin-macOS.git && ./BaiduNetdiskPlugin-macOS/Other/Install.sh

看到这段命令后,一眼就知道这个是从github下载过来的一个叫Install.sh的脚本文件。所以我也直接去github上看了。

2、按照步骤弄好之后,打开 百度网盘,点击登录。后面就弹出了一个访问钥匙串的弹框。

3、在第二部卡了非常久,输入电脑开机密码、和百度网盘密码也没用,这个弹框是要输入访问钥匙串的密码。这里有点懵逼,关于这种密码完全没有印象。所以只好再度google。看到这篇文章 https://www.mac69.com/mac/3477.html。

根据这篇文章去尝试输入mac密码,大概输入了4次之后就吧百度云盘给弄闪退了,我试了2次,后面看了一下这篇的相关评论,说这个工具已经没用了。好吧,到这里我就放弃了。

4、回到这个工程的github地址,按照方法进行卸载,到这里。百度网盘以破解失败介绍。1个小时就耗在这里了。上午就过去了

Xcode 所有工程包错:errsecinternalcomponent

1、运行Xcode项目,包错 errsecinternalcomponent 具提内容没有截图
这种错误第一眼看上去就是证书签名的问题。我仔细的检查了一下Xcode工程相关的设置,以及profile相关的文件,都没发现问题。就连钥匙串也检查了一遍。

2、检查了自身的问题只后,没折了,只好再次求助Google爸爸。在到相关的一些答案。看到最多的还是这个

1
2
3
4
xattr -rc .

实在不行在删除 这个目录
/Users/denny/Library/Developer/Xcode/DerivedData/

或者

1
2
cd ~/Library/Developer/Xcode/DerivedData
xattr -rc .

这2个方法是目前Goole爸爸给了最多的答案,但是还是没用,想了半天。才想起来那个钥匙串访问的弹框。后面我再运行一个其他的项目,结构报错一样。在这里我就猜到肯定是上午安装 百度云破解 所导致的问题

回到Github 搜索找到 BaiduNetdiskPlugin-macOS 这个开源库

从这里,才是找到解决问题的方法。

1、查看这个库的的Issuse。很快的发现有人也提了相关的issuse。相关连接果然和我遇到的问题一样。好吧,最后的答案就是: 卸载此插件,然后重启电脑

写了这么多,就这一句话。记录一下整个安装破解百度云破解所带出的相关问题

MLeaksFinder的使用介绍和原理

Posted on 2019-10-27

MLeaksFinder 的主要用途

检测UIViewController和UIView内存泄露的第三房库。引进MLeaksFinder后,可以向往常一样的开发。调试业务逻辑的过程并且自动的发现并提示内存泄露。开发者无需打开Instrument等工具,也无需为了找内存泄漏去跑额外的流程。这不仅很大的节省了开发者查找内存泄漏的时间成本,而且还能很及时的让开发这意识到哪了的代码有问题,并且修复。

下载地址点击这里

使用方式

把 MLeaksFinder 目录文件添加到你的项目中,就可以在运行时(debug模式下)帮你检测项目里的内存泄漏了,无需修改任何业务代码,而且只在debug下开启,完全也不影响你的release包

引入 MLeaksFinder 可选择 CocoaPods 安装,安装时注意有没有 warnings,特别是 OTHER_LDFLAGS 相关的warnings。如果有warnings,可以在工程的 Build Settings -> Other Linker Flags 加上 -ObjC

也可以手动引入,直接把MLeaksFinder的代码引入的引入到项目里即生效。如果把MLeaksFinder 做为子工程,需要在主工程的 Build Settings -> Other Linkers Flags 加上 -ObjC

引入后,先检验是否引入成功,在 UIViewController+MemoryLeak.m 的 +(void)load方法中打上断点,运行项目时该方法进入便引入成功。

引入 MLeaksFinder 的代码之后即可检测内存泄漏,但查找循环引用的功能还未生效。可以再手动的加入 FBRetainCycleDetector 代码,然后把 MLeaksFinder.h 里的 //#define MEMORY_LEAKS_FINDER_RETAIN_CYCLE_ENABLED 0 打开

MLeaksFinder 默认只在debug下生效,当然也可以通过 MLeaksFinder.h 里的 //#define MEMORY_LEAKS_FINDER_ENABLED 0 来手动控制开关。

MLeaksFinder -原理

iOS重签名

Posted on 2019-10-22 | Edited on 2019-10-26

第一步:获取脱壳的ipa包

1、越狱手机导出

2、各类网站上下载的ipa。

3、各类助手上下载越狱版ipa包

第二步:命令行实现重签名

1、 将ipa解压缩后前往.app所在的目录,输入

1
codesign -d -vv IpaDownloadTool.app

然后输出可执行文件的签名信息

1
2
3
4
5
6
7
8
9
10
11
12
13
Executable=/Users/tgkj/Downloads/Payload/IpaDownloadTool.app/IpaDownloadTool
Identifier=cn.zxlee.IpaDownloadTool
Format=app bundle with Mach-O universal (armv7 arm64)
CodeDirectory v=20400 size=4444 flags=0x0(none) hashes=130+5 location=embedded
Signature size=4798
Authority=iPhone Distribution: Siyuan Zhang (XW95KAQH96)
Authority=Apple Worldwide Developer Relations Certification Authority
Authority=Apple Root CA
Signed Time=Aug 20, 2019 at 2:06:16 PM
Info.plist entries=33
TeamIdentifier=XW95KAQH96
Sealed Resources version=2 rules=10 files=49
Internal requirements count=1 size=188

这里可以看到签名相关的信息

2、查看Mac本地的证书列表:

1
security find-identity -v -p codesigning

输出

1
2
3
57950B1A87C9A8E42F39B6E87EF37B34972FF4F4 "iPhone Distribution: Ji.....

BE01D5144626C1DC4330FF66B0B8924DA13AA06B "iPhone Develo....

记下你要用来签名的证书双引号(包括双引号)中的字符串,一会儿会用到

3、确认ipa包是否已经脱壳,输入:

1
2
3
cd IpaDownloadTool.app

otool -l IpaDownloadTool | grep crypt

输出

1
2
3
4
5
6
 cryptoff 16384
cryptsize 376832
cryptid 0
cryptoff 16384
cryptsize 409600
cryptid 0

cryptid为0即为已脱壳,为1为加密状态。这里有两组数据是因为这是个支持两种CPU架构的可执行文件。可输入file IpaDownloadTool查看可执行文件支持的架构

输出

1
2
3
IpaDownloadTool: Mach-O universal binary with 2 architectures: [arm_v7:Mach-O executable arm_v7] [arm64:Mach-O 64-bit executable arm64]
IpaDownloadTool (for architecture armv7): Mach-O executable arm_v7
IpaDownloadTool (for architecture arm64): Mach-O 64-bit executable arm64

4、删除无法签名的插件文件:PlugIns文件夹、Watch文件夹
note:没有这2个文件夹跳过这步

5、对.app文件夹内的Frameworks文件夹中的每一个framework强制重签名

1
codesign -fs 步骤2中记下的证书信息  要签名的.framework

note:这里Frameworks要签名的直接跳过这步

6、将自己的描述文件名改为embedded.mobileprovision,并拖入到.app中,再将.app中info.plist文件里的Bundle identifier改成我们自己的BundleID

7、在.app同级目录下新建一个entitlements.plist文件

1
touch entitlements.plist

在.app目录下查看描述文件内容:
security cms -D -i embedded.mobileprovision将Entitlements节点下的

1
2
3
4
<dict>
...
...
</dict>

复制粘贴到刚刚新建的entitlements.plist文件中

8、最后一步,对整个包签名,回到.app所在目录,输入

1
codesign -fs 步骤2中记下的证书信息 --no-strict --entitlements=entitlements.plist WeChat.app

9、打包

1
zip -ry IpaDownloadTool.ipa Payload

以上,就是通过命令行一步步的实现应用重签名。

用脚本实现签名

1、创建 appSign.sh 文件

1
touch appSign.sh

2、拷贝一下内容到 appSign.sh文件中

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
#${SRCROOT} 它是工程文件所在的目录
TEMP_PATH="${SRCROOT}/Temp"
#资源文件夹,我们提前在工程目录下新建一个APP文件夹,里面放ipa包
ASSETS_PATH="${SRCROOT}/APP"
#目标ipa包路径
TARGET_IPA_PATH="${ASSETS_PATH}/*.ipa"
#清空Temp文件夹
rm -rf "${SRCROOT}/Temp"
mkdir -p "${SRCROOT}/Temp"

#----------------------------------------
# 1. 解压IPA到Temp下
unzip -oqq "$TARGET_IPA_PATH" -d "$TEMP_PATH"
# 拿到解压的临时的APP的路径
TEMP_APP_PATH=$(set -- "$TEMP_PATH/Payload/"*.app;echo "$1")
# echo "路径是:$TEMP_APP_PATH"

#----------------------------------------
# 2. 将解压出来的.app拷贝进入工程下
# BUILT_PRODUCTS_DIR 工程生成的APP包的路径
# TARGET_NAME target名称
TARGET_APP_PATH="$BUILT_PRODUCTS_DIR/$TARGET_NAME.app"
echo "app路径:$TARGET_APP_PATH"

rm -rf "$TARGET_APP_PATH"
mkdir -p "$TARGET_APP_PATH"
cp -rf "$TEMP_APP_PATH/" "$TARGET_APP_PATH"

#----------------------------------------
# 3. 删除extension和WatchAPP.个人证书没法签名Extention
rm -rf "$TARGET_APP_PATH/PlugIns"
rm -rf "$TARGET_APP_PATH/Watch"

#----------------------------------------
# 4. 更新info.plist文件 CFBundleIdentifier
# 设置:"Set : KEY Value" "目标文件路径"
/usr/libexec/PlistBuddy -c "Set :CFBundleIdentifier $PRODUCT_BUNDLE_IDENTIFIER" "$TARGET_APP_PATH/Info.plist"

#----------------------------------------
# 5. 给MachO文件上执行权限
# 拿到MachO文件的路径
APP_BINARY=`plutil -convert xml1 -o - $TARGET_APP_PATH/Info.plist|grep -A1 Exec|tail -n1|cut -f2 -d\>|cut -f1 -d\<`
#上可执行权限
chmod +x "$TARGET_APP_PATH/$APP_BINARY"

#----------------------------------------
# 6. 重签名第三方 FrameWorks
TARGET_APP_FRAMEWORKS_PATH="$TARGET_APP_PATH/Frameworks"
if [ -d "$TARGET_APP_FRAMEWORKS_PATH" ];
then
for FRAMEWORK in "$TARGET_APP_FRAMEWORKS_PATH/"*
do

#签名
/usr/bin/codesign --force --sign "$EXPANDED_CODE_SIGN_IDENTITY" "$FRAMEWORK"
done
fi

3、然后将 appSign.sh 直接丢到.xcodeproj同级目录中

4、Xcode –> Build Phases –> New Run Script Phase

5、Run

note:这里Xcode编译可能会报的错

1: appSign.sh: Permission denied

解决办法:在终端上直接运行命令: chmod 777 appSign.sh

2: Xcode Internal launch error: process launch failed: Unspecified

网上找的重启手机,或者重新连接Xcode都没用。后面再找找答案

本文转载:https://juejin.im/post/5c460908f265da6167209b87

class-dump的安装和使用

Posted on 2019-10-18 | Edited on 2019-10-31

本文转载 https://www.jianshu.com/p/1e3fe0a8c048

class-dump 用来dump目标文件的class信息的工具。它利用Objective-C语言的runtime的特性,将存储在mach-O文件中的@interface和@protocol信息提取出来,并生成对应的.h文件。

安装步骤

1、下载地址:http://stevenygard.com/projects/class-dump/

2、打开终端输入

1
open /usr/local/bin

3、把dmg文件中的class-dump文件复制到/usr/local/bin

4、更改权限:终端输入

1
sudo chmod 777 /usr/local/bin/class-dump

到这里就安装好了。

这里为什么是777?

https://juejin.im/post/5c46dcad518825260d7ee605 这篇文章说得很清楚

显示class-dump的用法和版本

1
class-dump --help

使用方法

1、自己建项目生成一个ipa文件,更改文件为zip格式,然后解压之后得到.app的目标文件

2、用终端输入命令class-dump -H [.app文件的路径] -o [输出文件夹路径]

1
class-dump -H /Users/mac/Desktop/Payload/Kt.app -o /Users/mac/Desktop/Payload

就可以得到所有的.h文件了(在输出的文件夹里)

ps:

自己编译的项目没有加密,能够解析出来。class-dump不能直接将AppStore上的app的头文件导出来,你只会导出CDStructures.h这个头文件,而这里边基本是没有信息的。相当于Apple在app上加了一层壳(加密了),需要把这层壳砸破。(Dumpdecrypted破壳)

内存对齐

Posted on 2019-10-16

内存

1、为什么要内存对齐 ?

在Xcode编译器下。一下2个结构体的大小是多少?

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
struct StructOne {
char a; //1字节
double b; //8字节
int c; //4字节
short d; //2字节
} MyStruct1;

struct StructTwo {
double b; //8字节
char a; //1字节
short d; //2字节
int c; //4字节
} MyStruct2;
NSLog(@"%lu---%lu--", sizeof(MyStruct1), sizeof(MyStruct2));
上述代码打印出来的结果为

为什么相同的结构体,只是交换了变量 ab 在结构体中的顺序他们的大小就改变了呢?这就是“内存对齐”的现象

内存对齐规则

每个特定的平台上的编译器都有自己的默认“对齐系数”,程序员可以通过预编译命令 #pragma pack(n) n = 1,2,4,8,16 来改变这一系数,其中的n就是你要指定的对齐系数.

提示
Xcode 中默认为#pragma pack(8)。如果在代码执行前加一句#pragma pack(1) 时就代表不进行内存对齐,上述代码打印的结果就都为 16。

1、1数据成员对齐规则:

结构(struct)(或联合(union))的数据成员,第一个数据成员放在offset(偏移)为0的地方,以后每个数据成员的对齐按照 #pragma pack 指定的数值和这个数据成员自身长度中,比较小的那个进行

1、2结构(或联合)的整体对齐规则:

在数据成员完成各自对齐之后,结构(或联合)本身也要进行对齐,对齐将按照 #pragma pack 指定的数值和结构(或联合)最大数据成员长度中,比较小的那个进行。

例如:#pragma pack(4)
系数 = min(max(成员),n )
整体对齐系数 = min((max(int,short,char), 4) = 4

1、3结构体作为成员:
如果一个结构里有某些结构体成员,则结构体成员要从整体对齐系统的整数倍地址开始存储。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
struct StructOne { 
长度 对齐 偏移 区间
char a; 1 < 8 1 0 [0]
double b; 8 = 8 8 8 [8, 15]
int c; 4 < 8 4 16 [16, 19]
short d; 2 < 8 2 20 [20, 21]
} MyStruct1;

解读:
1、数据成员的对齐按照#pragma pack-8和自身长度中比较小的那个进行
-- char a 的自身长度为 1, 1 < 8, 按 1 对齐

2、第一个数据成员放在offset为0的地方
-- char a 的偏移为 1

3、整体对齐系数 = min((max(int,short,char,double), 8) = 8,
将 21 提升到 8 的倍数,则为 24,所以最终结果为 24 个字节
1
2
3
4
5
6
7
8
9
10
11
12
struct B {           长度        对齐      偏移     区间
char e[2]; 1 < 8 2 0 [0, 1]
short h; 2 < 8 2 2 [2, 3]
struct A {
//重这里开始,要考虑整体对齐系数,初始区间必须是整体对齐系数的倍数。
int a; 4 < 8 4 8 [8, 11]
double b; 8 = 8 8 16 [16, 23]
float c; 4 < 8 4 24 [24, 27]
};
// 整体对齐系数 = min((max(int,double ,float), 8) = 8
// 将内存大小由28补齐到8的整数倍32,∴ result = 32
};

###为什么要内存对齐

之所以要内存对齐,有2方面的原因:

平台原因:各个硬件平台对存储空间的处理上有很大的不同。一些平台的某些特定类型的数据只能从某些特定地址开始存取。——-比如,有些架构的CPU在访问一个没有进行对齐的变量的时候会发生错误,那么这种架构的编译器必须保证字节对齐。

性能原因:内存对齐可以提高存取效率。—–比如。有些平台每次读取都是从偶地址开始,如果一个int型(假设为32为系统)如果存放在偶地址开始的地方,那么一个读周期就可以都出这32bit,而如果存放在奇地址开始的地方,就需要2个读周期,并对两次读出的结果的高地位进行拼凑才能得到改32位数据。

12…4
ZQing

ZQing

平时学习的小笔记。
37 posts
13 categories
14 tags
GitHub E-Mail
© 2020 ZQing
Powered by Hexo v3.9.0
|
Theme – NexT.Gemini v7.1.1
|
0%