大多数对象在我们的APP中使用之前,都需要某种形式的设置。无论是我们要根据APP的品牌设置样式的视图(View
),还是要配置的视图控制器(View Controller
),亦或是在测试中创建存根的值时,我们经常发现需要将设置代码放在某个地方。
放置此类设置代码的一个非常常见的地方是子类。只需将您需要设置的对象子类化,覆盖其初始化程序并在那里进行设置——完成!尽管这肯定是一种可行的方法,但是本周,让我们看一下编写不需要任何子类形式的设置代码的另一种方法——使用静态工厂方法(static factory methods)。
swift: 静态工厂方法
同样,这是许多开发人员选择子类化并创建内置视图类的自定义变体的地方,就像这里的UILabel
一样,我们将使用它来渲染标题:
class TitleLabel: UILabel {
override init(frame: CGRect) {
super.init(frame: frame)
font = .boldSystemFont(ofSize: 24)
textColor = .darkGray
adjustsFontSizeToFitWidth = true
minimumScaleFactor = 0.75
}
}
TitleLabel
,SubtitleLabel
,FeaturedTitleLabel
等)。UILabel
中真正添加任何新行为,我们只是在设置一个实例。
因此,问题是子类是否真的适合此处的工作?UILabel
上添加一个扩展,使我们能够从上面创建与 TitleLabel
完全相同设置的新实例,如下所示:extension UILabel {
static func makeForTitle() -> UILabel {
let label = UILabel()
label.font = .boldSystemFont(ofSize: 24)
label.textColor = .darkGray
label.adjustsFontSizeToFitWidth = true
label.minimumScaleFactor = 0.75
return label
}
}
private
关键字),因此我们可以轻松地为需要创建特定视图的应用程序部分设置扩展名,只有一个功能即可://我们只会在单个视图控制器中使用它,因此我们将范围设为私有(暂时),
//以免将此功能添加到我们的应用程序全局使用UIButton中。
private extension UIButton {
static func makeForBuying() -> UIButton {
let button = UIButton()
...
return button
}
}
class ProductViewController {
private lazy var titleLabel = UILabel.makeForTitle()
private lazy var buyButton = UIButton.makeForBuying()
}
extension UILabel {
static var title: UILabel {
let label = UILabel()
...
return label
}
}
class ProductViewController {
private lazy var titleLabel = UILabel.title
private lazy var buyButton = UIButton.buy
}
尤其是在使用子视图控制器时,我们通常最终会得到一组视图控制器,它们只能在其中呈现特定状态,而不是在其中包含大量逻辑。对于那些视图控制器,将其设置移动到静态工厂API可能是一个很好的解决方案。
在这里,我们使用这种方法来实现一个计算属性,该属性返回一个加载视图控制器,用于显示加载旋转框:
extension UIViewController {
static var loading: UIViewController {
let viewController = UIViewController()
let indicator = UIActivityIndicatorView(activityIndicatorStyle: .gray)
indicator.translatesAutoresizingMaskIntoConstraints = false
indicator.startAnimating()
viewController.view.addSubview(indicator)
NSLayoutConstraint.activate([
indicator.centerXAnchor.constraint(
equalTo: viewController.view.centerXAnchor
),
indicator.centerYAnchor.constraint(
equalTo: viewController.view.centerYAnchor
)
])
return viewController
}
}
class ProductListViewController: UIViewController {
func loadProducts() {
let loadingVC = add(.loading)
productLoader.loadProducts { [weak self] result in
loadingVC.remove()
self?.handle(result)
}
}
}
对添加便捷API的唯一修改是使其返回添加的子视图控制器,从而可以在使用点语法的同时获取对其的引用。当不使用该新功能时,也可以添加
@discardableResult
来删除所有警告。
假设我们的应用程序中有一个User
模型,其中包含给定用户具有什么样的权限,并且我们的许多测试都是基于当前用户的权限来验证我们的逻辑。不必在所有测试中都使用样板数据手动创建用户,而是创建一个静态工厂方法,该方法基于一组权限返回一个用户存根,如下所示:
extension User {
static func makeStub(permissions: Set<User.Permission>) -> User {
return User(
name: "TestUser",
age: 30,
signUpDate: Date(),
permissions: permissions
)
}
}
deleteFolders
权限的用户是否可以删除文件夹:class FolderManagerTests: XCTestCase {
func testDeletingFolder() throws {
// 现在,我们可以快速创建具有所需权限的用户
let user = User.makeStub(permissions: [.deleteFolders])
let manager = FolderManager(user: user)
let folderName = "Test"
try manager.addFolder(named: folderName)
XCTAssertNotNil(manager.folder(named: folderName))
try manager.deleteFolder(named: folderName)
XCTAssertNil(manager.folder(named: folderName))
}
}
User
模型的更多内容,在创建存根时可能还需要设置其他属性。使用默认参数是一种简单的方式,这不需要我们添加新的方法:extension User {
static func makeStub(age: Int = 30,
permissions: Set<User.Permission> = []) -> User {
return User(
name: "TestUser",
age: age,
signUpDate: Date(),
permissions: permissions
)
}
}
User.makeStub()
创建空白用户。makeStub
,我们还可以清楚地知道此代码仅用于测试,因此将来不会意外将其添加到我们的主要应用程序目标中。文章来自 John Sundell的Static factory methods in Swift简单翻译了一下,希望对大家有用
extension UILabel {
class func makeForTitle() -> UILabel {
let label = UILabel()
label.font = .boldSystemFont(ofSize: 24)
label.textColor = .darkGray
label.adjustsFontSizeToFitWidth = true
label.minimumScaleFactor = 0.75
return label
}
}
OC: 创建一个UILabel
的Category
@interface UILabel (Factory)
+ (UILabel *)makeForTitle;
@end
@implementation UILabel (Factory)
+ (UILabel *)makeForTitle {
UILabel *label = [[UILabel alloc] init];
label.font = [UIFont boldSystemFontOfSize:24];
label.textColor = [UIColor darkGrayColor];
label.adjustsFontSizeToFitWidth = YES;
label.minimumScaleFactor = 0.75;
return label;
}
@end