iOS开发代码规范

针对我的开发经验ViewController应该是这样的。

#pragma mark - Life Cycle|生命周期
#pragma mark - UITableViewDataSource and UITableViewDelegate|主要是一些代理的方法
#pragma mark - Private Method|私有方法(内部可调用)
#pragma mark - Public  Method|公有方法
#pragma mark - Set & Get|getter 和 setter方法
#pragma mark - Notification|接收通知之后执行的方法
#pragma mark - Event|一些视图的点击事件 比如button
#pragma mark - DelegeteHandler|代理的实现时间

1️⃣代码命名基础

①一般原则

②前缀

前缀是编程接口中名称的重要部分。它们区分了软件的功能区域。前缀可防止第三方开发人员定义的符号与Apple定义的符号(以及Apple自己的框架中的符号之间)发生冲突。

  • 前缀具有规定的格式。它由两个或三个大写字母组成,不使用下划线或“子前缀”。以下是一些示例:

  • 在命名类,协议,函数,常量和typedef结构时使用前缀。千要不能在命名方法时,使用前缀; 方法存在于由定义它们的类创建的名称空间中。另外,不要使用前缀来命名结构体的字段。

2️⃣命名方法

①通用规则

在命名方法时,请记住以下几条一般准则:

  • 使用小写字母开始名称,并将嵌入单词的第一个字母大写。不要使用前缀。
  • 对于表示对象采取的操作的方法,使用动词开始名称(禁止使用“do”或“does”)。
  • 如果方法返回接收者的属性,则在属性后面命名方法。除非间接返回一个或多个值,否则不必使用get

    code comment
    - (NSSize)cellSize; 正确
    - (NSSize)getCellSize; 错误
  • 在所有参数之前使用关键字

    code comment
    - (void)sendAction:(SEL)aSelector toObject:(id)anObject forAllCells:(BOOL)flag; 正确
    - (void)sendAction:(SEL)aSelector :(id)anObject :(BOOL)flag; 错误
  • 在参数之前创建单词描述参数。

    code comment
    - (id)viewWithTag:(NSInteger)aTag; 正确
    - (id)taggedView:(int)aTag; 错误
  • 在创建比继承方法更具体的方法时,在现有方法的末尾添加新关键字。

    code comment
    - (id)initWithFrame:(CGRect)frameRect; 初始化方法
    - (id)initWithFrame:(CGRect)frameRect backgroundColor:(UIColor*)backgroundColor; 自定义初始化方法
  • 不要使用“and”来链接作为接收者属性的关键字。

    code comment
    - (int)runModalForDirectory:(NSString *)path file:(NSString *) name types:(NSArray *)fileTypes; 正确
    - (int)runModalForDirectory:(NSString *)path andFile:(NSString *)name andTypes:(NSArray *)fileTypes; 错误
  • 如果该方法描述了两个单独的操作,请使用“和”链接它们。

    - (BOOL)openFile:(NSString *)fullPath withApplication:(NSString *)appName andDeactivate:(BOOL)flag; NSWorkspace

②访问方法

访问器方法是设置和返回对象属性值的方法

  • 如果属性表示为名词,则格式为:

    - (type)noun;
    - (void)setNoun:(type)aNoun ;
    
  • 如果属性表示为形容词,则格式为:

    在声明属性的时候可以这么写
    @property(nonatomic,assign,getter=isAdjective)BOOL Adjective;

    - (BOOL)isAdjective;
    - (void)setAdjective:(BOOL)flag;
    
  • 如果属性表示为动词,则格式为:

    - (BOOL)verbObject;
    - (void)setVerbObject:(BOOL)flag;
    
  • 不要使用分词将动词转换为形容词:

    - (void)setAcceptsGlyphInfo:(BOOL)flag;
    - (BOOL)acceptsGlyphInfo;
    - (void)setGlyphInfoAccepted:(BOOL)flag;
    - (BOOL)glyphInfoAccepted;
  • 可以使用模态动词(动词前面加上“can”,“should”,“will”等)来澄清含义,但不要使用“do”或“does”。

    - (void)setCanHide:(BOOL)flag;
    - (BOOL)canHide;
    - (void)setShouldCloseDocument:(BOOL)flag;
    - (BOOL)shouldCloseDocument;
    - (void)setDoesAcceptGlyphInfo:(BOOL)flag;
    - (BOOL)doesAcceptGlyphInfo;
  • 仅对间接返回对象和值的方法使用“get”。并且当需要返回多个项目时,才应将此表单用于方法。

    - (void)getLineDash:(float *)pattern count:(int *)count phase:(float *)phase; NSBezierPath

③代理方法

委托方法(或委托方法)是在某些事件发生时对象在其委托中调用的方法(如果委托实现它们)。它们具有独特的形式,同样适用于在对象的数据源中调用的方法:

  • 方法的开始使用通过标识发送消息的对象的类(类名省略前缀,第一个字母为小写):

    - (BOOL)tableView:(NSTableView *)tableView shouldSelectRow:(int)row;
    - (BOOL)application:(NSApplication *)sender openFile:(NSString *)filename;
    
  • 冒号追加到类名前(参数是对委托对象的引用),并且该方法只有一个参数,即发送者。

    - (BOOL)applicationOpenUntitledFile:(NSApplication *)sender;
    
  • 对此的一个例外是由于发布通知而调用的方法。在这种情况下,唯一的参数是通知对象。

    - (void)windowDidChangeScreen:(NSNotification *)notification;
    
  • 对于被调用以通知委托已发生或即将发生某些事情的方法,请使用“did”或“will”。

    - (void)browserDidScroll:(NSBrowser *)sender;
    - (NSUndoManager *)windowWillReturnUndoManager:(NSWindow *)window;
    
  • 虽然您可以使用“did”或“will”来调用要求委托代表另一个对象执行某些操作的方法,但首选“should”。

    - (BOOL)windowShouldClose:(id)sender;
    

④集合方法

对于管理对象集合的对象(每个对象称为该集合的元素),约定是具有以下形式的方法:

- (void)addElement:(elementType)anObj;

- (void)insertElement:(elementType)anObj atIndex:(int)index;

- (void)removeElement:(elementType)anObj atIndex:(int)index;

- (void)removeElement:(elementType)anObj;

- (NSArray *)elements;

有一点需要主要如果集合是真正无序的,那么返回NSSet而不是NSArray

⑤方法参数

有一些关于方法参数名称的一般规则:

  • 与方法一样,参数以小写字母开头,连续单词的第一个字母大写(例如,removeObject:(id)anObject)。
  • 不要在名称中使用“pointer”或“ptr”。使用参数的类型而不是它的名称声明它是否是指针。
  • 避免使用单字母和双字母名称作为参数。
  • 避免只保存几个字母的缩写。

常用的方法参数

...alignment:(int)mode
...atIndex:(int)index
...content:(NSRect)aRect
...doubleValue:(double)aDouble
...floatValue:(float)aFloat
...font:(NSFont *)fontObj
...frame:(NSRect)frameRect
...intValue:(int)anInt
...keyEquivalent:(NSString *)charCode
...length:(int)numBytes
...point:(NSPoint)aPoint
...stringValue:(NSString *)aString
...tag:(int)anInt
...target:(id)anObject
...title:(NSString *)aString

⑥私有方法

在大多数情况下,私有方法名称通常遵循与公共方法名称相同的规则。但是,常见的约定是为私有方法提供前缀,以便很容易将它们与公共方法区分开来。即使采用这种约定,私有方法的名称也可能导致一种特殊类型的问题。当您设计Cocoa框架类的子类时,您无法知道您的私有方法是否无意中覆盖了具有相同名称的私有框架方法。

Cocoa框架中大多数私有方法的名称都有一个下划线前缀(例如_fooData),以将它们标记为私有。从这个事实可以看出两个建议。

  • 不要使用下划线字符作为私有方法的前缀。
  • 如果您是一个大型Cocoa框架类(如NSView或UIView)的子类,并且您希望绝对确定您的私有方法的名称与超类中的名称不同,则可以将自己的前缀添加到私有方法中。前缀应尽可能唯一,可能基于您的公司或项目以及“XX_”形式。因此,如果您的项目名为Byte Flogger,则前缀可能是 BF_addObject:

虽然给私有方法名称加一个前缀的建议似乎与先前声称方法存在于其类的命名空间中的说法相矛盾,但这里的意图是不同的:防止无意覆盖超类私有方法。

3️⃣命名函数

Objective-C允许您通过函数和方法表达行为。当底层对象总是单例或者处理明显功能的子系统时,你应该使用函数而不是类方法。

函数有一些通用的命名规则,您应该遵循:

  • 函数名称与方法名称类似,但有几个例外:

    • 它们以您用于类和常量的相同前缀开头。
    • 前缀后面的单词的第一个字母是大写的。
  • 大多数函数名称以描述函数具有的效果的动词开头:

    NSHighlightRect
    NSDeallocateObject
    

查询属性的函数具有另一组命名规则:

  • 如果函数返回其第一个参数的属性,则省略动词。

    unsigned int NSEventMaskFromType(NSEventType type)
    float NSHeight(NSRect aRect)
    
  • 如果通过引用返回值,请使用“get”。

    const char *NSGetSizeAndAlignment(const char *typePtr, unsigned int *sizep, unsigned int *alignp)
    
  • 如果返回的值是布尔值,则该函数应以变形动词开头。

    BOOL NSDecimalIsNotANumber(const NSDecimal *decimal)
    

4️⃣命名属性和数据类型

①声明的属性和实例变量

一个属性声明有效地声明了访问器的属性方法,如果属性表示为名词或动词,则格式为:@property (…) type nounOrVerb;

@property (strong) NSString *title;
@property (assign) BOOL showsAlpha;

但是,如果声明的属性的名称表示为形容词,则属性名称将省略“is”前缀,但指定get访问器的常规名称,例如:

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

②常量

常量的规则根据常量的创建方式而有所不同。

2.1 枚举常量
  • 对具有整数值的相关常量组使用枚举。
  • 枚举常量和它们分组的typedef遵循函数的命名约定。以下示例来自NSMatrix.h:

    typedef enum _NSMatrixMode {
        NSRadioModeMatrix           = 0,
    NSHighlightModeMatrix = 1,
    NSListModeMatrix = 2,
    NSTrackModeMatrix = 3
    } NSMatrixMode;

    ⚠️注意,typedef标签是不必要的。

  • 可以用位掩码等内容创建未命名的枚举,例如:

    enum {
        NSBorderlessWindowMask      = 0,
    NSTitledWindowMask = 1 << 0,
    NSClosableWindowMask = 1 << 1,
    NSMiniaturizableWindowMask = 1 << 2,
    NSResizableWindowMask = 1 << 3
    };
2.2 用const创建的常量
  • const创建浮点值常量。如果常量与其他常量无关,则可以用来const创建整数常量; 否则,使用枚举。
  • const常量的格式由以下声明举例说明:

    const float NSLightGray;
    
2.3其他类型的常量
  • 通常,不要使用#define预处理器命令来创建常量。对于整数常量,使用枚举,对于浮点常量,使用const限定符,如上所述。
  • 对预处理器在确定是否将处理代码块时评估的符号使用大写字母。例如:#ifdef DEBUG

  • 请注意,编译器定义的宏具有前导和尾随双下划线字符。例如:__MACH__

  • 定义用于通知名称和字典键等用途的字符串的常量。通过使用字符串常量。如:APPKIT_EXTERN NSString * NSPrintCopies;

    #import <UIKit/UIKitDefines.h>
    UIKIT_EXTERN  NSString * NSPrintCopies;
    

③通知和异常

通知和例外的名称遵循类似的规则。但两者都有自己推荐的使用模式。

  • 通知

如果一个类有一个委托,那么它的大部分通知都可能由委托通过一个已定义的委托方法接收。这些通知的名称应反映相应的委托方法。例如,全局NSApplication对象的委托自动注册,以便applicationDidBecomeActive:在应用程序发布时接收消息NSApplicationDidBecomeActiveNotification。

通知由全局NSString对象标识,其名称以这种方式组成:
[关联类的名称] + [Did | will] + [UniquePartOfName] +通知
NSApplicationDidBecomeActiveNotification
NSWindowDidMiniaturizeNotification
NSTextViewDidChangeSelectionNotification
NSColorPanelColorDidChangeNotification

  • 异常

用来处理抛出的NSException使用异常。

异常由全局NSString对象标识,其名称以这种方式组成:

[Prefix] + [UniquePartOfName] + Exception
比如

NSColorListIOException
NSColorListNotEditableException
NSDraggingException
NSFontUnavailableException
NSIllegalSelectorException

5️⃣框架开发人员的技巧和技巧

开发框架有他们如何编写自己的代码比其他开发商更加谨慎。许多客户端应用程序可以在其框架中链接,并且由于这种广泛的暴露,框架中的任何缺陷可能会在整个系统中被放大。以下各项讨论了可以采用的编程技术,以确保框架的效率和完整性。

  • 类初始化

在调用类的任何其他方法之前,initialize 类方法为您提供了一个懒惰地执行一次代码的地方。

运行时发送initialize到继承链中的每个类,即使它没有实现它; 因此它可能initialize不止一次地调用一个类的方法(例如,如果一个子类没有实现它)。通常,您希望初始化代码只执行一次。确保这种情况发生的一种方法是使用dispatch_once():

+ (void)initialize {
    static dispatch_once_t onceToken = 0;
    dispatch_once(&onceToken, ^{
        // the initializing code
    }
}

⚠️注意: 因为运行时将initialize发送到每个类,所以可能initialize会在子类的上下文中调用 - 如果子类没有实现initialize,那么调用将落入超类。如果您特别需要在相关类的上下文中执行初始化,则可以执行以下检查而不是使用dispatch_once():

if(self == [NSFoo class]){
    //初始化代码
}
  • 指定的初始化器

指定的初始值设定init项是调用init超类方法的类的方法。(其他初始值设定项调用init类定义的方法。)每个公共类都应该有一个或多个指定的初始值设定项。

当您实现一个框架类时,您通常还必须实现其归档方法:initWithCoder:和encodeWithCoder:。注意不要在初始化代码路径中执行在取消归档对象时不会发生的事情。实现这一目标的一个好方法是,initWithCoder:如果您的类实现了归档,则从您指定的初始化程序和(它本身是指定的初始化程序)调用一个公共例程。

  • 初始化期间的错误检测

    • 通过调用重新分配自我super的指定初始化。
    • 检查返回值nil,这表示超类初始化中发生了一些错误。
    • 如果在初始化当前类时发生错误,请释放该对象并返回nil。

下面是🌰代码

- (id)init {
    self = [super init];  // Call a designated initializer here.
    if (self != nil) {
        // Initialize object  ...
        if (someError) {
            [self release];
            self = nil;
        }
    }
    return self;
}

参考

[1]Apple Guideline
[2]iOS Coding Style Guide 代码规范

2018/12/1 posted in  iOS

Enumerate枚举遍历数组

let arr: NSArray = [1,2,3,4,5]
var result = 0
arr.enumerateObjectsUsingBlock { (num, idx, stop) -> Void in
    result += num as! Int
    if idx == 2 {
        stop.memory = true
    }
}
print(result)
// 输出:6

虽然说使用 enumerateObjectsUsingBlock: 非常方便,但是其实从性能上来说这个方法并不理想 (这里有一篇四年前的星期五问答阐述了这个问题,而且一直以来情况都没什么变化)。另外这个方法要求作用在 NSArray 上,这显然已经不符合 Swift 的编码方式了。在 Swift 中,我们在遇到这样的需求的时候,有一个效率,安全性和可读性都很好的替代,那就是快速枚举某个数组的 EnumerateGenerator,它的元素是同时包含了元素下标索引以及元素本身的多元组:

var result = 0
for (idx, num) in [1,2,3,4,5].enumerate() {
    result += num
    if idx == 2 {
        break
    }
}
print(result)

基本上来说,是时候可以和 enumerateObjectsUsingBlock: 说再见了。

2018/12/1 posted in  iOS

UICollectionViewFlowLayout

可以重写方法

  • (nullable NSArray<__kindof UICollectionViewLayoutAttributes *> *)layoutAttributesForElementsInRect:(CGRect)rect;

给每个 cell 重写布局

另外还有下面这个方法在 view 的位置改变的时候实时调用上一个方法

  • (BOOL)shouldInvalidateLayoutForBoundsChange:(CGRect)newBounds; // return YES to cause the collection view to requery the layout for geometry information

UIViewPropertyAnimator

动画除了[UIView animation...]
还可以使用UIView中的这个属性实现

创建一个 animator跟一个 block 实现具体的动画效果
然后启动 animator

添加Blur

let blurEffect = UIBlurEffect(style:.regular)

let visualEffectView = UIViewEffectView(effct:blurEffect)

self.addSubview(visualEffectView)

然后布局visualEffectView

蒙版的效果就有了

2018/11/20 posted in  iOS

Protocol

注意协议只是个声明文件,并没有实现。
谁声明,谁调用。

协议可用定义在单独.h文件中,也可用定义在某个类中:

(1) 如果这个协议只用在某个类中,应该把协议定义在该类中

(2) 如果这个协议用在很多类中,就应该定义在单独文件中

不过,如果子类自身又遵循了这个协议,但并没有实现,那么在运行时,系统会一级级往上查找,直到找到父类的方法实现。也就是说,只要知道苹果的私有方法名,并且确保自己的类是这个私有方法所属类的子类,就可以在子类中通过只声明不实现的方式执行父类中该私有方法的实现。

category

category也是一种特殊的协议,内部可以调用,外部不能调用、子类不能重写实现和重写,相当于是私有方法。

总结

协议就是定义公共接口的地方,只要遵守协议,就等于在头文件中定义了这些方法,只要实现就行了。

2018/11/16 posted in  iOS

Flutter && Python && OC

dart的函数定义如下所示

// Define a function.
printInteger(int aNumber) {
  print('The number is $aNumber.'); // Print to console.
}

// This is where the app starts executing.
main() {
  var number = 42; // Declare and initialize a variable.
  printInteger(number); // Call a function.
}

isNoble(atomicNumber) {
  return _nobleGases[atomicNumber] != null;
}

如果函数只有单个语句,可以采用简略的形式:

bool isNoble(int atomicNumber) => _nobleGases[atomicNumber] != null;


$dart取地址

String say(String from, String msg, [String device]) {
  var result = '$from says $msg';
  if (device != null) {
    result = '$result with a $device';
  }
  return result;
}

以分号结尾 参数类型要指定也可以不指定

=> 是{ return expr;} 的缩写

oc的函数定义

-(void)hello{

print(@"hello world");

}
-(void)hello(int a,int b){

print(@"hello world %d %d",a,b);

}


无需注意缩进 但是返回值类型一定要明确

python函数定义

def xxxx:
    print 'hello world'
    
def xxx(a,b):
   print(a,b)
   
def xxx(self,a,b)
   print a,b
   

由于没有分号和大括号所以一定要注意缩进,

2018/11/11 posted in  iOS

Swift中的字典转模型方法的迭代

模型可以是结构体也可以是继承自NSObject的类

class App: NSObject {
    var name:String = ""
    var age:Int = 0
    var boxDescription:String = ""
}


struct App :Decodable{
    var name:String
    var age:Int
    var description:String
    
    init(dic:[String:AnyObject]) {
        name = dic["name"] as! String
        age = dic["age"] as! Int
        description = dic["description"] as! String
    }
    
}

网络请求

 func fetchData(){
        let url = "https://maweefeng.github.io/appfeauterd.json"
        
        let session: URLSession = URLSession.shared
        
        let dataTask: URLSessionDataTask = session.dataTask(with: URL(string: url)!) { (data, response, error) in
            if(error == nil){
                var dict:NSDictionary? = nil
                do {
                    guard let jsondata = data else{return}
                    let app = try JSONDecoder().decode(App.self, from: jsondata)
                    print(app)
                    
//dict = try JSONSerialization.jsonObject(with: data!, options: .mutableContainers) as? NSDictionary
//
//                    let app = App(dic: dict)
//                    print(app)

                    
                } catch {
                }
                print(dict!)
            }
        }
        dataTask.resume()
        
    }

JSONSerialization

之前我们字典转模型可能使用的方法可能是使用

  • model为结构体

    dict = try JSONSerialization.jsonObject(with: data!, options: .mutableContainers) as? NSDictionary
    let app = App(dic: dict)
    print(app)
  • model为NSObject子类

    dict = try JSONSerialization.jsonObject(with: data!, options: .mutableContainers) as? NSDictionary
    let app = App()
    app.setValuesForKeys(dict as! [String : Any])

JSONDecode

现在使用swift4新增了一种更方法的方法,就是JSONDecode

let app = try JSONDecoder().decode(App.self, from: jsondata)`

如果返回的data数据是一个object的字典,那么可以用上面的代码

如果data是一个包含app对象的数组,则可以简单的使用

let app = try JSONDecoder().decode([App].self, from: jsondata)`

另外一些情况,可能数组当中并不是每个键值对都一一对应,可能存在部分键值对缺失的情况,这样的话就会报错,有一个解决办法便是使那些可以为空的属性为optional,这样就可以解决错误问题。

2018/10/30 posted in  iOS

Swift单例写法

介绍swift的单例类写法之前,可以先回顾一下oc中单例的写法如下。

//单例类
@interface Manger : NSObject
+ (instancetype)sharedManger;
@end

//然后.m文件里写实现
@implementation Manger
+ (instancetype)sharedManger {
    static Manger *sharedManger = nil;
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        sharedManger = [self new];
    });
    return sharedManger;
}

swift单例写法

  • 简单写法
import UIKit
//仿OC写法
class Manger: NSObject {
    static let instance: Manger = Manger()
    class func shared() -> Manger {
        return instance
    }
}
//简便写法
class Manger {
    static let shared = Manger()
}

这样对比下来swift真的是极大的减少了代码量,所有也会有一些人推荐如果开发新项目,建议使用swift开发,毫无疑问。

另外swift 还有一种类似oc的写法如下。

  • 复杂写法
class Singleton {
    class var sharedInstance: Singleton {
        struct Static {
            static var oncetoken: String? = NSUUID().uuidString

            static var instance: Singleton? = nil
        }
        DispatchQueue.once(token: Static.oncetoken) {
            Static.instance = Singleton()

        }
        return Static.instance!
    }
}

extension DispatchQueue {
    private static var _onceTracker = [String]()
    public class func once(token: String, block: () -> ()) {
        objc_sync_enter(self)
        defer {
            objc_sync_exit(self)
        }
        if _onceTracker.contains(token) {
            return
        }
        _onceTracker.append(token)
        block()
    }
    
    func async(block: @escaping ()->()) {
        self.async(execute: block)
    }

    func after(time: DispatchTime, block: @escaping ()->()) {
        self.asyncAfter(deadline: time, execute: block)
    }
}

之所以写DispatchQueue的扩展是因为swift3中取消了dispatch_once_t的使用
'dispatch_once' is unavailable in Swift: Use lazily initialized globals instead

建议使用懒加载的初始化全局替代。

因为原来自从swift 1.x开始swift就已经开始用dispatch_one机制在后台支持线程安全的全局lazy初始化和静态属性.所以static var背后已经在使用dispatch_once了.

2018/10/30 posted in  iOS

设置图片拉伸范围

- (UIImage *)stretchableImageWithLeftCapWidth:(NSInteger)leftCapWidth topCapHeight:(NSInteger)topCapHeight 

这个函数是UIImage的一个实例函数,它的功能是创建一个内容可拉伸,而边角不拉伸的图片,需要两个参数,第一个是左边不拉伸区域的宽度,第二个参数是上面不拉伸的高度。

根据设置的宽度和高度,将接下来的一个像素进行左右扩展和上下拉伸。

注意:可拉伸的范围都是距离leftCapWidth后的1竖排像素,和距离topCapHeight后的1横排像素。

参数的意义是,如果参数指定10,5。那么,图片左边10个像素,上边5个像素。不会被拉伸,x坐标为11和一个像素会被横向复制,y坐标为6的一个像素会被纵向复制。

注意:只是对一个像素进行复制到一定宽度。而图像后面的剩余像素也不会被拉伸。

UIImage *img=[UIImage imageNamed:@"bubbleSelf.png"];
img=[img stretchableImageWithLeftCapWidth:15 topCapHeight:12];
UIImageView *imgView=[[UIImageView alloc]initWithImage:img];
[imgView setFrame:CGRectMake(10, 10, 200, 200)];
[self. view addSubview:imgView];
2018/10/20 posted in  iOS